给图片管线定压缩参数的时候,我一直在凭感觉调质量。这次干脆量了一遍。

被测的是一张 1400x760 的中文报表截图,微软雅黑,标题 38 号字,正文 27 号。渲染成无损 PNG 后 79,729 字节,后面的体积都拿它当基准。

16 个变体,9 档 JPEG 质量、5 档缩放、一个二次压缩、一个组合。识别用 RapidOCR,识别结果跟原文逐字比。

本机原始输出,python exp_ocr.py 未改动
本机原始输出,python exp_ocr.py 未改动

第一版结论是错的

我第一版直接拿原文和识别结果算编辑距离。结果很难看,q=10 的字错率比 q=30 还低,曲线来回跳,等于没测出东西。

查下来是标点在捣乱。OCR 有时把半角逗号认成全角逗号,这在我那套算法里算一个错字。质量越高,它认得的字越多,标点也能认对,反而更容易跟原文对上全角半角。整套数字测的是标点噪声,跟图像质量没关系。

归一化之后再算,曲线才是单调的。下面所有数字都统一过标点。

体积与字错率,两条曲线的拐点不在同一个地方
体积与字错率,两条曲线的拐点不在同一个地方

文字截图,PNG 比 JPEG 还小

这张图 PNG 是 79,729 字节,存成 JPEG 最高质量 q=95 是 95,294 字节,反而大出 19.5%。

原因不难理解。大面积白底配纯黑文字,PNG 的无损压缩在这类图上一压一个准。JPEG 要存的是那些锐利的笔画边缘,质量调得越高,保留的高频细节越多,体积就越大。

要到 q=85 它才降到 70,990 字节,比 PNG 小 11%。

如果你现在的流程是「截图一律转 JPEG」,这一步本身就在白花体积。

质量从 95 压到 10,一个字没丢

这一档的数据最反直觉。

q=95 是 119.5%,字错率 0。q=60 掉到 62.9%,字错率还是 0。再往下压,q=30 时体积只剩 47.2%,q=20 到 39.7%,一直压到 q=10 是 29.8%,字错率全程 0。

167 个字符全对,11 个数字全对,一直到 q=10 都没变。

真正崩的是 q=5。体积 22.4%,字错率 0.60%,只丢了一个逗号。

JPEG 的量化表在低频区域砍得狠,在锐利边缘上留得多,而文字恰好全是锐利边缘。压到 q=10 你肉眼已经能看出块状噪点,OCR 还能一个不差地读出来。它看的是笔画骨架,人眼在意的是观感,两边关心的东西不一样。

二次压缩看不出来损失

q=60 存两次,50,065 字节,字错率 0。比一次 q=60 的 50,181 还小了 116 字节。

按理说 JPEG 反复压缩会累积代际损失,这次量不出来。第二次压缩的输入已经被抹平过一次,高频信息本来就少了,再砍也不多。

这一条别当成结论推广。换成反复压十次、中间还缩放过,结果会不一样,那种情况我没测。

真正让它崩的是尺寸

缩到 75%,98,471 字节,比原 PNG 还大,字错率 0。

缩到 50%,57,598 字节,字错率 1.80%。

缩到 35%,36,292 字节,2.99%。

缩到 25%,20,776 字节,8.38%。

缩到 15%,8,415 字节,83.83%。这张图只有 210x114 像素,正文 27 号字缩下去剩四五个像素高,笔画糊成一团。

注意缩到 75% 那张反而变大了。缩放引入了抗锯齿的灰度过渡,PNG 最不擅长压这种图,所以体积往上走。缩图和压质量在 PNG 上是两笔反着走的账。

三张图放一起看,左边原图,中间缩到 50%,右边缩到 15%
三张图放一起看,左边原图,中间缩到 50%,右边缩到 15%

一个比错字更阴的地方

缩到 50% 那张,字错率 1.80%,看数字还行,11 个数字里 9 个对上了。

对着原文逐条核,问题出在剩下两个上。原文的 1,284 被认成了 1.284,1,247 被认成 1.247。

千分位逗号被认成了小数点。

字错率这个指标一点都没拦住它。它只数字符对不对,1 和 1 一样,2 和 2 一样,逗号变成句点在它眼里的权重跟改一个数一样大。可你要是拿着这个结果去 float(),1284 单变成 1.284 单,差一千倍,而且程序不会报错。

OCR 出来的数字,标点一律自己重新校验。千分位、小数点、减号,这三样在低分辨率下最容易串。

我这边现在的选法

要省体积,先缩尺寸再调质量,别反过来。先调质量只能省到 30% 左右,先缩尺寸能到 10% 还留着结构。

最优的那一档是缩到 50% 再存 q=60,16,639 字节,是原 PNG 的 20.9%,字错率 1.20%。

纯文字截图直接上 PNG,别绕 JPEG。

凡是识别结果要进计算或者入库的,数字单独走一遍校验。上面那个一千倍的坑,只有在字段校验那一环拦得住。

你们那条管线压到什么程度?我这边原来定的是 q=80,现在看完全没这个必要。