内容中心 › 扫描件
有人发来一份扫描件,说翻出来的文字缺角,问我是不是翻译的时候漏字了。
我先看的不是译文,是那张图。缺角不发生在翻译里,发生在它被翻译之前。
凌晨一点半,我拿一页电子版论文(300dpi 渲染),按固定参数"扫"了一遍:纸张灰度乘 0.92、加 σ=6 的高斯噪声、JPEG q60 压一次。三个参数都写死,谁跑都是同一张图。
然后只改一件事:二值化阈值。规则很简单,灰度 ≤ 阈值的像素判为墨,其余判为纸。
阈值 100,墨量占比 4.04%,连通块 2784 个。单看这两个数字,挺正常。
但统计面积 ≤6 像素的孤立小块,有 48 个,碎片率 1.72%。翻到图上就明白了:表头那些细笔画的数字,"2"的起笔、"8"的收笔,被切成了一个个小点。数字还在,只是不再连成笔画。
阈值 160,墨量 4.86%,连通块 2541 个,碎块 0 个,笔画宽 4.39 像素。
到这一步我准备收工了,脑子里已经有了一句"阈值调到 160 就行"。
阈值 220,墨量 6.87%,连通块从 2596 涨到 40563 个,碎块 38524 个,碎片率 94.97%。其中噪点贡献了总墨量的 8.75%。
字没有变多一个。纸面上原本淡淡的麻点,被整片收成了墨。对后面那一步来说,这不是"看得更清楚",是"字和噪点再也分不开"。
一个参数的窗口,左边是笔画断,右边是噪点满,中间只剩 132 到 160 这么窄一段。这种参数,靠手感调是调不出来的。
这三张图缩在屏幕上看到 60% 时,差别几乎为零。放大到 300% 才看得见:阈值 100 那张,数字的拐角是虚的;阈值 220 那张,纸面浮着一层淡灰的颗粒。
问题在于,淡灰的颗粒对人是"纸的质感",对行切分算法是实实在在的墨。下一步要按水平投影找行的边界,行与行之间本该是一条干净的白带,这些孤立点把它填上了——行的边界跟着糊掉,切出来的行里混进上下行的碎片。
所以很多"扫描件翻出来串行",根子不在识别模型,在二值化把空白弄脏了。
同一页,扫成三档再各自用自动阈值:
| 扫描分辨率 | 像素尺寸 | 灰度图体积 | 自动阈值 | 碎块 | 碎片率 | 笔画宽中位 |
|---|---|---|---|---|---|---|
| 150dpi | 1275×1650 | 646KB | 145 | 588 | 18.68% | 2.8px |
| 200dpi | 1700×2200 | 1060KB | 140 | 158 | 5.78% | 4.0px |
| 300dpi | 2550×3300 | 2121KB | 132 | 2 | 0.08% | 4.39px |
150dpi 时笔画只有 2.8 像素厚。一个笔画本身就这么点厚度,再叠一层噪声,中间那道缝必然断。300dpi 时厚 4.39 像素,同样的噪声只够在边缘啃出毛刺。
阈值调得再准,也补不回被采样丢掉的那一半像素。
那一页 150dpi 的灰度图只有 646KB,看着省空间省流量。它换来的,是一份需要人逐行对着底稿核的交付——省下的是传输时间,花掉的是复核时间。
重扫一次 300dpi 是 2121KB,多花几秒钟。
二值化之后,统计孤立碎块(面积 ≤6 像素)占全部连通块的比例:
顺便说一句,自动阈值(Otsu)在这份样本上算出来是 132,正落在 0.08% 那一档。算法没给错答案,错的是喂给它的图。
扫描件翻译按语种分 OCR 通道,按页 9 积分计——贵的地方不在于"多了一道工序",而在于这一道工序之前,图必须先过关。