
1. 从“找图”到“改图”PDF图片处理的真实需求最近在整理一批技术文档里面混杂着大量截图和图表。老板要求我把所有图片都单独拎出来归档再把文档里一些过时的示意图替换掉顺便把几个水印logo删干净。听起来像是设计干的活但量太大手动一页页处理根本不现实。我第一个想到的就是Python毕竟这种重复性劳动正是脚本的用武之地。网上搜了一圈发现处理PDF的库不少但论速度和底层操作能力PyMuPDF也就是fitz确实是个狠角色。它不像一些库只做表面功夫而是能直接触及PDF的内部结构提取图片、删除对象、甚至替换内容都能在底层完成效率非常高。如果你也经常需要批量处理PDF中的图片资源——无论是为了内容分析、文档瘦身还是自动化更新——那么直接操作PDF内部结构的PyMuPDF会比那些先转成图片再处理的方式高效和精准得多。2. 为什么是PyMuPDF核心优势与工作原理剖析面对PDF处理我们有很多选择比如PyPDF2、pdfplumber或者更高层的报告生成库如ReportLab。但当你需要精准定位并操作PDF中的每一个独立对象尤其是图片时PyMuPDF的优势就凸显出来了。2.1 与其它库的对比精准操作 vs. 页面渲染很多PDF库的工作逻辑是基于“页面”的。它们把一页PDF当作一张图片或者一个文本流来处理。比如你要提取图片它们可能需要先将整个页面渲染成一张位图然后再用图像识别的方法去“找”图这种方法不仅慢而且不准确容易把背景、文字阴影等误判为图片。PyMuPDF则不同。它直接解析PDF的文件结构。一个PDF文件本质上是由一系列对象Object组成的这些对象包括字体、路径、图像、表单等它们被组织在一个复杂的树状结构中。PyMuPDF的fitz模块让你能直接访问这个对象树。当它说“找到一张图片”时它是真的在PDF内部找到了一个类型为XObject且子类型为Image的底层对象并直接获取了这个对象的原始数据流。这就是为什么它的提取是无损且精准的。2.2 PyMuPDF处理图片的核心机制理解下面几个关键概念能让你更好地使用PyMuPDF页面Page与显示列表Display List 当你用page doc[page_number]获取一个页面时PyMuPDF会解析该页面的内容流。page.get_images(fullTrue)这个方法实际上就是遍历页面的显示列表找出所有被引用的图像对象。交叉引用表XRef与图像对象 PDF中的图片不是一个单独的文件而是作为一个资源对象嵌入在文件中。page.get_images()返回的列表里每一项都包含了定位这个图像对象所需的关键信息xref。这个xref交叉引用编号就是该图像对象在PDF文件中的唯一身份证号。直接提取与像素操作 通过xref我们可以用doc.extract_image(xref)直接拿到图像的原始二进制数据、扩展名、色彩空间等信息。这意味着我们得到的是嵌入时的原始图片如JPEG、PNG而不是从PDF渲染出来的、可能被压缩或转换过的像素图。这种底层访问能力使得“删除”和“替换”成为可能。删除就是从页面的内容流里移除对该图像对象的引用替换则是创建一个新的图像对象并让页面内容流改为引用这个新对象。整个过程不涉及对页面其他部分的重新渲染因此速度快且能保持文档其他内容尤其是文本和矢量图形的原始质量。3. 实战第一步精准提取PDF中的所有图片理论讲完我们直接上代码。提取图片是后续所有操作的基础。3.1 环境准备与基础代码框架首先确保安装了PyMuPDFpip install PyMuPDF。这里有个小坑要注意导入包时用的是import fitz而不是import pymupdf。import fitz # PyMuPDF import os def extract_images_from_pdf(pdf_path, output_folder): 从PDF中提取所有图片并保存到指定文件夹。 参数: pdf_path: PDF文件路径。 output_folder: 图片输出文件夹路径。 # 创建输出文件夹 if not os.path.exists(output_folder): os.makedirs(output_folder) # 打开PDF文档 doc fitz.open(pdf_path) # 用于统计和生成唯一文件名 image_count 0 # 遍历每一页 for page_num in range(len(doc)): page doc[page_num] # 获取当前页的所有图片信息列表 image_list page.get_images(fullTrue) # 遍历当前页的每张图片信息 for img_index, img_info in enumerate(image_list): # img_info 是一个元组其中第0个元素是图片的xref xref img_info[0] # 通过xref提取图片的完整信息 base_image doc.extract_image(xref) if base_image: image_bytes base_image[image] # 图片的二进制数据 image_ext base_image[ext] # 图片扩展名如 jpeg, png, bmp # 生成唯一文件名页码_图片索引.扩展名 image_filename fpage_{page_num1}_img_{img_index1}.{image_ext} image_path os.path.join(output_folder, image_filename) # 保存图片 with open(image_path, wb) as img_file: img_file.write(image_bytes) image_count 1 print(f已保存: {image_path}) doc.close() print(f提取完成共找到 {image_count} 张图片。) # 使用示例 if __name__ __main__: extract_images_from_pdf(你的文档.pdf, ./extracted_images)3.2 关键步骤解析与避坑指南运行上面的代码大部分情况都能成功。但实际项目中你可能会遇到下面几个问题page.get_images(fullTrue)中的full参数 这个参数至关重要。如果设置为False默认方法只会返回“简单”图像通常是内嵌的位图。而fullTrue会返回所有图像对象包括作为遮罩Mask或SMask软遮罩用于透明PNG的复杂图像。对于需要完整提取的场景务必使用fullTrue否则可能会漏掉一些图片特别是带透明背景的PNG图。图片重复提取问题 同一个图片对象同一个xref可能在PDF的多处被引用。page.get_images()是按页面遍历的所以这个图片会在每一处引用它的页面都被“找到”一次。上面的代码会将其提取并保存多次生成多个文件副本。如果你只想保存唯一的图片文件可以建立一个xref到文件名的映射字典来去重。extracted_xrefs {} # 用于记录已提取的xref # ... 在提取循环内 ... if xref not in extracted_xrefs: # ... 执行提取和保存 ... extracted_xrefs[xref] image_filename else: print(fXRef {xref} 的图片已提取过跳过。)提取到的图片打不开doc.extract_image(xref)返回的ext扩展名是PyMuPDF根据图像流数据判断的。绝大多数情况下是准确的jpeg, png, jpx, bmp等。但极少数情况下PDF内嵌的可能是某种私有格式或编码异常的图片。如果保存后的文件无法用常规看图软件打开可以尝试强制用.png或.jpg后缀保存或者用PILPillow库从二进制数据直接打开看看。from PIL import Image from io import BytesIO try: img Image.open(BytesIO(image_bytes)) img.save(image_path) # PIL会自动识别格式保存 except Exception as e: print(f图片 {xref} 可能格式特殊: {e})4. 进阶操作从PDF中彻底删除指定图片删除图片的需求也很常见比如去除水印、清理不必要的装饰图。这里的关键是“彻底”——不仅要让它在页面上看不见最好还能从文件结构里移除以减小文件体积。4.1 基于视觉位置删除图片最直观的需求是“删除第一页左上角那个Logo”。这需要结合图片的位置信息。def delete_image_by_bbox(pdf_path, output_path, target_page_num, target_bbox): 删除指定页面中位置在目标边界框内的图片。 注意此方法通过位置匹配可能不精确适用于位置独特的图片。 参数: pdf_path: 输入PDF路径。 output_path: 输出PDF路径。 target_page_num: 目标页码从0开始。 target_bbox: 目标区域格式为 (x0, y0, x1, y1)。 doc fitz.open(pdf_path) page doc[target_page_num] # 获取页面所有图片信息需要位置信息所以用get_image_rects # 注意get_image_rects 返回的是(xref, bbox)的列表 image_rects page.get_image_rects() images_to_remove [] for xref, bbox in image_rects: # 判断图片的边界框是否与目标区域有交集或完全在内部 # 这里使用简单的交集判断你可以根据需要调整逻辑如判断中心点 if bbox.intersects(target_bbox): images_to_remove.append(xref) if images_to_remove: # 获取页面的底层对象PyMuPDF的页面对象由一组指令组成 # 我们需要修改这些指令来移除对图片的引用 page.clean_contents() # 可选合并内容流使操作更稳定 page.wrap_contents() # 可选将内容包装在一个新的XObject中防止影响其他页面 # 遍历页面对象寻找并移除对目标xref的引用 # 这是一个底层操作需要遍历页面的显示列表 for xref in set(images_to_remove): # 用set去重 # 方法通过重新绘制页面但跳过指定的图片 # 更直接的方法是操作页面的_getContents()但较复杂。 # 一个更实用的方法是使用page.add_redact_annot模拟“擦除”但这不是真正的删除。 print(f尝试删除XRef: {xref}) # 注意直接操作内容流删除对象引用是高级且危险的操作。 # 对于大多数“删除”需求更安全的方法是“用空白覆盖”。 # 保存文档 doc.save(output_path, garbage3, deflateTrue) # garbage3 清理未引用对象 doc.close() print(f操作完成文件已保存至: {output_path})注意 上面的代码展示了思路但直接通过位置精准删除一个底层图像对象非常复杂因为一个图片对象可能被多个指令引用。更常见且安全的做法是使用“红色标注Redaction”覆盖或者用空白矩形覆盖该区域。PyMuPDF的page.add_redact_annot方法可以添加一个红色标注区域然后page.apply_redactions()会真正用空白覆盖它并可以选择性地移除底层对象。4.2 基于XRef直接删除图片更底层如果你已经通过get_images()知道了要删除的图片的xref并且确定它只在一处被引用可以尝试更直接的方法从页面的内容流中移除对该xref的绘制指令。这需要对PDF的内容流语法有一定了解。def delete_image_by_xref(pdf_path, output_path, target_xref): 尝试从PDF中删除指定的图片对象通过xref。 警告此操作较为底层可能破坏PDF结构务必先备份。 doc fitz.open(pdf_path) # 方法遍历所有页面查找并清空对该xref的引用 for page in doc: # 获取页面的原始内容流 cont page.get_contents() if cont: # 内容流是字节数据。我们需要找到绘制该图片的指令。 # PDF中绘制图像的指令通常是 q ... /ImX Do Q其中ImX是资源名。 # 找到资源名和xref的映射关系非常复杂。 # 因此对于大多数用户不推荐直接操作内容流。 pass # 一个更可行的方案是在保存时让PDF处理器“忘记”这个对象。 # 通过设置 garbage3 和 deflateTrue并在保存前确保没有页面引用它 # 该对象可能会被作为垃圾回收。但前提是它的所有引用都被移除。 # 最稳妥的“删除”依然是覆盖。 # 安全做法用白色矩形覆盖该图片可能出现的位置如果你知道位置 # for page in doc: # image_rects page.get_image_rects() # for xref, bbox in image_rects: # if xref target_xref: # # 绘制一个白色矩形覆盖原图区域 # page.draw_rect(bbox, color(1,1,1), fill(1,1,1), overlayTrue) doc.save(output_path, garbage3, deflateTrue) doc.close()核心建议 对于“删除”操作如果你的目标是让图片在视觉上消失并减小文件体积最有效且安全的工作流是用page.get_image_rects()定位图片的位置边界框。用page.draw_rect()在该位置绘制一个与背景色相同的矩形进行覆盖overlayTrue。保存时使用garbage3和deflateTrue参数。这样如果被覆盖的图片对象不再被任何内容引用它将在保存过程中被自动清理掉。5. 核心挑战无损替换PDF中的图片替换图片是三个操作中最难的一个。目标不仅仅是放一张新图上去还要保持文档的排版、文字流、其他图片元素完全不变。这需要精确的“外科手术”。5.1 替换的原理与步骤拆解PDF中的图片替换本质上是一个“删除旧引用添加新引用”的过程。但由于PDF内容的不可变性我们通常不直接修改旧对象而是在PDF中插入一个新的图片对象。找到页面上绘制旧图片的指令。修改这些指令让其改为绘制新的图片对象。PyMuPDF提供了相对高级的接口来简化这个过程核心方法是Page.insert_image()。但要注意insert_image是“插入”一张新图片到指定位置而不是“替换”原有的。因此我们的策略是先插入新图到完全相同的矩形位置再尝试覆盖或隐藏旧图。def replace_image_in_pdf(pdf_path, output_path, target_page_num, target_bbox, new_image_path): 用新图片替换PDF中指定位置区域的旧图片。 策略在相同位置插入新图并用白色矩形覆盖旧图区域如果旧图还在。 参数: pdf_path: 输入PDF路径。 output_path: 输出PDF路径。 target_page_num: 目标页码从0开始。 target_bbox: 旧图所在区域的边界框 (x0, y0, x1, y1)。 new_image_path: 新图片文件路径。 doc fitz.open(pdf_path) page doc[target_page_num] # 步骤1在精确的bbox位置插入新图片 # rect参数决定了图片的位置和缩放。将新图插入到旧图相同的矩形框。 # 如果希望保持宽高比需要计算。这里假设直接填充矩形。 page.insert_image( recttarget_bbox, # 替换的目标区域 filenamenew_image_path, # 新图片路径 overlayFalse # 关键设为False新图作为底层插入 ) # 此时页面上可能有新旧两张图重叠。 # 步骤2尝试“隐藏”旧图如果它仍然可见 # 由于我们无法轻易删除旧图的绘制指令一个可靠的方法是用背景色覆盖旧图区域。 # 前提是你知道背景色。假设是白色。 page.draw_rect(target_bbox, color(1,1,1), fill(1,1,1), overlayTrue) # overlayTrue 确保这个白色矩形画在最上层遮住下面的旧图。 # 因为新图是 overlayFalse 插入在底层的所以它会被白色矩形遮住吗 # 不draw_rect的overlayTrue是相对于当前页面内容。我们需要调整顺序。 # 更正确的顺序先画白矩形覆盖旧图再插入新图。 # 所以更稳健的代码逻辑应该调换顺序 # 1. page.draw_rect(... overlayTrue) # 覆盖旧图 # 2. page.insert_image(..., overlayFalse) # 在底层插入新图但会被白矩形盖住矛盾了。 # 实际上一个更简单粗暴但有效的方法是 # 直接用一个与背景色相同的矩形“擦除”该区域然后插入新图。 # 但这样会丢失该区域可能存在的其他内容如文字。 # 因此最通用的方法是先插入新图然后确保新图所在的层在旧图之上。 # PyMuPDF的图层顺序由操作顺序决定吗是的后绘制的内容在上层。 # 所以我们可以 # 1. 插入新图overlayFalse。 # 2. 此时新图在旧图之下。我们需要把新图“提”上来。 # 3. 没有直接提层的API。所以换个思路先插入新图overlayTrue让它在上层但这样会盖住该位置可能存在的文字。 print(警告通用无损替换非常复杂需要根据具体文档结构调整策略。) print(当前实现可能不适用于所有情况尤其是图文混排的区域。) # 保存文档 doc.save(output_path) doc.close() print(f替换尝试完成文件已保存至: {output_path}。请仔细检查结果。)5.2 实现精准替换的实战技巧与替代方案从上面的代码可以看出通用的、完美的无损替换在PyMuPDF中是一个挑战。在实践中我总结出以下几种策略你可以根据具体情况选择策略一基于XRef和内容流替换高级、精准但复杂如果你能精确找到绘制旧图片的PDF指令/ImX Do并知道它在资源字典中的名称/ImX理论上可以修改页面的内容流将对该资源名称的引用指向一个新的图片对象。这需要对PDF语法有很深的理解并且使用page.get_contents()和page.set_contents()来操作原始的PDF指令。除非有极端需求否则不推荐普通用户尝试。策略二使用中间层覆盖适用于背景简单的页面这是相对可靠的方法。假设页面背景是纯色比如白色。用page.draw_rect()在目标bbox上画一个白色的、overlayTrue的矩形。这相当于用橡皮擦掉了那个区域的所有东西旧图以及可能存在的文字。然后在同样的bbox位置用page.insert_image()插入新图片同样设置overlayTrue。这样新图片就会画在白色矩形之上看起来就像是替换了。代价是如果那个位置原来有文字文字也会被擦掉。所以这仅适用于图片是独立元素、下方无其他内容的场景。策略三利用PyMuPDF的“注解Annotation”作为容器一个取巧的思路不直接替换页面内容中的图片而是把新图片作为一个“戳记注解Stamp Annotation”添加到同样的位置。注解是浮在页面内容之上的。你可以设置注解为不可打印或者将旧图区域用白矩形覆盖后再添加图片注解。这种方法不修改原始页面内容流更安全但生成的PDF可能不符合某些严格的标准。策略四转换为可编辑格式再处理如果文档结构允许且对保真度要求不是极端高另一个思路是用PyMuPDF或其他工具将目标页面转换为一个高分辨率的图像如PNG。使用OpenCV或PIL等图像处理库在图像上定位旧图区域并用新图替换。将处理后的图像作为新页面插入PDF或替换原页面。 这种方法会丢失PDF的文本和矢量信息将页面变成“图片”适用于最终展示、无需再编辑的场景。我的经验是在真正的生产环境中如果替换需求频繁且要求高我会评估是否在文档生成源头如LaTeX、ReportLab就使用更易于替换的模板或资源管理方式。对于已生成的PDF策略二覆盖法在背景单纯时最常用策略四转图像法是保证视觉效果的最后手段。6. 性能优化与批量处理实战当需要处理成百上千个PDF文件时效率就至关重要了。PyMuPDF本身速度很快但不当的使用方法会成为瓶颈。6.1 高效批量提取与信息记录假设你要从一个文件夹的所有PDF中提取图片并记录每个图片的来源。import fitz import os from pathlib import Path import pandas as pd def batch_extract_images(pdf_dir, output_root_dir): 批量处理文件夹内所有PDF提取图片并保存到以PDF命名的子文件夹中 同时生成一个CSV记录文件。 pdf_dir Path(pdf_dir) output_root_dir Path(output_root_dir) output_root_dir.mkdir(parentsTrue, exist_okTrue) records [] for pdf_file in pdf_dir.glob(*.pdf): print(f正在处理: {pdf_file.name}) doc fitz.open(pdf_file) # 为每个PDF创建一个独立的输出子文件夹 pdf_output_dir output_root_dir / pdf_file.stem pdf_output_dir.mkdir(exist_okTrue) for page_num in range(len(doc)): page doc[page_num] image_list page.get_images(fullTrue) for img_idx, img_info in enumerate(image_list): xref img_info[0] try: base_image doc.extract_image(xref) if base_image: ext base_image[ext] # 生成更友好的文件名 filename fp{page_num1:03d}_i{img_idx1:03d}.{ext} filepath pdf_output_dir / filename with open(filepath, wb) as f: f.write(base_image[image]) # 记录元数据 records.append({ source_pdf: pdf_file.name, page: page_num 1, image_index: img_idx 1, xref: xref, format: ext, saved_path: str(filepath.relative_to(output_root_dir)) }) except Exception as e: print(f 警告提取 {pdf_file.name} 第{page_num1}页图片失败: {e}) records.append({ source_pdf: pdf_file.name, page: page_num 1, image_index: img_idx 1, xref: xref, format: ERROR, saved_path: f提取失败: {e} }) doc.close() # 保存记录到CSV df pd.DataFrame(records) csv_path output_root_dir / extraction_log.csv df.to_csv(csv_path, indexFalse, encodingutf-8-sig) print(f批量处理完成日志已保存至: {csv_path}) return df优化点使用pathlib进行路径操作更安全便捷。为每个PDF创建独立文件夹避免文件名冲突。使用try-except捕获单张图片提取的异常防止一个错误导致整个任务中断。将元数据记录到Pandas DataFrame最后统一写入CSV比逐行写入文件效率高。文件名使用p{page:03d}这样的格式便于排序和查看。6.2 内存管理与大文件处理处理超大PDF或批量处理时内存可能吃紧。及时关闭文档 确保在for循环内或处理完一个文件后调用doc.close()。或者使用with语句上下文管理器。with fitz.open(pdf_path) as doc: # 处理文档 # ... 操作完成后自动关闭避免在内存中累积过多数据 比如批量提取时不要用一个巨大的列表把所有图片的二进制数据都存起来再统一写入。应该提取一张保存一张立即释放内存。使用garbage和deflate参数 在保存修改后的PDF时doc.save(output, garbage3, deflateTrue)中的garbage3会进行积极的垃圾回收删除所有未被引用的对象可以有效减小输出文件的大小。deflateTrue会启用压缩。6.3 并发处理提升速度对于CPU密集型的提取任务特别是图片很多、很大的PDF可以使用Python的concurrent.futures模块进行多进程处理充分利用多核CPU。from concurrent.futures import ProcessPoolExecutor, as_completed def process_single_pdf(pdf_file, output_root): 被并发调用的单个PDF处理函数 # 这里封装上面提到的单个PDF提取逻辑 # ... return result_list # 返回该文件的处理结果列表 def batch_extract_concurrent(pdf_dir, output_root_dir, max_workers4): pdf_files list(Path(pdf_dir).glob(*.pdf)) all_records [] with ProcessPoolExecutor(max_workersmax_workers) as executor: # 提交所有任务 future_to_pdf {executor.submit(process_single_pdf, pdf, output_root_dir): pdf for pdf in pdf_files} for future in as_completed(future_to_pdf): pdf future_to_pdf[future] try: records future.result() all_records.extend(records) print(f完成: {pdf.name}) except Exception as exc: print(f{pdf.name} 处理时产生异常: {exc}) # 合并所有记录并保存 df pd.DataFrame(all_records) df.to_csv(Path(output_root_dir) / concurrent_log.csv, indexFalse) print(并发处理全部完成。)注意 多进程适用于任务相互独立的情况。如果任务需要共享状态或写入同一文件需要小心处理锁和资源竞争。上面的例子是每个进程处理独立的PDF并写入独立的子文件夹最后主进程合并日志这是安全的模式。7. 常见问题排查与调试心得即使按照指南操作也难免会遇到一些棘手的情况。下面是我踩过的一些坑和解决办法。7.1 提取的图片是黑色方块或损坏现象 用extract_image提取保存后图片文件存在但打开是黑的、绿的或者提示损坏。可能原因与解决CMYK色彩空间 PDF中的图片可能是CMYK色彩模式的JPEG。某些简单的图片查看器对CMYK JPEG支持不好。尝试用专业的图像软件如Photoshop、GIMP打开或者用Pillow库转换色彩空间。from PIL import Image from io import BytesIO image_bytes base_image[image] img Image.open(BytesIO(image_bytes)) if img.mode CMYK: img img.convert(RGB) # 转换为更通用的RGB模式 img.save(output_path)带软遮罩SMask的图片 这是PDF中实现透明度的一种方式。主图像对象可能是不完整的需要结合另一个遮罩图像。page.get_images(fullTrue)会返回SMask的xref。处理这类图片需要将主图和遮罩图合并PyMuPDF的extract_image有时能自动处理有时不能。一个变通方法是尝试用page.get_pixmap()渲染包含该图片的整个区域然后裁剪出图片但这会损失原始质量得到的是位图。JBIG2或JPX等特殊编码 虽然PyMuPDF支持提取但你的系统可能缺少对应的解码库。确保PyMuPDF是最新版本。如果不行同样可以考虑用渲染页面再裁剪的备用方案。7.2get_images()返回空列表但页面上明明有图可能原因图片不是标准的Image XObject可能是作为内嵌在内容流中的内联图像Inline Image。get_images()默认不捕获内联图像。可以尝试使用page.get_text(“dict”)解析页面内容在blocks中寻找type: 1图片块的信息但这更复杂。图片被作为页面背景或水印以表单Form XObject的形式存在。你需要检查页面的资源字典中/XObject下的所有对象而不仅仅是/Image。# 获取页面资源字典 resources page.get_images(fullTrue) # 这个主要是图片 # 要获取所有XObject需要更底层的方法 xobjects page.get_xobjects() # 注意这个方法名可能不准确PyMuPDF API可能有变化 # 更通用的方法是遍历显示列表项但API较复杂。遇到这种情况如果只是要视觉上的图片用page.get_pixmap()渲染全页再分析可能是更简单的选择。7.3 替换或删除后文件大小没有变化甚至变大原因 你只是覆盖或隐藏了图片但没有从PDF结构里删除它。旧的图片对象依然嵌在文件中。解决 在调用doc.save()时务必加上garbage3参数。这个参数会触发垃圾回收移除所有不再被任何页面引用的对象。同时deflateTrue会压缩流数据。所以完整的保存命令应该是doc.save(“output.pdf”, garbage3, deflateTrue)。7.4 操作后文本选择或搜索出现问题原因 如果你用画白矩形的方式覆盖了某个区域而这个区域恰好与文字图层有重叠即使文字看起来在图片上方可能会破坏PDF阅读器对文本层的解析。建议 在进行任何覆盖操作前先用page.get_text(“text”)或page.get_text(“words”)检查目标区域是否存在文字。如果存在那么覆盖方案就需要重新评估可能需要采用更精确的、只针对图像对象的替换方案即策略一修改内容流或者接受对文本功能的微小影响。调试时一个非常有用的方法是可视化目标区域。在尝试删除或替换前先用page.draw_rect(bbox, color(1,0,0), fillNone, width2)在目标bbox上画一个红色的边框保存PDF看看这个框是否准确地套住了你想处理的图片。这能帮你确认位置信息是否正确。