web-dev-qa-db-fra.com

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

Comment extraire toutes les images d'un document pdf, avec une résolution et un format natifs? (Signifiant extraire tiff en tiff, jpeg en jpeg, etc. et sans rééchantillonnage). La mise en page est sans importance, peu m'importe si l'image source se trouve sur la page.

J'utilise python 2.7 mais peut utiliser 3.x si nécessaire.

56
matt wilkie

Souvent dans un 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 au milieu qui, une fois extrait, est un fichier jpg valide. Vous pouvez l'utiliser pour extraire très simplement des plages d'octets du PDF J'ai écrit à ce sujet il y a quelque temps, avec un exemple de code: Extraction de JPG à partir de PDF .

31
Ned Batchelder

Dans Python avec les bibliothèques PyPDF2 et Pillow, c'est simple:

import PyPDF2

from PIL import Image

if __== '__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()
30
sylvain

Dans Python avec PyPDF2 pour le 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: http://stackoverflow.com/questions/2693820/extract-images-from-pdf-without-resampling-in-python
Extract images coded with CCITTFaxDecode in .net: http://stackoverflow.com/questions/2641770/extracting-image-from-pdf-with-ccittfaxdecode-filter
TIFF format and tags: http://www.awaresystems.be/imaging/tiff/faq.html
"""


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()
20
Sergey Shashkov

Vous pouvez utiliser le module PyMuPDF. Cela produit toutes les images sous forme de fichiers .png, mais a fonctionné 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

17
kateryna

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

(Sur les systèmes Ubuntu, c'est dans le package poppler-utils)

http://poppler.freedesktop.org/

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

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

12
dkagedal

Je suis parti du 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 car 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)
8
Labo

Je préfère le 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)
6
VSZM

Après quelques recherches, j'ai trouvé le script suivant qui fonctionne très bien avec mes PDF. Il ne s'attaque qu'à JPG, mais il a parfaitement fonctionné avec mes fichiers non protégés. Il ne nécessite pas non plus de bibliothèques externes.

Pour ne pas prendre de crédit, le script provient 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
5

J'ai installé ImageMagick sur mon serveur, puis exécuté des appels de 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 en tant que temp-0.png, temp-1.png .... Ce n'est qu'une "extraction" si vous avez obtenu un pdf avec uniquement des images et pas de texte.

4
TompaLompa

Solution beaucoup plus simple:

Utilisez le package poppler-utils. Pour l'installer, utilisez homebrew (homebrew est spécifique à MacOS, mais vous pouvez trouver le package poppler-utils pour Widows ou Linux ici: https://poppler.freedesktop.org/ ). La première ligne de code ci-dessous installe les poppler-utils en utilisant homebrew. Après l'installation, la deuxième ligne (exécutée à partir de la ligne de commande) extrait ensuite les images d'un fichier PDF et les nomme "image *". Pour exécuter ce programme à partir de Python, utilisez le module os ou subprocess. La troisième ligne est le code utilisant le module os, en dessous c'est un exemple avec un sous-processus (python 3.5 ou supérieur 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)
3
Colton Hicks

Vous pouvez également utiliser la commande pdfimages dans Ubuntu.

Installez la bibliothèque poppler à l'aide des commandes ci-dessous.

Sudo apt install poppler-utils

Sudo apt-get install python-poppler

pdfimages file.pdf image

La liste des fichiers créés est, (par exemple., Il y a deux images en pdf)

image-000.png
image-001.png

Ça marche ! Vous pouvez maintenant utiliser un subprocess.run pour l'exécuter à partir de python.

2
SuperNova

Depuis février 2019, la solution proposée par @sylvain (du moins sur ma configuration) ne fonctionne pas sans une petite modification: xObject[obj]['/Filter'] n'est pas une valeur, mais une liste, donc pour faire fonctionner le script, j'ai dû modifier la vérification du format comme suit:

import PyPDF2, traceback

from PIL import Image

input1 = PyPDF2.PdfFileReader(open(src, "rb"))
nPages = input1.getNumPages()
print nPages

for i in range(nPages) :
    print i
    page0 = input1.getPage(i)
    try :
        xObject = page0['/Resources']['/XObject'].getObject()
    except : xObject = []

    for obj in xObject:
        if xObject[obj]['/Subtype'] == '/Image':
            size = (xObject[obj]['/Width'], xObject[obj]['/Height'])
            data = xObject[obj].getData()
            try :
                if xObject[obj]['/ColorSpace'] == '/DeviceRGB':
                    mode = "RGB"
                Elif xObject[obj]['/ColorSpace'] == '/DeviceCMYK':
                    mode = "CMYK"
                    # will cause errors when saving
                else:
                    mode = "P"

                fn = 'p%03d-%s' % (i + 1, obj[1:])
                print '\t', fn
                if '/FlateDecode' in xObject[obj]['/Filter'] :
                    img = Image.frombytes(mode, size, data)
                    img.save(fn + ".png")
                Elif '/DCTDecode' in xObject[obj]['/Filter']:
                    img = open(fn + ".jpg", "wb")
                    img.write(data)
                    img.close()
                Elif '/JPXDecode' in xObject[obj]['/Filter'] :
                    img = open(fn + ".jp2", "wb")
                    img.write(data)
                    img.close()
                Elif '/LZWDecode' in xObject[obj]['/Filter'] :
                    img = open(fn + ".tif", "wb")
                    img.write(data)
                    img.close()
                else :
                    print 'Unknown format:', xObject[obj]['/Filter']
            except :
                traceback.print_exc()
2
mxl

J'ai ajouté tous ces éléments ensemble dans PyPDFTK ici .

Ma propre contribution est la gestion de /Indexed fichiers 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 des fichiers sont trouvés, vous ne pouvez pas simplement comparer /ColorSpace à une chaîne, car il s'agit d'un ArrayObject. Nous devons donc vérifier le tableau et récupérer la palette indexée (lookup dans le code) et la définir dans l'objet Image PIL, sinon elle reste non initialisée (zéro) et l'image entière apparaît en noir.

Mon premier réflexe a été de les enregistrer au format GIF (qui est un format indexé), mais mes tests ont révélé que les PNG étaient plus petits et avaient la même apparence.

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

1
Ronan Paixão

Voici ma version de 2019 qui récupère récursivement toutes les images de PDF et les lit avec PIL. Compatible avec Python 2/3. J'ai également trouvé que parfois l'image in PDF peut être compressé par zlib, donc mon code prend en charge la décompression.

#!/usr/bin/env python3
try:
    from StringIO import StringIO
except ImportError:
    from io import BytesIO as StringIO
from PIL import Image
from PyPDF2 import PdfFileReader, generic
import zlib


def get_color_mode(obj):

    try:
        cspace = obj['/ColorSpace']
    except KeyError:
        return None

    if cspace == '/DeviceRGB':
        return "RGB"
    Elif cspace == '/DeviceCMYK':
        return "CMYK"
    Elif cspace == '/DeviceGray':
        return "P"

    if isinstance(cspace, generic.ArrayObject) and cspace[0] == '/ICCBased':
        color_map = obj['/ColorSpace'][1].getObject()['/N']
        if color_map == 1:
            return "P"
        Elif color_map == 3:
            return "RGB"
        Elif color_map == 4:
            return "CMYK"


def get_object_images(x_obj):
    images = []
    for obj_name in x_obj:
        sub_obj = x_obj[obj_name]

        if '/Resources' in sub_obj and '/XObject' in sub_obj['/Resources']:
            images += get_object_images(sub_obj['/Resources']['/XObject'].getObject())

        Elif sub_obj['/Subtype'] == '/Image':
            zlib_compressed = '/FlateDecode' in sub_obj.get('/Filter', '')
            if zlib_compressed:
               sub_obj._data = zlib.decompress(sub_obj._data)

            images.append((
                get_color_mode(sub_obj),
                (sub_obj['/Width'], sub_obj['/Height']),
                sub_obj._data
            ))

    return images


def get_pdf_images(pdf_fp):
    images = []
    try:
        pdf_in = PdfFileReader(open(pdf_fp, "rb"))
    except:
        return images

    for p_n in range(pdf_in.numPages):

        page = pdf_in.getPage(p_n)

        try:
            page_x_obj = page['/Resources']['/XObject'].getObject()
        except KeyError:
            continue

        images += get_object_images(page_x_obj)

    return images


if __== "__main__":

    pdf_fp = "test.pdf"

    for image in get_pdf_images(pdf_fp):
        (mode, size, data) = image
        try:
            img = Image.open(StringIO(data))
        except Exception as e:
            print ("Failed to read image with PIL: {}".format(e))
            continue
        # Do whatever you want with the image
1
Alex Paramonov