基于Tesseract的OCR识别--身份证

 

目录

  • 需求背景

  • 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,直接输入下面指令:


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

2.安装Tesseract

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


// 安装tesseract的同时安装训练工具

brew install --with-training-tools tesseract

// 安装tesseract的同时安装所有语言,语言包比较大,如果安装的话时间较长,建议不安装,按需选择

brew install --all-languages tesseract

// 安装tesseract,并安装训练工具和语言

brew install --all-languages --with-training-tools tesseract

// 只安装tesseract,不安装训练工具

brew install tesseract

下载语言库

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

使用

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

// 默认使用eng字库,imageName是图片绝对路径,result是识别结果

tesseract imageName result

// 指定使用简体中文

tesseract -l chi_sim imageName result

// 指定多语言,用+号相连

tesseract -l chi_sim+eng imageName result

// 查看本地存在的语言库

tesseract --list-langs

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


// 输入命令,查看psm的参数

tesseract --help-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.

翻译:

0 定向脚本监测(OSD)

1 使用OSD自动分页

2 自动分页,但是不使用OSD或OCR(Optical Character Recognition,光学字符识别)

3 全自动分页,但是没有使用OSD(默认)

4 假设可变大小的一个文本列。

5 假设垂直对齐文本的单个统一块。

6 假设一个统一的文本块。

7 将图像视为单个文本行。

8 将图像视为单个词。

9 将图像视为圆中的单个词。

10 将图像视为单个字符

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

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

num.png

使用命令:


tesseract num.png result -l chi_sim

打印:

Tesseract Open Source OCR Engine v3.05.01 with Leptonica

Empty page!!

Empty page!!

使用命令:


tesseract num.png result -l chi_sim -psm 6

打开result.txt文件,成功识别:

一二三四

一二三四

字库训练

安装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,进入该文件所在目录,执行指令


tesseract font.tif font batch.nochop makebox

生成文件名为font.box文件

字符矫正

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

image

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

执行批处理文件

生成字符特征

执行指令


tesseract font.tif font nobatch box.train

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

计算字符集

执行指令


unicharset_extractor font.box

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

字体属性

执行指令


echo 'font 0 0 0 0 0' > font_properties

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

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

聚合

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


// shapeclustering通过形状聚集创建了主形状表,并将它写到一个文件中: shapetable.

shapeclustering -F font_properties -U unicharset font.tr

// mftraining将输出两个其它的数据文件: inttemp(形状原型)和pffmtable(每个字符所希望的特征)

mftraining -F font_properties -U unicharset -O font.unicharset font.tr

// 输出normproto数据文件

cntraining font.tr

生成字库

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

执行下面指令得到traineddata文件


combine_tessdata font.

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

image

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

Tesseract for iOS

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

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

图像处理技术

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

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

image

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

image

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

image

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

image

代码处理:


// 将UIImage转换成Mat

cv::Mat resultImage;

UIImageToMat(image, resultImage);

// 转为灰度图

cvtColor(resultImage, resultImage, cv::COLOR_BGR2GRAY);

// 利用阈值二值化

cv::threshold(resultImage, resultImage, 100, 255, CV_THRESH_BINARY);

// 腐蚀,填充(腐蚀是让黑色点变大)

cv::Mat erodeElement = getStructuringElement(cv::MORPH_RECT, cv::Size(26,26));

cv::erode(resultImage, resultImage, erodeElement);

// 轮廊检测

std::vector<std::vector<cv::Point>> contours; // 定义一个容器来存储所有检测到的轮廊

cv::findContours(resultImage, contours, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, cvPoint(0, 0));


// 取出身份证号码区域

std::vector<cv::Rect> rects;

cv::Rect numberRect = cv::Rect(0,0,0,0);

std::vector<std::vector<cv::Point>>::const_iterator itContours = contours.begin();

for ( ; itContours != contours.end(); ++itContours) {

    cv::Rect rect = cv::boundingRect(*itContours);

    rects.push_back(rect);

    // 算法原理

    if (rect.width > numberRect.width && rect.width > rect.height * 5) {

        numberRect = rect;

    }

}

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

坐标计算处理

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

正面

反面

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

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

实现步骤

导入Tesseract的iOS库

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


pod 'TesseractOCRiOS'

导入字库

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

