Извлечение изображений из PDF без передискретизации, в python?
Как можно извлечь все изображения из PDF-документа, в собственном разрешении и в формате? (Значение extract tiff как tiff, jpeg как jpeg и т.д. И без повторной выборки). Макет неважен, мне все равно, что исходное изображение находится на странице.
Я использую python 2.7, но при необходимости могу использовать 3.x.
Ответы
Ответ 1
Часто в PDF файле изображение просто сохраняется как-есть. Например, PDF с вставленным jpg будет иметь диапазон байтов где-то посередине, который при извлечении является допустимым файлом jpg. Вы можете использовать это, чтобы очень просто извлекать диапазоны байтов из PDF. Я писал об этом некоторое время назад, с примером кода: Извлечение JPG из PDF файлов.
Ответ 2
В Python с библиотеками PyPDF2 и Pillow это просто:
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()
Ответ 3
В Python с PyPDF2 для фильтра 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()
Ответ 4
Вы можете использовать модуль PyMuPDF. Это выводит все изображения в виде файлов .png, но работает из коробки и работает быстро.
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
смотрите здесь для получения дополнительной информации
Ответ 5
Libpoppler поставляется с инструментом под названием "pdfimages", который делает именно это.
(В системах ubuntu это в пакете poppler-utils)
http://poppler.freedesktop.org/
http://en.wikipedia.org/wiki/Pdfimages
Двоичные файлы Windows: http://blog.alivate.com.au/poppler-windows/
Ответ 6
Я начал с кода @sylvain
Были некоторые недостатки, такие как исключение NotImplementedError: unsupported filter /DCTDecode
getData, или тот факт, что код не смог найти изображения на некоторых страницах, потому что они находились на более глубоком уровне, чем страница.
Есть мой код:
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)
Ответ 7
После некоторого поиска я нашел следующий script, который отлично работает с моими PDF файлами. Он работает только с JPG, но отлично работает с моими незащищенными файлами. Также не требуется никаких внешних библиотек.
Чтобы не брать кредит, script происходит от Ned Batchelder, а не от меня.
Код Python3: извлечение jpg из pdf. Быстрая и грязная
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
Ответ 8
Я предпочитаю Minecart, так как он чрезвычайно прост в использовании. В следующем фрагменте показано, как извлечь изображения из 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)
Ответ 9
Я установил ImageMagick на свой сервер, а затем запустил вызовы командной строки через 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)
Это создаст изображение для каждой страницы и сохранит их как temp-0.png, temp-1.png....
Это только "извлечение", если вы получили pdf файл с изображениями и текстом.
Ответ 10
Более простое решение:
Используйте пакет poppler-utils. Чтобы установить его, используйте homebrew (homebrew - это MacOS, но вы можете найти пакет poppler-utils для Widows или Linux здесь: https://poppler.freedesktop.org/), Первая строка кода, установленная ниже, устанавливает poppler-utils с использованием homebrew. После установки вторая строка (запускается из командной строки) извлекает изображения из файла PDF и называет их "image *". Для запуска этой программы из Python используйте модуль os или subprocess. Третья строка - это код с использованием os-модуля, ниже - пример с подпроцессом (функция python 3.5 или более поздняя для функции run()). Подробнее здесь: 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')
или
import subprocess
subprocess.run('pdfimages file.pdf image', shell=True)
Ответ 11
Вы также можете использовать команду pdfimages
в Ubuntu.
Установите poppler lib с помощью приведенных ниже команд.
sudo apt install poppler-utils
sudo apt-get install python-poppler
pdfimages file.pdf image
Список созданных файлов, (например, в формате PDF есть два изображения)
image-000.png
image-001.png
Оно работает ! Теперь вы можете использовать subprocess.run
для запуска этого из python.
Ответ 12
Я добавил все из них в PyPDFTK здесь.
Мой собственный вклад - обработка файлов /Indexed
как таковых:
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))
Обратите внимание, что когда найдены /Indexed
файлы, вы не можете просто сравнить /ColorSpace
с строкой, потому что она приходит как ArrayObject
. Итак, мы должны проверить массив и получить индексированную палитру (lookup
в коде) и установить ее в объекте PIL Image, иначе он останется неинициализированным (ноль), и все изображение будет выглядеть черным.
Мой первый инстинкт состоял в том, чтобы сохранить их как GIF (индексированный формат), но мои тесты показали, что PNG меньше и выглядят одинаково.
Я нашел эти типы изображений при печати в PDF с помощью PDF-принтера Foxit Reader.
Ответ 13
По состоянию на февраль 2019 года решение, данное @sylvain (по крайней мере, в моей настройке), не работает без небольшой модификации: xObject[obj]['/Filter']
- это не значение, а список, таким образом, чтобы сделать Для работы скрипта мне пришлось изменить проверку формата следующим образом:
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()
Ответ 14
Проблема с этим решением состоит в том, что фильтры остаются не реализованными, поэтому он все еще не работает.