再探老开源项目Tesseract,这次的OCR识别稍微会用了

2015年,我首次踏入在线教育行业时,技术总监给我安排了一个任务:研究一下OCR识别。

记得他是一个博士,给我推荐了谷歌的开源项目Tesseract。当时,我试了一下,感觉效果不好。他微微一笑,并没有说什么。

十年后,我对OCR稍微有了些经验。用过商业的,用过开源的,甚至自己也用基础的神经网络,手打过特定场景的数字、字母识别。

我想到,曾对Tesseract的质疑应当是误会。Tesseract是在1985年由惠普公司开发的收费OCR,当时是基于规则的字符识别。2006年,由谷歌接手。到目前,谷歌已经又维护了20年。它见证了OCR的发展史,支持100多种语言,率先引入了LSTM神经网络,96%的代码是底层和高效的C++语言,Github上有62.5k Star,是全球最受欢迎的开源OCR引擎之一,也是众多商业OCR服务的基石。

我居然感觉它是垃圾。忏悔,面壁。

我忐忑不安地又试了试。

语言的设置

我怀着朝圣的心,沐浴更衣后,拍下了这张图片。

我自认为拍得清晰、高质量。针对这张图片,让Tesseract去识别下。

我安装的是Windows版本的Tesseract。然后又安装了pytesseract,可实现Python代码调用Tesseract功能。

python 复制代码
from PIL import Image
import pytesseract
pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'

image = Image.open('test.jpg')
text = pytesseract.image_to_string(image)
print(f"识别结果:\n{text}")

很快,结果出来啦。

python 复制代码
识别结果:
/) BA IE FEF I --- TRAE, PRA "Data Science Basics" . wPREIEA 12 4%

HH, PRAAAS 30K AA. thitReKRAA 1 Dt, FWREB 5 Hl.
ARETE LOR AASEATA BA, (HALE Ftth BT AER ABE SKN

AEséA "Data Science Basics" J

SOAR TAR IX FE AE, A) A BLA ABE

我看看图,看看它,看看它,又看看图,心有点凉。

正当我转身要走,忽然在凌乱的结果里,发现了"Data Science Basics"和"Data Science Basics"。再对照原图,发现这两句英文的识别,非常正确。

那是不是因为要设置语言呢?

我又试了试,新增了语言设置。

python 复制代码
text = pytesseract.image_to_string(image, lang='chi_sim')

结果让我出乎意料。

python 复制代码
识别结果:
小明正在学习一门新课程,名称为"DataScienceBasics"。课程共有12个章
节,每章大约包含30页内容。他计划每天学习1小时,平均能看5页。
小明想在10天内学完所有章节,但有些日子他可能只能学习半小时。
如果按照这样的速度,请问小明需要几天才能完成"DataScienceBasics"的
学习?

幸好,我心存敬畏,回头看了一眼。否则,错失良机。

lang='chi_sim'是指定语言,这里指定的是简体中文。如果一张纸上同时有简体中文、繁体中文、英文、意大利语。那么,参数可以如此配置:

python 复制代码
pytesseract.image_to_string(image, lang='chi_sim+chi_tra+eng+ita')

分割模式

我又找到了这张图片,想让Tesseract给认认。

执行代码:

python 复制代码
text = pytesseract.image_to_string(image, lang='eng')

结果什么也没有识别出来,控制台打印如下:

python 复制代码
识别结果:

我转身要走,心想鼎鼎大名的Tesseract,也不过如此耳。不求你把骑缝处的小字认出来,最起码图上有个巨大"Water",你得能看到吧!

那是不是因为需要设置页面分割模式呢?

python 复制代码
text = pytesseract.image_to_string(image, lang='eng', config=r'--psm 11')

我增加了--psm 11,也就是指定页面分割模式为稀疏文本。结果控制台打印如下:

python 复制代码
识别结果:
sir Water | Eaten

C.
......

虽然,还有很多奇奇怪怪的字符,但是很明显出现了"Water"字符。说明这个配置优化了识别。

Tesseract提供14种页面分割模式,适用于不同的页面布局。

