你接手了一个图片翻译需求,第一批样片刚跑完,OCR 引擎就把“合同编号”识别成了“合同皌号”,“发货日期”变成“发货凵期”。更气人的是,这种乱码不是零散出现的,同一个错字换个阈值就变一个花样。我早几年做文档数字化项目时,被这类问题折腾过很长时间,最后跑通了一套稳定流程:在 OCR 和翻译之间,插入一个 LLM“校对员”,让大模型结合上下文把脏文本修回来再翻译。用这套方案在限定测试集上做过人工标定,整图可读准确率能达到 99.9% 级别。这篇文章会把这套方案完整拆开,讲讲乱码为什么会产生、LLM 为什么能当校对员,以及一条从图片输入到翻译结果输出的完整流水线。想做好 OCR 应用、图片翻译工具、文档信息抽取的开发者,应该能从里面找到可以直接复用的思路。
1. 先看清问题:OCR 乱码不是随机发散,而是系统性失真
1.1 乱码为什么会存在:识别器本质上是在赌概率
要修复乱码,首先得明白乱码是从哪来的。常规 OCR 引擎的识别链路大致分成两步:第一步是文字检测,把图片里的文本框框出来;第二步是文字识别,对每个框里的图像特征做序列建模,输出候选字符的概率分布,最后取概率最高的字符作为结果。
问题恰恰出在最后这一步。正常情况下,一个清晰的大号字体,模型对正确答案的置信度能到 0.95 以上,概率分布很尖锐。但图片只要稍微模糊、分辨率不足、光照不均、或者文字笔画粘连,模型看到的特征就变得模棱两可,概率分布会被拉平,几个候选字符的概率可能都只有 0.2 到 0.4。这时候引擎取 argmax 就有点像近视眼隔着毛玻璃猜字,猜错的概率自然飙升。
所以乱码并不是引擎“故意捣乱”,而是它在低信息量下做了一个低置信度的判断。这个认知特别重要,因为后面所有修复策略,都是围绕“低置信度的错误”来设计的。如果错误是高置信度的,说明图片本身或模型本身有问题,那属于另一个层面的问题。
1.2 真实业务里高频出现的四类乱码
拿我手上的真实样片来分类,长期跑 OCR 会遇到这么几类高频错误:
| 错误类型 | 典型成因 | 表现形式 |
|---|---|---|
| 形近字混淆 | 笔画相似,图像分辨率不足 | “已”误为“己”,“日”误为“曰” |
| 字母数字混淆 | 字体渲染导致特征相近 | “0”与“O”,“1”与“l”混淆 |
| 字符丢失或重复 | 笔画断裂或粘连,检测框不完整 | “金额”变成“金页”少一笔 |
| 乱序错位 | 文本行检测偏差,序列建模错乱 | 双栏文本串行,左右列文字交叉 |
第一类形近字混淆最多,中文里大量字形接近的字,在 100 像素高度的图像里人眼都难分。第二类常见于英文和数字混排的型号、单号。第三类最隐蔽,OCR 不是把字认错,而是把字的一部分丢了,整体看起来像个从来不存在的新字符。第四类在截图类素材里很常见,表格和双栏排版下文本顺序错乱。
我在项目里最怕的不是前三类,而是第四类。因为前三类虽然“字错了”,但上下文语境还在,LLM 有机会根据前后文猜回来。第四类顺序错乱破坏的是整句的语义结构,修复起来成本要高得多。后面第 4 部分会单独讲表格串列的排查思路。
2. 为什么选 LLM 当“校对员”:规则纠错卡在了哪
2.1 词典纠错与形近字映射表的边界
最直接的乱码修复方案,是做一张形近字表,比如“目/曰”,“己/已/巳”,“亻/彳”互相映射。再配合一个业务词典,OCR 输出某个词不在词典里,就用词典里形近的候选词替换。这种思路对固定术语场景很有效,比如发票抬头、地址库、标准品名,准确率能做得很高,因为候选集合是封闭的。
但真实业务里的文本是开放的。人名、公司名、街道名、非常规表达,词典根本覆盖不过来。举个例子,OCR 把“签闰合同”里的“闰”识别错了,词典里根本没有“签闰”这个词,自然不知道要改成“签暑”“签晋”还是“签订”。这时候任何一个固定映射表都会失灵。
还有一个更隐蔽的问题:规则纠错只能处理“词内错误”,处理不了“跨词错误”。比如一句话里同时有两个形近字出错,把“订单金额为500元”识别成“订单金页为500元”,然后“页”又和后面的“为”串了语境。这种级联错误,字符级规则根本无从下手。
2.2 LLM 能“猜”出正确字:语言模型在补上下文
LLM 校对的核心逻辑和形近字表完全不同。它不是在找字形替换,而是在做条件概率预测:给定前后文,当前位置最应该出现什么字。
OCR 输出的句子里虽然有个别字是错的,但前后的上下文保持了完整性,这就给语言模型提供了足够的“线索”。比如“请于 2024 年 6 月 30 日前签_合同”,这个下划线位置在自然语言里几乎只会填“订”“署”“妥”这类字,再结合形近关系,“闰”离“订”的形态差得远,但离“署”和“订”更近,模型能快速锁定。
打个比方,你看到一张照片墙上挂的画歪了,不需要重新测量墙面角度,只需要参照旁边的门框和地脚线,就能很快把画扶正。LLM 干的是这个活:OCR 负责“看见”,LLM 负责“参照上下文把画面扶正”。
另外要强调一点,LLM 不是万能的。当 OCR 错误率超过一定比例(我实测中文场景大概在 15% 以上),上下文本身的“线索”也被污染了,LLM 修复效果会断崖式下跌。所以这套方案的前提,是 OCR 引擎本身不能太拉胯,LLM 做的是把那几个百分点的错误收尾,而不是从零开始识别。
2.3 方案选型:服务化调用还是本地推理
确定了“LLM 当校对员”这个方向,下一步就是选型。我的选择对比经验是:
| 对比维度 | 商用大模型 API | 本地开源模型 |
|---|---|---|
| 修复效果 | 强,尤其古文、非规范表达 | 中上,依赖参数量 |
| 部署成本 | 低,按量付费 | 高,需要 GPU 资源 |
| 数据隐私 | 数据出网,需评估合规 | 数据不出机房 |
| 时延 | 网络往返 + 排队 | GPU 推理时延稳定 |
| 批量吞吐 | 受账号并发限制 | 可自行扩容 |
我自己的习惯是:原型阶段一律用商用 API,因为效果验证速度快,省去部署调优的功夫。如果业务跑通、日均处理量上万,再评估要不要切本地模型。如果图片里包含客户个人信息、合同金额、内部工单等敏感内容,那直接从本地模型方案开始。
选型时还要注意一个几乎没人提的点:上下文窗口长度和分段策略。OCR 一张 A4 文档可能输出上千字,商用 API 虽然有很长的上下文窗口,但每次校对请求塞太多字会增加耗时和费用。要提前设计好分段策略,按照自然段或者文本框切分,每段控制在几百字以内,LLM 的修复质量也更稳定。
3. 一套完整的六步流水线:图片翻译从进到出的实操方案
3.1 第一步 图片预处理:给 OCR 一个“看得清”的图
很多人拿到图片就直接丢给 OCR,这其实是把一道难题扔给最薄弱的环节。我在自己的流水线里固定了一套预处理流程:先转灰度,再用双边滤波或高斯滤波去噪,然后做自适应二值化,最后对倾斜文本做透视矫正。
这里最要注意的是二值化。固定全局阈值在光照均匀时没问题,但手机拍摄的图片经常有一半亮一半暗,全局阈值会把亮部文字融掉、暗部背景变黑。自适应二值化会对每个像素邻域单独算阈值,我在用 500 像素窗口、C 值设为 10 时,对大多数拍摄样片效果最稳。当然这个参数要根据实际图像分辨率微调,我通常是先跑几十张,看输出文本的置信度分布再定。
还要提一句倾斜矫正。手拍的图片文字经常歪几度,OCR 检测框会跟着斜,识别准确率显著下降。我在流程里先做一次边缘检测,找出文本行的主导角度,再旋转回水平。这个步骤能直接拉回 5 到 10 个百分点的字符准确率。
3.2 第二步 OCR 抽取:引擎选择与质量开关
选 OCR 引擎时,我最关注三个能力:一是能否输出置信度,二是能否返回每个文本框的坐标,三是多语言混排的支持程度。
置信度是后面整条流水线的核心信息,没有置信度就只能盲修。坐标信息是处理表格和双栏排版的关键,没有坐标就分不清哪段文字是属于哪个区域的。多语言混排则直接影响翻译场景——中英文混排是常态,纯中文 OCR 引擎会把英文单词拆成乱码。
我在实际项目里会把 OCR 结果抽成一个结构化的列表,每一条记录包含文本、坐标、置信度。伪代码如下:
ocr_engine = load_ocr_engine(lang="zh+en+digit") page_blocks = ocr_engine.run(image) records = [] for block in page_blocks: for line in block.lines: records.append({ "bbox": line.bbox, # 文本框坐标 "text": line.text, # 识别文本 "confidence": line.confidence # 识别置信度(0-1) })拿到这个列表后,先做一次粗过滤,把置信度低于 0.3 的疑似噪声行直接丢掉,减少对后续 LLM 的干扰。要说明的是,这里的阈值不是绝对的,我见过某些样片里半截噪声行的置信度也有 0.5,所以过滤后保留的内容还需要再人工复核抽检一轮。
3.3 第三步 校对 Prompt 设计:把“校对员”的岗位说明写好
LLM 能不能当好校对员,一半取决于模型本身,另一半取决于你有没有给它一份清晰的“岗位说明”。我在反复试错后沉淀了一套 prompt 模板,核心原则就四句话:角色明确、任务单一、边界清楚、输出可控。
修复场景的 prompt 模板:
你是一名 OCR 后处理校对员。下面是一段由光学字符识别系统输出的文本, 其中可能存在错别字、多字、漏字、顺序颠倒等问题。 请结合上下文把文本修复为通顺、准确的原内容。 要求: 1. 逐句修复,不改变原意; 2. 不确定的地方保留原文,不要臆造; 3. 不要补全原图片中不存在的信息; 4. 只输出修复后的文本,不要输出任何解释或分析。 待修复文本: {ocr_text}这里几个要求不是套话。第二条“不确定的地方保留原文”是为了防止模型发明内容,这个我吃过亏;第三条是防止模型顺着语感“补全”其实图片里没有的信息;第四条是为了便于程序解析结果,直接取输出文本即可。
翻译场景我通常会把修复和翻译合并成一步,减少一次 API 往返:
你是一名图片文本翻译助手。下面是一段由光学字符识别系统输出的文本, 可能含有错别字、多字、漏字。请先修复为通顺的原文,再翻译成英文。 要求: 1. 修复时保持原意,不确定处保留原文字; 2. 翻译结果要忠实修复后的文本; 3. 只输出翻译结果,不要解释。 待修复文本: {ocr_text}实测下来,修复和翻译合并做,语义一致性比分开两步更好,因为模型在翻译时能看到它自己修复的上下文,而不是在两个独立生成过程中产生信息损耗。当然也有代价:如果最终译文有问题,你很难判断是修复阶段出的错还是翻译阶段出的错,所以上线初期建议还是先分开跑,攒一批对比数据后再合。
3.4 第四步 修复与翻译:单段处理与批量并发
拿到结构化 OCR 记录后,我按文本块为单位构造 prompt。每个文本块通常对应一个自然段或一个文本框区域。这里要控制每次请求的文本量,我的经验是单次控制在 300 到 500 字以内,太长了模型修复质量下降,而且超时风险高。
批量处理时要注意并发控制。直接循环调用 API,一个响应慢了会阻塞整个队列。我在项目里会开一个固定线程池,并发数在 4 到 8 之间,再配合重试机制——超时或服务端返回异常时,退避重试三次。重试时要注意幂等,同一段文本不要因为重试而重复提交导致重复扣费。
如果用的是本地模型,批量处理反而简单,直接拼接批量请求,利用 GPU 的并行能力。但本地模型单条质量和商用 API 的差距,在错字密度高时体现得最明显。我测试过本地小尺寸模型,修复效果只能到商用 API 的七八成,适合大体量但对效果要求不极致的生产场景。
3.5 第五步 置信度分流:让 LLM 只干它该干的活
把全部 OCR 文本都交给 LLM,其实是一种浪费。高置信度文本是正确的,送进去反而存在被 LLM“改错”的风险(后面 4.4 会展开讲)。我的做法是按置信度分三条路:
| 置信度范围 | 处理策略 | 复核要求 |
|---|---|---|
| 大于 0.95 | 直接使用,不送 LLM | 无需复核 |
| 0.70 - 0.95 | 送 LLM 修复 | 抽样人工复核 |
| 小于 0.70 | 送 LLM 修复 + 原图对照 | 建议人工逐条确认 |
这套分流规则的核心思想是:让 LLM 专注处理“可疑文本”,而不是所有文本。一来提升效率,二来避免正确的文本被模型“画蛇添足”地改出问题。阈值 0.95 和 0.70 不是固定不变的,业务对错误的容忍度不同,可以上下浮动。在合同信息抽取场景,我会把高置信度阈值调到 0.98,宁多不少;在菜单翻译这类阅读性场景,0.9 就够了。
人工复核在产品上尽量做成并排对照的形态:左边原图片段、中间 OCR 文本、右边 LLM 修复结果。复核人只需要判断右侧文字是否与左侧原图一致,操作成本和认知成本都最低。
3.6 第六步 效果度量:99.9% 是怎么测出来的
说准确率之前必须先统一口径。行业里常见的口径有这么几种:字符级准确率、词级准确率、整图级可读准确率。字符级指标很严格,一个标点错了都算错;整图级可读准确率更接近用户的真实感知,定义是“标注者认为,在不看原图的情况下,修复后的文本能否完整表达原图信息”。
我对外汇报 99.9% 时,用的就是“整图级可读准确率”。具体测法是:抽 300 张覆盖不同版式、不同清晰度的样片,用这条流水线处理后,交给两名标注员独立判断每张图是否“可读无碍”,两人意见一致且判定为可读的图片占比就是最终指标。实际操作时会剔除极端模糊、完全无法辨认的样片,只在“人工能看清原图文字”的样本集上统计。
建议你在自己的项目里也先定好口径再定目标。如果你拿字符级指标去要求 99.9%,那几乎是做不到的,因为单个字识别错误在真实图片里不可避免。反过来,如果只报整图级可读准确率,也要把抽样方法和标注规则写清楚,否则数字没有任何说服力。
4. 跑批时踩过的坑和排查实录
4.1 低分辨率长截图:上下文断裂是最大的坑
有一次接到个需求,用户上传一张很长的网页截图,宽度只有 640 像素左右。OCR 引擎按行识别后,我按文本框把结果拆成了几段,再分别送给 LLM 修复。结果修出来的文本质量很差,很多句子语义不通。
排查后发现原因在于:长截图被 OCR 切成了多个不连续片段,每个片段单独看上下文信息不足。比如“本项目将于下月”和“正式启动”被切到两个文本框,LLM 单独修复前一句时会丢掉后一句的信息,关键实体名就被臆补错了。
解决办法是合并上下文:先用坐标信息把行按阅读顺序排序,再按自然段语义合并成更大的文本块,块之间保留少量重叠,再送给 LLM。同时把预处理阶段对低分辨率图做一次文本区域增强,比如对文字 ROI 区域做指定倍数的线性放大后再识别,置信度会明显提升。
4.2 艺术字和手写体:修复是伪命题
海报标题这类艺术字,笔画变形程度大,OCR 识别出来的结果经常是完全发散的。比如一个“每”字,在夸张字体下被识别成“侮”或者“悔”,这种错误不是形近字级别的,而是整个字形特征被大幅扭曲。
这类样本我走了不少弯路才接受一个现实:明确定位为“LLM 无法可靠修复”的类型。在流水线里对这类文本不做强制修复,而是把置信度极低的部分标注为“需人工确认”,直接渲染到人工复核队列里。与其让模型乱猜,不如老老实实让用户花几秒钟看一眼原图。
手写体稍微好一点,但同样没有通用解法。大量手写样张、连笔字识别,Python 层面的后处理改善有限,最有效的方案永远是换一个更强的手写识别模型,或者训练领域专用模型。LLM 校对在这里只能做非常有限的干预,比如修正标点、合并断行,指望它把手写错字全部改对不现实。
4.3 表格跨行串列:坐标重排比 LLM 硬修更有效
表格类图片是单独的一类难缠样本。OCR 按识别框输出时,经常会因为列宽太窄、文字溢出而把左右两列的片段按行混排。LLM 收到这一行文本时,根本分不清哪些字属于哪一列,再怎么修也恢复不了表格结构。
后来我的处理思路变了:不把表格的原始文本流按行直接给 LLM,而是先用坐标做结构化重排。坐标信息里 column 的位置固定,我会先定义几个列区间,然后把每个文本框按中心点 x 坐标分桶,配合 y 坐标排序,拼成列级文本块。比如“商品名称”列单独拼成一段,“金额”列单独拼成一段,两段分别送 LLM 修复和翻译。这一步能把表格翻译的整体可用率提升一个台阶。
结构化重排还有一种更彻底的玩法:把表格区域识别出来后,用文字坐标生成一个简化版的结构化 JSON,例如:
{ "columns": ["商品名称", "数量", "金额"], "rows": [ ["苹果", "2", "10.00"], ["香蕉", "3", "15.50"] ] }让 LLM 直接基于 JSON 结构修复和翻译,就不会发生跨列串字的问题。代价是实现成本高一些,要做列线检测或者列中心聚类,但一旦跑通,对票据、账单类图片的翻译效果提升是立竿见影的。
4.4 LLM 幻觉:把对的改成错的
这是我在所有坑里最想提醒你注意的一个。LLM 校对最大的风险不在于它修不好错误,而在于它会把本已正确的文本“修复”成错误内容。原因是模型有很强的“顺滑倾向”,碰到一个我们人类看起来很常见的搭配,会不由自主地补全或改写。
我遇到过最典型的案例是:图片里清清楚楚写着“首付 30%”,OCR 也正确识别了,LLM 却把它“修复”成“首付 30% 定金”。因为在日常语料里“定金”和“首付”经常一起出现,模型顺着语感加了词。这在合同类场景是致命失误,金额后面跟的术语完全变了。
我的对策组合很有效:
- 高置信度文本走旁路,不进 LLM;
- Prompt 里反复强调“不确定处保留原文,不要补全”;
- 模型温度调到 0,关闭随机性;
- 修复前后做 diff,对有改动的片段标记为“待确认”,在人工审核界面里提示。
LLM 修出来的结果,一定要让用户或复核人有能力看到“它改了什么”,而不是只看到最终结果。我会把 OCR 原文本和修复后文本做对比,用 diff 的方式列出来,这样任何一次模型“手滑”都能被快速发现。
4.5 批量任务的成本与排队:别让校对环节拖垮整体吞吐
OCR 本身很快,一张图几百毫秒,但 LLM 校对环节可能让单张延迟涨到几秒甚至十几秒。如果做实时翻译工具,这个延迟是致命的。批量离线任务的成本问题同样突出,我见过有人把全部 OCR 文本都丢给 LLM 翻译,导致单张成本翻了 5 倍还不止。
我的成本控制策略有三个:
- 只送低置信度片段进 LLM,高置信度直接透传,这是最大头的节省;
- 对重复图片做内容哈希缓存,同一张图二次处理直接读缓存;
- 对长文本块做重叠切分,避免一次请求塞太多 token,同时防止单次超时。
排队问题在调用商用 API 时尤其明显。某个账号并发一高,响应时间就开始膨胀,而且不同时间段的稳定性差异很大。我的做法是做一个任务队列,在调用 LLM 之前统计一下当前队列长度,如果积压超过预设阈值,就把部分低优先级任务切到本地小模型处理,保证整体吞吐不中断。
5. 一些不会写进文档的经验
5.1 先定义“对”,再追求 99.9%
每次听到有人直接开口要“99.9% 准确率”,我都会先反问一句:你的“准确”是怎么定义的?是按字符算、按句子算、还是按用户的使用体验算?这三个口径对同一套系统可能差出十几个百分点。
我建议做这类项目时,先找业务方一起定下“可接受质量”的准确定义。如果目标只是“用户看翻译结果能看懂”,那整图级可读懂准确率就是核心指标;如果目标是“发票金额识别错了要赔钱”,那单个数值字段的字段级准确率才是关键,甚至要按关键字段单独统计。定义清楚了,后面所有调优才有明确方向。
5.2 Prompt 迭代比换模型更见效
遇到修复效果不佳时,第一反应别是“换更大的模型”。我实测过,在同样的模型下,把 prompt 调对,效果提升可能比增加模型参数量还明显。
我的迭代方法是维护一个错误样本库:跑完一批样片后,把修复失败的 case 收集起来,分成三类——OCR 输出质量太差导致 LLM 没有足够线索、LLM 修错、LLM 漏修。针对每一类在 prompt 里增加边界条件说明。比如我手里积累了一批“已/己/巳”混淆的 case,就在 prompt 里加了一句“注意区分已、己、巳三个字,结合动词时态判断”。这样针对性提示的效果,比笼统地说“请仔细校对”好得多。
5.3 让用户有最后一道纠错权
技术做得再完善,也总会有极少数图片绕过所有防线。这时候产品不能设计成“黑盒输出”,要让用户能方便地看到原图、看到 OCR 文本、看到翻译结果,并可以一键纠正。我甚至建议对低置信度结果默认展示“原文对照模式”,用户瞄一眼就能确认翻译对不对。
这不仅是用户体验问题,也是在为你的评测集积累数据。用户每一次主动纠错,都是一条高质量标注样本,比你自己费劲找标注员高效多了。
5.4 积累评测样本才是长期资产
最后分享一个我最有体会的建议:从第一天开始就保存每一张样片的完整处理链路记录——原图、预处理参数、OCR 原始文本、置信度、LLM 修复文本、翻译结果、人工修正结果。这套数据积累三个月后,你就有了一份覆盖各种真实场景、各种错误类型的评测集。
之后无论换 OCR 引擎、调 prompt、还是升级模型,都用同一份评测集去评估,所有改动的好坏一目了然。项目做久了你会发现,模型会过时、API 会涨价、prompt 会调优,但那份属于自己的评测集,是永远增值的资产。我个人的体会是,OCR 图片翻译这个方向,真正的门槛不在某一个模型有多强,而在你把“看得见的文字”和“读得懂的语义”之间的桥搭得有多顺。OCR 负责看见,LLM 负责读懂,人负责确认,三条腿配合好了,剩下的事情只是让这个流程跑得更快、更稳、更便宜。