使用 PaddleOCR 实现文字识别。为了在 web(Electron)上使用,需要把 PaddleOCR 的 Python 代码转换为 JavaScript,同时把模型转换为ONNX格式。
原本打算使用 Paddle.js,但当时支持有限,所以代码是直接从 Python 中翻译过来的,一边调试一边学习,解码部分参考了 Paddle.js 的实现。后来 LLM 横空出世,一些图片处理功能在以前没来得及实现的都使用 AI 翻译了。
基于 v3 版本,尽管现在的模型是 v4。
文字识别分为文字检测(rec)和文字识别(det)(目前不考虑方向、排版等)。
文字检测获取文字坐标(Paddle 里按行),裁切后送入文字识别模型。
结果是二值图,每个像素点为 0 或 1,代表是否有文字。通过 opencv 查找轮廓,确定边框。
输入是裁切后的图像,输出数组的一级是按字的数组,二级是字典索引对应的概率。字典一字一行,行号-1 是每个字的索引。输出的概率列中,对应了每个字与图片中文字的相似程度,找到概率最大的索引,对应的文字就是结果文字。
使用模型进行排版识别的成本有点高,我打算用算法来分析。
文字检测输出的矩形坐标比较精细,像一行里的话,模型会把空格隔开的小段文字视为一个矩形区域。
我们先把这些小片段合并成一行,再合并成一段。
一开始的算法是把 y 坐标相近(偏差不超过平均高度 50%)的小片段合并在一起,这种算法在大部分情况下运行良好,但遇到左右分栏的文本,会粗暴地跨栏合并,毕竟他们 y 坐标确实是相近的。
通过分析,我们知道分栏的间距一般都会比句子空格大,或用线条或颜色分隔开。现在只考虑间距问题。
我们把矩形按行分类,y 相差不大(0.5 高度)且直接间距小于高度的合一起。
上面的操作已经隐式分栏了,同一 y 坐标存在不同的 x 坐标,意味着属于不同栏。
模拟人阅读方式,从左上开始,遍历 y 坐标。找出中心最近的矩形,通过一定规则的过滤(左、中、右至少有一个大致对齐),判断是添加到已有的栏还是新建栏。
有两种分段方式:通过空行和通过行首空格。
空行分段同样可以使用间距分析,直接统计间距频度,以下是一个真实数据:
{
"-3": 2,
"-2": 1,
"0": 4,
"1": 4,
"2": 4,
"3": 3,
"4": 4,
"5": 5,
"7": 1,
"8": 1,
"9": 1,
"10": 1,
"13": 1
}
我们注意到 5-7 的位置应该是我们需要分段的间距:大于等于 7 的间距都是段间距,其他是行间距。取导数绝对值最大的即可。我还提升他们使其大于 0,并去掉最大频率的数来减少极端数据的影响。