模式 描述
0 仅检测方向和脚本,不进行 OCR
1 自动页面分割,假设图像为单列文本
2 自动页面分割,假设图像为单块垂直对齐文本
3 自动页面分割(不做方向检测),适合单一文本块
4 将页面视为单个文本块的行
5 将页面视为单个文本块的竖排文本
6 将页面视为单个文本块的混排文本
7 将页面视为单行文本
8 将页面视为单个单词
9 将页面视为单个圈出的单词
10 将页面视为单个字符
11 稀疏文本
12 稀疏文本(带 OSD)
13 原始图像的行检测,不进行 OCR

调用方法就是通过--psm 编号传给config参数。

恕小弟无理了,幸好多看了一眼。

在此基础上,本来这张图识别不出来的。

通过设置--psm 5,也就是竖排文本,识别出来了。

python 复制代码
识别结果:
UpSample

黑白名单

来吧,继续展示。

这张图是用户在网页手写板涂鸦的图。软件厂家用在小孩学写数字上,他们只想识别数字,别的都忽略掉。因为3岁的孩子不大可能写出超纲的字。那么,我们可以设置白名单。

python 复制代码
text = pytesseract.image_to_string(image, lang='eng', config=r'-c tessedit_char_whitelist=0123456789')
print(f"识别结果:\n{text}")

结果是只考虑数字。

python 复制代码
识别结果:
668

参数配置-c tessedit_char_whitelist表示结果只能是白名单里的内容。如果出现大写字母O会被识别为0,字母Z会被识别为2,小写l会被识别为1。因为,白名单优先。

有时候,从视觉上,不只是人工智能,人类也很难识别。比如L的小写是l,i的大写是I,可能都是一条竖线。

因此,我们不能期望任何一个OCR能100%识别出符合预期的内容。我们要做的就是尽量去做规则上的鼓励和限制。Tesseract也提供了黑名单的功能,就是限制不可能出现的字符。

