网上搜「OCR 识别率低」排在前面的建议翻来覆去就那几条。先转灰度再二值化、字小就放大两倍、有噪点再顺手去个噪。我正好攒了一堆要识别的界面截图想看看这几步到底能帮多少忙。结果有点扫兴。清晰截图上我试的 13 种预处理没有一种让错误率低过原图,好几种还扣得很厉害。下面按扣分从少到多列一遍,竖排这个另类的坑放到最后再说。
我把13种预处理逐个跑了
样本是我自己写了个网页截的图,内容是自编的「社区服务时间调整说明」。页面里有 16 px 正文、12 px 侧栏、11 px 灰色页脚和一张 13 px 的收费表格。同一个页面在普通屏和 Retina 屏上我各截了一套。中文系出身的老毛病让我在侧栏里特意塞了「已巳己」三个字,不拿它们考一下总觉得不算测过。另外两块红底海报文字是从网上找的一张放假通知模板里裁下来的,模板站的 logo 和二维码都没留。网页那 8 块的标准答案直接取页面原文,海报那两块是我对着图手工整理的。预处理我用 Python 加 OpenCV 做完存成 PNG 再拿去识别。字符错误率就是识别结果和原文的编辑距离除以原文字数,比较之前我先把全半角标点和空白都统一掉了。
识别我用的是图映(imging.cn/)的图片文字识别,主要… OCR 档,极速和极致两档也都跑了一遍。我在一台 M4 的 Mac 上用 Chromium 149 开源构建跑。对照组用的是开源的 tesseract.js 5(默认参数,chi_sim+eng)。13 种预处理里有 12 种是我在 Python 里做的。它们分别是放大 2 倍、灰度、Otsu 自动阈值、100 和 128 和 160 和 200 这四个固定阈值、局部自适应阈值、中值 3×3、非局部均值去噪以及 JPEG 质量 60 和 30 两次重压。第 13 种是图映界面上默认关着的「扫描增强」开关,说明原话是「仅轻度灰度、对比度与锐化,不强制二值化」。识别全程在本机的 Worker 里跑,非 GET 请求我数下来是 0 个。头一回打开要先下载约 31.2 MB 的专业档模型,下载的是模型不是图片。
蓝橙绿三色是图映三档,黄色是 tesseract.js。条越短越好,原图那组专业档只有 0.1%。
最好的几种也只是和原图打平
扣分最少的是灰度和扫描增强这两种。灰度版三档都和原图一模一样,专业档还是 0.1%。扫描增强的专业档 0.2% 这点波动我没当回事,每种组合我只跑了 1 次。JPEG 重压成质量 60 的专业档是 0.8% 也几乎没掉分。压到质量 30 这么低的档,专业档也才 1.1%。这比我预想的要扛造得多。放大 2 倍的 0.6% 反倒比原图多错了一点。原图专业档仅有的那个错是把「9:00—20:30」中间那条长横线认成了汉字「一」。这个错在 16 px 那 6 次原图识别里出现了 5 次。只有普通屏截图的专业档那一次读对了。我自己盯着截图看也觉得那条线像个「一」字,实在不好怪它。
极速档在这批截图里倒是有个小插曲。12 px 侧栏的普通屏截图里它把「末班车」认成了「未班车」,还把「已巳己」认成了「已已己」。开了扫描增强再跑一次「末」字就读对了,这一块的错误率从 2.9% 降到 1.5%。「巳」字开了扫描增强也还是没救回来。我一度以为找到了扫描增强的用处,回头看合计也只是从 0.8% 到 0.7%。单独一块的变化算不上什么规律。
固定阈值二值化扣分最多
真正扣分的是固定阈值的二值化。整张图上阈值 100 和阈值 128 那两组的条长得最扎眼。自动阈值 Otsu 的专业档是 1.1% 还算克制。阈值 200 和局部自适应分别是 1.2% 和 1.4%。阈值一往下调,160 的专业档是 9.4%,128 是 25.8%,100 到了 32.2%。拿阈值 128 的专业档跟原图比差了大约 25 个百分点。反过来看三个档位的原图之间差距还不到 1 个百分点。与其纠结该用哪一档不如先别乱加这一步。
阈值 128 扣这么多主要是因为灰色页脚那块整个没了。页脚那行 #999 灰字在截图里最暗的像素也有 153,比 128 还亮。二值化一做所有字都被判成背景,整块变成了一张白纸。图映三档、两种屏幕的截图和 tesseract.js 全都识别出 0 个字。一个都没剩。图映界面上只回了一句没识别到可靠文字,顺带建议旋转一下、关闭扫描增强或者换个更高的档位。我本来以为是模型出了问题,打开二值化后的图片才发现是我自己先把字删了。网上的二值化教程多半拿黑字白底的文档来演示,碰上浅灰字就是这个下场。
去噪是我没想到的另一个坑。截图本来就没有噪点,中值 3×3 对 12 px 小字却很要命。
上下两条分别是 12 px 侧栏的普通屏原图和中值 3×3 之后的样子。笔画之间原本只有一两个像素的缝,滤完全糊成了一团团灰块。「提醒」「借阅证」这种笔画多的字已经看不出轮廓了。糊得很彻底。专业档在这一块上的错误率从 0% 直接涨到了 32.4%。非局部均值去噪温和一些,合计专业档 2.7% 但也没有哪一块因为它变好。tesseract.js 这边原图合计 6.2%,放大 2 倍后的 5.8% 是它这一轮最好的成绩。放大对它能挽回一点,对图映三档就没用了。图映内部怎么缩放图片我看不到。放大为什么对它不起作用我也没法核实。先记下这个现象。
竖排截图要先旋转再识别
竖排我单独拎出来算没放进上面那张图。样本是 4 列从右往左读的 16 px 竖排文字。
原样丢进去之后图映三档的错误率都在 92% 左右,光看数字像是全军覆没。这张图是预览下半、识别结果和任务进度三块拼起来的。左边四个框其实都框对了,每一列都认成了一行。毛病出在中间的版面文本把列从左往右拼,开头成了「请拨分机号二零八报修」。按从右往左读的正确顺序应该从「秋分已过昼短夜长」开始。要是只数字对不对不管顺序,专业档的错误率只有 0 到 7.7%。字基本都认对了。错的是阅读顺序。二值化、去噪和放大都没能让竖排这两块跳出 89.7% 到 94.9% 这个区间。
后来我点了界面上的「↶ 左转 90°」把四列转成四行再识别。Retina 截图三档都是 0 错,普通屏小图上极速和专业丢了 2 个标点。
我现在识别前只检查两件事
这 13 种预处理没有一种在清晰截图上帮到图映。扣分最多的恰好是教程里最常推荐的二值化和去噪。我现在的习惯是截图清楚就直接丢原图,连灰度都懒得转。识别前我只检查两件事。别的步骤我都省了。一件是看字是不是浅灰或者彩色底,碰上了我就干脆不做二值化。真要做也先量一下文字最暗的灰度,再把阈值定得比它高。另一件是看版面是不是竖排,是竖排就先旋转再识别。被聊天软件压过一轮的小图我现在也不指望靠放大救回来了。灰色页脚那块缩到一半以后专业档是 12.2%,放大后也还有 11.2%。缩小时丢掉的像素靠放大是补不回来的。
还有几块我没测到。真实手机拍的纸质通知和菜单还没测,我手上只有程序合成的模拟拍照样本。阴影和倾斜跟截图不是一回事,截图上的结论我不敢硬套过去。手写字、艺术字和带 emoji 的聊天截图这次也都没跑。二值化只试了 5 个固定阈值加 Otsu 和一组自适应参数,CLAHE 和形态学都没碰。等凑齐了无品牌的真照片我再补一轮。