属性选择

创建OcrDetectView

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

布局身份证区域框

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

截取图片

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

坐标计算

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

如获取身份证区域:


// 获取屏幕缩放比例(我是在6s机型上做的,当时设定的宽度为347.0,屏幕宽度为375.0,所以屏幕缩放比为347.0 / 375.0)

CGFloat scale  = 347.0 / 375.0;

// 获取身份证区域image

- (UIImage *)fetchIDCardImage:(UIImage *)image isLandscape:(BOOL)isLandscape

{

    CGSize  size = [UIScreen mainScreen].bounds.size;

    CGFloat screenWidth  = size.width;

    CGFloat screenHeight = size.height;

// 图片实际宽高

    CGFloat width  = screenWidth  * scale;

    CGFloat height = screenHeight * scale;

// image相对于屏幕的缩放比

    float px = image.size.width / screenWidth;

    float py = image.size.height / screenHeight;

    // 根据横竖屏计算x,y,w,h

    float x, y, w, h;

    if (isLandscape)

    {

    // 由于切换为横屏实际上是以竖屏的身份证区域框的中心为基点旋转90°

    // 所以横屏下的x实际上是身份证区域框高度的一半加上竖屏状态下距离屏幕顶部的距离

    // 再减掉横屏状态下的宽度的一半

        x = height / 2.0 + idcardBoxTopOffset - width / 2.0;

        // 同理横屏状态下的y实际上是屏幕宽度减身份证区域高度的一半

        y = (screenWidth - height) / 2.0;

        w = width;

        h = height;

        image = [UIImage imageWithCGImage:image.CGImage

                                    scale:image.scale

                              orientation:UIImageOrientationUp];

    }

    else

    {

        x = (screenWidth - width) / 2.0;

        y = idcardBoxTopOffset;

        w = width;

        h = height;

    }

// 身份证区域

    CGRect cutFrame = CGRectMake(x*py, y*px, w*py, h*px);

// 根据传入身份证区域获取相应的image

    UIImage *croppedImage = [JKOcrDetectUtils croppedImage:image inRect:cutFrame];

    croppedImage = [JKOcrDetectUtils adjustImageOrientation:croppedImage];

    return croppedImage;

}

获取身份证号区域:


- (UIImage *)fetchIDCardNoImage:(UIImage *)image isLandscape:(BOOL)isLandscape

{

    CGSize  size = [UIScreen mainScreen].bounds.size;

    CGFloat screenWidth  = size.width;

    CGFloat screenHeight = size.height;

    CGFloat width  = screenWidth  * self.widthScale;

    CGFloat height = screenHeight * self.heightScale;

    float px = image.size.width / screenWidth;

    float py = image.size.height / screenHeight;

    float x, y, w, h;

    if (isLandscape)

    {

        x = height / 2.0 + idcardBoxTopOffset - width / 2.0 + idcardNoOffsetX;

        y = (screenWidth - height) / 2.0 + idcardNoOffsetY;

        w = idcardNoWidth;

        h = idcardNoHeight;

        image = [UIImage imageWithCGImage:image.CGImage

                                    scale:image.scale

                              orientation:UIImageOrientationUp];

    }

    else

    {

        x = (screenWidth - width) / 2.0 + idcardNoOffsetX;

        y = idcardBoxTopOffset + idcardNoOffsetY;

        w = idcardNoWidth;

        h = idcardNoHeight;

    }

    CGRect cutFrame = CGRectMake(x*py, y*px, w*py, h*px);

    UIImage *croppedImage = [JKOcrDetectUtils croppedImage:image inRect:cutFrame];

    croppedImage = [JKOcrDetectUtils adjustImageOrientation:croppedImage];

    return croppedImage;

}

识别

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

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

识别身份证号代码:


- (void)recognizeImageWithTesseract:(UIImage *)image mode:(DetectMode)mode completionBlock:(void(^)(BOOL isRecognized, NSString *recognizedText))completionBlock

