当前位置:   article > 正文

基于Tesseract的OCR识别--身份证_tesseract 身份证模型

tesseract 身份证模型

 

目录

  • 需求背景

  • Tesseract简介及环境搭建

  • 字库训练

  • Tesseract for iOS

  • 总结

需求背景

由于客户端内核的限制,市场上大多数身份证识别都会放在服务器校验,客户端一般只是负责抓取图片,将抓取到的图片上送到服务器识别。这样一来如果客户端抓取到的身份证图片的质量无法保障,服务器也很难识别得出来,会拖慢身份证识别进程,造成用户体验不好的情况。(如我项目的流程是只要摄像头一打开就开始抓取图片上传到服务器去识别,不管抓取到的图片是否是身份证图片,客户端等待服务端返回结果,如果没解析出来则继续抓取图片上传,直至识别出来为止)。

针对客户端抓取身份证图片质量的情况,客户端应先对身份证所必须的字库进行训练(在Tesseract提供的字库中英文库21.9M,中文库52.7M,如果直接使用大大增加APP包大小)。然后将训练好的字库集成进Tesseract框架分别对性别、出生日期、身份证号、有效日期、人像等区域识别并进行校验,都通过校验之后再把得到的高质量图片上传到服务器去识别。

如此一来,大大减少客户端与服务器交互的同时,把高质量图片上传到服务器识别可以增加身份证识别成功率,减少身份证识别时间,提升用户体验。

Tesseract简介及环境搭建

简介

Tesseract的OCR引擎最先由HP实验室于1985年开始研发,至1995年时已经成为OCR业内最准确的三款识别引擎之一。然而,HP不久便决定放弃OCR业务,Tesseract也从此尘封。数年以后,HP意识到,与其将Tesseract束之高阁,不如贡献给开源软件业,让其重焕新生。在2005年,Tesseract由美国内华达州信息技术研究所获得,并委托Google对其进行改进、优化工作。

Tesseract目前已作为开源项目发布在Google Project,它与Leptonica图片处理库结合,可以读取各种格式的图像并将它们转化成超过60种语言的文本,我们还可以不断训练自己的库,使图像转换文本的能力不断增强。如果团队深度需要,还可以以它为模板,开发出符合自身需求的OCR引擎。

环境搭建

安装

1.安装Homebrew

打开Command Line Tool,直接输入下面指令:

  1. ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

2.安装Tesseract

打开Command Line Tool,根据自己的需求选择相应的指令进行安装:

  1. // 安装tesseract的同时安装训练工具
  2. brew install --with-training-tools tesseract
  3. // 安装tesseract的同时安装所有语言,语言包比较大,如果安装的话时间较长,建议不安装,按需选择
  4. brew install --all-languages tesseract
  5. // 安装tesseract,并安装训练工具和语言
  6. brew install --all-languages --with-training-tools tesseract
  7. // 只安装tesseract,不安装训练工具
  8. brew install tesseract

下载语言库

根据自己的需求可以到这里选择所需要的语言库,如我们选择的简体中文库是: chi_sim.traineddata,将下载好的文件拷贝到: /usr/local/Cellar/tesseract/3.05.01(tesseract版本号)/share/tessdata目录下。

使用

使用如下指令进行图片文字识别:

  1. // 默认使用eng字库,imageName是图片绝对路径,result是识别结果
  2. tesseract imageName result
  3. // 指定使用简体中文
  4. tesseract -l chi_sim imageName result
  5. // 指定多语言,用+号相连
  6. tesseract -l chi_sim+eng imageName result
  7. // 查看本地存在的语言库
  8. tesseract --list-langs

