带图PDF做RAG问答总翻车?五步流程把图片变成可检索文本
PDF 里的图片一直是做 RAG 知识库问答最容易翻车的区域。你高高兴兴把一份技术资料喂给系统问它“流程图里那个异常分支怎么处理的”结果它只会说“我找到相关页面但页面上没有文字记载”。这背后的问题很简单传统切块流程把页面当作文字流处理图片区域直接被当成矩形空白忽略掉了。我这两年做了不少带图 PDF 的问答项目慢慢摸出了一套相对稳定的处理顺序——先抽图、再审图、再转文本、再合并切块最后才进向量检索。这篇文章就是这份流程的完整复盘按步骤拆开讲每一步为什么会这样做、中间有哪些坑尽量写清楚。适合正在搭知识库问答、或者被扫描件和图文混排 PDF 折磨过的朋友参考。1. 整体设计与思路拆解1.1 为什么传统 PDF 解析流程对图片失效几乎所有的 RAG 管线都会说支持 PDF但大部分所谓的支持只停留在文本层。PDF 本身是一个页面描述格式页面上的文字、图片、矢量路径是不同类型的对象。解析库能提取到文字对象却无法把图片对象“读”成语义内容——它只会告诉你这页有一张图在哪一段坐标区域内尺寸多大像素数据是多少。结果就是包含图片信息的页面在切块后变成了一堆不完整的文字碎片。比如一份带产品架构图的说明文档架构图里明明画着模块之间的依赖箭头和关键节点名这些信息全部存在于图里可文本层没有对应内容。切块之后向量化索引里根本不存在“架构图里节点 A 连接到节点 B”这类语义自然也就无法检索到。更麻烦的是扫描版 PDF。整个页面就是一张高分辨率位图文字只是图像上的一道颜色文本层完全缺失。你在解析时看到的结果是零文字空页或者偶尔抽出一两个水印词。换句话讲传统流程对图片类内容完全失效跟有没有用向量库没有关系问题从文档解析的第一步就开始了。1.2 两种处理路线的选择文本化 vs 多模态编码针对图片内容参与 RAG业内大致有两条路线可选。第一类是“图片文本化”。把每张抽取出来的图片通过 OCR 提取图片中的文字再通过视觉语言模型生成自然语言描述最终让图片退化成一段文字。这段文字和普通文本块一样去切分、向量化、建索引。优点是链路简单调试方便通用向量模型直接就能用对现有 RAG 框架改动最小。缺点是图片描述本身的准确性会直接影响检索质量而且描述模型对复杂图形、密集表格的理解有时会走样。第二类是“多模态向量化”。跳过文字描述直接用支持图片输入的编码模型把图片编码成一个向量向量库里存两种模态的数据。检索时把用户问题转成文本向量再与图片向量做相似度计算。优点是保留更多原始语义不需要经过“翻译”损耗缺点是对向量库和模型资源要求更高而且纯图片检索对中文手写体、复杂表格的支持目前依然有限。我自己在多数项目里是主用文本化路线条件允许时叠加多模态编码做融合召回。原因很实际文本化路线抽出来的描述能直接人肉检查排错成本低同时对既有系统改动小即便换向量库也没太多额外负担。多模态编码则更像一个加分通道用来兜底那些描述生成得不够好的复杂图形。1.3 步骤顺序为什么关键一开始我踩过不少顺序上的坑。有一版流程是先做整页渲染再 OCR也就是把整个 PDF 页面变成一张大图然后交给 OCR 引擎。这种做法对扫描件勉强有效但遇到图文混排页面就很尴尬文字部分重复识别、图片部分被压缩失真、上下文层级完全丢失。另一版是先切文本块再找图片结果图片坐标和文本块坐标对不上合并阶段反复出错。后来我把顺序固定为“五步走”抽取从 PDF 页面维度提取原始内嵌图片保留坐标、页码、尺寸信息。判别区分装饰性图片、内容性图片、扫描页面决定后续处理方式。转化对内容性图片走 OCR 和视觉描述产出可检索的文本。合并把同一页的正文文本与图片描述文本按空间位置关系合并成复合块。索引复合块切分、向量化、入库支撑后续检索问答。这个顺序的核心逻辑是先做物理层解析再做语义层转化。物理层解析只要保证“图能出来、坐标能对上”不涉及语义判断语义层转化则等物理层数据干净了再动手。这样每一层出了问题都可以独立定位不会出现“不知道是解析错了还是描述错了”的糊涂账。2. 核心细节解析与实操要点2.1 图片抽取的坐标与尺寸处理图片抽取是整个流程的地基。地基不稳后面所有环节都会有毒。实操中我用得比较多的是 Python 环境中常见的 PDF 处理库提取页面对象后遍历页面资源里的图片对象读出每个图片对象的矩形区域然后按区域裁剪。这里有两个非常容易忽略的细节。第一个是页面旋转。部分 PDF 页面带有 90 度或 270 度的旋转属性如果你不处理旋转抽出来的图可能是横着躺的OCR 结果自然乱掉。抽取时最好先把页面坐标统一转成最终显示坐标再裁图。第二个是图片的 DPI 和缩放。PDF 里内嵌图片的显示尺寸和像素尺寸经常不一致一张 3000 像素宽的图可能被压缩到页面上半栏显示。如果直接按显示尺寸保存保存出来的图只有几百像素识别效果大打折扣。正确做法是把图片对象按原始像素尺寸导出而不是按页面显示区域导出。还有一类特殊情况是多重嵌套。有些 PDF 由其他工具生成图片对象被嵌在 Form XObject 之类的容器里一层套一层。简单遍历页面资源会漏掉这些嵌套图片需要递归遍历容器对象才能把它们全部找出来。漏图是最隐蔽的问题外表看起来流程跑完了但索引里就是少了一部分关键信息。2.2 OCR 选型与参数细节图片里如果带有大量文字OCR 就是必经之路。现在开源界可选的 OCR 引擎不少我常用的组合是文档预处理配合通用 OCR 模型。无论你选哪个引擎有几个参数一定要盯住。第一个是语言方向检测。中文文档建议开启方向分类不然竖排文字、旋转文字容易变成乱码。第二个是文本框合并阈值。默认设置常常会把同一行文字切成若干碎片导致后面检索单位过碎。把距离较近的文本框按横轴合并能显著提升整句语义的完整性。第三个是置信度阈值。过低会把一堆噪声框进来过高又会滤掉低质量扫描件里的有效信息根据实际语料的扫描质量调节到 0.60.8 之间比较常见。OCR 之后的文本我会额外加一步后处理。这一步主要做三件事清除重复识别产生的连续空格和空白行把阿拉伯数字和英文标点统一成全半角对明显的识别错误词做替换。这些看起来是细节但直接决定了后面切块和向量化的质量。需要说明的是OCR 不能完全替代视觉语义理解。它能认字但认不了图表里的趋势含义、流程图里的箭头关系、界面截图里的布局意图。所以 OCR 输出的是图片的“文字层”视觉模型生成的是图片的“语义层”两者需要配合使用。2.3 图片描述生成与内容判别不是每一张图片都值得进入 RAG 索引。装饰性的背景图、logo、图标进入索引只会增加噪声、浪费存储和检索时间。我一般会做一次内容判别规则大致如下尺寸太小的图直接跳过比如小于 50×50 像素的往往是图标或分隔线。OCR 文本量和语义描述得分都很低的图大概率是装饰图跳过。扫描整页的图片走 OCR 全文识别不再额外生成描述。数据图表、架构图、UML、截图这类图OCR 提取文字后再让视觉语言模型生成一段 24 句的结构化描述。这里必须强调描述内容的写法。很多人让模型生成描述时太随性得到的是“这是一张流程图图中包含多个步骤”这类废话式描述对检索毫无帮助。我后来学到的做法是把描述模板化强制模型按要素输出。比如对架构图描述里必须包括图中包含哪些模块/节点、模块之间的连接或依赖关系、图里出现的全部关键专有名词、这张图想表达的核心结论。模板化成段的好处一是让检索向量落在相对固定的语义空间中命中率更稳定二是便于人工检查一眼能看出哪张图的描述坏了。2.4 切块策略文本与图片描述的合流图片描述生成了OCR 文本也得到了如果这时候直接把它们各自当作独立块扔进向量库又会埋下上下文断裂的隐患。比如同一页有一张架构图和一段文字文字写的是“系统的容错机制由下图中两个节点共同承担”这两者本来是一体的如果被切成互不关联的两个块检索时这句文字的向量很难被问题命中而架构图的描述块又脱离了问题上下文。我的做法是建立“复合块合并”机制。按页面坐标把正文文本和图片描述文本放到同一个页面上用空间位置排序后拼接成一个大文本块再按字符数切分覆盖重叠的窗口。这样处理既保留了图片与正文的相邻性又避免了整页都变成一个块导致检索粒度太粗。切块长度没有标准答案。我实践下来中文场景下 chunk 控制在 500800 个字符左右比较平衡重叠窗口 50100 字符。当然具体要根据内容调整如果一份文档本身是按章节组织的可以先按章节切再在章节内部按段落和图片位置二次切割。3. 实操过程与核心环节实现3.1 环境准备与工具安装整个流程我是在 Python 环境下实现的依赖的库不多但每一步都需要确认版本兼容。基础环境包括处理解析 PDF 的库、图像处理库、OCR 组件、视觉语言模型调用接口或者本地模型。具体依赖在不同操作系统下安装会有细微差异建议先用虚拟环境隔离。处理 PDF 的库安装后最好验证一下能否读取带图片对象的页面。有的版本对某些加密 PDF 或者特殊字体支持不够好提前验证能省去后面排查问题的时间。图像处理库用来做图片的裁切、缩放、格式转换是 OCR 预处理必不可少的环节。OCR 引擎可以按需选择如果只处理印刷体通用 OCR 模型足够了要是涉及手写体或复杂表格就得搭配额外的模型或表格结构识别工具。3.2 步骤一解析 PDF 页面与资源解析阶段的目标不是直接拿到图片文件而是拿到页面元信息与图片对象的索引。我用代码来说明这个步骤的核心思路import pdf def extract_page_images(pdf_path): doc pdf.open(pdf_path) page_img_meta {} for page_num in range(doc.page_count): page doc[page_num] meta_list [] for img_index, img in enumerate(page.get_images(fullTrue)): xref img[0] base_image doc.extract_image(xref) meta_list.append({ xref: xref, width: base_image[width], height: base_image[height], ext: base_image[ext] }) page_img_meta[page_num] meta_list return page_img_meta这段代码把每页出现的图片对象做了一个清单包含原始宽度、高度和格式。这里还没有裁图只是先把对象找出来。注意get_images返回的列表里可能包含重复引用如果一个图片在多个页面出现xref 会是同一个去重逻辑要加上。拿到 xref 列表后可以通过 extract_image 拿到图像字节。不过在这个阶段我通常会多存一份元信息比如该图片在页面上的显示坐标。这个坐标信息对后面的合并步骤极其关键没有它图片描述就不知道该插在页面文本流的哪个位置。3.3 步骤二按坐标裁剪图片解析拿到的是原始图片对象但它不一定是页面上“看起来”的那张图。有些 PDF 会做裁剪变换图片对象本身很大页面上只显示其中一小块。这种情况如果只按原始图导出会把大量无关内容混进 OCR 和描述环节。正确做法是用页面显示矩阵算出图片在页面上的实际可视区域再针对该区域做裁剪。下面是一个简化的处理思路def crop_image_to_display_rect(page, img_xref, base_image): # 获取图片在页面上的变换矩阵和矩形区域 display_rect get_image_rect_on_page(page, img_xref) img_data base_image[image] # 转换字节为可处理的图像对象 pil_img open_image(img_data) # 根据可视区域裁剪 cropped pil_img.crop(display_rect) return cropped至于具体的变换矩阵获取不同 PDF 处理库提供了不同接口关键是务必保证输出图片的宽度、高度与你最终看到的页面区域一致。扫描版 PDF 常常整页就是一张大图这类图片不需要按坐标裁剪直接把整页渲染成图片或者取原图即可。图片裁出来之后统一转换成 RGB 格式DPI 不足 150 的建议先做插值放大。这一步对 OCR 效果影响很大很多低质量扫描件直接识别效果很差放大一两倍之后准确率显著提升。3.4 步骤三图片内容判别与双通道处理图片进入处理环节前先根据尺寸、位置、内容类型做一次分流。我把它设计成一个简单的分类函数def classify_image(meta, ocr_result_length, has_semantic_info): # 小图标直接跳过 if meta[width] 50 or meta[height] 50: return skip # 整页扫描图走 OCR 通道 if meta[width] / meta[height] 1.5 or is_full_page(meta): return ocr_full_page # 有大量文字且有结构的图双通道都走 if ocr_result_length 30 and has_semantic_info: return ocr_and_caption # 纯图形 logo 之类的直接跳过 if not ocr_result_length and not has_semantic_info: return skip # 默认走语义描述 return caption这个分类规则是多年踩坑总结出来的你可以按自己的数据微调。核心思想就一句话不要对所有图片一视同仁地走同一个处理流程先分类再处理效率和质量都会上去。OCR 通道与语义描述通道各自产出的文本在合并阶段再汇入同一页面组合逻辑。为了不至于混淆来源我给每段文本打上了来源标签比如[ocr_page_12_img_3]和[caption_page_12_img_3]。这个标签平时不参与向量化但对人工排查非常有帮助。3.5 步骤四复合块生成与切分得到页面文本和图片描述文本后下一步是合并。我按坐标排序把同一页的文本片段拼起来形成一个接近页面阅读顺序的文本流。图片描述文本插入到对应的图片坐标位置附近而不是追加在页面末尾。合并之后进行切分。下面是核心切分的示意def chunk_document(page_text_blocks, chunk_size800, overlap80): full_text merge_blocks(page_text_blocks) chunks [] start 0 while start len(full_text): end start chunk_size chunks.append(full_text[start:end]) start end - overlap return chunks切分时注意不要切在句子中间。如果切点落在标点符号前尝试往前找到最近的段落标记作为切分点。另外如果某个图片描述文本刚好落在切分边界附近我会强制让它整体保留在同一切块里避免一张图的内容被切成两段导致检索时语义双残。切分后的每个 chunk 都保留了来源页面信息这一步是不可省的管理元数据。检索命中后你可以顺着页面号定位原始文档方便人工验证答案正确性。3.6 步骤五向量化与检索问答复合块生成后用常规文本向量模型进行向量化存入向量库。这一步和正常的 RAG 流程完全一致只是每条记录多了几个元数据字段来源 PDF、页码、块起始位置、是否包含图片描述等。检索时把用户问题向量化在向量库中召回 top-k 块然后把块内容拼接起来交给大模型生成回答。多模态加持的场景我会额外做一次图文混合召回再把两路结果合并重排。不过保证基础文本通道的质量永远是最重要的多模态只是一个辅助增强。一段简化示意如下def rag_query(question, table, llm, embed_model): q_vec embed_model.encode(question) candidates table.search(q_vec, top_k5) context merge_for_prompt(candidates) answer llm.generate(context \n问题 question) return answer到这一步整个流程已经跑通。你可能会发现真正影响问答质量的往往不是最后的生成环节而是前面的图片抽取、内容判别和合并切分。这是我反复强调流程完整性的原因。4. 常见问题与排查技巧实录4.1 扫描件抽出的图片黑边严重扫描版 PDF 有个典型特征页面上除了文字内容之外还有大块黑色或灰色背景。直接 OCR 时这些黑色背景会被识别成无意义的噪声字符甚至把附近正常文字也吞掉。遇到这种情况我通常先做图像预处理转灰度、做自适应二值化、用中值滤波去噪。处理完之后再交给 OCR识别速度和准确率都会有明显改善。如果扫描件背景还有明显歪斜需要先做旋转纠偏别节约这一步倾斜文字会让 OCR 模型非常痛苦。4.2 OCR 结果大量乱码或漏字OCR 乱码的原因通常是三选一原始图片分辨率不够、文字层背景噪声太重、语言参数选错。先检查抽出来的图片是不是原始像素尺寸很多情况是 PDF 解析库只给了显示尺寸导致图片被缩小保存。再检查 OCR 引擎是不是开了正确的语言模型中文文档开成英文模式识别结果基本没法用。如果以上都没问题还乱码那就得考虑用更好的识别模型了。印刷体用通用模型没问题但碰到打字机字体、特殊字体、表格密集排版需要换用带版面分析的模型。这属于模型能力的差距不是流程能解决的。4.3 图片相关的检索命中率低流程跑通了但问答时始终检索不到图片内容这是我被问得最多的问题。排查思路按顺序来第一步确认图片有没有被抽取出来。用脚本写一个统计报表检查 PDF 里图片对象数量与实际处理数量是否一致漏图是概率最高的原因。第二步确认图片描述文本有没有进入向量库。检索一个简单的关键词比如图片里的某个专有名词看看能不能召回。如果召回不了检查切分合并逻辑是否把图片描述块丢掉了。第三步确认向量检索本身。有些向量模型对长文本里嵌入的“图片描述”这类短句不敏感考虑把图片描述块单独加权重或者在召回时额外跑一次关键词匹配。我碰到过最隐蔽的问题是图片描述文本生成得太多太长完全淹没了正文文本导致正文里的关键信息反而排到后面。解决方法是给图片描述块设置单独的分块大小上限比如最多 200 字超出的部分截断。4.4 常见问题对照速查表现象可能原因优先排查动作页面抽出来没有图片图片嵌套在容器对象中递归遍历所有 Form XObject图片识别结果模糊导出的是显示尺寸而非像素尺寸改为按原始像素导出OCR 识别输出乱码语言参数或页面旋转未处理开启方向检测设置正确语言模型描述文本过于空泛未使用结构化模板强制按要素生成描述检索不到图内关键词图片未进入合并切块检查切分边界是否丢弃描述检索结果正文丢失图片描述块占比过大限制图片描述块长度上限同一内容重复索引未做图片对象去重按 xref 去重后再处理这张表基本覆盖了我遇到的八成问题。实际排查时从最上游开始顺着链路一层层过滤别跳步很快就能定位到问题环节。5. 最后分享一点实操体会处理 PDF 图片做 RAG技术栈不难难的是对全流程有把控。我的经验是不要试图用一个超级大模型把 PDF、图片、文本一把梭哈式处理看起来很省事一旦出错毫无头绪。拆成独立的解析、判别、转化、合并、索引步骤每一步都有产出物每一步都能单独检查这才是长期维护知识库问答系统该有的心态。另外一个建议是建立中间产物检查习惯。每次跑完流程随机抽十几页看看图片被抽成什么样、描述生成得是否准确、切块后的文本通不通顺。这套动作十分钟就能完成但能救回来大量质量问题。我自己就是因为一次漏了嵌套图片导致客户问答一直命中不到数据从那以后就再也不敢跳过中间检查了。如果后续你还想让图片直接参与语义匹配可以考虑把多模态编码通道加进来与文本化通道做融合召回。先把文本化基础流程跑稳再去叠加增强路径会顺很多。