LLM 能读几万字上下文,但有一个前置问题经常被忽略:大量文档根本复制不了。扫描版 PDF 里的内容是图片,网页里的文字被禁选,合同、银行流水、硬件文档、纸质笔记在屏幕上显示得清清楚楚,却没法直接变成文本喂给模型。这类场景必须用 OCR。OCR It 这类方案要解决的就是把不可复制文档抽取成干净文本,再交给 LLM 做总结、问答、提取结构化信息。如果你手里有一堆扫描件、截图或图片,想让大模型直接分析里面的内容,这篇文章会把完整链路拆开:环境准备、单任务怎么跑、批量任务怎么处理、参数怎么调、出问题先查哪里。
1. 为什么说“复制不了”是 LLM 场景里最容易被低估的卡点
很多人觉得 LLM 都这么强了,直接把 PDF 丢进去不就行了。实际上,普通大模型读取的是文本 token,不是屏幕像素。扫描版 PDF 没有文本层,图片里的内容也不是天然可读的文本。这个卡点不解决,后面的 Prompt 优化、RAG 检索、Agent 工具调用都接不上。
1.1 不是所有文档都能直接复制,截图、扫描件、加密 PDF 都会中断工作流
我在实际整理资料时,经常碰到这几类“复制不动”的情况。
第一类是扫描版 PDF。很多书、论文、老资料都是扫描件,整个文件就是一页页图片。鼠标选中时根本没有文字,复制出来是空白。第二类是网页和知识库。部分站点禁右键、禁选中,或者为了让排版好看,把正文做进图片里。第三类是加密或限制编辑的 PDF。文件能打开、能看,但复制功能被锁掉。第四类是截图和手机拍照,纸质文档、单据、App 页面拍下来就是图片。
这些文件如果直接交给 LLM,多模态模型虽然能读图,但长文档里几十页图片很难全部塞进上下文。即便能塞,成本会很高,识别质量也未必稳定。更常规的做法是先用 OCR 把文字抽取出来,再交给模型处理。OCR 在这条链路里的角色,相当于一个“文本还原层”。
1.2 OCR 不是把图片变成文字这么简单,还要考虑喂给 LLM 的格式质量
不少人以为 OCR 跑完,拿到一段字符串就算结束。真实场景里,喂给 LLM 的文本需要的不是一串字符,而是可读、有序、尽可能还原版面的内容。
我一般会检查三件事。
第一,顺序不能乱。扫描 PDF 是逐页识别的,如果页序错乱,模型读到的内容就是跳跃的。第二,标题和段落不要全粘在一起。LLM 可以处理连续文本,但完全没有结构的文本会让总结效果打折扣。第三,中英文混排时不能错乱。中文识别和英文识别经常需要不同语言包,如果混排处理不好,输出里会出现大量空格和乱码。
如果你后续还要做 RAG 或知识库,OCR 结果甚至需要保留位置信息和置信度。比如某个表格单元格在哪一页、哪个坐标,这些元信息可以帮助你定位原文。OCR It 这类工具的价值不只是识别文字,而是把“图片 → 可读文本 → 模型输入”这条链路整理清楚。
2. 本地 OCR 方案怎么选:先明确任务类型,再选工具和依赖
OCR 工具很多,网上也经常看到“最强、最好用”的说法。我不建议直接抄别人的推荐,而是先想清楚自己的任务类型,再决定用什么引擎。
2.1 常见 OCR 引擎和它们的适用边界
这里给一个通用对比,具体参数和能力要以你安装的版本为准。
| 引擎 | 优势 | 适合场景 | 注意点 |
|---|---|---|---|
| Tesseract | 老牌、离线、跨平台,生态成熟 | 印刷体英文、清晰扫描件 | 中文识别需要额外语言包,复杂版面一般 |
| PaddleOCR | 中文效果好,自带方向分类和版面分析 | 中文扫描件、拍照图、表格 | 依赖较多,安装比 Tesseract 重 |
| EasyOCR | 上手简单,支持多语种 | 快速验证、多语种混合 | 模型体积大,CPU 速度偏慢 |
| 云 OCR | 接口稳定,版面还原好 | 不想折腾本地环境和批量调度 | 需要联网,按调用量计费 |
| 截图型 OCR 工具 | 快捷方便,适合桌面取词 | 单张截图、碎片化取字 | 批量自动化能力有限 |
如果只是偶尔从截图里提取一段文字,用 Tesseract 或常见截图 OCR 工具就够了。如果经常处理中文扫描件,PaddleOCR 这类带版面分析的方案会更合适。如果不想维护本地环境,可以考虑云接口,但要注意数据隐私和调用成本。
2.2 环境准备:Python、Tesseract/PaddleOCR、依赖安装的通用顺序
环境准备不复杂,但顺序不对容易浪费很多时间。我建议按这个顺序来。
先确认系统里有没有 Python,执行python --version和pip --version,看版本是否匹配。很多 OCR 库对 Python 版本有要求,版本太老容易装不上依赖。然后安装 OCR 引擎本身。Tesseract 在 Windows 上需要单独下载安装包,Linux 上可以用系统包管理器安装;安装之后顺手把语言包也装上,中英文场景至少需要中文和英文语言包。接着安装 Python 封装库,比如pytesseract、paddleocr、easyocr这类。
最后做一个最小验证:拿一张清晰的印刷体图片,跑一次识别,看能不能输出正常文本。
python -c "import pytesseract; print(pytesseract.get_tesseract_version())"如果这一步报错,先别急着调识别参数。大概率是引擎没装好,或者 Python 封装库找不到系统里的 Tesseract 路径。
2.3 低配置机器能不能跑,主要看模型体积和输入图片大小
热搜里经常有人问 OCR 能不能在 RK3588 这类 ARM 板子上跑。答案是能跑,但不要按电脑配置去预期。
OCR 资源占用有两个关键变量:模型体积和输入图片大小。Tesseract 这种传统 OCR 对硬件要求很低,CPU 就能跑,内存占用也不大。PaddleOCR 或 EasyOCR 这类深度学习方案,需要加载神经网络模型,CPU 能跑但速度比 GPU 慢,尤其是大图和高分辨率扫描件。
如果是 ARM 板子,部署时要注意 Python 版本、系统依赖、onnxruntime 或 paddle 的 ARM 支持情况。有些依赖在 ARM 上没有预编译包,需要源码编译,编译时间会比较长。低配置机器不是不能用,而是要把输入图片的分辨率和批量数降下来。我的建议是:先用单张小图验证环境能跑通,再考虑批量任务。
3. 用 OCR It 的方式跑通最小链路:从图片文件到可读文本
真正进入实操,不要一上来就做复杂流程。我建议先用一张图片跑通最小链路,再逐步扩展。
3.1 单张图片识别:先确认输出内容完整、编码正确
拿一张清晰的印刷体截图,先做单张识别。用 Tesseract 的话,核心命令很简单:
from PIL import Image import pytesseract image_path = "sample.png" text = pytesseract.image_to_string(Image.open(image_path), lang="eng") print(text)如果是中文,把lang参数改成chi_sim+eng,表示同时使用简体中文和英文识别。第一次跑完,先不要看准确率,先看三件事。
第一,控制台有没有报错。第二,输出文本里有没有大量乱码。第三,文本顺序是否正常。如果图片文字很多,但输出只有几个字符,先检查图片路径、语言包和图片分辨率。
这里最容易踩的坑是语言包没装。英文文本用eng没问题,中文文本如果只指定eng,输出会非常奇怪。还有一点,图片越大,识别越慢,但不代表越清晰。如果图片本身模糊,放大图片反而会让识别效果更差。
3.2 保留版式和元信息:Markdown 输出、置信度筛选和位置信息
单张识别跑通之后,考虑输出格式。普通文本适合直接看,但喂给 LLM 时,我更喜欢带基础 Markdown 结构的输出,因为模型能更清楚地区分标题、列表和正文。
如果你用 PaddleOCR,识别结果里通常会包含文本框坐标和置信度。这些信息很有用。比如,某一行文字置信度很低,说明可能是脏污、模糊或特殊字体;你可以选择去掉或标出来。
不同 OCR 引擎的接口不一样,但通用思路是:识别每行文字 → 把坐标相近的行合并成段落 → 根据字号和位置判断标题层级 → 输出成 Markdown。这个过程不需要一开始就做得很完整,简单保留标题和段落就够了。过度结构化反而可能引入错误判断。
还有一类需求是保留位置信息。如果你要把 OCR 结果用于 RAG 检索,最好在输出 JSON 里保留页码和坐标:
{ "page": 3, "text": "服务器配置参数", "box": [120, 45, 240, 60], "confidence": 0.98 }这样后续定位引用的时候,可以回溯到原文位置。
3.3 把识别文本交付给 LLM:提示词里的上下文组织方式
OCR 结果拿到后,不是直接把大段文本塞进 Prompt 就完事。要让 LLM 理解这段文本的来源和任务,需要做简单的上下文包装。
我一般会在 Prompt 里说明文本来源和当前目标:
以下是某份扫描版文档经过 OCR 识别后的文本,可能存在少量识别错误。请根据文本回答下面的问题,遇到明显 OCR 错误的地方可以忽略或标注。 文档内容: {ocr_text} 问题:这份合同里的付款期限是什么?这样做的原因是,OCR 文本有时会有少量错字,如果模型不知道文本来自 OCR,可能会把识别错误当成原文信息,导致后续判断出错。提前说明,模型会更有针对性地处理。
如果文本很长,还需要分段。比如把文档拆成 1000 到 2000 字的小块,让模型分块总结,再汇总。注意,分块时要尽量保持语义完整,不要把一个句子或一个表格从中间切断。
4. 批量文档、长文档和 API 场景,不能只用“识别单图”的思路
单张图片能识别,不代表批量任务能稳定跑完。批量里最常见的不是“能不能识别”,而是“任务跑到一半卡住,不知道哪张图失败了”。
4.1 批量处理的三个关键点:输入列表、输出命名、失败重试
批量处理要单独考虑三件事:输入文件列表、输出命名规则、失败重试机制。
先列输入文件列表。不要直接在循环里os.listdir()读完所有文件后马上处理,而是先打印一下文件数量和文件名,确认有没有混杂非图片文件。有些文件名里带特殊字符,比如空格、括号、中文,在命令行或脚本里容易出问题。
输出命名规则要能对应原始文件。我常用的规则是保留原名,加后缀。比如scan_001.pdf输出为scan_001.txt,或者放到ocr_output目录下。不要用无意义的递增编号,否则失败重试时很难定位是哪张图。
失败重试要分两类。第一类是脚本崩溃,比如某张图片格式损坏,导致整个程序退出。第二类是单张识别结果为空或置信度极低,程序没报错,但输出文件是空白的。空文件比报错更坑,因为它不会打断流程,但会造成下游数据缺失。我建议每次识别完检查输出文本长度,为空时记录到日志里。
4.2 长 PDF 先分页再识别,注意内存和顺序
长 PDF 不能直接当一张大图处理。内存和模型输入尺寸都有限制,更合理的方式是分页。
先提取页码信息,再逐页转成图片。关键参数是 DPI,也就是渲染分辨率。一般扫描件用 300 DPI 比较稳妥,太高会明显降低速度,太低则模糊。
分页处理时要注意顺序。每页识别完,按页号顺序写入同一个输出文件,并加上分页标记:
--- 第 1 页 --- (第 1 页识别文本) --- 第 2 页 --- (第 2 页识别文本)这样即使某页识别失败,也不会影响其他页的内容。失败页码记录到error.log里,方便重跑。
如果 PDF 有几百页,建议设置断点续跑。每次记录当前处理到第几页,中断后从断点继续,不要重新跑全部。这个思路在 GPU 资源受限的场景下尤其重要。
4.3 API 化时需要约束并发、超时和返回结构
当你的 OCR 能力要被多个任务调用时,需要把它封装成接口。接口化不是简单写一个 Flask 路由,而是要考虑并发和稳定性。
并发数不要一开始就拉满。本地 OCR 服务在并发请求增加时,CPU、内存、GPU 显存都会快速上升。如果超卖,任务会排队,接口超时率会上升,最后可能全部卡死。更稳妥的做法是:设置最大并发数,比如同时最多 4 个识别任务;超出并发数的请求进入队列;每个请求设置超时时间。
返回结构建议统一。成功返回文本、耗时、置信度;失败返回错误码和原因。下游调用方可以根据返回状态决定是否重试,而不是每次都自己解析裸文本。
{ "code": 0, "data": { "text": "识别出的文本内容", "duration_ms": 1240, "pages": 1 }, "message": "ok" }如果 OCR 服务要接入 Agent 或 RAG 系统,这个结构就很重要。Agent 工具需要知道调用是否成功,以及结果能不能直接用。
5. 识别质量不稳定时,按输入、环境、参数、工具一路查下去
OCR 识别质量变差时,不要先怀疑模型能力。很多问题不是模型不行,而是输入图片和环境没有处理干净。
5.1 先看现象,再确认输入:分辨率、方向、语言、字体类型
遇到识别错误,先做一件事:把原始图片打开看一遍,而不是只看识别结果。
看分辨率。文字太小、字迹太浅,模型很难识别。看方向。有些扫描件或手机拍照是横向的,OCR 引擎如果没有方向分类,可能整页输出错乱。看语言。中英文混排时,如果只设置了中文语言包,英文单词会被识别成奇怪的中文字符。看字体。花体、艺术字、手写体、手机截图里的特殊字体,传统 OCR 经常失败。
这些问题的处理顺序是:先提高输入质量,再调整模型参数。不要一上来就调阈值或加图像增强,因为输入本身有问题时,增强也是白搭。
5.2 环境问题:依赖版本混杂、路径含中文、权限和资源占用
环境问题经常伪装成识别问题。比如,同样一段代码,电脑 A 能跑,电脑 B 输出全是乱码。这时先检查依赖版本。
OCR 引擎和 Python 封装库版本需要兼容。有些封装库更新了接口,旧代码调用新库会报参数错误;有些系统里同时装了两个版本的 Tesseract,导致路径指向错误。排查时先看版本,再跑最小例子,确认环境本身没问题。
路径含中文也是常见问题。Windows 上有些 OCR 组件对中文字符路径支持不好,图片路径或者输出目录包含中文时,会随机报错。解决办法是先把文件复制到纯英文路径下测试。如果纯英文路径能跑通,说明就是路径编码问题。
还要看磁盘空间和内存。批量识别大量扫描件时,输出文件可能很大,临时文件也会占用空间。磁盘写满时,程序可能不报错,但输出文件是残缺的。
5.3 参数调整节奏:图像增强、阈值、语言包和分块策略
环境没问题时,再调参数。参数调整要有节奏,一次只改一处,改完跑同一张测试图。
图像处理方面,常见的操作有转灰度、二值化、锐化、去噪。但注意,二值化阈值太激进,可能把文字笔画断开,反而让识别变差。更稳妥的做法是,先看原图的对比度。如果背景干净、文字清晰,直接识别;如果图片偏暗、有阴影,再做灰度化和自适应阈值。
语言包方面,中英文混排时设置chi_sim+eng,但要注意语言包顺序。有些引擎语言包顺序会影响识别优先级,实际以测试结果为准。
分块策略方面,超大图片不要一次性识别。比如一张超高分辨率的截图,可能被压缩或超时。可以先按区域切分,识别完再按坐标拼接。这个方式一次投入比较多,但处理复杂版面时很有效。
6. OCR It 的真正价值不在于“识别得准”,而在于给 LLM 提供干净的“可推理文本”
到了最后一步,我想说一个容易被忽视的判断标准:OCR 输出不是给人看的,是给模型推理用的。因此“准确率”不是唯一指标,还要看文本是否适合让 LLM 继续处理。
6.1 判断 OCR 结果是否满足 LLM 使用:文本可读性、结构化、错误密度
我一般会用三个指标判断 OCR 结果能不能喂给 LLM。
第一是可读性。通读一段输出,如果不看原文也能理解内容,说明文本基本可用。如果错误密集到没法阅读,就不适合直接使用。第二是结构化。标题、列表、段落尽量保留,模型在总结时更容易遵循逻辑结构。第三是错误密度。OCR 错字不可能完全避免,但错误密度高到一定水平后,模型的回答就会开始“一本正经地胡说八道”,因为它会把错字当成原文知识。
如果你做的是 RAG 或知识库,OCR 文本的质量会直接影响检索效果。检索时,文本如果错字太多,向量匹配也会受影响。这也是为什么不少人把 Karpathy 提出的 LLM Wiki 这类知识整理思路接入 OCR 时,会先做一轮文本清洗。
6.2 边界提醒:表格结构、手写体、复杂排版、低质量扫描件始终是难点
OCR 不是万能提取器。几个场景需要降低预期。
表格是最难的。识别出表格里的文字不难,难的是把单元格对应关系恢复出来。简单表格可以靠坐标判断,复杂表格比如跨行跨列、合并单元格,普通 OCR 输出很容易丢掉结构。如果表格是数据提取的核心,建议单独用表格识别模型,而不是通用 OCR。
手写体也难。中文手写识别和印刷体识别是两个难度级别。潦草字迹、连笔字,识别效果基本不可用。低质量扫描件同样棘手,模糊、倾斜、光线不均、墨水晕染,这些都会大幅降低识别效果。
我不太建议在博客里夸某项 OCR 技术“百发百中”。更实际的做法是:先识别一部分,人工检查质量,再决定是否全量处理。OCR 的目的不是追求“完美识别”,而是把大量需要人工录入的文本变成“机器可处理”,剩余部分再靠人工或 LLM 修正。
6.3 更稳的落地顺序:先小样本验证,再批量,再对接 Agent 或 RAG
最后给一个我反复使用的落地顺序。
先用一个真实文档做小样本验证。不要用官方 Demo 图片,要用你自己业务里的合同、扫描件、截图。确认识别结果能不能满足下游需要。然后小批量跑 5 到 10 个文件,检查输出文本、日志、失败率,这时候的重点是发现流程问题,比如路径、命名、内存、依赖。流程稳定后,再跑全量任务。最后再对接 LLM、RAG 或 Agent,对接时不要把 OCR 结果裸传,最好加上来源、页码、置信度等元信息。
如果只是学习,默认配置通常够用。如果要长期使用,日志、输出目录、任务队列要提前整理好。OCR It 这类方案真正落地时,最该盯住的不是功能列表,而是输入格式、资源占用和失败重试。很多时候项目卡住的不是模型能力不够,而是前置环境和输入材料没有处理干净。