有个地方需要特别注意,参数psm

  1. // 输入命令,查看psm的参数
  2. tesseract --help-psm
  3. 0 Orientation and script detection (OSD) only.
  4. 1 Automatic page segmentation with OSD.
  5. 2 Automatic page segmentation, but no OSD, or OCR.
  6. 3 Fully automatic page segmentation, but no OSD. (Default)
  7. 4 Assume a single column of text of variable sizes.
  8. 5 Assume a single uniform block of vertically aligned text.
  9. 6 Assume a single uniform block of text.
  10. 7 Treat the image as a single text line.
  11. 8 Treat the image as a single word.
  12. 9 Treat the image as a single word in a circle.
  13. 10 Treat the image as a single character.
  14. 翻译:
  15. 0 定向脚本监测(OSD)
  16. 1 使用OSD自动分页
  17. 2 自动分页,但是不使用OSD或OCR(Optical Character Recognition,光学字符识别)
  18. 3 全自动分页,但是没有使用OSD(默认)
  19. 4 假设可变大小的一个文本列。
  20. 5 假设垂直对齐文本的单个统一块。
  21. 6 假设一个统一的文本块。
  22. 7 将图像视为单个文本行。
  23. 8 将图像视为单个词。
  24. 9 将图像视为圆中的单个词。
  25. 10 将图像视为单个字符

根据情况选择不同的psm值,这很重要,如果选择到不恰当的值会导致识别失败。

例如下面这张图应假设为一个统一的文本块:

num.png

使用命令:

  1. tesseract num.png result -l chi_sim
  2. 打印:
  3. Tesseract Open Source OCR Engine v3.05.01 with Leptonica
  4. Empty page!!
  5. Empty page!!

使用命令:

  1. tesseract num.png result -l chi_sim -psm 6
  2. 打开result.txt文件,成功识别:
  3. 一二三四
  4. 一二三四

字库训练

安装jTessBoxEditor

翻墙后,在这里下载 jTessBoxEditorFX-2.0-Beta.zip,解压后得到jTessBoxEditorFX文件夹,由于这是由Java开发的,所以我们应确保运行jTessBoxEditor前先安装JRE(Java Runtime Environment,Java运行环境),由于JRE的安装教程很多,这里就不做过多介绍了。

获取样本文件

由于身份证的字体是比较固定的,所以不需要做太多样本进行训练。像身份证号的数字和X是黑体,性别、生日、有效期等字体是方正黑体简体,所以我们只需要在word上输入身份证上对应字体的文字,然后用切图工具把文字切出来,这样样本就可以获取到了。下面是我获取身份证文字的样本图片:

黑体数字:

黑体数字.jpg

黑体X:

image

华文黑体简体汉字:

image

华文黑体简体数字:

image

【注意】:样本图像文件格式必须为tif/tiff格式

Merge样本文件

进入jTessBoxEditor目录,在终端执行java -Xms128m -Xmx1024m -jar jTessBoxEditorFX.jar命令,会出现如下操作界面:

image

Tools->Merge TIFF,将样本文件全部选上,并将合并文件保存为font.tif,进入该文件所在目录,执行指令

  1. tesseract font.tif font batch.nochop makebox

生成文件名为font.box文件

字符矫正

生成font.box文件后,可以使用jTessBoxEditor对字符进行矫正了。选中Box Editor->Open,打开font.tif,会出现如下操作界面:

image

刚开始对数字和字母识别得比较准确,但是识别的中文应该是乱码,选中文字在Character一栏输入正确的文字进行字符矫正、保存。这样就可以得到一个较为准确的字符集(PS:如果要识别其他字体和文字得获取大量样本进行训练矫正,由于身份证字体比较固定,所以只需识别固定文字即可,创建出来的字库在165KB左右)。

执行批处理文件

生成字符特征

执行指令

  1. tesseract font.tif font nobatch box.train

生成.tr文件,它包含了训练页的每个字符的特征。

计算字符集

执行指令

  1. unicharset_extractor font.box

生成unicharset数据文件,它包含了tesseract需要知道可能要输出的字符集。

字体属性

执行指令

  1. echo 'font 0 0 0 0 0' > font_properties

可以将提供字体形式信息重定向到font_properties文本文件中,通过-F filename选项指定来进行mftraining.

当运行mftraining时,每个.tr文件名必须有相关entry在font_properties文件中,否则将中止mftraining。

聚合

当所有训练页的字符特征被抽取出来时,我们需要将它们聚集起来创建prototype文件。这些字符形状特性可以通过使用shapeclustering、mftraining和cntraining程序进行聚焦。

  1. // shapeclustering通过形状聚集创建了主形状表,并将它写到一个文件中: shapetable.
  2. shapeclustering -F font_properties -U unicharset font.tr
  3. // mftraining将输出两个其它的数据文件: inttemp(形状原型)和pffmtable(每个字符所希望的特征)
  4. mftraining -F font_properties -U unicharset -O font.unicharset font.tr
  5. // 输出normproto数据文件
  6. cntraining font.tr

