OCR 预处理做过头,准确率从 98.9% 掉到 28.5%

0 阅读7分钟

同一页 1895 年版《时间机器》的扫描件不做任何处理直接交给 Tesseract 5.5.3 识别,1,002 个字符里错了 11 个。先做 Otsu 二值化再加一次 5×5 闭运算,同一页错了 716 个。准确率从 98.9% 掉到 28.5%。中文页换成 macOS 自带的文字识别(Vision 框架),1986 年《简化字总表》说明页一共 557 个字。原图错 5 个,做完 9×9 中值滤波错 178 个。这几组数来自同一组对照,都是一台 Mac 上的单次运行。错字数按字符编辑距离算,多字、少字、错字各记 1。两页都是字号适中的清晰印刷扫描件,样本也只有这两页。下面讲这两页上的现象和我对原因的理解,其中推断的部分会单独标出来。

中值核要跟着字号走

先把中值滤波的数放在一张表里看,表里是错字数,括号里是准确率。

页面识别器不处理中值 3×3中值 5×5中值 7×7中值 9×9
英文原页Tesseract11(98.9%)2(99.8%)6(99.4%)14(98.6%)90(91.0%)
英文缩小一半Tesseract4(99.6%)28(97.2%)142(85.8%)765(23.7%)—
中文原页macOS 自带5(99.1%)4(99.3%)9(98.4%)34(93.9%)178(68.0%)

英文原页上 3×3 中值是这次对照里效果最好的处理。错字从 11 个降到 2 个。核再往上加就开始伤字,到 9×9 一下变成 90 个。中文页的拐点来得更早,到 7×7 已经错 34 个。放大看 7×7 以后的中文页最先没掉的是细横、点、偏旁和顿号。

缩小一半那一行是把英文页的宽度从 1516 变成 758 像素,相当于字号小一半或者扫描分辨率低一半。这时同一个 3×3 不再帮忙。它在原尺寸上把错字从 11 压到 2,到了半尺寸页面上却把错字从 4 推到 28。方向正好反过来。

原因是我按中值的定义推出来的,属于推断。k×k 的窗口取中位数时,一条宽 w 像素的横笔画从窗口正中穿过。这时窗口里的深色像素大约是 w×k 个。这个数要超过 k² 的一半才保得住。换句话说笔画宽度得大于 k 的一半。按这个算法 9×9 的窗口会把宽度在 4 像素以内的笔画换成纸的颜色。点和顿号横竖两个方向都短所以更吃亏。字号缩小一半时笔画宽度跟着减半,原来安全的核就越过了这条线。这两页的笔画到底几像素宽我没有量,现象和推算对得上也只能说到这一步。「几号字配几号核」的对照表我给不出来。

去噪强度也有拐点。非局部均值 h=15 时英文页错 7 个,比不处理还少。h 加到 40 就错 28 个,旋钮拧过头以后去掉的就不只是噪声了。

白底黑字上的闭运算会抹掉笔画

闭运算的反例更极端。英文原页 Otsu 加 3×3 闭运算错 62 个(93.8%),加 5×5 错 716 个(28.5%)。半尺寸页面上只做 Otsu 加 3×3 闭运算,Tesseract 直接输出空文本。中文页 Otsu 加 3×3 闭运算错 78 个(86.0%),换成 5×5 错 519 个(6.8%)。

问题出在极性上。OpenCV 的形态学运算把亮的一边当前景。闭运算先膨胀再腐蚀,膨胀那一步白色区域会往外长。扫描页是白底黑字,这里往外长的白色就是纸。纸往笔画里长的时候比核窄的黑笔画在膨胀这一步就被填平了。后面的腐蚀手里已经没有这些笔画,也就还原不回来。对黑字来说这等于做了一次开运算。闭运算常被拿来连断笔、补小洞,那说的是先反相过的黑底白字图。

