【PyMuPDF和pdf2image】Python将PDF转成图片PNG和JPG

公众号:软测小生, 获取更多精彩内容。
前言:在最近的测试中遇到一个与PDF相关的测试需求,其中有一个过程是将PDF转换成图片,然后对图片进行测试。

粗略的试了好几种方式,其中语言尝试了Python和Java,总体而言所找到的Python方式相对比Java更快一些,更简单一些。
下面首先分享一下Python将PDF转换成图片,Java后续有时间在进行分享。

需求:我需要先将PDF转换成为PNG图片,并截取图片的一部分存储,然后作为测试目标进行测试。
操作:
1、PDF转PNG图片
2、对PNG图片进行指定区域截图,在另存到指定文件夹下
针对截图此处所找到的方法如上一篇文章:Python图片裁剪的两种方式——Pillow和OpenCV

1、PyMuPDF将PDF转换成图片

pip install PyMuPDF

import sys, fitz, os, datetime

def pyMuPDF_fitz(pdfPath, imagePath):
    startTime_pdf2img = datetime.datetime.now()#开始时间

    print("imagePath="+imagePath)
    pdfDoc = fitz.open(pdfPath)
    for pg in range(pdfDoc.pageCount):
        page = pdfDoc[pg]
        rotate = int(0)
        # 每个尺寸的缩放系数为1.3,这将为我们生成分辨率提高2.6的图像。
        # 此处若是不做设置,默认图片大小为:792X612, dpi=96
        zoom_x = 1.33333333 #(1.33333333-->1056x816)   (2-->1584x1224)
        zoom_y = 1.33333333
        mat = fitz.Matrix(zoom_x, zoom_y).preRotate(rotate)
        pix = page.getPixmap(matrix=mat, alpha=False)

        if not os.path.exists(imagePath):#判断存放图片的文件夹是否存在
            os.makedirs(imagePath) # 若图片文件夹不存在就创建

        pix.writePNG(imagePath+'/'+'images_%s.png' % pg)#将图片写入指定的文件夹内

    endTime_pdf2img = datetime.datetime.now()#结束时间
    print('pdf2img时间=',(endTime_pdf2img - startTime_pdf2img).seconds)

if __name__ == "__main__":
    pdfPath = '../path/demo.pdf'
    imagePath = '../path/image'
    pyMuPDF_fitz(pdfPath, imagePath)
image.gif

PDF文档页数超过100页的话需要十几秒,因为先转换成一整张1056X816的图片,再对本地文件中的所有图片进行遍历截图,时间上比较慢,通过查看文档发现:
还可以在转换的同时指定图片的大小,对图片指定区域进行截取,这样快很多,一步到位,省去了二次截图的过程,前提使我们必须要知道想要截取哪一块区域并保存
官方示例代码如下:

image

#下面的这段代码就是想要从一页PDF的中心点为起点截取到右下角的区域,截取整张图的1/4.
>>> mat = fitz.Matrix(2, 2)                  # 在每个方向缩放因子2
>>> rect = page.rect                         # 页面的矩形
>>> mp = rect.tl + (rect.br - rect.tl) * 0.5 # 矩形的中心
>>> clip = fitz.Rect(mp, rect.br)            # 我们想要的剪切区域
>>> pix = page.getPixmap(matrix = mat, clip = clip)

image.gif

实际用到的例子是:
整张图片导出之后是1056x816,但是我想要的是这张图片最底部的部分1056x75,相当于PDF文档的页脚部分。

import sys, fitz, os, datetime

