RAG 项目做久了,你会发现一个跑不掉的坎:知识库里不只有 Word 和纯文本,大量业务资料是 PDF、扫描件、截图、产品手册、合同扫描版,甚至还有带着印章的发票照片。上一篇文章聊了文本类数据的导入和清洗,这次专门解决图文和 PDF 怎么进 RAG 的问题,重点拆 OCR、多模态大模型以及九种 PDF 解析工具的选型。内容全部来自我这几年实际跑过的数据导入项目,包含真实踩坑和最后的落地链路,正在搭知识库或者被“图片上的字提取不出来”卡住的朋友,这篇可以直接抄作业。
1. 图文数据到底要不要进 RAG:先想清楚场景再选技术
很多人一上来就问“RAG 知识库能存储图片嘛”,我的回答是:能,但你要先搞清楚,你是想“存图片”还是想“让模型理解图片内容”。这两个需求对应的技术方案完全不同,预算和复杂度也差一个数量级。
1.1 RAG 知识库能存储图片嘛:先区分“存图片”和“理解图片”
传统 RAG 的链路是“文档 → 文本切分 → 向量化 → 检索 → 大模型生成”,图片在这个链路里基本是死路一条。因为常规的文本 Embedding 模型(比如 bge、text-embedding-ada-002)根本不接收图像输入,你就算把图片 Base64 塞进去,模型也只当它是乱码。所以很多团队说“图片进不了知识库”,本质上是 Embedding 层不支持图像,而不是知识库软件不支持附件存储。
要真正让图片参与检索,有两条路。
第一条是“图文分离”:把图片里的文字用 OCR 提出来,图片本体扔到对象存储或者数据库的 BLOB 字段里,OCR 出来的文本块正常进入向量库。检索时命中文本块,再通过元数据关联回原图。这条路成本低,现有架构完全不用动,适合大部分企业场景。
第二条是“多模态 Embedding”:直接用支持图文联合建模的向量模型(比如 CLIP 系、UNITER 系,以及最新的多模态大模型自带的向量化能力),把图片和文本映射到同一个向量空间。查询“发票上的税额总计”时,系统既检索文本,也检索图片本身。这条路效果上限高,但模型部署成本、检索服务复杂度和硬件要求都上来了。
我先说结论:如果你只是希望知识库能回答“合同第三页写了什么”“这份手册的架构图讲的是啥”,走图文分离就够。如果业务场景是“用户上传一张照片,让系统在图库中找相似图片再回答问题”,那才需要考虑多模态 Embedding。
1.2 不上多模态,先做“图文分离”照样能解
我在实际项目里发现一个规律:大部分企业的资料,图里的信息和图周边的文字是高度相关的。比如一份设备手册,某一页左侧是维修流程图,右侧就是文字步骤说明;一张财务报表截图,表上方往往是报表标题和编制单位。这种情况下,OCR 提取出的文字本身具有独立的检索价值,并不一定非要做图像理解。
具体做法是这样的:
- PDF 里嵌的图片区域,先通过版面分析识别出来,截取成独立图片;
- 图片送去 OCR,提取文字、坐标、表格结构;
- OCR 结果作为文本块进向量库,元数据里记录“来源文件”“页码”“图片坐标”“图片存储路径”;
- 原图按文件哈希命名存起来,在 Web 端展示答案时,如果有图片关联,就把原图一起渲染出来。
这套方案我跑过不下十个项目,问答准确率能覆盖 80% 以上的需求,而且不需要升级模型、不需要额外买 GPU,性价比极高。这里也回应一下热词里那个“rag知识库能存储图片嘛”——能,而且最实用的一种存储方式就是:图片本体存对象存储,图片内容存向量库,两者靠元数据关联。
2. OCR 选型:三条技术路线的取舍
OCR 是图文进 RAG 的第一道门。我把市面上的方案归纳成三条路线:传统开源引擎、商用云 OCR、多模态大模型 OCR。三者不是替代关系,而是分层关系,关键看你的文档类型、预算和对延迟的容忍度。
2.1 传统 OCR 引擎:Tesseract 与云厂商 OCR 的对比
Tesseract 是绕不开的免费方案。我用它处理过大量英文扫描件,识别率尚可,但一碰到中文、表格和低分辨率图片就明显吃力。中文识别需要用 chi_sim 语言包,同时还要做图像预处理,比如转灰度、二值化、去噪点,否则准确率会掉到惨不忍睹。当初有同事吐槽“Tesseract 就是给英文 PDF 准备的”,虽然偏激,但也不无道理。
云厂商 OCR 是 Tesseract 的主要替代方案。百度 OCR、腾讯 OCR、阿里 OCR 的通用文字识别和表格识别能力差距不大,核心优势是版式复杂时依然稳定,而且有现成的表格还原、印章检测、身份证识别等专项能力。代价是收费、有 QPS 限制,部分敏感数据不允许出域。
我拿同一张增值税发票做过对比测试:Tesseract 4.1 + chi_sim 的字符级准确率大约 82%,表格线全乱;百度 OCR 的高精度版准确率 96% 以上,表格结构基本还原。如果你的业务涉及财务票据、合同扫描件这种高标准场景,云 OCR 省下来的清洗时间,往往比那点接口费值钱得多。
热词里有人问“百度 OCR 返回 file format error”,这就是典型的不规范调用。百度 OCR 接口要求图片必须是 Base64 编码且不能带 data URI 前缀,很多人直接把前端上传的 Base64 字符串原样传过去,或者传了纯二进制,自然报错。正确做法是后端收到文件后重新编码一次,并去掉前缀。
2.2 PaddleOCR/PaddleX:开源里的最优解
如果既要本地部署、又想识别中文和表格,PaddleOCR 是我用下来最稳的开源方案。它的架构比 Tesseract 领先很多,自带版面分析、表格结构识别、公式识别,还能通过 PaddleX 这套流水线框架快速组装。
PaddleX 的用法很简单,用 pipeline 方式直接调用:
from paddlex import create_pipeline pipeline = create_pipeline("OCR") result = pipeline.predict("scan_page.jpg") for item in result: print(item["text"], item["score"])注意这里的坑:PaddleOCR 的语言模型默认是中文 + 英文,如果你扫描的是韩文、日文或者越南文,必须显式指定语言模型,否则识别结果就是空或者乱码。网上有人问“以下 OCR 代码识别不了韩文”,多半就是没加载韩文识别模型。正确做法是用 PaddleOCR 的lang="korean"参数,或者下载对应语言的识别模型后手动指定。
部署层面,PaddleOCR 在 CPU 上跑一张普通 A4 扫描件大概 2~5 秒,GPU 上可以压到 300~800 毫秒。如果批量导入阶段对延迟不敏感,CPU 完全够用;如果要做实时问答、用户频繁上传图片,建议至少上一张消费级 GPU。
另外强烈建议开启 PaddleOCR 的文本方向分类器。很多手机拍摄的文档是旋转过的,不纠正方向,识别准确率直接砍半。
2.3 多模态大模型做 OCR:一次破局尝试
传统 OCR 的瓶颈很明显:复杂版面、图表、手写体、印章遮挡,这些都是规则模型的死穴。多模态大模型(Qwen-VL、GLM-4V、GPT-4V 等)则直接把图片当作输入,让模型以“看图说话”的方式输出结构化内容。
举个例子,传统 OCR 面对一张包含饼图和图例的页面,只能把图上的文字零散提取出来,完全丢失“哪块区域代表什么比例”的结构关系。但多模态大模型可以一句话输出:
{ "type": "pie_chart", "title": "2025年营收构成", "segments": [ {"label": "软件服务", "value": "62%"}, {"label": "硬件销售", "value": "24%"}, {"label": "技术支持", "value": "14%"} ] }这种结构化输出直接可以被 RAG 落库,检索时按字段命中,答案质量比纯文本高一个档次。我拿一批手写报销单做过测试,传统 OCR 的准确率不足 60%,而多模态大模型在提示词约束下基本能提取出关键字段,只是偶尔有金额数字看错。
实际选型时,我常用“分层策略”:清晰印刷体走 PaddleOCR 或云 OCR,便宜又快;复杂图表、手写体、流程图这类传统 OCR 搞不定的,再花高价调用多模态大模型。这样既控成本,又保效果。
3. PDF 解析:九种工具选型全景
PDF 是所有文档类型里最磨人的。热词里一大堆“pdf 转换”“pdf 转 word”“pdf 编辑器”的搜索,说明大家都被 PDF 搞烦了。但 PDF 麻烦的根源不是格式本身,而是它有三种截然不同的“皮肤”,每种皮肤需要的解析工具完全不同。
3.1 先认清 PDF 的三种“皮肤”
第一种是文本型 PDF,文字可以直接选中、复制,占多数电子文档的 60% 左右。解析这类 PDF,核心是从内容流里提取文本和布局。
第二种是扫描型 PDF,本质是一张或一堆图片套着 PDF 外壳,文字完全无法选中。解析这类 PDF,必须走“渲染成图片 → OCR”的链路。
第三种是混合型 PDF,部分页面有文本层、部分页面是扫描图,最常见的就是“文字说明 + 附件扫描合同”组合文件。解析时既要分层处理,还要保证顺序不乱。
九种工具之所以没有银弹,就是因为每种工具只擅长对付一种皮肤。
3.2 九种 PDF 解析工具逐个说
我按实际使用频率和适用场景,把这几年真正用过的九种工具盘了一遍,整理成一张对比表。
| 工具 | 适用场景 | 输出质量 | 速度 | 成本 | 推荐指数 |
|---|---|---|---|---|---|
| PyMuPDF (fitz) | 文本型 PDF 快速提取 | 中上 | 极快 | 免费 | ★★★★★ |
| pdfplumber | 文本型 PDF 表格提取 | 中上 | 较慢 | 免费 | ★★★★☆ |
| PDFMiner | 复杂版式底层解析 | 中 | 慢 | 免费 | ★★★☆☆ |
| camelot | 规则表格批量提取 | 高 | 慢 | 免费 | ★★★★☆ |
| PyPDF2/pypdf | 简单文本、合并拆分 | 低 | 快 | 免费 | ★★★☆☆ |
| Adobe Acrobat | 人工校对、复杂版面 | 高 | 人工慢 | 高 | ★★★☆☆ |
| 福昕高级 PDF 编辑器 | 人工校对 + OCR 集成 | 高 | 人工慢 | 中 | ★★★☆☆ |
| Marker / MinerU | 混合型 PDF 一键解析 | 高 | 依赖 GPU | 免费开源 | ★★★★★ |
| unstructured | 批量多格式文档解析 | 中上 | 中等 | 免费开源 | ★★★★☆ |
PyMuPDF 是我默认首选的工具。它读取速度快,内存占用小,提取文本的同时还能拿到页宽、字体、坐标这些元信息。对纯文本型 PDF,我一般直接用 fitz 提取完整文本,按页和标题层级切成块,效率极高。
pdfplumber 的优势在表格。它的extract_table方法能根据线条识别单元格边界,规则表格的还原效果比 PyMuPDF 好很多。缺点是慢,一份 100 页的 PDF,pdfplumber 可能要跑十几秒。所以我通常是先用 PyMuPDF 判断页面是否含表格,只有含表格的页面才交给 pdfplumber。
camelot 是表格专项选手,适合表格线很清晰、行列规则明确的批量场景。它有 lattice 和 stream 两种模式,前者依赖显式线条,后者靠文本坐标推断。我用 camelot 处理过上千份上市公司公告里的财务附表,准确率非常稳定,但遇到无边框表格就废了。
Marker 和 MinerU 是这两年冲击传统解析链路的新工具。它们本质上是“版面检测模型 + OCR + 大模型后处理”的组合,能直接把 PDF 转成带 Markdown 结构的文本,自动识别标题、段落、表格、图片。我用 MinerU 处理混合型 PDF 的效果确实惊艳,扫描页和文字页混排时也能保持结构一致,但 CPU 上慢,必须配 GPU。
unstructured 是文档解析全家桶,支持 PDF、DOCX、PPT、图片等多种格式。它最实用的能力是按文档类型自动选择解析策略,PDF 里检测出扫描件就自动触发 OCR。缺点是依赖组件多,安装稍麻烦,版本升级有时会破坏兼容性。
3.3 工具选型的决策流程
我总结了一套适配 90% 场景的选型逻辑,分享给大家:
第一步判断 PDF 有没有文本层。最简单的办法是用 PyMuPDF 读取每一页的文本长度,如果超过一定阈值就是文本型,否则是扫描型或混合型。
第二步按文件类型分流:
- 文本型 PDF,首选 PyMuPDF 提取文本,表格页交给 pdfplumber;
- 扫描型 PDF,按 300 DPI 渲染成图片,走 PaddleOCR 或云 OCR;
- 混合型 PDF,直接上 Marker 或 MinerU 做整体解析,省去自己分页判断的麻烦;
- 人工校验场景,保留 Adobe Acrobat 兜底,特别是合同、标书这种不容出错的文档。
第三步按预算和隐私分流。敏感数据必须本地解析,就锁定 PaddleOCR + PyMuPDF;数据可出域且预算充裕,直接云 OCR,省维护成本。
4. 实操链路:搭一套可落地的图文 PDF 解析流水线
选型只是第一步,真正麻烦的是把工具串成流水线。下面这条链路我在多个项目中验证过,效果稳定,可以直接参考。
4.1 文件类型识别与分流决策
流水线第一步是识别 PDF 类型,避免所有文件都走同一个解析路径。我用 Python 写了一个预检模块:
import fitz def analyze_pdf(path): doc = fitz.open(path) total_chars = 0 pages_with_text = 0 pages_with_images = 0 for page in doc: text = page.get_text() if text.strip(): total_chars += len(text) pages_with_text += 1 imgs = page.get_images() if imgs: pages_with_images += 1 text_ratio = pages_with_text / len(doc) if text_ratio > 0.8: return "text_pdf" elif text_ratio > 0.2: return "mixed_pdf" else: return "scan_pdf"这个判断逻辑简单但非常实用。文本比例超过 80% 直接走文本解析,低于 20% 直接走 OCR,中间地带用混合解析。与其花时间训练一个花哨的分类模型,不如先把这个规则用起来,能解决绝大多数问题。
4.2 文本型 PDF 解析的标准化流程
文本型 PDF 的流程是“PyMuPDF 提取 → 表格页分拣 → 结构化输出”。我常用的提取代码框架如下:
import fitz doc = fitz.open("document.pdf") result = [] for page_num, page in enumerate(doc): text = page.get_text("dict") # 保留布局信息 page_data = { "page": page_num + 1, "blocks": [] } for block in text["blocks"]: if "lines" not in block: continue # 合并行文本 block_text = "".join( span["text"] for line in block["lines"] for span in line["spans"] ) block_bbox = block["bbox"] page_data["blocks"].append({ "text": block_text, "bbox": block_bbox }) result.append(page_data)提取出的 block 数组按 bbox 的 y 坐标排序,可以还原大致的阅读顺序。这里有个很重要的细节:PDF 的文本块顺序可能是乱的,提取时一定要按坐标排序,不能直接按内容流顺序拼接。
表格页如何识别?我的做法是统计 page 中表格线特征:page.find_tables()在 PyMuPDF 新版本里已经可用,能检测到表格区域就标记为表格页,然后交给 pdfplumber 提取单元格数据。文本块和表格对象最后统一转成 JSON,保持结构清晰。
4.3 扫描版 PDF:OCR 接力方案
扫描型 PDF 的核心是渲染参数。DPI 太低,小字体会糊;DPI 太高,OCR 慢得不行。我做过几轮对比,300 DPI 是性价比最好的阈值,大多数印刷体都能被 OCR 正确识别。
import fitz def render_page(doc, page_num, dpi=300): page = doc[page_num] zoom = dpi / 72 # PDF 基础分辨率是 72 mat = fitz.Matrix(zoom, zoom) pix = page.get_pixmap(matrix=mat) pix.save(f"page_{page_num+1}.png")渲染出的图片交给 PaddleOCR:
from paddlex import create_pipeline pipeline = create_pipeline("OCR") result = pipeline.predict("page_1.png") for item in result: print(item["text"], item["score"], item["bbox"])PaddleOCR 返回的 bbox 是四角坐标,我用它来拼装段落顺序。规则是:同一行内,x 坐标相近的文本块合并;行与行之间,按 y 坐标从上到下排序。这样拼出的文本可以最大程度还原原文档的阅读顺序,而不是 OCR 从上到下硬读的乱序内容。
这里要强调一个经验:如果你发现 OCR 结果里标题和正文顺序错乱,不要盲目调排序算法,先检查是不是页面方向没纠正。很多手机扫描件带 EXIF 旋转信息,PaddleOCR 的文本方向分类器能不能正确识别,直接影响最终顺序。
4.4 结果入库与召回评估
解析产物统一设计成 JSON 结构再入库,是我优化的关键。每个页面输出如下结构:
{ "source": "contract_2025.pdf", "page": 3, "content_type": "text|table|image|scan", "text": "解析后的文本内容", "metadata": { "title": "合同编号", "page_width": 595, "page_height": 842 }, "images": [ {"path": "images/contract_2025_p3.jpg", "caption": "图1 组织结构图"} ] }文本块进入向量库时,我会把 metadata 里的“来源文件”“页码”作为过滤字段存进去,查询时高效过滤,同时在召回结果中带出原图路径,前端展示时可以图文并茂。这样用户看到一个答案,能追溯到原文第几页、哪张图,信任感会强很多。
召回评估这块,我最常用的指标不是向量相似度,而是“最终问答准确率”。导入一批图片型资料后,我用 30 个真实业务问题跑一轮测试,统计回答正确率。多模态 RAG 和文本 RAG 各跑一次,对比哪个更准。经验是:当文本检索能命中 80% 的问题时,没必要立刻上多模态。
5. 常见问题与排查技巧实录
实操中踩过的坑,比官方案例多得多。我把高频问题整理成速查表,方便大家遇到时快速定位。
5.1 为什么 OCR 结果乱码、漏字、顺序错乱
| 症状 | 可能原因 | 解决办法 |
|---|---|---|
| OCR 乱码 | 语言模型没切换 | 中文用默认,韩文日文要显式lang参数 |
| 漏字严重 | 分辨率不够或图片过亮 | 渲染 DPI 提到 300,先做灰度化和背景增强 |
| 顺序错乱 | 页面旋转未纠正 | 开启方向分类器,或用 EXIF 方向信息纠正 |
| 表格内容串行 | 表格线不完整 | 改用多模态大模型,或先用传统表格识别校正 |
有一次我处理一套政府发布的红头文件扫描件,结果“xx局”被识别成“xX局”,原因是扫描件褶皱阴影干扰。后来加了形态学开运算和背景减弱,漏识别率大幅下降。图片预处理虽然听起来“老套”,但在 OCR 链路里依然是最有效的一环。
5.2 PDF 解析特慢怎么优化
批量导入几千份 PDF 时,性能问题会立刻暴露。我的优化顺序是这样的:
第一,并发优先。用多进程而不是多线程,因为 PyMuPDF 和 PaddleOCR 都释放 GIL,multiprocessing 起 8 个 worker,速度接近 8 倍提升。线程会被 GIL 卡死,实测几乎没有加速效果。
第二,做缓存。同一个 PDF 只要首轮解析过,把解析结果存在磁盘上,文件名加版本后缀。后续重跑时直接读缓存,跳过 OCR。这个对重复导入的文档尤其有效。
第三,降 DPI。如果扫描件的字体够大,180 DPI 就够识别,何必每次都 300 DPI?我是先抽样几页试跑,能识别就降低 DPI,批量时速度翻倍。
GPU 优化是最需要钱的,其实不是第一选择。先做好并发和缓存往往能解决 80% 的性能问题,剩下 20% 才该考虑 GPU。
5.3 关于“RAG 瓶颈”的真相
热词里“rag瓶颈”搜得很多,我在实际项目中看到的瓶颈排序是:解析质量 > 检索策略 > 模型能力。文本解析本身做得烂,后面检索和生成再好也没用。尤其是图文混排的文档,文本丢失、表格错乱、顺序颠倒,这些直接让向量检索的准确率掉到 60% 以下,再多 Rerank 技巧都救不回来。
所以如果你觉得自己的 RAG 问答效果差,先别急着换模型,试着从源头检查:原始文档解析后,文本内容是否和源文件一致?标题层级是否保留?表格是否真的被结构化了?这三关过不了,后面全是白搭。
我个人在实际操作中的体会是:图文和 PDF 解析没有一劳永逸的方案,项目推进过程中一定要先拿小批量样本跑通,再上全量。九种 PDF 工具不用全上,先用 PyMuPDF + PaddleOCR 就能解决七八成需求;确有多模态需求的,优先用多模态大模型处理传统 OCR 搞不定的 10%,成本可控且收益明显。最后再送大家一个小技巧:解析完的 PDF 文本,记得在落库前做一次“回读对比”——把解析结果重新渲染成 PDF 或文本,人工抽检 5 页,看有没有漏页、错字、公式断裂。这个小动作,能替你挡住 80% 的生产事故。