我们这个识别,其实也能用到。这张图不加任何限制,识别结果是668 (BMW。因为某些原因,字母i被识别成了符号(。可能i的下部分写得有些弧度了,更像是(。如果这是一个数字+字母场景,那么不会出现符号,因此将符号(列入黑名单。

python 复制代码
image = Image.open('test.png')
text = pytesseract.image_to_string(image, lang='eng', config=r'-c tessedit_char_blacklist=(')
print(f"识别结果:\n{text}")

控制台说话了。

python 复制代码
识别结果:
668
iBMW

OCR识别是一个分类问题,其实i有很多备选项,比如1l(。当我们把(通过-c tessedit_char_blacklist=(列入黑名单之后,排在第二位的i就因为第一位被过滤掉,成功上位。

这类需求,尤其体现在车牌号识别上。

模型的选择

谈这个很多余,因为有好用的,没有人会用不好用的。但是,也不一定。可能有人要解决兼容问题呢?

看这张图。

这张图比较模糊,我们人类甚至需要费点儿精力才能连看带猜地识别完全。

如果你这么调用,Tesseract的识别效果很不好。

python 复制代码
pytesseract.image_to_string(image, lang='eng', config=r'--oem 0')

--oem(OCR Engine Mode)是Tesseract OCR的引擎模式。我们前文说过,Tesseract在40年前用的是规则匹配,后来才引入的神经网络。因此,它也是一个模式参数。

Tesseract支持4种模式:

  • --oem 0 使用传统的Tesseract OCR 引擎,旧版本的模型,传统的OCR任务。
  • --oem 1 使用基于神经网络的LSTM(一种神经网络)进行识别,适合低质量图像。
  • --oem 2 将传统引擎和LSTM引擎结合起来使用,两种都走,然后对结果整合。
  • --oem 3 使用LSTM,在准确度和性能之间取得良好平衡。

如果你不设置,它默认是--oem 3

至于前几种,算是历史遗留问题。它一路走来,你不能说以前的不支持了。而且,并不是所有设备都可以运行神经网络。对于比如打印机设备,规则匹配算法兼容性更好。

选择模型3,走一个代码运行。

python 复制代码
pytesseract.image_to_string(image, lang='eng', config=r'--oem 3')

模糊图也是可以识别的。

行检测增强

上面的图像都比较规范,清晰无污染,是理想的识别素材。但是在实际场景中,情况不一定。

比如下面这张图。

图像有些部分被划花了。一般的识别结果是这样的。

python 复制代码
识别结果:
小奚正在学习一门新课程、吊称为"BataScienceBasics"......

小明被识别成了小奚名称被识别成了吊称。因为,那部分的字体模糊了。没有关系,有一个参数-c textord_heavy_nr=1。它的应用场景是含有很多噪声的文档。

走一个代码:

python 复制代码
pytesseract.image_to_string(image, lang='chi_sim', config=r'-c textord_heavy_nr=1')

控制台打印。

python 复制代码
识别结果:
小明正在学习一门新课程。名称为"TataScienceBasics"。......

OK了。

图像预处理

有这么一张图。

我怎么调整参数,它都识别不出来。

这张图命运悲惨。它经过多次加工、转发,早就千疮百孔了。因此,我们需要对它进行一些预处理。

首先,把这图片变成灰度图。

python 复制代码
import cv2
mg = cv2.imread('test.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

还是不能识别?进行二值化,让它黑白分明。

python 复制代码
_, thresh  = cv2.threshold(gray, 125, 225, cv2.THRESH_BINARY)

还是不能识别?进行腐蚀一下,清场,瘦身。

python 复制代码
kernel = np.ones((4, 4), np.uint8)
eroded_image = cv2.erode(thresh, kernel, iterations=1)

还是不能识别?改成白纸黑字!

python 复制代码
thresh_not = cv2.bitwise_not(eroded_image)

这下总行了吧,识别一下:

python 复制代码
text = pytesseract.image_to_string(thresh_not, lang='chi_sim')
print(f"识别结果:\n{text}")

看看识别结果是什么。

python 复制代码
识别结果:
胖晚回到家,女朋友已经睡着了,
胡计是做坤梦了,尖叫着坐了起来,
说东十角有不干浑的东西,

我提着棍子走过去
发现是砌没有洗。

大体识别出来了,还有四五个字没有识别准。比如识别成了识别成了。但是大家记住,之前它是一个字也识别不出来的。这说明,经过我们的预处理,它进步了。我们还可以继续优化。

更详细的分析

我们可以看看Tesseract在识别过程中,哪些字被选中了,又被识别成了什么。

我的思路是调用image_to_boxes,它返回的是识别出来的字符以及它们的坐标。我们将这些坐标进行编号然后画出来,看看结果和位置是否对应,以便确定优化方向。

python 复制代码
pil_image = Image.fromarray(thresh_not)
detection_boxes = pytesseract.image_to_boxes(pil_image, lang='chi_sim')
h, w, _ = img.shape
img_copy = img.copy()
counter = 1
for box in detection_boxes.splitlines():
    b = box.split()  
    char = b[0]  
    x, y, x2, y2 = int(b[1]), int(b[2]), int(b[3]), int(b[4])
    cv2.rectangle(img_copy, (x, h - y), (x2, h - y2), (0, 0, 255), 3)
    cv2.putText(img_copy, str(counter), (x, h - y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
    print(f"区域编号 {counter}: {char}, 坐标: ({x}, {y}) 到 ({x2}, {y2})")
    counter += 1
cv2.imwrite('result.jpg', img_copy)

最后结果绘制在result.jpg上。

然后控制台打印结果为:

python 复制代码
区域编号 1: ~, 坐标: (212, 1484) 到 (983, 1526)
区域编号 2: 胖, 坐标: (164, 1287) 到 (547, 1389)
区域编号 3: 晚, 坐标: (269, 1258) 到 (355, 1393)
区域编号 4: 回, 坐标: (354, 1258) 到 (422, 1393)
区域编号 5: 到, 坐标: (421, 1258) 到 (506, 1393)
区域编号 6: 家, 坐标: (505, 1258) 到 (574, 1393)
......
区域编号 55: 是, 坐标: (595, 57) 到 (715, 269)
区域编号 56: 砌, 坐标: (715, 57) 到 (835, 269)
区域编号 57: 没, 坐标: (834, 57) 到 (954, 269)
区域编号 58: 有, 坐标: (1011, 113) 到 (1037, 254)
区域编号 59: 洗, 坐标: (1059, 114) 到 (1115, 265)
区域编号 60: 。, 坐标: (1136, 125) 到 (1164, 163)
区域编号 61: ~, 坐标: (1166, 1482) 到 (1194, 1526)

这样,我们可以对应一下位置和内容,便于分析。

总结

最后,我相信大家会有一个感觉、一个疑问。

一个感觉就是Tesseract的配置太复杂了。它甚至还整出14个页面分割模式。还有那么多配置都要手动添加。对它不了解的人,或者稍微没有耐心的人,很容易对它做出误判断。

一个疑问就是你全部改成自动的不好吗?为什么增强要单独自己加参数?为什么不把所有优化都用上?管他图像什么质量,把最好的增强,最强的模型都用上。因为,很多国内的OCR就是这样,一句话调用,从不废话。

我作为开发过AI模型的人,我想做下解释。你认为通用的是最好的吗?

当然不是。世界上根本就没有通用的解决方案

举个例子。有没有一种药,可以治疗所有的感染?把这些药都混在一起?有没有一种胶水,什么材料都能粘合?把各种胶水混在一起就可以?

实际上,我们发现在很多领域,都会有参数和配置的概念。比如一个简单的家用打孔钻头,有钻混凝土的,有钻木头的,有钻瓷砖的。这几种我都买过,所以我清楚。后来研究发现,哦,原来他们的纹路各不相同,都是根据目标材质来设计的。甚至旋转方式还有平钻和冲击钻的区别。这都是参数。能混用吗?或者设计成一个通用的,可以吗?可以,我曾经用钻木头的钻了墙,不是说不能用,你用安迪的锤子也能掏洞,但是效率极低。

我做AI模型对外提供能力时,一般会提供两种模式。一种叫高精度模式,一种叫高容错模式。这都是被需求逼出来的。有人对准确性要求高,他说有一个字识别不对,用户下次就不用了。有人对速度要求高,他说让我的用户超过5秒没有识别结果,他就走了。有人对两者都要求高,我对他说我对钱要求高,他也走了

其实,Tesseract的所有参数,都是一种取舍。它没有平衡,就是取舍。

你的图很清晰,那么就这么调用。你就是个图多字少的广告牌,你就用稀疏文本分割模式。你的识别区域只有一行文本,那就用单行模式好了。选对了,结果是完美。至于,你怎么去分辨,那不是Tesseract考虑的范围。那将会是个无底洞的工作,世间的文本类型情况太多了。你自己走图片分类也好,图片预处理也好,自己做裁剪矫正也好,那是你的事情。反正它就做一件事情,那就是清晰文字的高质量识别。这一点,就是Tesseract的强项。

经典就是经典。读开源项目如同读人生,初识不懂,再识感慨万千。

我是TF男孩,一个平时爱喝75ml 50度白酒的老程序员。

相关推荐
油泼辣子多加2 分钟前
2024年12月18日Github流行趋势
github
海棠AI实验室6 分钟前
AI的进阶之路:从机器学习到深度学习的演变(一)
人工智能·深度学习·机器学习
hunteritself8 分钟前
AI Weekly『12月16-22日』:OpenAI公布o3,谷歌发布首个推理模型,GitHub Copilot免费版上线!
人工智能·gpt·chatgpt·github·openai·copilot
IT古董1 小时前
【机器学习】机器学习的基本分类-强化学习-策略梯度(Policy Gradient,PG)
人工智能·机器学习·分类
centurysee1 小时前
【最佳实践】Anthropic:Agentic系统实践案例
人工智能
mahuifa1 小时前
混合开发环境---使用编程AI辅助开发Qt
人工智能·vscode·qt·qtcreator·编程ai
四口鲸鱼爱吃盐1 小时前
Pytorch | 从零构建GoogleNet对CIFAR10进行分类
人工智能·pytorch·分类
蓝天星空1 小时前
Python调用open ai接口
人工智能·python
睡觉狂魔er1 小时前
自动驾驶控制与规划——Project 3: LQR车辆横向控制
人工智能·机器学习·自动驾驶
scan7242 小时前
LILAC采样算法
人工智能·算法·机器学习