def pyMuPDF_fitz(pdfPath, imagePath):
    startTime_pdf2img = datetime.datetime.now()#开始时间

    pdfDoc = fitz.open(pdfPath)
    for pg in range(pdfDoc.pageCount):
        page = pdfDoc[pg]
        rotate = int(0)
        # 每个尺寸的缩放系数为1.3,这将为我们生成分辨率提高2.6的图像。
        # 此处若是不做设置,默认图片大小为:792X612, dpi=96
        zoom_x = 1.33333333 #(1.33333333-->1056x816)   (2-->1584x1224)
        zoom_y = 1.33333333
        mat = fitz.Matrix(zoom_x, zoom_y).preRotate(rotate)
        pix = page.getPixmap(matrix=mat, alpha=False)

        if not os.path.exists(imagePath):#判断存放图片的文件夹是否存在
            os.makedirs(imagePath) # 若图片文件夹不存在就创建

        pix.writePNG(imagePath+'/'+'images_%s.png' % pg)#将图片写入指定的文件夹内

    endTime_pdf2img = datetime.datetime.now()#结束时间
    print('pdf2img时间=',(endTime_pdf2img - startTime_pdf2img).seconds)

def pyMuPDF2_fitz(pdfPath, imagePath):
    pdfDoc = fitz.open(pdfPath) # open document
    for pg in range(pdfDoc.pageCount): # iterate through the pages
        page = pdfDoc[pg]
        rotate = int(0)
        # 每个尺寸的缩放系数为1.3,这将为我们生成分辨率提高2.6的图像
        # 此处若是不做设置,默认图片大小为:792X612, dpi=96
        zoom_x = 1.33333333 #(1.33333333-->1056x816)   (2-->1584x1224)
        zoom_y = 1.33333333
        # 缩放系数1.3在每个维度  .preRotate(rotate)是执行一个旋转
        mat = fitz.Matrix(zoom_x, zoom_y).preRotate(rotate) 
        rect = page.rect                         # 页面大小
        mp = rect.tl + (rect.bl - (0,75/zoom_x)) # 矩形区域    56=75/1.3333
        clip = fitz.Rect(mp, rect.br)            # 想要截取的区域
        pix = page.getPixmap(matrix=mat, alpha=False, clip=clip) # 将页面转换为图像
        if not os.path.exists(imagePath):
            os.makedirs(imagePath)
        pix.writePNG(imagePath+'/'+'psReport_%s.png' % pg)# store image as a PNG

if __name__ == "__main__":
    pdfPath = '../path/demo.pdf'
    imagePath = '../path/image'
    #pyMuPDF_fitz(pdfPath, imagePath)#只是转换图片
    pyMuPDF2_fitz(pdfPath, imagePath)#指定想要的区域转换成图片

当然上面这种是综合下来最快的,另外再介绍一种方法pdf2image

2、pdf2image 将PDF转换成图片

pdf2image也是个包装器,真正的转换工具是poppler
GitHub地址:https://github.com/Belval/pdf2image ,上面也有相关的配置说明。

