UFO ET IT

파이썬에서 리샘플링하지 않고 PDF에서 이미지를 추출 하시겠습니까?

ufoet 2020. 12. 14. 20:24
반응형

파이썬에서 리샘플링하지 않고 PDF에서 이미지를 추출 하시겠습니까?


기본 해상도와 형식으로 pdf 문서에서 모든 이미지를 어떻게 추출 할 수 있습니까? (즉, tiff를 tiff, jpeg를 jpeg 등으로 추출하고 리샘플링하지 않음). 레이아웃은 중요하지 않습니다. 소스 이미지가 페이지에 있는지 상관 없습니다.

파이썬 2.7을 사용하고 있지만 필요한 경우 3.x를 사용할 수 있습니다.


종종 PDF에서 이미지는 그대로 저장됩니다. 예를 들어, jpg가 삽입 된 PDF는 추출 될 때 유효한 jpg 파일이되는 중간 어딘가에 바이트 범위가 있습니다. 이것을 사용하여 PDF에서 바이트 범위를 매우 간단하게 추출 할 수 있습니다. 얼마 전에 샘플 코드를 사용하여 이에 대해 썼습니다 . PDF에서 JPGs 추출 .


PyPDF2 및 Pillow 라이브러리가있는 Python에서는 간단합니다.

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

CCITTFaxDecode 필터 용 PyPDF2가있는 Python에서 :

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

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

더 많은 리소스는 여기를 참조하십시오.


Libpoppler는이를 정확히 수행하는 "pdfimages"라는 도구와 함께 제공됩니다.

(우분투 시스템에서는 poppler-utils 패키지에 있습니다)

http://poppler.freedesktop.org/

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

Windows 바이너리 : http://blog.alivate.com.au/poppler-windows/


@sylvain의 코드에서 시작했습니다. NotImplementedError: unsupported filter /DCTDecodegetData 의 예외와 같은 몇 가지 결함이 있거나 코드가 페이지보다 더 깊은 수준에 있기 때문에 일부 페이지에서 이미지를 찾지 못했다는 사실이있었습니다.

내 코드가 있습니다.

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)

나는 사용하기 매우 쉽기 때문에 광산 수레를 선호합니다. 아래 스 니펫은 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)

몇 가지 검색 후 내 PDF에서 정말 잘 작동하는 다음 스크립트를 발견했습니다. JPG 만 다루지 만 보호되지 않은 파일에서는 완벽하게 작동했습니다. 또한 외부 라이브러리가 필요하지 않습니다.

신용을 얻지 않기 위해 스크립트는 내가 아닌 Ned Batchelder에서 시작되었습니다. Python3 코드 : pdf에서 jpg를 추출합니다. 빠르고 더러운

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

내 서버에 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를 얻은 경우에만 '추출'됩니다.


훨씬 더 쉬운 솔루션 :

poppler-utils 패키지를 사용하십시오. 설치하려면 homebrew를 사용하십시오 (homebrew는 MacOS에 따라 다르지만 여기에서 Widows 또는 Linux 용 poppler-utils 패키지를 찾을 수 있습니다 : https://poppler.freedesktop.org/ ). 아래 코드의 첫 줄은 homebrew를 사용하여 poppler-utils를 설치합니다. 설치 후 두 번째 줄 (명령 줄에서 실행)은 PDF 파일에서 이미지를 추출하고 이름을 "image *"로 지정합니다. Python 내에서이 프로그램을 실행하려면 os 또는 subprocess 모듈을 사용하십시오. 세 번째 줄은 os 모듈을 사용하는 코드이며 그 아래에는 하위 프로세스 (run () 함수의 경우 Python 3.5 이상)가있는 예제가 있습니다. 자세한 정보 : 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)

pdfimagesUbuntu에서도 명령을 사용할 수 있습니다 .

아래 명령을 사용하여 poppler lib를 설치하십시오.

sudo apt install poppler-utils

sudo apt-get install python-poppler

pdfimages file.pdf image

생성 된 파일 목록은 다음과 같습니다 (예 : pdf에 두 개의 이미지가 있음).

image-000.png
image-001.png

효과가있다 ! 이제 a subprocess.run사용하여 파이썬에서 실행할 수 있습니다 .


2019 년 2 월 현재 @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()

다음은 PDF에서 모든 이미지를 재귀 적으로 가져와 PIL로 읽는 2019의 내 버전입니다. Python 2/3와 호환됩니다. 또한 때때로 PDF의 이미지가 zlib로 압축 될 수 있으므로 내 코드가 압축 해제를 지원합니다.

#!/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 __name__ == "__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

여기 에 모든 것을 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))

Note that when /Indexed files are found, you can't just compare /ColorSpace to a string, because it comes as an ArrayObject. So, we have to check the array and retrieve the indexed palette (lookup in the code) and set it in the PIL Image object, otherwise it stays uninitialized (zero) and the whole image shows as black.

My first instinct was to save them as GIFs (which is an indexed format), but my tests turned out that PNGs were smaller and looked the same way.

I found those types of images when printing to PDF with Foxit Reader PDF Printer.

참고URL : https://stackoverflow.com/questions/2693820/extract-images-from-pdf-without-resampling-in-python

반응형