生成字库

将聚合后得到的normproto、inttemp、pffmtable、shapetable文件重命名为font.normproto、font.inttemp、font.pffmtable、font.shapetable

执行下面指令得到traineddata文件

  1. combine_tessdata font.

最终的文件目录应该如下图所示:

image

生成的font.traineddata就是我们所需要的字库。

Tesseract for iOS

通过前面的介绍我们知道了Tesseract框架是根据我们提供的字库对图片上的文字进行识别,然后转化为文本的形式输出,并且我们也创建了自己的字库。但往往一张图片上不一定只有一个文本块,有可能有多个文本块。例如身份证它就有身份证号、性别、民族、出生年月、姓名等多个区块,那么如何把它们截取为一个个区块,然后将每个区块分别提供给Tesseract框架进行识别呢?

目前找到了两种方法对这些区块进行分离:

图像处理技术

图像处理技术是使用OpenCV库对图像进行灰度化,二值化,腐蚀,轮廓检测等。

1.灰度化处理:图片灰度化处理就是将指定图片每个像素点的RGB三个分量通过一定的算法计算出该像素点的灰度值,使图像只含亮度而不含色彩信息。

image

2.二值化:二值化处理就是将经过灰度化处理的图片转换为只包含黑色和白色两种颜色的图像,他们之间没有其他灰度的变化。在二值图中用255便是白色,0表示黑色。

image

3.腐蚀:图片的腐蚀就是将得到的二值图中的黑色块进行放大。即连接图片中相邻黑色像素点的元素。通过腐蚀可以把身份证上的身份证号码连接在一起形成一个矩形区域。

image

4.轮廓检测:图片经过腐蚀操作后相邻点会连接在一起形成一个大的区域,这个时候通过轮廊检测就可以把每个大的区域找出来,这样就可以定位到身份证上面号码的区域。

image

代码处理:

  1. // 将UIImage转换成Mat
  2. cv::Mat resultImage;
  3. UIImageToMat(image, resultImage);
  4. // 转为灰度图
  5. cvtColor(resultImage, resultImage, cv::COLOR_BGR2GRAY);
  6. // 利用阈值二值化
  7. cv::threshold(resultImage, resultImage, 100, 255, CV_THRESH_BINARY);
  8. // 腐蚀,填充(腐蚀是让黑色点变大)
  9. cv::Mat erodeElement = getStructuringElement(cv::MORPH_RECT, cv::Size(26,26));
  10. cv::erode(resultImage, resultImage, erodeElement);
  11. // 轮廊检测
  12. std::vector<std::vector<cv::Point>> contours; // 定义一个容器来存储所有检测到的轮廊
  13. cv::findContours(resultImage, contours, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, cvPoint(0, 0));
  1. // 取出身份证号码区域
  2. std::vector<cv::Rect> rects;
  3. cv::Rect numberRect = cv::Rect(0,0,0,0);
  4. std::vector<std::vector<cv::Point>>::const_iterator itContours = contours.begin();
  5. for ( ; itContours != contours.end(); ++itContours) {
  6. cv::Rect rect = cv::boundingRect(*itContours);
  7. rects.push_back(rect);
  8. // 算法原理
  9. if (rect.width > numberRect.width && rect.width > rect.height * 5) {
  10. numberRect = rect;
  11. }
  12. }

这种方式的优点是不需要用户定点描框,只要用户将身份证放到摄像头内便可自动处理,代码也相对简单,能够很大程度上提升用户体验。缺点是引入OpenCV库会增加了4M包的大小。

坐标计算处理

坐标计算处理原理是在手机上给定一个身份证区域让用户将身份证放入该区域内,通过计算坐标获取指定的区域(如我Demo的效果是这样):

正面

反面

