1. 起因:菜单没翻译明白,反而变成了一台“乱码生成器”
1.1 用户反馈的怪象:肉眼能读的内容,译文全成了猜谜
先说一个我真实做“图片翻译”Demo时遇到的场景。某个周末,我把一个带拍照翻译能力的小工具发给几位朋友内测,其中一个人拍了一张日料店的菜单。原图里的“炙烤三文鱼寿司”印得清清楚楚,但我这工具给出的译文却是“烤鱼寿司”再加一行莫名其妙的字符。更离谱的是,当图片里有红底白字、艺术字体、便宜打印机打出来的小票时,译文常常连“人工智障”都算不上,基本是在给用户表演“看图编故事”。
复盘时,问题其实不难定位:图片翻译不是“看图说话”,而是“先把图片变成文字,再把文字变成另一种语言”。前者是 OCR,后者是翻译模型。只要 OCR 这一环吐出来的文本是乱码,翻译模型拿到的就不是“原句”,而是一堆长得像原语言的噪音。很多做图片翻译的人,第一个想到的是换个更贵的 OCR 引擎,第二个想到的是换个更强的翻译模型。但实际上,OCR 在复杂背景、低分辨率、异形字体下必然出错,翻译模型又对纯噪音文本毫无抵抗力。真正能把两条断桥接起来的,是中间那个看起来不太起眼的角色——LLM 校对员。
1.2 为什么直接把 OCR 文本丢给翻译模型等于让模型吃夹生饭
我见过不少项目,流程看似很顺:拍照 -> OCR -> 得到字符串 -> 拼接成段落 -> 丢给翻译模型。但问题就藏在“拼接”这一步。OCR 的输出并不是干净语料,它的特征非常折磨人:行尾可能没有标点,词和词之间可能多出空格,相近的字会被识别成另一个字,表格里的数字和文字混在一起。更致命的是,OCR 返回的内容往往还带置信度信息,但很多接 OCR 的人完全忽略了这个字段,把低置信度的识别结果和高置信度的结果一视同仁。
翻译模型的训练数据几乎是干净文本,它默认输入是“人写出来的句子”。一旦 OCR 把“己”识别成“已”,把“末”识别成“未”,把横线当作连接符,翻译模型并不会像人一样停下来怀疑,它会在损失函数允许的范围内给出一个“自洽却错误”的译文。就像你给一个人听一句话,其中两个关键字被念错了,他能猜出原意;但你给一个人一张纸条,上面有十个字被涂改笔画,他大概率只能顺着错字编故事。翻译模型就是那个顺着错字编故事的人。
所以,后来我调整了思路:不要求 OCR 一步到位,不指望翻译模型有超能力,而是把 LLM 放进流程里,让它专门做“校对员”。这个动作,才是图片翻译准确率从翻车走向可用的分水岭。下面我会把完整思路、提示词设计、评测方法和踩过的坑一次性说清楚。
2. 把乱码拆开看:OCR 不是读错,而是读“碎”了
2.1 形近字混淆和笔画粘连:最典型也最难防的一类错误
OCR 出现乱码,绝不总是“整个字都认错了”。大多数时候,它只是认错了局部笔画。中文字形信息密度高,几个常见字形在低分辨率下几乎没有差别。比如“己、已、巳”,三个字就差半开的程度;“干”和“千”就差一横的长短;“天”和“夭”、“未”和“末”更是考验人类的眼力。OCR 引擎本质上是一套视觉分类器,它看的是像素模式,不是语义。当字体非常规、笔画较粗、背景有纹理时,分类器很容易在 top-1 上摇摆,而我们通常只取 top-1 结果,这个错误就顺理成章地流到了下游。
我把这类错误归纳成下表,项目里排查问题时可以直接对照。
| 错误形态 | 典型例子 | 根因 | 校对难度 |
|---|---|---|---|
| 短横长横混淆 | 王 -> 玉、干 -> 千 | 图像分辨率不足、字体笔画粗细不均 | 低,语义可纠正 |
| 半封闭结构混淆 | 己 -> 已、已 -> 巳 | 字符检测框边缘切割导致缺口丢失 | 中,需上下文 |
| 多点少点 | 鸟 -> 乌、免 -> 兔 | 低对比区域丢点 | 中,需上下文 |
| 左右偏旁倒置 | 部 -> 陪、邻 -> 怜 | 视觉模型对左右结构位置不敏感 | 低,通常可纠 |
| 多字合并/拆字 | “朋友”读成“月月又” | 字符检测框漂移,把一个词切成两个块 | 高,需要语言模型参与 |
处理这类错误的思路不是靠更好的 OCR,而是靠 LLM 的语言先验。一个足够大的语言模型,见过“朋友”这个词的无数种写法,当输入是“月月又”时,它会更倾向认为这是 OCR 对“朋友”的误读,而不是一个新造的汉字组合。
2.2 标点、空格和分行信息丢失:乱码不只是字错,还是语境崩塌
很多人只顾着看单字,却忽略了 OCR 乱码里更隐蔽的杀手:标点丢失、断行错误、空格错位。拍摄菜单时,餐厅的排版是多栏的;打印小票时,文字大小不一;拍书页时,行距会被透视拉扁。OCR 为了适应版面,经常把一行完整的话拆成两行半,或把两个半行拼成一句。结果就是,翻译模型拿到手的“段落”根本不是人类的自然段落。
举一个很常见的例子:原句是“本店招牌,每天限量供应”,OCR 可能分成两行输出“本店招牌每”和“天限量供应”。从单行去看,每句话都缺胳膊少腿。如果这时候直接翻译,模型看到是“本店招牌每”,它会翻译成类似 every signboard 的荒谬句子。而 LLM 校对员要做的事,不只是纠单字,更要通过整段上下文把断裂的部分重新接好。校对 prompt 里我会专门告诉它:“允许根据上下文调整断行,但不要改变原有顺序和语义。”
2.3 语言模型凭什么能“脑补”正确内容
LLM 之所以能当校对员,核心是它学过的语料里包含了大量正确的文本分布。给它的输入是一串带噪声的字,它会计算:在真实语言里,“本店招牌每天限量供应”的概率,远高于“本店招牌每”加“天限量供应”的概率。于是它把高概率序列作为修正结果输出。这个过程的本质,是按语言先验做重排和纠错。
用个通俗类比:在嘈杂的饭馆里,朋友说“明天一起爬香山”,你只听清了“明天一起”和最后两个字,你会自动脑补出“爬香山”,因为这是符合语境的推测。LLM 做校对就是类似的推测,只不过它脑补的粒度更细,可以精确到一个字、一个标点。但它有个前提:上下文不能太少。所以校对时不能一次性喂五个字,要喂完整段落,至少也要十几行,让模型有足够的“证据”去猜测。
3. 搭建一条“OCR→校对→翻译”流水线,中间这步是灵魂
3.1 整条链路的技术结构和数据流向
我在项目里把链路拆成五个阶段:图片预处理、OCR 识别、LLM 校对、翻译引擎、结果回填。预处理解决倾斜、反光、对比度问题;OCR 输出带位置信息的文本块;LLM 对照原始 OCR 文本进行纠错;翻译引擎基于纠错后的文本翻译;最后把译文按原坐标回填到图片上,或者直接在界面上展示。
这里最关键的是第四步和第三步之间不要直接共用同一段文字。很多人在工程上省懒事,OCR 返回什么,LLM 校对函数就拿什么来读,读完了再调用翻译接口。这样做不是不行,但会丢失一个特别有价值的信息:每行的置信度。我通常会让 OCR 输出一个结构化 JSON,包含四个字段:行号、文本、置信度、检测框坐标。然后,把行号和置信度一并交给 LLM,让它在校对时特别关注低置信度的行。
数据流向大致是这样:
- 图片 -> 倾角矫正与对比度增强 -> 文本检测 -> 逐行识别 -> 结构化结果
- 结构化结果 -> 合并为段落 -> 喂给 LLM 校对模块 -> 得到规范化的文本
- 规范化文本 -> 按原始行号切分 -> 翻译引擎 -> 译文按坐标回填
3.2 校对 Prompt 的设计:什么该管,什么不该管,必须写清楚
LLM 校对模块能不能做好,八成靠 prompt。我的第一版 prompt 写得太简单,只说“请修正 OCR 错误”,结果模型把原本正确的专有名词改了,还把“NIKE”改成了“耐克”,把“hello”改成了“哈罗”,最后翻译出来一团糟。后来我把要求收敛成四条:
- 保持原语言不变,不要翻译原文。
- 只修正 OCR 识别错误,不要润色文风。
- 遇到不确定的内容,保留原文并加标记。
- 每行只输出修正结果,不要解释。
给一段我实际在用的提示词模板(脱敏后)。
你是一名 OCR 校对员。我会给你一组从图片中识别出的文本,它们可能包含形近字错误、 断行错误、标点缺失和多余空格。请根据上下文推测原始内容,输出修正后的文本。 要求: 1. 保持原语言,不要翻译; 2. 专有名词、品牌名、地名人名不要主动改写; 3. 不确定的地方保留原字符,并在两侧加“[?]”标记; 4. 只输出修正后的内容,不要解释过程。 待校对文本: {OCR 文本拼接段}加了“不确定就保留”这条之后,效果立刻不一样了。模型不再自作主张,而是把真正的难点留给后续翻译模块或人工复核,乱改专有名词的问题基本消失。
3.3 坐标回填和分段策略:别让编排毁掉准头
另一个容易被忽略的工程细节是分段。OCR 输出的行数可能非常多,如果一股脑把几百行文本全喂给 LLM,既容易超上下文窗口,又容易出现“中间部分被忽略”的问题。我按版面块去分段,而不是按行数硬截。比如菜单的“主菜”区域是一块,“饮品”区域是一块,每一块之间用空行隔开,并告诉 LLM“空行表示自然段落”。
翻译完之后的回填同样有讲究。译文长度通常和原文字数不对等,中文译成英文时会变短,英文译成中文时会变长,如果直接按原始位置贴上去,字会溢出图片外。我的做法是:回填前先跑一次“文本框自适应”,根据检测框宽度和字数估算字号,超宽就自动折行或缩字号。这个不算核心原理,但做不好会让整个功能显得不专业。很多人的图片翻译之所以效果差,不是校对、翻译没做好,而是最后一步排版把一切毁于一旦。
4. 想谈 99.9%,先建立一个不骗自己的评测集
4.1 我搭建的评测集:不能只在两张菜单上测效果
网上很多人张口就是“准确率 99.9%”,但真正的问题在于,99.9% 是什么的准确率?是 OCR 字符准确率,还是最终译文的语义准确率?是同一批固定图片的准确率,还是随机抽样的真实场景准确率?如果不提前定义,这个数字没有任何参考价值。
我给自己搭了一套评测集,一共 120 张图,按来源分成四类:打印文档截图、手机翻拍菜单、发票小票、带水印的网页截图。每类 30 张,每张图里都有对应的标准翻译文本作为标注。这样做的原因是,只测干净的截图很容易,但手机翻拍有透视形变,小票有低对比度,网页截图有水印干扰,这些才是真实场景的复杂度来源。
4.2 三种准确率口径:字符级、行级、语义级
测试集建好之后,我把结果按三个维度分别统计。
| 口径 | 定义 | 我实测的大致区间 | 说明 |
|---|---|---|---|
| 字符级 | 译文中的字词与原译文标注完全相同的比例 | 95% - 98% | 容易受标点、断行影响 |
| 行级 | 整行译文语义等同且无关键错误的比例 | 90% - 95% | 要求更严格,受专有名词影响大 |
| 语义级 | 用户能否准确理解译文要表达的含义 | 98% - 99.9%(干净场景) | 这个口径最容易刷到漂亮数字 |
你会发现,标题里那个“99.9%”只有在“语义级 + 干净印刷体”这一特定条件下才成立。真实场景里混合手机翻拍、艺术字体、小票时,语义级准确率会回落到 96% 左右。所以以后看到别人宣称“99.9%”,第一反应不是羡慕,而是问他用的是哪个口径、哪个测试集。
4.3 实测结论:LLM 校对能把准确率从不可用拉到可用
我做过一次对照组实验:一组直接用 OCR 文本 + 翻译模型,另一组用 OCR 文本 + LLM 校对 + 翻译模型。实验结果是第一组的语义级准确率只有 78%,挑剔一点的用户在手机上根本没法用;第二组在干净场景下达到 99% 以上,在小票类图片上也有 95%。这说明 LLM 校对真正救的不是那些已经识别得很准的内容,而是把“基本可用”拉到“可用”的关键一环。
但这里必须说一个容易被忽略的点:LLM 校对提升的是“文本规范性”,不是“翻译质量”。如果你用的翻译模型本身很弱,即使校对得再干净,译文依然会很生硬。我试过一次把校对后的文本喂给一个轻量翻译接口,准确率反而比不校对更低,因为轻量模型对术语和语序的处理能力有限。后来我改用更强的翻译模型,校对的收益才完全释放出来。这条经验值得记下来:校对是必要条件,不是充分条件。
5. 过度纠正的翻车现场:什么文本千万别让校对员乱改
5.1 专有名词和品牌名:LLM 最爱“好心办坏事”的地方
我发现 LLM 校对有一个难以抑制的冲动:把看起来像错别字的东西“修正”成常见词。最典型的例子是品牌名。某运动品牌包含一个非常规拼写的词,LLM 不认识,看到 OCR 结果里有一串类似拼写,就自动“纠正”成它自己熟悉的单词。这在做菜单翻译时尤其致命,因为菜品名称里本来就有大量音译词、方言词、特殊命名。
我的应对办法是建一个“受保护词表”。在 prompt 里附上一句“以下词汇为本项目常见专有名词,禁止改写”,并把这些词列表传进去。另一个办法是只对低于置信度阈值的区域启用校对,高置信度区域直接原样转发。这样即使 LLM 想乱改,也没有机会接触正确的内容。
5.2 多语言混排:日文汉字、英文、数字三板斧
多语言混排是目前最难处理的场景。日文菜单里大量出现汉字,字形和中文字体不完全一致。OCR 可能把日文的“駅”识别成“驿”,再把“营业时间”识别成繁体或者错体。LLM 校对时如果设定的“主语言”是中文,它会把日文汉字全改成中文,甚至把日文专有名词抹掉。
我现在的做法是给校对模块传一个 language_hint 参数,明确告诉它源语言是什么。例如“该批文本主要语言为:zh-CN,允许出现 ja/en 专有名词和数字”。同时,我会在 prompt 里强调“全角半角差异不算错误,保持原样”。对于数字、日期、时间,我直接要求 LLM 不得修改,只修复与数字相邻的字符错误。这样能有效减少“脑补语义”带来的误伤。
5.3 化学式、代码片段和 ID 编号:宁可让 OCR 裸奔也别让校对乱跑
有些内容天然就不能被“语言模型”纠正。最典型的三种:化学分子式、代码片段、订单编号。拿代码片段来说,“ifname== 'main':” 这一行如果被 OCR 识别成“if name != main”,LLM 很可能自作聪明地补成“if name == main”,因为它觉得这更符合 Python 语法。但补完之后代码的行为可能完全不同。
处理这类内容,我的策略是提前识别并绕过。通过正则或字段分类,在进入校对模块之前,把含大括号、分号、等号或连续数字的块直接标记为“非自然语言文本”,直接走翻译原样通道。如果翻译模型对代码翻译无意义,那就干脆只做“原样回填”。这个细节在财报、报表、学术截图场景下特别重要。宁可保留 OCR 的不完美,也不要让校对员自作主张地创造语义。
6. 工程化落地:成本、延迟和置信度分级策略
6.1 对低置信度区域做“分级校对”,别让每张图都跑LLM
最开始我把所有 OCR 文本都送进 LLM 校对,准确率确实上来了,但成本也上来了。项目跑到第 5 天,我的账单就超出了预算。后来我加了一个逻辑:置信度高于 0.97 的文本直接跳过校对,置信度在 0.85 到 0.97 之间的交给 LLM 校对,置信度低于 0.85 的内容除了校对,还要额外加[recheck]标记,方便人工介入。
这一改动让 LLM 调用量减少了大约 60%,而语义级准确率只下降了 0.8%。这笔账很划算。另一个优化是“按块去重”:图片中重复出现的招牌、店名,第一次校对完就缓存结果,后续图片直接复用,减少了大量重复调用。
6.2 批量请求与上下文复用:给“校对员”完整的证据再干活
LLM 校对非常依赖上下文。如果我只是把孤零零的一行文本发给模型,纠错效果会大打折扣。工程化时必须把同一版面块的多行文本拼成一段再发出去。我用两次批量调用完成一个任务:第一次调用只读上下文,识别断行和疑似错误;第二次调用在前一次结果基础上,只对标记为不确定的行做二次校对。两轮校对的好处是,第一轮产生“可疑点”,第二轮专注处理可疑点,避免模型来回反复。
这里有个重要细节:不要让 LLM 自己生成行号。OCR 输出的结构化结果里有真实行号,我会把行号当作不可变 ID 传给模型,并要求它必须在每行输出前保留对应行号。虽然这会增加一点 token 开销,但能彻底避免“校对完不知道哪段文字属于哪个检测框”的尴尬。
6.3 什么时候别自己搭这套东西
如果你只是偶尔翻译一两张截图,完全没必要搭这么重的流水线。直接用普通翻译软件或端到端多模态大模型就够了。端到端大模型的优点是中文名翻译更自然,理解长文本上下文的能力强,缺点是不好控制位置信息,对版面复杂、表格密集、需要精确回填的场景并不友好。
我的判断标准是这个:如果项目里需要“文字位置精确对应原图”,那端到端多模态模型反而不如 OCR + LLM 校对 + 翻译的流水线;如果只需要“大致看懂意思”,端到端模型更省事。两种方案并不冲突,可以在系统里做路由。文档处理类走专业 OCR 流水线,快速翻拍类走端到端模型,综合效果最好。
在这套流程跑起来之后,我最大的体会是:不要把 LLM 当成万能钥匙,而要把它当成一个“有常识但手容易痒的实习生”。给它明确边界、给它必要的证据、最后再给它一把只能修错字的改刀,它才能真正做好校对员这个角色。最后留一个小经验:上线之前,务必准备一个十张图的“烟囱测试集”,每轮改完算法都先跑一遍,对比前后准确率和调用成本。你肉眼看起来“好像更准了”的效果,和测试集里的数字,往往是两回事。