{

    // 创建`G8RecognitionOperation`对象异步执行OCR识别并初始化字库

    G8RecognitionOperation *operation = [[G8RecognitionOperation alloc] initWithLanguage:@"font"];

    // 设置psm参数

    operation.tesseract.pageSegmentationMode = G8PageSegmentationModeSingleBlock;

    // 设置最大识别时间

    operation.tesseract.maximumRecognitionTime = 1.0;

    // 设置识别图片

    operation.tesseract.image = image;

    __weak JKOcrService *wself = self;

    operation.recognitionCompleteBlock = ^(G8Tesseract *tesseract) {

        // 识别后的文本

        NSString *recognizedText = tesseract.recognizedText;

        __strong JKOcrService *sself = wself;

// 校验文本

        if ([JKOcrDetectUtils accurateVerifyIDCardNumber:recognizedText])

        {

            // 识别成功回调

            if (completionBlock) completionBlock(YES, recognizedText);

        }

        else

        {

// 识别失败回调

            if (completionBlock) completionBlock(NO, @"");

        }

    };

    // 添加队列

    [self.operationQueue addOperation:operation];

}

校验身份证号代码:


+ (BOOL)accurateVerifyIDCardNumber:(NSString *)value

{

    value = [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];

    int length = 0;

    if (!value)

    {

        return NO;

    }

    else

    {

        length = (int)value.length;

        if (length !=15 && length !=18)

        {

            return NO;

        }

    }

    // 省份代码

    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"];

    NSString *valueStart2 = [value substringToIndex:2];

    BOOL areaFlag = NO;

    for (NSString *areaCode in areasArray)

    {

        if ([areaCode isEqualToString:valueStart2])

        {

            areaFlag = YES;

            break;

        }

    }

    if (!areaFlag)

    {

        return false;

    }

    NSRegularExpression *regularExpression;

    NSUInteger numberofMatch;

    int year = 0;

    switch (length)

    {

        case 15:

            year = [value substringWithRange:NSMakeRange(6,2)].intValue +1900;

            if (year %4 == 0 || (year % 100 == 0 && year % 4 ==0))

            {

                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}$"

                                                                        options:NSRegularExpressionCaseInsensitive

                                                                          error:nil];//测试出生日期的合法性

            }

            else

            {

                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}$"

                                                                        options:NSRegularExpressionCaseInsensitive

                                                                          error:nil];//测试出生日期的合法性

            }

            numberofMatch = [regularExpression numberOfMatchesInString:value

                                                              options:NSMatchingReportProgress

                                                                range:NSMakeRange(0, value.length)];

            if (numberofMatch > 0)

            {

                return YES;

            }

            else

            {

                return NO;

            }

        case 18:

            year = [value substringWithRange:NSMakeRange(6,4)].intValue;

            if (year % 4 ==0 || (year % 100 ==0 && year % 4 ==0))

            {

                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]$"

                                                                        options:NSRegularExpressionCaseInsensitive

                                                                          error:nil];//测试出生日期的合法性

            }

            else

            {

                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]$"

                                                                        options:NSRegularExpressionCaseInsensitive

                                                                          error:nil];//测试出生日期的合法性

            }

            numberofMatch = [regularExpression numberOfMatchesInString:value

                                                              options:NSMatchingReportProgress

                                                                range:NSMakeRange(0, value.length)];

            if(numberofMatch >0)

            {

                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;

                int Y = S % 11;

                NSString *M = @"F";

                NSString *JYM = @"10X98765432";

                M = [JYM substringWithRange:NSMakeRange(Y,1)];// 判断校验位

                if ([M isEqualToString:[value substringWithRange:NSMakeRange(17,1)]])

                {

                    return YES;// 检测ID的校验位

                }

                else

                {

                    return NO;

                }

            }

            else

            {

                return NO;

            }

        default:

            return NO;

    }

}

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


__block BOOL isIDCardRecognized = NO;

__block BOOL isGenderRecognized = NO;

__block BOOL isBirthdayRecognized = NO;

__block BOOL isFaceRecognized = NO;

__weak JKOcrService *wself = self;

[self recognizeImageWithTesseract:idcardNoImage

                            mode:DetectModeIDCard

                  completionBlock:^(BOOL isRecognized, NSString *recognizedText) {

                      __strong JKOcrService *sself = wself;

                      if (isRecognized == YES)

                      {

                          isIDCardRecognized = YES;

                          sself.idcardNo = recognizedText;

                      }

                  }];

