1. 背景
在10月24日程序员节,公司决定向每位技术人员发放购物实体卡以示庆祝。然而,手动输入实体卡上的一大串卡密可能是一项繁琐且不那么智能的任务;同时,线上用户在绑定购物卡的时候,同样也是需要手动输入。
基于以上背景,我们决定开发一套能够自动识别卡密的OCR文字识别模型:简单地将卡密放在相机前,便可以无需费心记忆和手动输入卡密,快速完成兑换过程,减少了可能存在的人为错误,以带来更便捷的用户体验。
2. 基于通用模型的识别
2.1 OCR框架选型
随着文档数字化的发展,光学字符识别 (OCR) 变得越来越流行。OCR 在处理基于图像的文档中发挥着至关重要的作用。在文字识别方面,主要有两款主流的开源框架Tesseract和EasyOCR。
根据参考文献[1]中进行的一项实验,Tesseract 在字母识别方面做得更好,而 EasyOCR 在数字识别方面做得更好。在我们的实际使用场景下,字母的使用频率要远高于字母,因此我们选择使用Tesseract,它是一款目前由 Google 维护的开源 OCR引擎。
2.2 OCR框架安装&使用
如果你使用的是Mac系统,可以直接使用Homebrew进行安装:
brew install tesseract
框架自带的少量通用模型存储在/usr/local/share/tessdata目录下,后缀名为.traineddata,如果需要更多类型的通用模型,可以从github中下载并导入该目录下。GitHub - tesseract-ocr/tessdata_best: Best (most accurate) trained LSTM models.
模型使用可以直接在命令行中输入:
tesseract <input-pic-path> <output-path> -l <model-name> --psm 7
2.3 通用模型的识别效果
我们使用最通用的英文数字模型eng.traineddata,对一张实体卡的卡密进行识别,效果如下,可以看到,识别效果很一般,错误率较高,不仅把特殊字体的0识别成了B,还可能会把B识别成E。原因就是我们的卡密字体并不是一种常规字体,会造成通用模型的识别困难。
2.4 交互层面弥补错误识别
针对通用模型识别错误率高的问题,首先想到的是交互方面的优化。
将容易识别错的相似元素进行分组,譬如0、8、B、E为一组,R、A为一组等等。当识别到存在组内元素的时候,为用户提供一个用于交互的UI滚轮组件,使用户能够手动修正为组内的其他相似元素。
思考再三,还是觉得这样的解决方案太粗暴了,用户可能会疑惑为什么OCR每次都需要手动修正,这么明显的数字0为什么识别不出来?最终还是放弃了从交互层面来弥补错误的思路。
3. 训练专属的OCR模型
Tesseract除了可以使用官方提供的语言包(traineddata文件),还可以自己训练模型,特别适用于某些官方语言包识别效果不佳的场景下。本章将基于Tesseract-OCR5.0来训练自己的模型、以及如何提高准确率。
由于购物卡的卡密字体在生产过程中是不会改变的,因此我们可以根据它的字体样本,来训练一套专属的OCR模型,来改善通用模型在识别过程中的一些错误,提高OCR的准确率。
3.1 模型训练 - 样本采集准备
训练的前置准备工作就是样本采集,字体的数量千千万,我们无法通过肉眼识别实体卡的卡密到底用了哪种字体。首先我们尝试使用字体识别工具,发现最相似的字体匹配程度也只在70%左右,由于样本本身并不是百分百的匹配,经过后续的训练测试验证,识别效果也确实不理想。
解铃还须系铃人,只能想方设法找购物卡的制造商来解决了。经过沟通,然而制造商回应:
因此我们并不能拿到卡密字体的电子样本,为了不让这次沟通无功而返,我打算让制造商将0~9、A~Z打印出来并拍照。于是便有了下面这张照片,也是仅有的可用于训练的样本。
3.2 模型训练 - 生成用于训练的tif和box文件
首先生成训练用的tif文件,需要将多个训练图片合成到一个tif文件里。tif文件格式是一种用于存储图像数据的文件格式,具有无损压缩、支持多页等特性。我们可以直接使用jTessBoxEditor工具来完成,工具下载地址VietOCR - Browse /jTessBoxEditor at SourceForge.net。
打开jTessBoxEditor,选择Tools -> Merge TIFF,进入训练样本所在文件夹,选中要参与训练的样本图片,并将tif文件命名为"eng.myfont.exp0.tif"。
tif文件命名格式 [lang].[fontname].exp[num].tif
lang是语言名,fontname是字体名称,num为自定义版本号
接着,在训练目录下打开cmd,输入下面命令,用现有的eng模型初步识别.tif文件并生成对应的.box文件,执行后在当前路径下生成 “eng.myfont.exp0.box” 文件。
tesseract eng.myfont.exp0.tif eng.myfont.exp0 -l eng --psm 7 batch.nochop makebox
box文件中将记录每个字符在图片上的位置以及识别出的内容。其中,psm参数用于设置识别模式,7代表将图像视为单个文本行,我们的场景使用的是单行统一文本,因此采用参数7。其他psm常用参数所代表的识别模式汇总如下:
0 Orientation and script detection (OSD) only.
1 Automatic page segmentation with OSD.
2 Automatic page segmentation, but no OSD, or OCR.
3 Fully automatic page segmentation, but no OSD. (Default)
4 Assume a single column of text of variable sizes.
5 Assume a single uniform block of vertically aligned text.
6 Assume a single uniform block of text.
7 Treat the image as a single text line.
8 Treat the image as a single word.
9 Treat the image as a single word in a circle.
10 Treat the image as a single character.
11 Sparse text. Find as much text as possible in no particular order.
12 Sparse text with OSD.
13 Raw line. Treat the image as a single text line, bypassing hacks that are Tesseract-specific.
3.3 模型训练 - 矫正box文件错误
由于我们使用的是通用模型对tif进行的初步识别,因此box文件中字符的内容和位置大概率是会出错的,且需要来手动调整的。依然使用jTessBoxEditor工具,点击Box Editor -> Open,打开步骤2中生成的“zwp.test.exp0.tif”,会自动关联到“zwp.test.exp0.box”文件,这两文件要求在同一目录下。
下图是对box文件中字符内容和位置的错误纠正示例:
调整完样本中的错误之后,点击"save"保存修改。box文件中的字符信息也会相应地进行更改,只是jTessBoxEditor工具帮助我们可视化地完成了这件事。
3.4 模型训练 - 生成基于通用模型的lstm文件
从0到1开始训练一个新模型需要海量的样本和超强的算力,如果样本不足容易过拟合,不适合我们快速出结果,因此我们的模型将基于已有通用模型进行微调训练。
从步骤2.2中的tessdata_best代码库里,下载所需语言的通用模型.traineddata文件,在本案例中选择下载eng.traineddata文件,并将该文件保存到当前工作目录下。并使用combine_tessdata命令,用当前目录下的eng.traineddata文件生成eng.lstm文件:
combine_tessdata -e eng.traineddata eng.lstm
打印输出:
Extracting tessdata components from eng.traineddata
Wrote eng.lstm
Version:4.00.00alpha:eng:synth20170629:[1,36,0,1Ct3,3,16Mp3,3Lfys64Lfx96Lrx96Lfx512O1c1]
17:lstm:size=11689099, offset=192
18:lstm-punc-dawg:size=4322, offset=11689291
19:lstm-word-dawg:size=3694794, offset=11693613
20:lstm-number-dawg:size=4738, offset=15388407
21:lstm-unicharset:size=6360, offset=15393145
22:lstm-recoder:size=1012, offset=15399505
23:version:size=80, offset=15400517
Tesseract从版本4.0开始,训练方法使用的是基于LSTM的深度学习网络,lstm文件的命名也由此而来,为模型训练的中间产物。
3.5 模型训练 - 基于样本生成lstmf文件
这一步利用tif图片文件生成lstmf文件,如下命令会生成eng.myfont.exp0.lstmf文件,这个是典型tesseract命令:
tesseract eng.myfont.exp0.tif eng.myfont.exp0 -l eng --psm 7 lstm.train
--参数介绍--
0 eng.myfont.exp0.tif 输入图像
1 eng.myfont.exp0 输出的lstmf文件名称
2 -l eng 使用的基础模型名称
3 --psm 7 分割模式,上文已介绍
4 lstm.train 指明进行lstm训练
生成lstmf之后,在同层级目录下新建一个eng.training_files.txt文本文件,将lstmf的绝对路径写入文件,在我本地电脑中的文本文件如下所示:
/Users/calvin/tesseract/training/eng.myfont.exp0.lstmf
3.6 模型训练 - 开始训练
使用lstmtraining命令开始训练,这一步将生成训练中间文件_checkpoint:
lstmtraining
--model_output="/Users/calvin/tesseract/training/"
--continue_from="/Users/calvin/tesseract/training/eng.lstm"
--train_listfile="/Users/calvin/tesseract/training/eng.training_files.txt"
--traineddata="/Users/calvin/tesseract/training/eng.traineddata"
--debug_interval -1
--max_iterations 2000
--target_error_rate 0.01
- –debug_interval -1 调试打印等级
- –max_iterations 2000 最大迭代次数
- –target_error_rate 0.01 期望错误率
- –continue_from 基于eng字体的lstm文件
- –model_output 输出checkpoint文件的名称
- –train_listfile 训练清单文件名称
- –traineddata 训练使用的字体
输出打印:
Loaded file /Users/calvin/tesseract/training/eng.lstm, unpacking...
Continuing from /Users/calvin/tesseract/training/eng.lstm
Loaded 5/5 lines (1-5) of document /Users/calvin/tesseract/training/eng.myfont.exp0.lstmf
Iteration 0: GROUND TRUTH : 0124567890
Iteration 0: BEST OCR TEXT : 8124567898
File eng.myfont.exp0.lstmf line 0 :
Mean rms=2.635%, delta=7.463%, train=40%(100%), skip ratio=0%
Iteration 1: GROUND TRUTH : RSTUVWXYZ
Iteration 1: BEST OCR TEXT : RSTUUWSEYZ
File eng.myfont.exp0.lstmf line 1 :
Mean rms=2.764%, delta=8.649%, train=47.778%(100%), skip ratio=0%
..........
..........
At iteration 15/1100/1100, mean rms=0.033%, delta=0.000%, BCER train=0.000%, BWER train=0.000%,
skip ratio=0.000%, New best BCER = 0.000 wrote best model:/Users/calvin/tesseract/training/_0.000_15_1100.checkpoint wrote checkpoint.
Finished! Selected model with minimal training error rate (BCER) = 0
3.7 模型训练 - 基于训练结果生成模型文件
使用lstmtraining命令结束训练,这一步将生成traineddata模型文件:
lstmtraining --stop_training
--traineddata="/Users/calvin/tesseract/training/eng.traineddata"
--continue_from="/Users/calvin/tesseract/training/_checkpoint"
--model_output="/Users/calvin/tesseract/training/yx.traineddata"
- –stop_training 停止训练
- –traineddata 训练使用的字体
- –continue_from 训练中间文件名称
- –model_output 生成模型文件的名称
输出日志:
Loaded file /Users/calvin/tesseract/training/_checkpoint, unpacking...
3.8 模型测试
最后将生成的yx.traineddata文件拷贝到tesseract安装目录的tessdata路径下,即完成字体的安装。再次使用2.2中提及的命令,对我们的模型进行测试:
tesseract <input-pic-path> <output-path> -l yx --psm 7
可以看到,在我们的新模型下,图片的OCR识别效果得到了很大的改善,错误率明显降低。
4. 模型准确率优化
从前面的训练步骤可以看到,开始训练时需要用到一个已存在的模型eng.traineddata,比如第3.4步抽取它的lstm文件、第3.6步的训练等。既然这个原始字体训练出来的新字体识别的准确率不高,那我们是不是可以用训练好的新字体替代eng.traineddata再训练一次呢,这样产生的第2代的新字体会不会有更好地表现?
基于参考文献[4]中的测试结果,在大量测试数据下,第二次迭代后的识别准确率是要明显高于第一次的,且不受psm识别模式的影响,同时在后续第三次、第四次、第五次迭代后准确率则提升不明显。
因此,在兼顾效率和准确率的前提下,我们选择可以对模型进行二次迭代优化,回到步骤3.4步~步骤3.7,将eng.traineddata相关的内容替换为yx.traineddata,新生成的第2代模型取名为yx2.traineddata,得到更高精度的字体识别模型。
5. 模型在移动端上的落地应用
模型在移动端上的落地应用需要分三层工作来完成:
- 模型的训练放到本地进行,经过反复测试、优化,将训练好的文件模型上传到服务器。
- 服务器负责为移动端提供接口,提供简单的版本管理和资源下发。
- 应用端基于Tesseract SDK提供模型解析环境,并将模型应用于具体的OCR场景中。
6. 总结与后续工作
基于上文的训练过程,我们完成了针对特定字体的OCR模型识别训练,和通用模型相比,我们在特定字体场景下具有更高程度的OCR识别准确度。后续的工作主要从以下两方面进行:
- 大量测试与纠错优化
使用更多的购物卡对模型进行测试,若出现识别错误,则基于出错样本对已有模型继续进行训练优化。 - 移动端上的资源下载/管理的能力建设
生成的模型的大小约15MB左右,说大不大,说小也不小。可以肯定的是,无论如何都不可能把模型内置于移动端的安装包中,原则上是按需下发加载,因此需要移动端上具有资源下载的基础能力,需要考虑资源下载失败、版本更新、完整性校验等工作。最后,模型的下发与否需要遵从用户的意愿,因此在业务层面,需要保留OCR和手动输入两种入口。
7. 参考文献
-
OCR Engine Comparison — Tesseract vs. EasyOCR
-
Tesseract OCR in Python with Pytesseract andOpenCV
-
Tesseract-OCR页面分割模式 (PSM) 使用详解
-
Tesseract-OCR5.0字体训练以及提高准确率、提升训练效率的方法