# 按定义简化的示意,文中的数来自完整对照,不是这几行跑出来的
bw = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]  # 白底黑字
ker = np.ones((5, 5), np.uint8)
eaten = cv2.morphologyEx(bw, cv2.MORPH_CLOSE, ker)  # 纸被闭合,细黑笔画被填平
joined = 255 - cv2.morphologyEx(255 - bw, cv2.MORPH_CLOSE, ker)  # 反相后再闭合才是连笔画

上面反例里的数对应 eaten 这种写法。joined 那一行没有测,它对识别是帮是伤我不下结论。5×5 对这两页的笔画来说本身也可能偏大。

预处理过度的反例,英文与中文各四格

这张图上排是英文页、下排是中文页,每格上方标着处理方式和准确率。要看的是第三格。英文那格做了 Otsu 加 5×5 闭运算以后字母断成零散的黑块几乎拼不出单词。中文那格是 Otsu 加 3×3 闭运算后的结果,字整体变黑变粗,「第一表」里的「一」已经不见了。英文页样本是 H. G. Wells《The Time Machine》1895 年版第 8 页,来自维基共享资源(公有领域)。中文页样本是国家语言文字工作委员会《简化字总表》1986 年新版说明页,来自 Internet Archive(公有领域标记)。二值化、滤波和缩小这些处理版本是自制的。

识别器自己可能已经处理过一遍

第三个原因是推断。只做 Otsu 这一步对 Tesseract 几乎没有影响。英文页无噪声和加高斯噪声的 8 个条件下,它和不处理相差都不超过 1.2 个点。我的理解是 Tesseract 内部本来就会把图二值化。外面再做一次喂给它的还是差不多的东西。这一点我没有去读源码确认。换成 macOS 自带的识别,Otsu 一律略差。中文页无噪声时 0°、1°、3°、5° 不处理分别错 5、4、9、5 个。做了 Otsu 变成 9、6、11、8 个。我猜神经网络识别器吃的是灰度图。笔画边缘那圈灰色过渡里有信息,二值化把这部分一刀切掉了。这个原因我没查,只是和数据不矛盾。

第三个识别器在浏览器里跑,我用的是图映 ImgIng imging.cn/ 的 PDF 内容提取,页面上写的模型是 PP-OCRv6 的 small 检测和识别。图映的 OCR 这块不是我做的,我只能从结果看。它对倾斜很敏感。中文页歪 3° 加噪声时错 175 个,行序被打乱了,还整行丢了两行短句。先去噪、纠斜、再 Otsu 送进去,又回到错 2 个。macOS 自带的识别在同一页上从 0° 到 5° 一直是 4 到 8 个错字,纠斜对它几乎没有贡献。同一套预处理对这个识别器是救命,对那个是白做。放到 Tesseract 上某些步骤还会伤字。

表里还有一个容易误读的数。英文页歪 5° 只做 3×3 中值时 Tesseract 准确率是 34.0%,看着像中值闯的祸。实际是版面切分崩了,一栏正文被切成几块交错输出,字本身没怎么认错。同一张图先纠斜再识别就是 98.9%。这是单次结果。

所以预处理做哪几步、做到多狠要看后面接的是哪个识别器和手里的字号。动手前可以先找一页有可靠真值的扫描件。公有领域的书页加上维基文库里校对过的转录就够用。每一步单独开关各跑一遍,再用字符编辑距离对比。中值核从 3×3 起步,再拿缩小一半的同一页跑一次看拐点落在哪里。做形态学之前先确认图是白底还是黑底。这两页都是清晰印刷件,倾斜和噪声是程序加的。真扫歪、真脏的页面和手机拍照的透视变形都没有覆盖。英文页的真值取自英文维基文库同页的校对转录(CC BY-SA 4.0)。

样本署名:中文页为国家语言文字工作委员会《简化字总表》1986 年新版说明页(Internet Archive,公有领域标记)。英文页为 H. G. Wells《The Time Machine》1895 年版第 8 页(维基共享资源,公有领域)。处理版本为自制。