PDF转图片再转回PDF时如何兼顾文件大小与画质
我完全懂你的困扰——把PDF转成图片处理后再转回,要么画质糊得没法看,要么文件体积直接翻倍,两头都难顾!咱们可以从几个细节调整你的代码逻辑,来平衡画质和文件大小:
换用JPEG格式保存图片,调整压缩质量
你现在用的PNG是无损格式,体积天生就大。如果你的PDF内容没有透明层,完全可以换成JPEG格式,通过调整压缩质量来平衡画质和体积。比如用cv2.imwrite时加上质量参数,85%的质量在视觉上和原图几乎没差别,但体积能小一大截:cv2.imwrite(f"test_im_{page_number}.jpg", img, [int(cv2.IMWRITE_JPEG_QUALITY), 85])要是非得用PNG,也可以加压缩级别参数
[int(cv2.IMWRITE_PNG_COMPRESSION), 6](0-9级,6是兼顾压缩率和速度的平衡点)。用DPI替代Zoom,更精准控制画质
你之前用的zoom参数其实对应PDF默认的72dpi,zoom=2就是144dpi。不如直接用dpi参数指定画质,比如150dpi是打印级别的清晰程度,比zoom=1的画质好太多,又不会像zoom=2那样让体积暴涨。修改get_pixmap的调用:pix = page.get_pixmap(dpi=150, alpha=False) # alpha=False关闭透明层,进一步减小体积跳过本地文件读写,直接内存处理
你现在先把图片存到本地再插入PDF,不仅慢,还可能因为文件格式的额外开销增加体积。可以用io.BytesIO在内存中处理图片字节流,直接插入到新PDF里,效率更高也更省空间:import io # 处理完img后,转成内存字节流 img_bytes = io.BytesIO() cv2.imencode('.jpg', img, [int(cv2.IMWRITE_JPEG_QUALITY), 85])[1].tofile(img_bytes) img_bytes.seek(0) # 直接插入字节流 page.insert_image(rect, stream=img_bytes)直接复用原PDF的页面尺寸
你之前手动计算img.shape[1]/zoom来得到页面尺寸,不如直接读取原页面的rect参数,这样能保证输出PDF和原文件的尺寸完全一致,避免不必要的缩放误差:original_rect = page.rect new_page = new_pdf_document.new_page(width=original_rect.width, height=original_rect.height) new_page.insert_image(original_rect, stream=img_bytes)
优化后的完整代码
import cv2 import fitz import numpy as np import io def pdf_to_images_and_back(pdf_path: str, dpi=150, img_quality=85): new_pdf_document = fitz.open() with fitz.open(pdf_path) as pdf_document: for page_number in range(len(pdf_document)): page = pdf_document.load_page(page_number) # 用DPI控制画质,关闭透明层减少体积 pix = page.get_pixmap(dpi=dpi, alpha=False) img = np.frombuffer(pix.samples, dtype=np.uint8).reshape( pix.height, pix.width, pix.n ) if pix.n == 3: img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) # 内存中生成JPEG字节流,跳过本地文件 img_bytes = io.BytesIO() cv2.imencode('.jpg', img, [int(cv2.IMWRITE_JPEG_QUALITY), img_quality])[1].tofile(img_bytes) img_bytes.seek(0) # 复用原页面尺寸,保证输出PDF和原尺寸一致 original_rect = page.rect new_page = new_pdf_document.new_page(width=original_rect.width, height=original_rect.height) new_page.insert_image(original_rect, stream=img_bytes) # 启用压缩和彻底的垃圾清理,进一步减小体积 new_pdf_document.save("output_pdf.pdf", deflate=True, garbage=3, clean=True)
最后提个小建议:如果你的PDF是矢量内容(比如文字、线条),转成图片本身就会丢失矢量特性,导致体积变大。如果你的图像处理操作可以直接在PDF矢量层上完成,那会是更优的选择;但如果必须转图片处理,上面的方法应该能帮你兼顾画质和文件大小啦!
备注:内容来源于stack exchange,提问作者Jonas

