Extraire des images de PDF sans rééchantillonnage, en python?

comment extraire toutes les images d'un document pdf, en résolution et format natifs? (Ce qui signifie extraire tiff comme tiff, jpeg comme jpeg,etc. et sans rééchantillonnage). La mise en page est sans importance, je m'en fiche si l'image source se trouve sur la page.

j'utilise python 2.7 mais je peux utiliser 3.x si nécessaire.

42
demandé sur matt wilkie 2010-04-22 23:26:14

12 réponses

souvent en PDF, l'image est simplement stockée telle quelle. Par exemple, un PDF avec un jpg inséré aura une plage d'octets quelque part dans le milieu que lorsque extrait est un fichier jpg valide. Vous pouvez l'utiliser pour extraire très simplement les plages de byte à partir du PDF. J'ai écrit à ce sujet il y a quelque temps, avec le code échantillon: extraction JPGs de PDFs .

26
répondu Ned Batchelder 2010-04-23 00:08:43

en Python avec les bibliothèques PyPDF2 et Pillow il est simple:

import PyPDF2

from PIL import Image

if __name__ == '__main__':
    input1 = PyPDF2.PdfFileReader(open("input.pdf", "rb"))
    page0 = input1.getPage(0)
    xObject = page0['/Resources']['/XObject'].getObject()

    for obj in xObject:
        if xObject[obj]['/Subtype'] == '/Image':
            size = (xObject[obj]['/Width'], xObject[obj]['/Height'])
            data = xObject[obj].getData()
            if xObject[obj]['/ColorSpace'] == '/DeviceRGB':
                mode = "RGB"
            else:
                mode = "P"

            if xObject[obj]['/Filter'] == '/FlateDecode':
                img = Image.frombytes(mode, size, data)
                img.save(obj[1:] + ".png")
            elif xObject[obj]['/Filter'] == '/DCTDecode':
                img = open(obj[1:] + ".jpg", "wb")
                img.write(data)
                img.close()
            elif xObject[obj]['/Filter'] == '/JPXDecode':
                img = open(obj[1:] + ".jp2", "wb")
                img.write(data)
                img.close()
22
répondu sylvain 2015-12-10 10:00:05

en Python avec PyPDF2 pour filtre CCITTFaxDecode:

import PyPDF2
import struct

"""
Links:
PDF format: http://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf
CCITT Group 4: https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-T.6-198811-I!!PDF-E&type=items
Extract images from pdf: /q/extract-images-from-pdf-without-resampling-in-python-38462/"""


def tiff_header_for_CCITT(width, height, img_size, CCITT_group=4):
    tiff_header_struct = '<' + '2s' + 'h' + 'l' + 'h' + 'hhll' * 8 + 'h'
    return struct.pack(tiff_header_struct,
                       b'II',  # Byte order indication: Little indian
                       42,  # Version number (always 42)
                       8,  # Offset to first IFD
                       8,  # Number of tags in IFD
                       256, 4, 1, width,  # ImageWidth, LONG, 1, width
                       257, 4, 1, height,  # ImageLength, LONG, 1, lenght
                       258, 3, 1, 1,  # BitsPerSample, SHORT, 1, 1
                       259, 3, 1, CCITT_group,  # Compression, SHORT, 1, 4 = CCITT Group 4 fax encoding
                       262, 3, 1, 0,  # Threshholding, SHORT, 1, 0 = WhiteIsZero
                       273, 4, 1, struct.calcsize(tiff_header_struct),  # StripOffsets, LONG, 1, len of header
                       278, 4, 1, height,  # RowsPerStrip, LONG, 1, lenght
                       279, 4, 1, img_size,  # StripByteCounts, LONG, 1, size of image
                       0  # last IFD
                       )

pdf_filename = 'scan.pdf'
pdf_file = open(pdf_filename, 'rb')
cond_scan_reader = PyPDF2.PdfFileReader(pdf_file)
for i in range(0, cond_scan_reader.getNumPages()):
    page = cond_scan_reader.getPage(i)
    xObject = page['/Resources']['/XObject'].getObject()
    for obj in xObject:
        if xObject[obj]['/Subtype'] == '/Image':
            """
            The  CCITTFaxDecode filter decodes image data that has been encoded using
            either Group 3 or Group 4 CCITT facsimile (fax) encoding. CCITT encoding is
            designed to achieve efficient compression of monochrome (1 bit per pixel) image
            data at relatively low resolutions, and so is useful only for bitmap image data, not
            for color images, grayscale images, or general data.

            K < 0 --- Pure two-dimensional encoding (Group 4)
            K = 0 --- Pure one-dimensional encoding (Group 3, 1-D)
            K > 0 --- Mixed one- and two-dimensional encoding (Group 3, 2-D)
            """
            if xObject[obj]['/Filter'] == '/CCITTFaxDecode':
                if xObject[obj]['/DecodeParms']['/K'] == -1:
                    CCITT_group = 4
                else:
                    CCITT_group = 3
                width = xObject[obj]['/Width']
                height = xObject[obj]['/Height']
                data = xObject[obj]._data  # sorry, getData() does not work for CCITTFaxDecode
                img_size = len(data)
                tiff_header = tiff_header_for_CCITT(width, height, img_size, CCITT_group)
                img_name = obj[1:] + '.tiff'
                with open(img_name, 'wb') as img_file:
                    img_file.write(tiff_header + data)
                #
                # import io
                # from PIL import Image
                # im = Image.open(io.BytesIO(tiff_header + data))
