上个月接了个活儿,要把一批扫描合同和产品说明书里的文字全部抠出来做结构化归档。一开始我很天真,直接拿手上最熟的那个老牌引擎跑了一遍,结果中文标点全丢、竖排文字直接读成乱码、表格更是惨不忍睹。换第二个、第三个,情况各有各的糟法:有的装都装不上,有的装上了但模型体积压垮了容器,还有的在 CPU 上跑一张 A4 图要两秒多,几千张图排下来直接能去睡一觉。折腾到这个份上,我干脆把市面上主流的开源OCR识别引擎攒了十二款,统一数据集、统一硬件、统一指标,认认真真做了一次能力测评。这篇就把整套测评口径、实测数据、踩坑记录和最终选型结论摊开讲,不管你是刚接触 OCR 想快速上手,还是已经在做工程选型,应该都能直接从里面抄到东西。
1. 这次横评的由来:一次简历批量解析把我逼上了对比之路
1.1 为什么不能只看"官方宣传的准确率"
几乎所有开源 OCR 项目在 README 里都会给一个漂亮的准确率数字,99%、98.7% 之类。我最早就是被这些数字骗过的。实际跑下来才发现,这些数字往往对应的是"清晰扫描件 + 特定语言 + 特定字体"这种理想条件,和真实业务里那种歪斜、带水印、光照不均、分辨率只有 200dpi 的手机翻拍图完全是两个世界。
举个具体的例子。我手上有一批产品说明书扫描件,纸张发黄、页边有装订孔的阴影,还有一部分是双栏排版。某款以"多语种通吃"著称的引擎在这批图上的字符错误率(CER)接近 18%,而它在标准测试集上公布的英文 CER 是 1% 出头。差了十几倍,原因就三个:图像质量差、版面复杂、语言混杂。所以这次测评我坚持一件事——所有引擎必须跑同一批"脏数据",而且脏数据的比例要占到六成以上,不然测出来的东西没有参考价值。
1.2 我到底想回答哪几个问题
做这次横评不是为了排个一二三四名,那种榜单意义不大,因为业务场景差异太大了。我真正想搞清楚的是四件事:
- 中文印刷体这条基本盘,谁最稳?这是绝大多数国内项目的核心需求,必须单列。
- 速度和精度的性价比拐点在哪?有的引擎精度高 2 个点,但耗时是别人的五倍,这笔账得算清楚。
- 特殊场景(手写、表格、竖排、票据)有没有专精选手?通用引擎在这些场景上往往拉胯得厉害。
- 工程化成本有多高?装不装得上、模型多大、CPU 能不能跑、有没有现成的推理加速方案,这些在真实项目里比精度更致命。
提醒一句:下面所有实测数据都基于我自己搭的一套固定环境和一批固定样本,换机器、换数据,绝对值一定会有出入。看趋势、看相对差距,别死抠小数点。
1.3 文中代号体系的说明
为了避开版本快速迭代带来的干扰,也避免把文章写成产品软文,下文统一用引擎 A 到引擎 L这套代号指代,代号后面会括注它的核心技术特征(比如底层框架、模型结构、主打场景、授权方式)。熟悉这个圈子的朋友基本能从特征描述里对号入座,不太熟的朋友也可以直接按"特征 + 场景"来做选型,不会影响阅读。
2. 测评口径先摆明:数据集、指标、硬件三件套怎么定
2.1 样本构成:四类数据、六个子集、共 3200 张
数据集这块我花了差不多一周时间整理,因为这是整个测评的地基。如果样本选偏了,后面所有结论都是空中楼阁。最终构成是这样的:
| 子集 | 数量 | 典型特征 | 难度 |
|---|---|---|---|
| 清晰印刷中文 | 800 | 300dpi 扫描、单栏、宋体黑体为主 | 低 |
| 清晰印刷英文 | 600 | 技术文档、含公式符号、混排数字 | 中 |
| 复杂版面 | 600 | 双栏/三栏、图文混排、表格嵌套 | 高 |
| 手机翻拍 | 500 | 透视畸变、阴影、反光、倾斜 5~15 度 | 高 |
| 中文手写 | 400 | 便签、表单填写、字迹连笔 | 极高 |
| 竖排与繁体 | 300 | 古籍影印、竖排标点、异体字 | 极高 |
每张样本我都人工标注了真值文本,标注时统一了两条规则:标点保留全角原样(不做半角转换),换行不做保留(按阅读顺序拼成一整段)。这两条规则非常关键,因为很多引擎默认会输出带换行的结果,如果不对齐规则,算出来的编辑距离会虚高,误差能到 10% 以上。
2.2 指标:为什么我主用 CER,而不是准确率
准确率(整句全对的比例)这个指标太苛刻了,一句话里错一个字就算全错,对长文本几乎没区分度。所以我主指标用字符错误率 CER,计算方式是:
CER = (替换字符数 + 删除字符数 + 插入字符数) / 真值总字符数这套算法本质就是编辑距离(Levenshtein Distance)归一化。它有个好处,就是对错误类型不敏感但总量敏感:漏字、多字、错字都会累加,不会因为错一个标点就把整段判死刑。中文场景下 CER 能压到 2% 以内,基本就是"人眼扫一遍挑不出几处错"的水平;超过 10%,就得靠后处理或者人工复核兜底了。
辅助指标我用了三个:
- 检测召回率:文本行框有没有漏检。这个指标在密集小字场景特别重要,漏检一行就是整段丢失,比识别错几个字严重得多。
- 单图耗时:同一台机器、同一批图、并发 1,取 P50 和 P95。为什么要看 P95,因为工程上真正让你加班的是那一小撮慢样本。
- 峰值内存:这是容器化部署的生死线,尤其在 Kubernetes 上跑,内存超了就是 OOM 重启循环。
2.3 硬件与运行环境
所有测试在两种环境各跑一遍:
- CPU 环境:8 核通用 x86 处理器,16GB 内存,不启用任何独立加速卡。这是绝大多数中小项目真实的落地环境,必须测。
- GPU 环境:入门级推理显卡,8GB 显存。用来观察各引擎对并行加速的支持程度。
软件层面统一用同一个 Linux 发行版、同一个 Python 大版本,依赖冲突的引擎单独开虚拟环境,记录下安装过程中所有需要手动打补丁的地方。这些"安装血泪史"后面第 6 章会详细讲,很多坑比精度问题更劝退。
3. 十二款引擎的定位分层:从老牌通用型到端侧轻量派
3.1 第一梯队:通用全能型,什么都能干但什么都不算极致
这一档的共同特点是语言覆盖广、文档齐全、社区活跃,属于"闭着眼睛装一个也不会太差"的选择。
引擎 A(老牌通用型)是这个领域资格最老的选手之一。它早期靠传统图像处理起家——二值化、连通域分析、投影切割,后来才补上神经网络模型。优点是支持的语言数量惊人,上百种语言的开箱即用包都有,命令行工具链极其完整,连批量处理的脚本模板都给你写好了。缺点是它的模型结构偏老,中文场景下的表现只能说"能用",遇到复杂版面容易整段乱序。
引擎 C(Python 友好型)走的是另一条路,把检测模型和识别模型打包成一套极简的 Python 接口,三行代码出结果,对新手极其友好。它的模型权重里内置了多语种支持,中英混排不需要额外配置。代价是首次加载模型要联网下载几百 MB,离线环境得提前把权重文件搬进去。
引擎 B(中文全流程型)是我认为在中文场景下最"懂行"的一个。它把整个 OCR 拆成三个独立的模型:文本检测、方向分类、文本识别,三个模块可以单独替换和微调。检测用的是基于分割的方法,对弯曲文本和密集小字比较友好;识别端从早期的卷积循环结构一路演进到纯 Transformer 结构,长文本的上下文建模能力强了不少。它最大的优势是中文语料训练得非常扎实,标点、生僻字、数字字母混排的鲁棒性明显高于同档位的其他引擎。同时它提供了从服务端大模型到移动端超轻量模型的一整套尺寸梯度,选型空间很大。
3.2 第二梯队:轻量与端侧派,为速度和小体积而生
引擎 D(轻量推理型)的思路很有意思:它不自己训模型,而是把成熟的深度学习 OCR 模型转换成通用推理格式,再用一个极轻的运行时去跑。这么做的直接好处是依赖极少、包体积小、CPU 上速度飞快,适合塞进命令行工具、桌面应用或者边缘设备。代价是对冷门语言和特殊字体的覆盖不如大而全的选手。
引擎 I(嵌入式型)把这个思路推到极致,模型量化后只有几 MB,能在手机和低功耗芯片上实时跑。精度上必然要让步,清晰印刷体的识别没问题,一旦图像质量下降,错误率上升得非常快。
引擎 L(无重依赖型)主打纯 C++ 实现,不依赖任何深度学习框架的运行时,编译产物可以直接甩到离线服务器上跑。这种设计在封闭环境里非常吃香,但编译过程对新手不算友好,依赖库版本稍有不匹配就是一堆链接错误。
3.3 第三梯队:专精场景型,通用能力一般但单项突出
这一档的引擎如果拿去跑通用评测,成绩往往不好看,但在自己的主场里能把通用选手按在地上摩擦。
引擎 E(表格结构型)不只输出文字,还输出单元格的行列坐标和合并关系,能直接还原成表格结构。做财务报表、检验报告自动录入的,没有它基本等于自虐。
引擎 F(版面分析型)强在文档结构理解:能区分标题、正文、表格、图片、页眉页脚、页码,并给出阅读顺序。处理几百页的标书、论文、年报这类长文档,必须先过它一遍做版面切分,再喂给识别引擎,否则多栏排版的阅读顺序一定是错的。
引擎 G(手写与公式型)专攻手写体和数学公式,公式还能直接输出结构化标记语言。印刷体上它未必比通用选手强,但手写场景下 CER 能比通用引擎低一半。
引擎 H(古籍竖排型)支持竖排阅读顺序、繁体字、异体字、竖排标点。这个赛道非常窄,但一旦你有这个需求,基本没得选。
引擎 J(票据字段型)面向票据、证件、表单,输出的是结构化的字段键值对而不是纯文本。它内部做了大量规则和模板匹配,针对固定版式的准确率极高,但换个版式就得重新配模板,泛化能力弱。
引擎 K(多模态大模型型)是最近两年冒出来的新形态,用视觉语言模型端到端出结果,不区分检测和识别两个阶段。它在复杂版面理解、模糊图像推理、甚至"看图说话"式的信息抽取上表现惊艳,但推理速度是传统方案的十几倍甚至几十倍,而且要吃掉大量显存。目前更适合做离线批处理或者对精度要求极高、对成本不敏感的场景。
4. 印刷体中文与英文的硬碰硬:准确率与速度的真实排序
4.1 清晰印刷中文:差距比想象中小,细节见真章
在 800 张清晰印刷中文样本上,第一梯队引擎的 CER 差距其实没有宣传的那么大,基本都在 1% 到 3% 之间。真正拉开差距的是标点和生僻字这两项。
我统计了各引擎在中文标点上的表现,发现一个很有意思的现象:不少引擎在训练时对标点的处理是"可选的",导致输出里全角逗号句号丢失、引号被吞、破折号变成两个短横。这类错误在 CER 上贡献不大(因为标点占比低),但在实际业务里是灾难——你把文本喂给下游的分句、抽取、检索模块,标点丢了整个语义边界就乱了。
生僻字这块,引擎 B 的优势很明显。我特意在样本里放了"鬻、龘、甪、邨"这类字,大部分引擎要么输出成空,要么给出一个形近的常用字,只有引擎 B 和引擎 H 能给对。原因也不复杂,就是训练语料里有没有这些字的样本,以及词表有没有覆盖到。
| 引擎 | 中文 CER | 标点保留 | 生僻字 | CPU P50 耗时 |
|---|---|---|---|---|
| 引擎 A | 2.8% | 中 | 弱 | 380 ms |
| 引擎 B | 1.1% | 优 | 优 | 620 ms |
| 引擎 C | 2.2% | 中 | 中 | 720 ms |
| 引擎 D | 2.5% | 中 | 弱 | 180 ms |
| 引擎 K | 0.8% | 优 | 优 | 4200 ms |
4.2 英文与数字混排:老牌选手的舒适区
英文场景下排名就完全变了。引擎 A 在这块扬眉吐气,CER 只有 0.9%,尤其是技术文档里那些夹杂着下标、上标、希腊字母、数学符号的段落,它的表现比很多新模型都稳。这跟它的训练语料构成有关,英文公开语料的规模和质量远高于中文,老模型吃到的红利还在。
引擎 C 的英文表现也不错,而且它对连字符断行的处理比较聪明。英文排版里一个单词被行尾连字符拆成两半是很常见的,有些引擎会老老实实输出"informa-\ntion",有些则直接丢掉连字符变成"information"。后者才是我们要的结果。这个小细节,如果没在实际业务里被坑过,根本想不到要去测。
数字这块要单独提一句。金额、编号、日期这类纯数字串,一旦识别错一位,下游全盘皆错。我测下来,引擎 J 在数字串上的准确率最高,因为它内部有针对数字的字符集约束和后处理校验。通用引擎在这方面就差一些,尤其"0"和"O"、"1"和"l"、"5"和"S"这几组混淆,几乎是所有通用引擎的通病。
4.3 速度账本:快五倍意味着什么
单看耗时数字可能没感觉,换算成业务成本就直观了。假设你要处理 10 万张图:
- 引擎 D 按 180ms 算,单核大约 5 小时,四核并行一个半小时搞定。
- 引擎 B 按 620ms 算,单核 17 小时,四核大约 4.3 小时。
- 引擎 K 按 4200ms 算,单核 116 小时,四核也要 29 小时,而且还得有显卡。
这就是为什么我说"精度高 2 个点值不值"要算账。如果你的业务对错误率极其敏感(比如合同金额),那多花几倍时间是值得的;如果是海量历史档案做全文检索,错几个字根本不影响搜索命中,那速度就是第一优先级。
一个反直觉的结论:我在实测中发现,把引擎 D 的输入图像先做一轮超分辨率放大再识别,精度能从 2.5% 提到 1.6%,但耗时只增加 30%。也就是说,预处理带来的性价比,往往高于换一个更重的模型。这个思路后面第 6 章还会展开。
5. 难场景专项:手写、表格、竖排古籍与低质量手机拍照
5.1 手机翻拍:预处理决定生死
手机翻拍这 500 张样本是所有通用引擎的滑铁卢。CER 普遍从 2% 飙升到 8% 以上,最差的一个直接冲到 15%。原因集中在三个地方:透视畸变导致文字行弯曲、阴影造成局部过暗、反光导致局部过曝。
我拿引擎 B 做了个对照实验,不加任何预处理直接跑,和先用文档矫正 + 去阴影 + 对比度拉伸处理后再跑,差距非常大:
| 处理方式 | CER | 耗时增量 |
|---|---|---|
| 原图直跑 | 9.4% | 0 |
| 仅做透视矫正 | 6.1% | +40 ms |
| 透视矫正 + 去阴影 | 4.3% | +95 ms |
| 透视矫正 + 去阴影 + 自适应二值化 | 3.6% | +130 ms |
透视矫正是这里的关键。它要做的其实是四步:先检测文档的四个角点,再做透视变换把它拉成标准矩形,最后裁剪边缘。很多引擎自己没有这一步,你就得自己补。检测角点的方法,简单点可以用边缘检测加轮廓筛选,复杂点可以直接调一个轻量的文档检测模型。我实测下来,上面这个组合把 CER 从 9.4% 压到 3.6%,而增加的 130ms 相对于识别本身 600ms 的耗时,是非常划算的买卖。
5.2 手写体:通用引擎基本交白卷
400 张中文手写样本上,通用引擎的 CER 普遍在 20% 到 35% 之间,基本等于不可用。引擎 G 的 CER 是 12.8%,虽然也不算低,但已经是断层领先了。
手写难在哪?一是字形变化极大,同一个人不同时候写的字都不一样;二是连笔,两个字的笔画粘在一起,检测阶段就分不开;三是书写习惯,比如把"的"写成草书的一个圈。这三点都不是靠加大模型能解决的,得靠专门的手写语料训练。
另外提一个实操经验:手写场景下,后处理纠错的收益远大于印刷体。因为手写识别错的往往不是随机错,而是稳定地错成同一个形近字。你可以统计一批样本里高频的错字对,建一个映射表做定向纠正,我有一次靠这个把一个表单场景的准确率从 78% 拉到 91%,成本几乎为零。
5.3 表格与竖排:两个完全不同的技术路径
表格这块,引擎 E 和其他选手不是一个维度。通用引擎输出的是"一堆文本",顺序还经常错乱;引擎 E 输出的是带行列坐标的结构化结果,能直接渲染回表格。它的处理流程大致是这样:先检测表格线或者用无框表格的行列对齐特征做推断,再确定单元格的合并关系,最后把 OCR 结果按坐标填回单元格。
无框表格是它的弱项。纯靠文本对齐推断行列,一旦某一行特别长或者特别短,行列切分就会错。我遇到过一次,一份两列的参数表里,某一格内容换行变成了三行,结果整个表格被识别成四列。解决办法是先做一个投影分析,统计每一行文本的水平分布,把明显偏离主流的行单独处理,准确率能提不少。
竖排古籍这块,引擎 H 几乎是唯一解。它的核心不在于识别单个字,而在于阅读顺序的判定——竖排是从右往左、从上往下,而且经常有双行小注夹在中间。通用引擎即使把每个字都认对了,输出的顺序也是乱的。我试过用引擎 A 跑一份竖排影印件,单个字准确率还行,但整段输出完全是乱序的,拼回去的文本根本没法读。
5.4 复杂版面:先分析再识别,别指望一步到位
600 张复杂版面样本上,单靠任何一个识别引擎都做不好。我的做法是两段式:先用引擎 F 做版面分析,把页面切成若干个区块,标出每个区块是标题、正文、表格还是图片,并给出阅读顺序;然后按区块类型分派给不同的识别引擎——正文给引擎 B,表格给引擎 E,公式给引擎 G。
这个方案听起来麻烦,但效果是真的好。直接把整页丢给通用引擎,双栏排版几乎百分之百会串行,左栏第一行接右栏第一行,读起来像天书。加了版面分析之后,阅读顺序正确率从 60% 多提到了 95% 以上。
代价是链路变长、耗时叠加。我的优化思路是:版面分析模型跑在整页缩略图上,把图缩到宽度 1000 像素左右再分析,耗时能从 800ms 压到 200ms 以内,而且对区块划分的精度几乎没有影响,因为版面结构本来就只需要看大轮廓。
6. 工程化落地:安装踩坑、模型体积、推理后端与量化取舍
6.1 依赖地狱:三次让人想把键盘砸了的经历
精度再高,装不上就是零。这十二款引擎里,有差不多一半在首次安装时给我制造过麻烦,记录几个典型的:
第一个坑是依赖版本锁死。某款引擎的 Python 包把底层推理框架的版本写死了,而我的环境里另一个服务需要新版本,两者不兼容。解决办法只有开独立虚拟环境,或者用容器把两个服务隔开。这件事教育我:做 OCR 选型时,第一件要问的不是精度多少,而是它依赖什么框架、什么版本,和我现有技术栈冲不冲突。
第二个坑是编译型引擎的链接错误。那个纯 C++ 的引擎,L 版本依赖库版本差一个小版本号,编译时报了一屏的未定义符号。排查了一下午才发现是系统里装的开发库和新版本头文件路径不一致。这类问题没有捷径,只能靠看编译日志、ldd查依赖、逐层往回找。
第三个坑是模型权重下载。好几款引擎首次运行会自动去拉权重文件,内网环境直接卡死。提前把权重文件下好、放到指定目录、再用环境变量指向本地路径,这个流程要写进部署文档,不然换个人部署又是一轮踩坑。
6.2 模型体积与内存:容器化部署的真·生死线
我把各引擎的模型体积和推理峰值内存列了一下,这个表在选型时比精度表更实用:
| 类别 | 模型体积 | 峰值内存(CPU) | 冷启动耗时 |
|---|---|---|---|
| 端侧轻量型(引擎 I) | 5~15 MB | 200 MB | < 0.5 s |
| 轻量推理型(引擎 D) | 30~60 MB | 400 MB | ~1 s |
| 中文全流程型(引擎 B) | 80~200 MB | 900 MB | ~2 s |
| 多模态大模型型(引擎 K) | 2~8 GB | 需显卡 | 10 s 以上 |
这里的门道在于:很多团队只算模型体积,不算运行时的内存开销。一个 200MB 的模型,加载之后加上中间张量、框架运行时、批处理缓冲区,实际内存占用可能是 900MB 甚至更多。如果你的容器限制是 1GB,并发两个请求就直接 OOM。我的经验值是按"模型体积 × 4"来估算内存下限,再留 30% 余量,这样比较稳。
还有一个容易被忽略的点是冷启动。服务化部署时,第一笔请求要等模型加载完才能处理。引擎 K 这种大模型冷启动十几秒,如果你的服务是按需伸缩的,每次扩容都有一段时间完全不可用。解法是加预热接口,或者干脆常驻一个最小副本。
6.3 推理加速:哪条路性价比最高
加速手段我试了四种,按性价比排序:
- 量化。把浮点模型转成定点,体积能压到四分之一,CPU 速度提升 2~3 倍,精度损失通常在一个点以内。这是性价比最高的手段,几乎所有引擎都支持,而且现在主流的转换工具做得很成熟。
- 推理运行时替换。把模型导出成通用中间格式,再用针对 CPU 优化的运行时去跑。这条路的收益取决于你的 CPU 型号,支持向量指令集的机器提升明显,老机器提升有限。
- 批处理。一次喂多张图给模型,能大幅摊薄单张耗时。但要注意,批处理会增加单次响应延迟,而且对内存的要求是线性增长的。
- 显卡加速。收益最大但成本也最高,而且要处理显存不够时的降级逻辑。我的建议是,除非日均处理量真的到了十万张以上,否则先别急着上显卡。
量化这块有个坑要提:识别模型和检测模型对量化的敏感度不一样。我在实测中发现,检测模型量化后精度几乎无损,但识别模型量化后,对细笔画汉字的辨识能力会下降,尤其"未"和"末"、"己"和"已"这种差一笔的字。所以我的做法是只量化检测模型和方向分类模型,识别模型保留浮点,这样速度能拿到大半,精度损失控制得很小。
6.4 服务化的几个必备设计
把 OCR 做成服务的时候,有几个设计我踩过坑之后觉得是必需品:
- 超时与降级:单张图识别超过阈值就切断,返回空结果并标记待复核,不要让一个畸形图片拖垮整个队列。
- 结果缓存:很多业务场景里图片会被重复提交(用户刷新、重试),用图片内容的哈希做缓存键,能省掉大量重复计算。
- 异步任务队列:批量处理绝不能用同步接口,一定要走队列。任务里记录阶段进度,前端能显示"已处理 300/5000",体验完全不一样。
- 识别结果落库时保留原始坐标:哪怕你现在做的是纯文本提取,也要把每个文本行的框坐标存下来。将来做高亮定位、结果校对、重新切分的时候,没有坐标就得从头再跑一遍。
7. 选型清单:按业务预算和场景直接对号入座
7.1 按场景的直给建议
折腾完这一圈,我把选型结论整理成了一张对照表,你可以直接按自己的情况抄:
| 你的场景 | 首选 | 备选 | 关键理由 |
|---|---|---|---|
| 中文印刷体为主,精度优先 | 引擎 B | 引擎 C | 中文标点和生僻字覆盖最好 |
| 英文技术文档、多语种混排 | 引擎 A | 引擎 C | 语种覆盖广,符号处理稳 |
| 高并发、成本敏感、精度可放宽 | 引擎 D | 引擎 I | CPU 速度快,依赖少 |
| 手机拍照、图像质量不可控 | 引擎 B + 预处理链路 | 引擎 C | 配合矫正去阴影效果显著 |
| 表格与报表结构化 | 引擎 E | 引擎 F + 引擎 B | 直接输出单元格结构 |
| 长文档、多栏、需要阅读顺序 | 引擎 F + 引擎 B | 引擎 F + 引擎 C | 版面分析是前置刚需 |
| 手写表单填写 | 引擎 G | 引擎 B 微调 | 专用手写语料效果差距明显 |
| 竖排古籍、繁体异体字 | 引擎 H | 无 | 这条赛道几乎没得选 |
| 票据证件字段抽取 | 引擎 J | 引擎 K | 模板化后准确率极高 |
| 离线封闭环境、无重依赖 | 引擎 L | 引擎 D | 静态编译,部署简单 |
| 复杂模糊图像、追求极致精度 | 引擎 K | 引擎 B | 但要做好算力和延迟预算 |
7.2 一个被低估的组合思路:轻量前端 + 重型兜底
很多时候没必要非此即彼。我最近在做的一个方案是双引擎级联:先用轻量引擎 D 快速跑一遍,同时输出每个文本行的置信度;置信度高于阈值的直接采用,低于阈值的行单独裁出来,交给引擎 B 重跑一遍。
这么做的效果超出我预期:CER 从轻量引擎的 2.5% 降到了 1.4%,而总耗时只比纯轻量方案多了 25%。原因是低置信度的行通常只占 10% 到 20%,重跑的成本很小。这套思路特别适合那种"精度要过关但预算又上不了大模型"的项目,性价比非常高。
有个细节要注意:不同引擎的置信度分数不在同一个尺度上,不能直接混用阈值。我的做法是在自己的验证集上分别跑一遍,各画一条精度-召回曲线,找到各自的最优切分点,独立设阈值。这一步不做,级联效果会打折扣。
7.3 什么情况下应该考虑商业方案
这话我得说清楚,不是所有场景都值得死磕开源。如果满足下面任意两条,建议认真评估一下商业方案:
- 日均处理量在百万张以上,自建的机器成本和人力维护成本会超过采购成本;
- 场景极度垂直且对精度要求极高,比如特定行业的单据,开源方案的定制成本远超预期;
- 团队里没有专门负责模型调优的人,而业务方对准确率有硬指标。
开源方案的价值在于可定制、可私有化、无授权约束,但它不是免费的——你省下的授权费,最终会以工程人力的形式付出去。这笔账要提前算。
8. 那些年踩过的坑:误判、乱序、坐标错位的排查链路
8.1 中文引号成对丢失:一个花了两天才找到的根因
现象是:识别出来的文本里,前引号还在,后引号全没了。我一开始以为是后处理的问题,翻了一遍代码没找到。又怀疑是标点字典没对上,查了字典也正常。
真正的排查链路是这样的:先把原始图片裁出引号所在的小块单独跑,发现结果是对的——引号都在。这说明不是识别模型的问题。然后回到整图,发现只有位于行尾的后引号会丢。再往深了查,是文本检测阶段的问题:这个后引号贴在行尾的边界上,检测框把它切掉了一半,导致识别模型看到的图像里引号不完整,直接被判成噪点过滤掉了。
解决办法有两个:一是把检测框往外扩几个像素(通常扩 2~3 像素就够),二是后处理阶段做引号配对校验,发现单数引号就根据上下文补齐。我最后两个都做了,前者治本,后者兜底。
这个案例的教训是:排查 OCR 问题,一定要把"检测"和"识别"两段分开验证。把可疑区域裁出来单独识别,如果结果对,问题就在检测;如果结果也错,问题才在识别模型或者后处理。这一步能把排查范围砍掉一半。
8.2 数字串首字符丢失:又是边界问题
第二个坑更隐蔽。现象是所有以数字开头的行,第一个数字会丢。比如"2024年第三季度"变成"024年第三季度"。
根因和上面类似,但成因不同:这批样本是扫描件,页面左边距很窄,数字紧贴纸张边缘。检测模型在边缘处的特征响应弱,框的左边界往内缩了 5 个像素左右,正好把首字符切掉。
这次的修复方案是在输入阶段就做边缘白边扩充——在图片四周各补 20 个像素的白色背景。这个操作听起来很土,但效果立竿见影,而且成本几乎为零。我现在做所有 OCR 预处理管线,第一步就是补白边,已经成了肌肉记忆。
8.3 多栏排版的阅读顺序:必须靠版面分析解决
这个坑前面提过,这里展开讲排查思路。现象是识别出来的文字都对,但顺序完全乱。如果你只是做全文检索,这个问题可能不明显;但如果要做摘要、抽取、或者展示给用户看,乱序就是致命的。
排查方法很直接:把每个文本行的坐标打出来,画在一张图上,看看它们的顺序关系。你会发现双栏排版的坐标特征是——左栏和右栏的 y 坐标大量重叠,x 坐标明显分成两簇。这时候只要按 x 坐标做一次聚类,分成两簇,再分别按 y 排序,顺序就对了。
但这个简单方法的适用范围有限。三栏、图文混排、穿插的图注和侧边栏,单纯靠坐标聚类就不行了,必须上版面分析模型。所以我的建议是:先用坐标聚类应付单栏和双栏,遇到复杂版面直接上模型,别自己造轮子。造出来的东西大概率不如现成的模型稳。
8.4 中英混排的语种误判
还有一个经典问题:一段中文里夹着几个英文单词,识别结果里英文全变成了形近的拼音字母,或者反过来。根因是识别模型有语种切换逻辑,它需要判断当前文本行属于哪种语言,判断错了整个行就按错误语言解码。
排查这个要看模型有没有暴露语言标识参数。很多引擎支持在中英混排场景下手动指定"以中文为主、兼容英文"的模式,打开之后准确率提升明显。如果引擎不支持,那就只能按行切分,对每一行单独判断语种。判断方法可以很简单:统计行内中文字符占比,超过 30% 就按中文模式跑。
这个经验我吃过亏才记住:不要指望引擎自动判断语种,在混合场景下,显式指定比让它猜要稳得多。
8.5 一个通用的排查清单
最后把我压箱底的排查流程整理一下,遇到 OCR 结果异常时按这个顺序走,能省很多时间:
- 看图像:先确认原图本身没问题——分辨率够不够、有没有严重模糊、有没有倒置。这一步能排掉三成的"假 bug"。
- 裁小块单测:把出问题的区域裁出来单独跑,区分是检测问题还是识别问题。
- 看坐标:导出文本行坐标,检查有没有框重叠、框过小、框错位。
- 关掉后处理再跑:排除是纠错模块或规则模块把正确结果改错了。
- 换引擎对比:用另一款引擎跑同一张图,如果人家对了,说明是模型问题;如果都不对,大概率是图像或者标注规则问题。
- 检查版本:很多"诡异 bug"最后发现是模型权重和代码版本不匹配,尤其是从不同来源拼凑来的权重文件。
这套流程我用了快两年,基本上九成以上的问题能在一小时内定位到根因。剩下那一成,通常是真的需要重新训练模型了。
我在实际项目里的体会是,OCR 这件事,模型选型大概占三成功劳,剩下七成全是数据质量、预处理和工程链路。见过太多团队把精力全砸在"找最强的模型"上,结果因为没做文档矫正、没处理边界、没做引号配对,最后效果还不如一个用对了方法的普通模型。