这种方式的优点是不需要引入OpenCV库而导致又一程度上增加包的大小。缺点也是显而易见的,那就是需要用户对准指定区域进行定点描框,要不坐标计算后获取到的错误区域抛给Tesseract框架是识别不出的,用户体验不是很好。

综上所述,在实现方式的选择上可以根据自己的项目具体情况具体分析,由于我司项目是一个金融APP,引入TesseractOCRiOS框架已经增加5.1M包大小,若再引入OpenCV,那么为了一个OCR优化功能增加9M的包大小这是不能够接受的。若是类似于美图秀秀那类型的APP,使用图像处理的地方比较多则比较适合引入OpenCV库。所以下面将着重对坐标计算处理这种方式进行介绍。

实现步骤

导入Tesseract的iOS库

这里通过CocoaPods的方式引入第三方库:

  1. pod 'TesseractOCRiOS'

导入字库

将创建好的字库放入tessdata文件夹并拖进工程,这里要特别注意,因为TesseractOCRiOS这个库寻找字库时不支持路径传递,并且找寻的路径是主Bundle路径下的tessdata文件夹里面的字库。所以一定要使用Create folder references这个选项在主Bundle下创建tessdata文件夹才能够获取到里面的字库。

属性选择

创建OcrDetectView

实时显示摄像头成像,并提供截取图片API供外部调用,它是照片数据源的来源。

布局身份证区域框

这里通过重力感应支持横竖屏切换。这里要特别注意当身份证区域框进行横竖屏切换时截取的文字区域也同步需要进行坐标转换,后面再详细介绍。

截取图片

每隔一定的时间(Demo里为1秒)定时截取图片,由于得到的图片是整个手机屏幕的图片,所以这里需要根据坐标及横竖屏进行图片处理。

坐标计算

由于限定了身份证区域框让用户将身份证放入该区域内进行识别,所以这些坐标是可以获取到的。

如获取身份证区域:

  1. // 获取屏幕缩放比例(我是在6s机型上做的,当时设定的宽度为347.0,屏幕宽度为375.0,所以屏幕缩放比为347.0 / 375.0)
  2. CGFloat scale = 347.0 / 375.0;
  3. // 获取身份证区域image
  4. - (UIImage *)fetchIDCardImage:(UIImage *)image isLandscape:(BOOL)isLandscape
  5. {
  6. CGSize size = [UIScreen mainScreen].bounds.size;
  7. CGFloat screenWidth = size.width;
  8. CGFloat screenHeight = size.height;
  9. // 图片实际宽高
  10. CGFloat width = screenWidth * scale;
  11. CGFloat height = screenHeight * scale;
  12. // image相对于屏幕的缩放比
  13. float px = image.size.width / screenWidth;
  14. float py = image.size.height / screenHeight;
  15. // 根据横竖屏计算x,y,w,h
  16. float x, y, w, h;
  17. if (isLandscape)
  18. {
  19. // 由于切换为横屏实际上是以竖屏的身份证区域框的中心为基点旋转90°
  20. // 所以横屏下的x实际上是身份证区域框高度的一半加上竖屏状态下距离屏幕顶部的距离
  21. // 再减掉横屏状态下的宽度的一半
  22. x = height / 2.0 + idcardBoxTopOffset - width / 2.0;
  23. // 同理横屏状态下的y实际上是屏幕宽度减身份证区域高度的一半
  24. y = (screenWidth - height) / 2.0;
  25. w = width;
  26. h = height;
  27. image = [UIImage imageWithCGImage:image.CGImage
  28. scale:image.scale
  29. orientation:UIImageOrientationUp];
  30. }
  31. else
  32. {
  33. x = (screenWidth - width) / 2.0;
  34. y = idcardBoxTopOffset;
  35. w = width;
  36. h = height;
  37. }
  38. // 身份证区域
  39. CGRect cutFrame = CGRectMake(x*py, y*px, w*py, h*px);
  40. // 根据传入身份证区域获取相应的image
  41. UIImage *croppedImage = [JKOcrDetectUtils croppedImage:image inRect:cutFrame];
  42. croppedImage = [JKOcrDetectUtils adjustImageOrientation:croppedImage];
  43. return croppedImage;
  44. }