1、安装pdf2image: pip install pdf2image
2、Windows安装配置poppler(这里只介绍Windows,Mac和Linux去上面Github地址里面参考官网)
Windows用户必须为Windows安装poppler (http://blog.alivate.com.au/poppler-windows/),然后将bin/文件夹添加到PATH(开始>输入env>编辑系统环境变量>环境变量...>系统变量>Path)

注意这里配置之后需要重启一下电脑才会生效,不然会报如下错误:
ERROE:FileNotFoundError: [WinError 2] The system cannot find the file specified
During handling of the above exception, another exception occurred:


3、pip install pillow (如果你还没有安装过的话)

from pdf2image import convert_from_path,convert_from_bytes
import tempfile
from pdf2image.exceptions import (
    PDFInfoNotInstalledError,
    PDFPageCountError,
    PDFSyntaxError
)

def pdf2image2(pdfPath, imagePath, pageNum):
  #方法一:
  #convert_from_path('a.pdf', dpi=500, "output",fmt="JPEG",output_file="ok"
  #,thread_count=4)
  #这会将a.pdf转换成在output文件夹下形如ok_线程id-页码.jpg的一些文件。
  #若不指定thread_count则默认为1,并且在文件名中显示id. 这种转换是直接写入到磁盘上的,
  #因此不会占用太多内存。

#下面的写法直接写入到内存,默认是C:\Users\pppp\AppData\Local\Temp\生成的uuid4名字
    images = convert_from_path(pdfPath, dpi=96)
    for image in images:
        if not os.path.exists(imagePath):
            os.makedirs(imagePath)
        image.save(imagePath+'/'+'psReport_%s.png' % images.index(image), 'PNG')

    #方法二:
    images = convert_from_bytes(open('/home/belval/example.pdf', 'rb').read())
    for image in images:
        if not os.path.exists(imagePath):
            os.makedirs(imagePath)
        image.save(imagePath+'/'+'psReport_%s.png' % images.index(image), 'PNG')    

    #方法三,也是最推荐的方法
    with tempfile.TemporaryDirectory() as path:
        images_from_path = convert_from_path(pdfPath, output_folder=path,
 dpi=96)
        for image in images_from_path:
            if not os.path.exists(imagePath):
                os.makedirs(imagePath)
            image.save(imagePath+'/'
+'psReport_%s.png' % images_from_path.index(image), 'PNG')
        print(images_from_path)

以下是参数定义:

convert_from_path(pdf_path, dpi=200, output_folder=None, first_page=None, last_page=None, fmt='ppm', thread_count=1, userpw=None, use_cropbox=False, strict=False, transparent=False, single_file=False, output_file=str(uuid.uuid4()), poppler_path=None)

convert_from_bytes(pdf_file, dpi=200, output_folder=None, first_page=None, last_page=None, fmt='ppm', thread_count=1, userpw=None, use_cropbox=False, strict=False, transparent=False, single_file=False, output_file=str(uuid.uuid4()), poppler_path=None)

pdf_path --> 要转换的PDF文档路径
dpi -->DPI中的图像质量(默认为200),Windows默认为96dpi
output_folder --> 将生成的图像写入文件夹(而不是直接写入内存)若是path不做指定的话,path的默认地址是:C:\Users\pppp\AppData\Local\Temp\生成的uuid4。
first_page --> 从哪一页开始转换,默认是PDF的第一页
last_page -->转换到哪一页,默认是PDF的最后一页
fmt --> 输出图像格式默认格式是ppm,还可以设置为png和jpeg等
thread_count --> 允许生成多少个线程进行处理,一般不超过4个线程;
userpw --> PDF的密码(若有密码的话需要添加)
use_cropbox -->使用cropbox而不是mediabox
strict --> 参数允许您使用自定义类型PDFSyntaxError捕获pdftoppm语法错误
transparent --> 参数允许生成没有背景的图像,而不是通常的白色图像(为此需要pdftocairo)
single_file --> 使用pdftoppm / pdftocairo中的-singlefile选项
output_file --> 输出文件名是什么
poppler_path --> 查找poppler二进制文件的路径,允许用户使用poppler_path指定poppler的安装路径;默认不指定的话需要将bin添加到系统PATH

pdf2image应该也可以对指定区域进行截取,暂时还没详细研究其方法,因为已经找到更快的方法解决问题了,对比如下所示:

3、 比较PyMuPDF和pdf2image

以下是对一份75页的PDF,输出DPI=96的时间性能对比,pdf2image使用的是默认线程数,下面的对比并没有设置多线程,使用多线程会快一点,当线程数设为5的时候,速度是9秒。

可以看出使用pyMuPDF_Fitz明显快一倍多,最终选取了这种方式。

4、Wand将PDF转换成图片

和pdf2image一样,wand都是包装接口(bindings),而实际进行转换的工具是ImageMagick.
Wind官网:http://docs.wand-py.org/en/0.5.6/

ImageMagick: https://imagemagick.org/script/download.php#windows

from wand.image import Image

filename="somefile.pdf"

with(Image(filename=filename, resolution=120)) as source: 
    images = source.sequence
    pages = len(images)
    for i in range(pages):
        n = i + 1
        newfilename = filename[:-4] + str(n) + '.jpeg'
        Image(images[i]).save(filename=newfilename)

由于问题已经解决,而且性能也还不错,就没有具体去研究Wind这种方式了,感兴趣的可以去看看。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容