ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

pypdf 图片提取完全指南:从 PDF 页面与注解中抽取图像的实战方案

pypdf 图片提取完全指南:从 PDF 页面与注解中抽取图像的实战方案 pypdf 图片提取完全指南从 PDF 页面与注解中抽取图像的实战方案【免费下载链接】pypdfA pure-python PDF library capable of splitting, merging, cropping, and transforming the pages of PDF files项目地址: https://gitcode.com/GitHub_Trending/py/pypdf本文是 pypdf 图片提取Image Extraction功能的实战技术指南。PDF 的每一页都可以包含任意数量的图像资源pypdf 通过PageObject.images这一虚拟列表属性让开发者能以接近普通 Python 列表的方式遍历、按名访问页面中的全部图片并能将图片对象另存为本地文件或进行二次处理。读完本文你将掌握如何安装图片提取所需的可选依赖、用 5 行代码批量导出页面图片、从印章注解Stamp Annotation等非常规位置提取图片以及如何针对结构损坏的 PDF 编写容错的批量提取流程。前置条件安装图片提取所需的可选依赖pypdf 的图片提取依赖 Pillow 图像处理库该依赖属于可选依赖默认安装的 pypdf 并不包含。文档开头即明确指出使用下文代码前必须先安装可选依赖详见安装指南。# 仅安装图片提取所需的 Pillow pip install pypdf[image]如果希望一并安装加密、图片等全部可选功能可以执行pip install pypdf[full]从源码层面看Pillow 是在pypdf/generic/_image_xobject.py中强制导入的该模块第 2733 行执行from PIL import Image, UnidentifiedImageError若未安装则直接抛出ImportError(pillow is required to do image extraction. It can be installed via pip install pypdf[image])。因此只要缺失 Pillow任何图片提取 APIpage.images、decode_as_image、ImageFile.replace都会立即失败而不是静默返回空结果。基础用法遍历并保存页面中的全部图片每个 PDF 页面都可以包含任意数量的图片而图片在 PDF 内部的资源名称例如/Im1、/I0并不保证唯一。pypdf 提供PageObject.images属性返回一个VirtualListImages对象它行为上类似列表但内部是惰性解析的虚拟列表见 pypdf/_page.py 中VirtualListImages类的定义。最基础的提取代码如下出自官方文档from pypdf import PdfReader reader PdfReader(example.pdf) page reader.pages[0] for i, image_file_object in enumerate(page.images): file_name out-image- str(i) - image_file_object.name image_file_object.image.save(file_name)这段代码做了三件事打开 PDF 并取得第 1 页reader.pages[0]遍历该页的全部图片对象page.images是可迭代的Sequence[ImageFile]对每个ImageFile取其 Pillow 图像实例.image字段调用.save()保存到磁盘文件名用序号前缀加以区分避免不同图片同名互相覆盖。ImageFile是一个 dataclass定义于 pypdf/_page.py其核心字段包括字段类型含义namestr图片在 PDF 内部的资源名如Im1.png带扩展名databytes图片原始字节流imagePIL.ImagePillow 解码后的图像对象indirect_referenceIndirectObject指向存储图片流的 PDF 间接对象引用is_inlinebool是否为内联图片BI/EI操作符直接嵌入内容流is_displayedbool是否在页面内容流中被Do操作符实际引用显示⚠️安全警告官方文档明确提示图片在 PDF 中的名称可能包含任意字符。文档原文警告The names can contain arbitrary characters. Please make sure to sanitize them before using them to write the file content to the disk for example.上述示例之所以在文件名前拼接序号out-image-{i}-正是为了避免直接用不可信的name作为文件名导致路径穿越、非法字符等问题。在真实业务中请务必先对name做清洗如过滤/、\、控制字符等再落盘。更丰富的访问方式下标、键名、切片与嵌套图片VirtualListImages除了可迭代外还支持多种索引方式见 pypdf/_page.py 的__getitem__实现# 按整数下标访问 first reader.pages[0].images[0] # 按 PDF 资源键名访问键名以 / 开头 img_i0 reader.pages[0].images[/I0] # 按嵌套路径访问 Form XObject 内部的图片 img_in_form reader.pages[0].images[/TP1, /Image1] # 切片访问返回新的 VirtualListImages subset reader.pages[0].images[0:2] # 查看全部键名可能包含嵌套的 [name1, name2] 列表 keys reader.pages[0].images.keys() # 键值对遍历 for name, image_file in reader.pages[0].images.items(): print(name, image_file.data)这里有一个值得注意的实现细节PDF 页面资源/Resources下的/XObject字典中的条目并不都是图片——/Subtype /Form的 XObject 是表单其内部还可能嵌套图片。pypdf 的_get_ids_imagepypdf/_page.py会递归遍历这些 Form XObject将其内部的图片以[/TPL1, /Image5]这样的键路径暴露出来。因此page.images收集到的不仅是页面直接引用的图片还包括嵌套在表单 XObject 中的图片且去重后保持顺序。同时要注意VirtualListImages是只读的虚拟列表它只提供访问与遍历能力ImageFile对象本身也不应被随意修改——dataclass 文档明确说明This object should not be modified except usingImageFile.replaceto replace the image with a new one.从其他对象提取图片以印章注解为例除了页面内容流PDF 中的一些其他对象也可能包含图片例如印章注解Stamp Annotation。官方文档给出了完整的提取示例from pypdf import PdfReader reader PdfReader(example.pdf) im ( reader.pages[0][/Annots][4][/Parent] .get_object()[/AP][/N][/Resources][/XObject][/Im4] .decode_as_image() ) im.save(out-annotation-image.png)这段代码的调用链较长逐层拆解如下reader.pages[0][/Annots]取得页面注解数组Annotation Array[4]取出第 5 个注解索引从 0 开始这里是一个印章注解[/Parent]取得该注解的父级对象注解可能嵌套在弹出注解Popup或父子关系中.get_object()解析间接引用得到实际的字典对象[/AP][/N]进入注解的外观流Appearance Stream/AP是外观字典/N是普通外观[/Resources][/XObject][/Im4]在外观流资源中定位到名为/Im4的图片 XObject.decode_as_image()将流对象解码为 PIL 图像。其中第 7 步的decode_as_image()是 pypdf 在流对象上提供的通用解码方法定义于 pypdf/generic/_data_structures.py。它的实现要点如下接受可选参数pillow_parameters用于向 Pillow 的Image.save()透传参数内部调用_xobj_to_image位于 pypdf/generic/_image_xobject.py完成滤镜解码、色彩空间转换与 Pillow 图像构造若流对象的/Subtype不是/Image会发出告警日志提示does not seem to be an Image但不会因此中断解码失败时抛出异常官方文档建议捕获异常以避免程序中断见下述错误处理。值得注意的是decode_as_image与page.images背后的图像解析逻辑是同一套底层实现_xobj_to_imageVirtualListImages._get_imagepypdf/_page.py在按名获取图片时同样调用它来产出ImageFile.data与ImageFile.image。区别仅在于入口不同——一个面向任意流对象一个面向页面资源。错误处理面向损坏 PDF 的容错批量提取直接for image_file_object in page.images:迭代时一旦遇到第一个解析失败的图片就会抛出异常整个循环中止。官方文档明确指出Iterating overpage.imagesdirectly will raise an exception on the first issue.如果你面对的是一批或多或少有些损坏的 PDF这在真实生产环境中非常常见但仍希望尽可能多地抢救出图片官方推荐的做法是把枚举键名和取图片拆成两步from pypdf import PdfReader reader PdfReader(example.pdf) for page in reader.pages: for name in page.images.keys(): try: # Try to retrieve actual image. image page.images[name] except Exception as exception: # Handle exceptions. pass这种两步走模式的关键在于page.images.keys()只做结构遍历读取/Resources字典并枚举 XObject 条目通常不会触发流数据解码而真正的解码、滤镜处理发生在page.images[name]这一步。将取图操作放进try/except后单个坏图只会让该条目返回异常被吞掉循环得以继续处理后续图片。在此基础上你还可以针对异常类型做更精细的处理例如from pypdf import PdfReader from pypdf.errors import EmptyImageDataError, PdfReadError reader PdfReader(example.pdf) saved 0 failed 0 for page in reader.pages: for name in page.images.keys(): try: image_file page.images[name] image_file.image.save(fsaved-{saved}-{image_file.name}) saved 1 except (EmptyImageDataError, PdfReadError) as exc: failed 1 print(f跳过损坏图片 {name}{exc}) except Exception as exc: # 其他未知错误 failed 1 print(f处理图片 {name} 时出错{exc!r}) print(f成功导出 {saved} 张跳过 {failed} 张)pypdf 的图片解析代码中定义了一组专门面向异常场景的防护措施例如 pypdf/generic/_image_xobject.py 定义了MAX_IMAGE_MODE_NESTING_DEPTH 10当色彩空间ColorSpace嵌套过深时抛出PdfReadErrorEmptyImageDataError则用于图像数据为空的情况。这些异常类都可以被上述容错代码捕获并跳过而不会拖垮整个提取任务。进阶能力图片替换与内联图片ImageFile还提供了replace()方法pypdf/_page.py允许用一张新的 PIL 图像替换 PDF 中已有的图片适用于不重建 PDF 就替换其中图片的场景。其使用限制值得注意不能替换内联图片is_inlineTrue无间接引用图片必须属于PdfWriter即通过写入流程生成的文档PdfReader中读取的图片不可替换new_image必须是 PILImage实例可选**kwargs会透传给Image.save()例如quality等参数。此外VirtualListImages暴露的图片分为三类由_parse_images_from_content_stream区分见 pypdf/_page.py内联图片Inline Image通过BI ... ID ... EI操作符直接嵌入内容流is_inlineTrue、is_displayedTrue、indirect_referenceNoneDo 引用的图片 XObject在内容流中通过Do操作符绘制is_inlineFalse、is_displayedTrue仅存在于资源中的图片 XObject只在/Resources中定义、未被页面实际引用is_displayedFalse。page.images会把这三种来源的图片统一去重合并返回若你只关心内联图片可用is_inline字段过滤旧属性PageObject.inline_images已标记弃用计划在 pypdf 7.0 移除见 pypdf/_page.py。验证与测试仓库中的图片提取测试用例pypdf 仓库在 tests/test_images.py 中提供了丰富的图片提取测试是理解该功能行为边界的绝佳参考test_image_new_property约 tests/test_images.py验证page.images.keys()返回的键名列表含嵌套表单内的图片路径如[/TPL2, /Image53]、page.images[0].name I0.png、image.format JPEG等行为并断言images[b0]抛TypeError、images[9999]抛IndexErrortest_get_image_keyerror_for_non_image_xobjecttests/test_images.py访问类型为 Form 而非图片的 XObject 会抛KeyError——即page.images[/TPL1]会失败因为/TPL1是表单而不是图片test_image_extractiontests/test_images.py对多种真实样例 PDFpdflatex 生成的图片 PDF、base64 编码图片、灰度图片等逐一验证提取出的图像与期望 PNG/JPG 的相似度 ≥ 0.99image_similarity辅助函数tests/test_images.py基于均方误差MSE计算图像相似度可作为你自己测试提取结果的参考实现。这些测试同时印证了本文的要点page.images支持整数、字符串、嵌套元组/列表三种取图方式非图片 XObject 会抛KeyError提取结果可通过 Pillow 直接比较验证。小结pypdf 的图片提取功能围绕三个核心 API 展开PageObject.images页面图片虚拟列表、ImageFile图片对象含name/data/image字段、StreamObject.decode_as_image()任意流对象解码为图片。实操时牢记三条原则先装pypdf[image]可选依赖、对图片名称做清洗再落盘、对批量任务采用先枚举键名、再逐图 try/except的容错模式即可稳定应对大多数真实场景包括嵌套表单图片与印章注解等非常规来源的图片。【免费下载链接】pypdfA pure-python PDF library capable of splitting, merging, cropping, and transforming the pages of PDF files项目地址: https://gitcode.com/GitHub_Trending/py/pypdf创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表