获取身份证号区域:

  1. - (UIImage *)fetchIDCardNoImage:(UIImage *)image isLandscape:(BOOL)isLandscape
  2. {
  3. CGSize size = [UIScreen mainScreen].bounds.size;
  4. CGFloat screenWidth = size.width;
  5. CGFloat screenHeight = size.height;
  6. CGFloat width = screenWidth * self.widthScale;
  7. CGFloat height = screenHeight * self.heightScale;
  8. float px = image.size.width / screenWidth;
  9. float py = image.size.height / screenHeight;
  10. float x, y, w, h;
  11. if (isLandscape)
  12. {
  13. x = height / 2.0 + idcardBoxTopOffset - width / 2.0 + idcardNoOffsetX;
  14. y = (screenWidth - height) / 2.0 + idcardNoOffsetY;
  15. w = idcardNoWidth;
  16. h = idcardNoHeight;
  17. image = [UIImage imageWithCGImage:image.CGImage
  18. scale:image.scale
  19. orientation:UIImageOrientationUp];
  20. }
  21. else
  22. {
  23. x = (screenWidth - width) / 2.0 + idcardNoOffsetX;
  24. y = idcardBoxTopOffset + idcardNoOffsetY;
  25. w = idcardNoWidth;
  26. h = idcardNoHeight;
  27. }
  28. CGRect cutFrame = CGRectMake(x*py, y*px, w*py, h*px);
  29. UIImage *croppedImage = [JKOcrDetectUtils croppedImage:image inRect:cutFrame];
  30. croppedImage = [JKOcrDetectUtils adjustImageOrientation:croppedImage];
  31. return croppedImage;
  32. }

识别

通过坐标计算这个步骤可以获得可供识别的文本块图片样本,在这里我获取了5个区块,分别是性别、出生日期、身份证号、有效日期、人像。

同之前在Mac上识别步骤一样,初始化字库->设置psm等参数->传入待识别的图片->得到识别后的文本->校验文本。

识别身份证号代码:

  1. - (void)recognizeImageWithTesseract:(UIImage *)image mode:(DetectMode)mode completionBlock:(void(^)(BOOL isRecognized, NSString *recognizedText))completionBlock
  2. {
  3. // 创建`G8RecognitionOperation`对象异步执行OCR识别并初始化字库
  4. G8RecognitionOperation *operation = [[G8RecognitionOperation alloc] initWithLanguage:@"font"];
  5. // 设置psm参数
  6. operation.tesseract.pageSegmentationMode = G8PageSegmentationModeSingleBlock;
  7. // 设置最大识别时间
  8. operation.tesseract.maximumRecognitionTime = 1.0;
  9. // 设置识别图片
  10. operation.tesseract.image = image;
  11. __weak JKOcrService *wself = self;
  12. operation.recognitionCompleteBlock = ^(G8Tesseract *tesseract) {
  13. // 识别后的文本
  14. NSString *recognizedText = tesseract.recognizedText;
  15. __strong JKOcrService *sself = wself;
  16. // 校验文本
  17. if ([JKOcrDetectUtils accurateVerifyIDCardNumber:recognizedText])
  18. {
  19. // 识别成功回调
  20. if (completionBlock) completionBlock(YES, recognizedText);
  21. }
  22. else
  23. {
  24. // 识别失败回调
  25. if (completionBlock) completionBlock(NO, @"");
  26. }
  27. };
  28. // 添加队列
  29. [self.operationQueue addOperation:operation];
  30. }

