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.
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 .
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()
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()
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
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/
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)
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)
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
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.
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)
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.
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()
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.
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