要是给自己的小工具站加一个「上传扫描件提取文字」功能,我第一反应是把识别放在用户浏览器里跑,因为识别不走我的服务器就不用按页付接口费。百度和腾讯的文字识别接口在官网写的是实名开通后每月 1,000 次免费(2026-11-04 查阅),超出才算钱。我拿两页公有领域扫描件把几种识别器各跑了一遍,算账之外撞上一件更麻烦的事。浏览器端那一路对页面倾斜特别敏感,歪 5° 时中文丢了将近一半而界面上照样显示处理完成。用户传上来的扫描件谁也保证不了是正的,识别之前得先估一次角度。
测的是两份印刷清楚的出版物扫描页。中文页取自国家语言文字工作委员会 1986 年《简化字总表》新版的说明页,共 557 字。英文页是 H. G. Wells《The Time Machine》1895 年版第 8 页,共 1,002 个字符。我以页面中心把它们旋转 1°、3°、5° 后存成 JPEG 再嵌进没有文字层的 PDF。被测的识别器有三种:macOS 自带的文字识别、只装了英文语言包的 Tesseract 5.5.3、浏览器端的 PP-OCR。浏览器端这一路我用的是图映 ImgIng imging.cn/ 的 PDF 内容提取默认专业档,页面上写的模型是 PP-OCRv6 small 的检测加识别,机器是一台 M4 的 Mac。错字数按字符编辑距离算,多一个字、少一个字、错一个字各记 1,我测的每组都只跑了一次。
歪到3度浏览器端识别开始乱序
正着放的时候三家都没什么问题。中文页 macOS 自带识别错 5 个、浏览器端错 2 个。英文页 macOS 错 0 个、Tesseract 错 11 个、浏览器端错 1 个。歪 1° 时中文两家分别错 4 个和 1 个,几乎没变。到了 3° 情况就变了。浏览器端的中文输出错 175 处,其中丢掉的字只有 31 个。剩下的主要是行与行的先后被打乱。5° 时中文错 362 处、丢 275 字,原文 14 行只剩一半左右。英文错 446 处、丢 115 个字符。同样的角度下 macOS 自带识别从 0° 到 5° 一直在错 4 到 8 个之间。Tesseract 的英文从 11 涨到 23 再到 38,也比浏览器端涨得慢得多。
噪点倒不怎么影响它。歪 3° 再叠一层高斯噪声后浏览器端的中文错字数和丢字数都跟不加噪时相同,还是错 175、丢 31。在这一页上伤它的是角度。
丢了半页界面也不报错
真正让我觉得要单独处理的是这一条。5° 那次界面照样显示「处理完成,结果尚未保存」。没有任何警告。输出也不是空的,开头几行读起来还挺通顺。放进产品里用户拿到的是一份看着正常、实际缺了一半的文字,多半要等到用的时候才发现。后端只能看到「有结果」却判断不出它少没少。
这张图要看什么:左边是歪了 5° 的原页。右边识别结果的第三行就跳到原文中段的「字头下删去繁体字」。后面几段前后颠倒,篇幅比左边原页短了一大截。样本为国家语言文字工作委员会《简化字总表》1986 年新版说明页(Internet Archive 公有领域标记)。倾斜是自制的。
我想过拿输出行数去对原页行数,少了就报警。这招在 5° 那次大概能抓到。到 3° 就未必了,因为 3° 时字只丢了 31 个,毛病主要出在顺序上。这个想法我没拿数据验证过,先记着。
先估角转正又回到错2个
办法是识别前先把页面摆正。估角我用的是投影法。先把灰度图缩小到四分之一并二值化,再在 ±7° 之间每 0.1° 转一次。哪个角度下每一行像素和的方差最大,文字行就最齐。英文页旋转 0、1、3、5° 时它估出来是 0.3、1.2、3.3、5.3°。多出的 0.3° 是原书页本来就带的倾斜。中文页估出来是 −0.2 到 −0.3、0.8、2.8、4.8°。我把「去噪、投影法纠斜、Otsu 二值化」三步放在识别前面再把歪 3° 加噪声的那页送进浏览器端。中文和英文都回到错 2 个,跟正着放时差不多。
Tesseract 也吃这一套。下面是英文页歪 3° 的那张 JPEG 只做纠斜、前后各识别一次的结果:
$ tesseract page_skew3.jpg before -l eng && tesseract page_fixed.png after -l eng
Estimating resolution as 429
Estimating resolution as 430
$ python3 cer.py truth.txt before.txt; python3 cer.py truth.txt after.txt
before.txt: 编辑距离 23 / 1002 字符,准确率 97.7%
after.txt: 编辑距离 6 / 1002 字符,准确率 99.4%
23 处错降到 6 处。两行 Estimating resolution 是 Tesseract 自己打的。cer.py 是按字符算编辑距离的小脚本,比对前统一了空白、引号和行尾连字符断词。page_fixed.png 就是投影法估出 3.30° 后反向转回去的那张。
门槛先设在1度因为2度没测
落到上传流程里我会这么排:用户选完文件先在前端估一次角,绝对值超过 1° 就转正后再识别。门槛定 1° 是因为这一页 1° 测过没影响、2° 没测、3° 已经乱了,只能往保守那边放。估角也在用户浏览器里算。缩小后的图转 141 次取最大值,不占我服务器一分钱。代价是用户第一次识别时要下 31.2 MB 的模型。下载模型不等于上传文件。页面上最好写一句免得有人以为文件被传走了。去噪我会放在纠斜之前。椒盐那类点状噪声要是先转再去,插值会把噪点糊成灰斑(这是推断)。
几处没覆盖到的也记一下。投影法只在 ±7° 里找,歪得更厉害的页它估不出来。这是从算法范围推的,没测。手机斜着拍出来的透视变形也不是转一下能解决的。样本只有两页字号适中的单栏清晰印刷件。倾斜和噪声都是程序加的,不是真扫歪了。浏览器端我只测了图映这一个 PP-OCR 实现。换别的前端封装会不会一样怕歪我猜差不多,但没测。这个工具只能在电脑浏览器上用,手机和平板打开只做图片压缩。网上下的扫描 PDF 有不少页面图是 JPEG 2000 或 JBIG2 压的。它目前识别不了,得先渲染成普通图片。
上线前可以先拿自己用户最常传的那类扫描件转 3° 和 5° 各送一次,看输出有没有少行、有没有串段。这比只看准确率表靠谱。
样本署名:中文样本为国家语言文字工作委员会《简化字总表》1986 年新版说明页(Internet Archive 公有领域标记)。英文页:H. G. Wells 的《The Time Machine》(1895 年版)第 8 页,来源维基共享资源,公有领域)。倾斜、噪声版本为自制。