校验身份证号代码:

  1. + (BOOL)accurateVerifyIDCardNumber:(NSString *)value
  2. {
  3. value = [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
  4. int length = 0;
  5. if (!value)
  6. {
  7. return NO;
  8. }
  9. else
  10. {
  11. length = (int)value.length;
  12. if (length !=15 && length !=18)
  13. {
  14. return NO;
  15. }
  16. }
  17. // 省份代码
  18. NSArray *areasArray = @[ @"11", @"12", @"13", @"14", @"15", @"21", @"22", @"23", @"31", @"32", @"33", @"34", @"35", @"36", @"37", @"41", @"42", @"43", @"44", @"45", @"46", @"50", @"51", @"52", @"53", @"54", @"61", @"62", @"63", @"64", @"65", @"71", @"81", @"82", @"91"];
  19. NSString *valueStart2 = [value substringToIndex:2];
  20. BOOL areaFlag = NO;
  21. for (NSString *areaCode in areasArray)
  22. {
  23. if ([areaCode isEqualToString:valueStart2])
  24. {
  25. areaFlag = YES;
  26. break;
  27. }
  28. }
  29. if (!areaFlag)
  30. {
  31. return false;
  32. }
  33. NSRegularExpression *regularExpression;
  34. NSUInteger numberofMatch;
  35. int year = 0;
  36. switch (length)
  37. {
  38. case 15:
  39. year = [value substringWithRange:NSMakeRange(6,2)].intValue +1900;
  40. if (year %4 == 0 || (year % 100 == 0 && year % 4 ==0))
  41. {
  42. regularExpression = [[NSRegularExpression alloc] initWithPattern:@"^[1-9][0-9]{5}[0-9]{2}((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|[1-2][0-9]))[0-9]{3}$"
  43. options:NSRegularExpressionCaseInsensitive
  44. error:nil];//测试出生日期的合法性
  45. }
  46. else
  47. {
  48. regularExpression = [[NSRegularExpression alloc]initWithPattern:@"^[1-9][0-9]{5}[0-9]{2}((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|1[0-9]|2[0-8]))[0-9]{3}$"
  49. options:NSRegularExpressionCaseInsensitive
  50. error:nil];//测试出生日期的合法性
  51. }
  52. numberofMatch = [regularExpression numberOfMatchesInString:value
  53. options:NSMatchingReportProgress
  54. range:NSMakeRange(0, value.length)];
  55. if (numberofMatch > 0)
  56. {
  57. return YES;
  58. }
  59. else
  60. {
  61. return NO;
  62. }
  63. case 18:
  64. year = [value substringWithRange:NSMakeRange(6,4)].intValue;
  65. if (year % 4 ==0 || (year % 100 ==0 && year % 4 ==0))
  66. {
  67. regularExpression = [[NSRegularExpression alloc] initWithPattern:@"^[1-9][0-9]{5}19[0-9]{2}((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|[1-2][0-9]))[0-9]{3}[0-9Xx]$"
  68. options:NSRegularExpressionCaseInsensitive
  69. error:nil];//测试出生日期的合法性
  70. }
  71. else
  72. {
  73. regularExpression = [[NSRegularExpression alloc] initWithPattern:@"^[1-9][0-9]{5}19[0-9]{2}((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|1[0-9]|2[0-8]))[0-9]{3}[0-9Xx]$"
  74. options:NSRegularExpressionCaseInsensitive
  75. error:nil];//测试出生日期的合法性
  76. }
  77. numberofMatch = [regularExpression numberOfMatchesInString:value
  78. options:NSMatchingReportProgress
  79. range:NSMakeRange(0, value.length)];
  80. if(numberofMatch >0)
  81. {
  82. int S = ([value substringWithRange:NSMakeRange(0,1)].intValue + [value substringWithRange:NSMakeRange(10,1)].intValue) *7 + ([value substringWithRange:NSMakeRange(1,1)].intValue + [value substringWithRange:NSMakeRange(11,1)].intValue) *9 + ([value substringWithRange:NSMakeRange(2,1)].intValue + [value substringWithRange:NSMakeRange(12,1)].intValue) *10 + ([value substringWithRange:NSMakeRange(3,1)].intValue + [value substringWithRange:NSMakeRange(13,1)].intValue) *5 + ([value substringWithRange:NSMakeRange(4,1)].intValue + [value substringWithRange:NSMakeRange(14,1)].intValue) *8 + ([value substringWithRange:NSMakeRange(5,1)].intValue + [value substringWithRange:NSMakeRange(15,1)].intValue) *4 + ([value substringWithRange:NSMakeRange(6,1)].intValue + [value substringWithRange:NSMakeRange(16,1)].intValue) *2 + [value substringWithRange:NSMakeRange(7,1)].intValue *1 + [value substringWithRange:NSMakeRange(8,1)].intValue *6 + [value substringWithRange:NSMakeRange(9,1)].intValue *3;
  83. int Y = S % 11;
  84. NSString *M = @"F";
  85. NSString *JYM = @"10X98765432";
  86. M = [JYM substringWithRange:NSMakeRange(Y,1)];// 判断校验位
  87. if ([M isEqualToString:[value substringWithRange:NSMakeRange(17,1)]])
  88. {
  89. return YES;// 检测ID的校验位
  90. }
  91. else
  92. {
  93. return NO;
  94. }
  95. }
  96. else
  97. {
  98. return NO;
  99. }
  100. default:
  101. return NO;
  102. }
  103. }

性别、出生日期、有效日期的识别步骤大同小异,这里就不一一列举了。然后可以通过使用dispatch_group并发分别进行识别,得到一个结果集后再统一进行验证,如果都识别通过的话则表示这是一张高质量的身份证图片,就可以把这张身份证图片上传到服务器进行识别了。

  1. __block BOOL isIDCardRecognized = NO;
  2. __block BOOL isGenderRecognized = NO;
  3. __block BOOL isBirthdayRecognized = NO;
  4. __block BOOL isFaceRecognized = NO;
  5. __weak JKOcrService *wself = self;
  6. [self recognizeImageWithTesseract:idcardNoImage
  7. mode:DetectModeIDCard
  8. completionBlock:^(BOOL isRecognized, NSString *recognizedText) {
  9. __strong JKOcrService *sself = wself;
  10. if (isRecognized == YES)
  11. {
  12. isIDCardRecognized = YES;
  13. sself.idcardNo = recognizedText;
  14. }
  15. }];
  16. [self recognizeImageWithTesseract:genderImage
  17. mode:DetectModeGender
  18. completionBlock:^(BOOL isRecognized, NSString *recognizedText) {
  19. __strong JKOcrService *sself = wself;
  20. if (isRecognized == YES)
  21. {
  22. isGenderRecognized = YES;
  23. sself.sex = recognizedText;
  24. }
  25. }];
  26. [self recognizeImageWithTesseract:birthdayImage
  27. mode:DetectModeBirthday
  28. completionBlock:^(BOOL isRecognized, NSString *recognizedText) {
  29. __strong JKOcrService *sself = wself;
  30. if (isRecognized == YES)
  31. {
  32. isBirthdayRecognized = YES;
  33. sself.birthday = recognizedText;
  34. }
  35. }];
  36. [self accurateVerifyFace:faceImage
  37. completionBlock:^(BOOL isRecognized) {
  38. isFaceRecognized = isRecognized;
  39. }];
  40. dispatch_group_wait(self.group, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC));
  41. if (isGenderRecognized && isIDCardRecognized && isBirthdayRecognized && isFaceRecognized)
  42. {
  43. [self.operationQueue cancelAllOperations];
  44. dispatch_async(dispatch_get_main_queue(), ^{
  45. if (successHandler)
  46. {
  47. // 高质量的身份证图片
  48. UIImage *idcardImage = [self fetchIDCardImage:image isLandscape:isLandscape];
  49. // TODO: 识别成功
  50. }
  51. });
  52. }
  53. else
  54. {
  55. // TODO: 识别失败
  56. }

据我统计,每个样本识别时间间隔为20ms左右,也就是说,只要样本没问题,逐个识别所用的时间也不到100ms,相对于设定1秒的时间间隔来说是绰绰有余,但是使用异步也没毛病。

总结

通过前面的介绍了解到了Tesseract是什么,可以用于什么样业务场景,如何进行样本训练生成字库。还介绍了如何在Mac OS X上使用Tesseract进行图片识别生成文本,并在这基础之上引入了基于iOS的Tesseract库TesseractOCRiOS,使其能够为移动端服务。

但是引入Tesseract这个库打包出来的APP会增加5.1M包大小,如果再加上系统字库,包大小更是会显著增加。虽然自己训练样本生成字库可以解决这一问题,但是单单为了身份证流程优化这一功能,这样的结果往往还是难以接受的。

既然这样,是否能将Tesseract的作用发挥到极致,例如银行卡自动识别也使用它进行流程优化等等。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/酷酷是懒虫/article/detail/933633
推荐阅读
相关标签
  

闽ICP备14008679号