pdf_file.close()
16
répondu Sergey Shashkov 2016-01-01 10:34:50

Libpoppler est livré avec un outil appelé "pdfimages" qui fait exactement cela.

(sur les systèmes ubuntu, c'est dans le paquet poppler-utils)

http://poppler.freedesktop.org /

http://en.wikipedia.org/wiki/Pdfimages

Windows binaires: http://blog.alivate.com.au/poppler-windows /

10
répondu dkagedal 2017-12-06 16:57:34

j'ai commencé par le code de @sylvain Il y avait quelques défauts, comme l'exception NotImplementedError: unsupported filter /DCTDecode de getData, ou le fait que le code n'a pas trouvé d'images dans certaines pages parce qu'elles étaient à un niveau plus profond que la page.

il y a mon code:

import PyPDF2

from PIL import Image

import sys
from os import path
import warnings
warnings.filterwarnings("ignore")

number = 0

def recurse(page, xObject):
    global number

    xObject = xObject['/Resources']['/XObject'].getObject()

    for obj in xObject:

        if xObject[obj]['/Subtype'] == '/Image':
            size = (xObject[obj]['/Width'], xObject[obj]['/Height'])
            data = xObject[obj]._data
            if xObject[obj]['/ColorSpace'] == '/DeviceRGB':
                mode = "RGB"
            else:
                mode = "P"

            imagename = "%s - p. %s - %s"%(abspath[:-4], p, obj[1:])

            if xObject[obj]['/Filter'] == '/FlateDecode':
                img = Image.frombytes(mode, size, data)
                img.save(imagename + ".png")
                number += 1
            elif xObject[obj]['/Filter'] == '/DCTDecode':
                img = open(imagename + ".jpg", "wb")
                img.write(data)
                img.close()
                number += 1
            elif xObject[obj]['/Filter'] == '/JPXDecode':
                img = open(imagename + ".jp2", "wb")
                img.write(data)
                img.close()
                number += 1
        else:
            recurse(page, xObject[obj])



try:
    _, filename, *pages = sys.argv
    *pages, = map(int, pages)
    abspath = path.abspath(filename)
except BaseException:
    print('Usage :\nPDF_extract_images file.pdf page1 page2 page3 …')
    sys.exit()


file = PyPDF2.PdfFileReader(open(filename, "rb"))

for p in pages:    
    page0 = file.getPage(p-1)
    recurse(p, page0)

print('%s extracted images'% number)
6
répondu Labo 2016-05-05 15:57:47

vous pouvez utiliser le module PyMuPDF. Cela affiche toutes les images sous .les fichiers png, mais il a travaillé hors de la boîte et est rapide.

import fitz
doc = fitz.open("file.pdf")
for i in range(len(doc)):
for img in doc.getPageImageList(i):
    xref = img[0]
    pix = fitz.Pixmap(doc, xref)
    if pix.n < 5:       # this is GRAY or RGB
        pix.writePNG("p%s-%s.png" % (i, xref))
    else:               # CMYK: convert to RGB first
        pix1 = fitz.Pixmap(fitz.csRGB, pix)
        pix1.writePNG("p%s-%s.png" % (i, xref))
        pix1 = None
    pix = None

voir ici pour plus de ressources

5
répondu kateryna 2017-12-18 23:26:47

j'ai installé ImageMagick sur mon serveur et j'ai ensuite lancé des appels en ligne de commande via Popen :

 #!/usr/bin/python

 import sys
 import os
 import subprocess
 import settings

 IMAGE_PATH = os.path.join(settings.MEDIA_ROOT , 'pdf_input' )

 def extract_images(pdf):
     output = 'temp.png'
     cmd = 'convert ' + os.path.join(IMAGE_PATH, pdf) + ' ' + os.path.join(IMAGE_PATH, output)
     subprocess.Popen(cmd.split(), stderr=subprocess.STDOUT, stdout=subprocess.PIPE)

cela créera une image pour chaque page et les stockera comme temp-0.PNG, temp-1.png .... Ce n'est qu'une' extraction ' si vous avez un pdf avec seulement des images et pas de texte.

3
répondu TompaLompa 2012-03-29 13:00:07

après quelques recherches j'ai trouvé le script suivant qui fonctionne vraiment bien avec mes PDF. Il ne s'attaque qu'au JPG, mais il a parfaitement fonctionné avec mes fichiers non protégés. Aussi est ne nécessite pas de en dehors des bibliothèques.

pour ne prendre aucun crédit, le script vient de Ned Batchelder, et pas de moi. Code Python3: extraire les jpg des pdf. Rapide et sale

import sys

with open(sys.argv[1],"rb") as file:
    file.seek(0)
    pdf = file.read()

startmark = b"\xff\xd8"
startfix = 0
endmark = b"\xff\xd9"
endfix = 2
i = 0

njpg = 0
while True:
    istream = pdf.find(b"stream", i)
    if istream < 0:
        break
    istart = pdf.find(startmark, istream, istream + 20)
    if istart < 0:
        i = istream + 20
        continue
    iend = pdf.find(b"endstream", istart)
    if iend < 0:
        raise Exception("Didn't find end of stream!")
    iend = pdf.find(endmark, iend - 20)
    if iend < 0:
        raise Exception("Didn't find end of JPG!")

    istart += startfix
    iend += endfix
    print("JPG %d from %d to %d" % (njpg, istart, iend))
    jpg = pdf[istart:iend]
    with open("jpg%d.jpg" % njpg, "wb") as jpgfile:
        jpgfile.write(jpg)

    njpg += 1
    i = iend
2
répondu Max A. H. Hartvigsen 2017-06-08 04:31:55

beaucoup plus facile solution:

utilisez le paquet poppler-utils. Pour l'installer, utilisez homebrew (homebrew est spécifique à MacOS, mais vous pouvez trouver le paquet poppler-utils pour les veuves ou Linux ici: https://poppler.freedesktop.org / ). La première ligne de code ci-dessous installe poppler-utils en utilisant homebrew. Après l'installation, la deuxième ligne (exécutée à partir de la ligne de commande) extrait les images D'un fichier PDF et les nomme "image*". Pour exécuter ce programme à partir de dans Python, utilisez le module os ou subprocess. La troisième ligne est le code utilisant le module os, sous lequel se trouve un exemple avec subprocess (python 3.5 ou plus tard pour la fonction run ()). Plus d'informations ici: https://www.cyberciti.biz/faq/easily-extract-images-from-pdf-file /

brew install poppler

pdfimages file.pdf image

import os
os.system('pdfimages file.pdf image')

ou

import subprocess
subprocess.run('pdfimages file.pdf image', shell=True)
2
répondu Colton Hicks 2017-12-11 20:58:25

j'ai ajouté tous ceux ensemble dans PyPDFTK ici .

ma propre contribution est le traitement des fichiers /Indexed en tant que tels:

for obj in xObject:
    if xObject[obj]['/Subtype'] == '/Image':
        size = (xObject[obj]['/Width'], xObject[obj]['/Height'])
        color_space = xObject[obj]['/ColorSpace']
        if isinstance(color_space, pdf.generic.ArrayObject) and color_space[0] == '/Indexed':
            color_space, base, hival, lookup = [v.getObject() for v in color_space] # pg 262
        mode = img_modes[color_space]

        if xObject[obj]['/Filter'] == '/FlateDecode':
            data = xObject[obj].getData()
            img = Image.frombytes(mode, size, data)
            if color_space == '/Indexed':
                img.putpalette(lookup.getData())
                img = img.convert('RGB')
            img.save("{}{:04}.png".format(filename_prefix, i))

notez que lorsque /Indexed fichiers sont trouvés, vous ne pouvez pas simplement comparer /ColorSpace à une chaîne de caractères, parce qu'il vient comme une ArrayObject . Donc, nous devons vérifier le tableau et récupérer la palette indexée ( lookup dans le code) et la mettre dans l'objet Image PIL, sinon il reste non initialisé (zéro) et l'image entière est noire.

mon premier instinct a été de les sauver comme GIFs (qui est un format indexé), mais mes tests se sont avérés que les png étaient plus petits et regardaient de la même manière.

j'ai trouvé ces types d'images lors de l'impression en PDF avec Foxit Reader PDF Printer.

1
répondu Ronan Paixão 2016-03-23 01:38:54

vous pouvez également utiliser la commande pdfimages dans Ubuntu.

installez poppler lib en utilisant les commandes ci-dessous.

sudo apt install poppler-utils

sudo apt-get install python-poppler

pdfimages file.pdf image

liste des fichiers créés sont, (par ex.,. il y a deux images en pdf)

image-000.png
image-001.png

ça marche ! Maintenant, vous pouvez utiliser un subprocess.run pour exécuter ceci à partir de python.

0
répondu Vikram S 2018-08-10 08:20:58

je préfère minecart, car il est extrêmement facile à utiliser. L'extrait ci-dessous montre comment extraire des images d'un pdf:

#pip install minecart
import minecart

pdffile = open('Invoices.pdf', 'rb')
doc = minecart.Document(pdffile)

page = doc.get_page(0) # getting a single page

#iterating through all pages
for page in doc.iter_pages():
    im = page.images[0].as_pil()  # requires pillow
    display(im)
0
répondu VSZM 2018-09-19 23:29:22