[self recognizeImageWithTesseract:genderImage

                            mode:DetectModeGender

                  completionBlock:^(BOOL isRecognized, NSString *recognizedText) {

                      __strong JKOcrService *sself = wself;

                      if (isRecognized == YES)

                      {

                          isGenderRecognized = YES;

                          sself.sex = recognizedText;

                      }

                  }];

[self recognizeImageWithTesseract:birthdayImage

                            mode:DetectModeBirthday

                  completionBlock:^(BOOL isRecognized, NSString *recognizedText) {

                      __strong JKOcrService *sself = wself;

                      if (isRecognized == YES)

                      {

                          isBirthdayRecognized = YES;

                          sself.birthday = recognizedText;

                      }

                  }];

[self accurateVerifyFace:faceImage

        completionBlock:^(BOOL isRecognized) {

            isFaceRecognized = isRecognized;

        }];

dispatch_group_wait(self.group, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC));

if (isGenderRecognized && isIDCardRecognized && isBirthdayRecognized && isFaceRecognized)

{

    [self.operationQueue cancelAllOperations];

    dispatch_async(dispatch_get_main_queue(), ^{

        if (successHandler)

        {

        // 高质量的身份证图片

            UIImage *idcardImage = [self fetchIDCardImage:image isLandscape:isLandscape];

// TODO: 识别成功

        }

    });

}

else

{

    // TODO: 识别失败

}

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

总结

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

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

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

  • 2
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Tesseract-OCR 是一个开源的字符识别库,支持多种语言,其适用于通过光学字符识别OCR)技术处理身份证信息。而 Java Tesseract-OCR 是基于 Tesseract-OCR 的 Java API,为开发人员提供了在 Java 程序中使用 Tesseract-OCR 的功能。 使用 Java Tesseract-OCR 处理身份证,首先需要安装 Tesseract-OCR 的依赖库,并将其与 Java 项目进行集成。然后,可以通过 Java Tesseract-OCR 提供的 API,对身份证进行文字识别识别身份证的过程一般包括以下几个步骤: 1. 图像预处理:首先需要加载身份证图片并进行预处理,包括图像灰度化、二值化等操作,以便提高识别的准确性。 2. 文字识别:使用 Java Tesseract-OCR 的 API,对预处理后的图像进行文字识别。可以根据需求,选择不同的识别模式和语言设置,以达到更好的识别效果。 3. 结果输出:获取到识别结果后,可以将识别的文字信息输出到控制台、文件或者存储到数据库中。 需要注意的是,由于身份证上的文字信息存在一定的特殊性,如字体、背景等因素的影响,识别的准确率可能会受到一些限制。因此,在使用 Java Tesseract-OCR 进行身份证文字识别时,可能需要对图片进行适当的预处理,以及对识别结果进行后期的校验和修正,以保证最终的准确性。 总之,Java Tesseract-OCR 是一个强大的工具,能够帮助开发人员在 Java 程序中实现身份证文字识别功能,但需要根据实际情况对其进行调试和优化,以达到更好的识别效果。 ### 回答2: Java Tesseract-OCR是一个基于Java开发的OCR(光学字符识别)库,可以用于识别身份证OCR技术可以将身份证上的文字和数字转化为计算机可读的文本数据,从而实现身份证信息的自动化处理和识别。 使用Java Tesseract-OCR识别身份证,首先需要将身份证的图像文件加载到程序中。然后,通过调用相应的方法,对图像进行预处理和分析。预处理包括图像二值化、去噪等操作,以提高识别准确度。接下来,调用OCR识别方法,将图像转化为文本数据。最后,对识别结果进行后期处理和分析,提取身份证上的各项信息。 使用Java Tesseract-OCR识别身份证有以下几个优点。首先,Java Tesseract-OCR是一个开源的OCR库,提供了丰富的功能和灵活的定制选项,可以根据具体需求进行配置和调整。其次,Java Tesseract-OCR是基于Tesseract引擎开发的,该引擎是目前最为成熟和广泛应用的OCR引擎之一,具有较高的识别准确度和稳定性。再次,Java Tesseract-OCR支持多种图像格式和语言,可以应对不同类型和语种的身份证识别需求。 总而言之,Java Tesseract-OCR是一个强大而灵活的工具,可以用于身份证OCR识别。通过使用该工具,可以实现身份证信息的自动化处理和识别,提高工作效率和准确性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值