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