基于Tesseract的OCR識別

目錄

  • 需求背景

  • Tesseract簡介及環(huán)境搭建

  • 字庫訓(xùn)練

  • Tesseract for iOS

  • 總結(jié)

需求背景

由于客戶端內(nèi)核的限制,市場上大多數(shù)身份證識別都會放在服務(wù)器校驗,客戶端一般只是負(fù)責(zé)抓取圖片,將抓取到的圖片上送到服務(wù)器識別。這樣一來如果客戶端抓取到的身份證圖片的質(zhì)量無法保障,服務(wù)器也很難識別得出來,會拖慢身份證識別進(jìn)程,造成用戶體驗不好的情況。(如我項目的流程是只要攝像頭一打開就開始抓取圖片上傳到服務(wù)器去識別,不管抓取到的圖片是否是身份證圖片,客戶端等待服務(wù)端返回結(jié)果,如果沒解析出來則繼續(xù)抓取圖片上傳,直至識別出來為止)。

針對客戶端抓取身份證圖片質(zhì)量的情況,客戶端應(yīng)先對身份證所必須的字庫進(jìn)行訓(xùn)練(在Tesseract提供的字庫中英文庫21.9M,中文庫52.7M,如果直接使用大大增加APP包大小)。然后將訓(xùn)練好的字庫集成進(jìn)Tesseract框架分別對性別、出生日期、身份證號、有效日期、人像等區(qū)域識別并進(jìn)行校驗,都通過校驗之后再把得到的高質(zhì)量圖片上傳到服務(wù)器去識別。

如此一來,大大減少客戶端與服務(wù)器交互的同時,把高質(zhì)量圖片上傳到服務(wù)器識別可以增加身份證識別成功率,減少身份證識別時間,提升用戶體驗。

Tesseract簡介及環(huán)境搭建

簡介

Tesseract的OCR引擎最先由HP實驗室于1985年開始研發(fā),至1995年時已經(jīng)成為OCR業(yè)內(nèi)最準(zhǔn)確的三款識別引擎之一。然而,HP不久便決定放棄OCR業(yè)務(wù),Tesseract也從此塵封。數(shù)年以后,HP意識到,與其將Tesseract束之高閣,不如貢獻(xiàn)給開源軟件業(yè),讓其重?zé)ㄐ律?。?005年,Tesseract由美國內(nèi)華達(dá)州信息技術(shù)研究所獲得,并委托Google對其進(jìn)行改進(jìn)、優(yōu)化工作。

Tesseract目前已作為開源項目發(fā)布在Google Project,它與Leptonica圖片處理庫結(jié)合,可以讀取各種格式的圖像并將它們轉(zhuǎn)化成超過60種語言的文本,我們還可以不斷訓(xùn)練自己的庫,使圖像轉(zhuǎn)換文本的能力不斷增強。如果團隊深度需要,還可以以它為模板,開發(fā)出符合自身需求的OCR引擎。

環(huán)境搭建

安裝

1.安裝Homebrew

打開Command Line Tool,直接輸入下面指令:


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

2.安裝Tesseract

打開Command Line Tool,根據(jù)自己的需求選擇相應(yīng)的指令進(jìn)行安裝:


// 安裝tesseract的同時安裝訓(xùn)練工具

brew install --with-training-tools tesseract

// 安裝tesseract的同時安裝所有語言,語言包比較大,如果安裝的話時間較長,建議不安裝,按需選擇

brew install --all-languages tesseract

// 安裝tesseract,并安裝訓(xùn)練工具和語言

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

// 只安裝tesseract,不安裝訓(xùn)練工具

brew install tesseract

下載語言庫

根據(jù)自己的需求可以到這里選擇所需要的語言庫,如我們選擇的簡體中文庫是: chi_sim.traineddata,將下載好的文件拷貝到: /usr/local/Cellar/tesseract/3.05.01(tesseract版本號)/share/tessdata目錄下。

使用

使用如下指令進(jìn)行圖片文字識別:

// 默認(rèn)使用eng字庫,imageName是圖片絕對路徑,result是識別結(jié)果

tesseract imageName result

// 指定使用簡體中文

tesseract -l chi_sim imageName result

// 指定多語言,用+號相連

tesseract -l chi_sim+eng imageName result

// 查看本地存在的語言庫

tesseract --list-langs

有個地方需要特別注意,參數(shù)psm


// 輸入命令,查看psm的參數(shù)

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 定向腳本監(jiān)測(OSD)

1 使用OSD自動分頁

2 自動分頁,但是不使用OSD或OCR(Optical Character Recognition,光學(xué)字符識別)

3 全自動分頁,但是沒有使用OSD(默認(rèn))

4 假設(shè)可變大小的一個文本列。

5 假設(shè)垂直對齊文本的單個統(tǒng)一塊。

6 假設(shè)一個統(tǒng)一的文本塊。

7 將圖像視為單個文本行。

8 將圖像視為單個詞。

9 將圖像視為圓中的單個詞。

10 將圖像視為單個字符

根據(jù)情況選擇不同的psm值,這很重要,如果選擇到不恰當(dāng)?shù)闹禃?dǎo)致識別失敗。

例如下面這張圖應(yīng)假設(shè)為一個統(tǒng)一的文本塊:

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文件,成功識別:

一二三四

一二三四

字庫訓(xùn)練

安裝jTessBoxEditor

翻墻后,在這里下載 jTessBoxEditorFX-2.0-Beta.zip,解壓后得到j(luò)TessBoxEditorFX文件夾,由于這是由Java開發(fā)的,所以我們應(yīng)確保運行jTessBoxEditor前先安裝JRE(Java Runtime Environment,Java運行環(huán)境),由于JRE的安裝教程很多,這里就不做過多介紹了。

獲取樣本文件

由于身份證的字體是比較固定的,所以不需要做太多樣本進(jìn)行訓(xùn)練。像身份證號的數(shù)字和X是黑體,性別、生日、有效期等字體是方正黑體簡體,所以我們只需要在word上輸入身份證上對應(yīng)字體的文字,然后用切圖工具把文字切出來,這樣樣本就可以獲取到了。下面是我獲取身份證文字的樣本圖片:

黑體數(shù)字:

黑體數(shù)字.jpg

黑體X:

image

華文黑體簡體漢字:

image

華文黑體簡體數(shù)字:

image

【注意】:樣本圖像文件格式必須為tif/tiff格式

Merge樣本文件

進(jìn)入jTessBoxEditor目錄,在終端執(zhí)行java -Xms128m -Xmx1024m -jar jTessBoxEditorFX.jar命令,會出現(xiàn)如下操作界面:

image

Tools->Merge TIFF,將樣本文件全部選上,并將合并文件保存為font.tif,進(jìn)入該文件所在目錄,執(zhí)行指令


tesseract font.tif font batch.nochop makebox

生成文件名為font.box文件

字符矯正

生成font.box文件后,可以使用jTessBoxEditor對字符進(jìn)行矯正了。選中Box Editor->Open,打開font.tif,會出現(xiàn)如下操作界面:

image

剛開始對數(shù)字和字母識別得比較準(zhǔn)確,但是識別的中文應(yīng)該是亂碼,選中文字在Character一欄輸入正確的文字進(jìn)行字符矯正、保存。這樣就可以得到一個較為準(zhǔn)確的字符集(PS:如果要識別其他字體和文字得獲取大量樣本進(jìn)行訓(xùn)練矯正,由于身份證字體比較固定,所以只需識別固定文字即可,創(chuàng)建出來的字庫在165KB左右)。

執(zhí)行批處理文件

生成字符特征

執(zhí)行指令


tesseract font.tif font nobatch box.train

生成.tr文件,它包含了訓(xùn)練頁的每個字符的特征。

計算字符集

執(zhí)行指令


unicharset_extractor font.box

生成unicharset數(shù)據(jù)文件,它包含了tesseract需要知道可能要輸出的字符集。

字體屬性

執(zhí)行指令


echo 'font 0 0 0 0 0' > font_properties

可以將提供字體形式信息重定向到font_properties文本文件中,通過-F filename選項指定來進(jìn)行mftraining.

當(dāng)運行mftraining時,每個.tr文件名必須有相關(guān)entry在font_properties文件中,否則將中止mftraining。

聚合

當(dāng)所有訓(xùn)練頁的字符特征被抽取出來時,我們需要將它們聚集起來創(chuàng)建prototype文件。這些字符形狀特性可以通過使用shapeclustering、mftraining和cntraining程序進(jìn)行聚焦。


// shapeclustering通過形狀聚集創(chuàng)建了主形狀表,并將它寫到一個文件中: shapetable.

shapeclustering -F font_properties -U unicharset font.tr

// mftraining將輸出兩個其它的數(shù)據(jù)文件: inttemp(形狀原型)和pffmtable(每個字符所希望的特征)

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

// 輸出normproto數(shù)據(jù)文件

cntraining font.tr

生成字庫

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

執(zhí)行下面指令得到traineddata文件


combine_tessdata font.

最終的文件目錄應(yīng)該如下圖所示:

image

生成的font.traineddata就是我們所需要的字庫。

Tesseract for iOS

通過前面的介紹我們知道了Tesseract框架是根據(jù)我們提供的字庫對圖片上的文字進(jìn)行識別,然后轉(zhuǎn)化為文本的形式輸出,并且我們也創(chuàng)建了自己的字庫。但往往一張圖片上不一定只有一個文本塊,有可能有多個文本塊。例如身份證它就有身份證號、性別、民族、出生年月、姓名等多個區(qū)塊,那么如何把它們截取為一個個區(qū)塊,然后將每個區(qū)塊分別提供給Tesseract框架進(jìn)行識別呢?

目前找到了兩種方法對這些區(qū)塊進(jìn)行分離:

圖像處理技術(shù)

圖像處理技術(shù)是使用OpenCV庫對圖像進(jìn)行灰度化,二值化,腐蝕,輪廓檢測等。

1.灰度化處理:圖片灰度化處理就是將指定圖片每個像素點的RGB三個分量通過一定的算法計算出該像素點的灰度值,使圖像只含亮度而不含色彩信息。

image

2.二值化:二值化處理就是將經(jīng)過灰度化處理的圖片轉(zhuǎn)換為只包含黑色和白色兩種顏色的圖像,他們之間沒有其他灰度的變化。在二值圖中用255便是白色,0表示黑色。

image

3.腐蝕:圖片的腐蝕就是將得到的二值圖中的黑色塊進(jìn)行放大。即連接圖片中相鄰黑色像素點的元素。通過腐蝕可以把身份證上的身份證號碼連接在一起形成一個矩形區(qū)域。

image

4.輪廓檢測:圖片經(jīng)過腐蝕操作后相鄰點會連接在一起形成一個大的區(qū)域,這個時候通過輪廊檢測就可以把每個大的區(qū)域找出來,這樣就可以定位到身份證上面號碼的區(qū)域。

image

代碼處理:


// 將UIImage轉(zhuǎn)換成Mat

cv::Mat resultImage;

UIImageToMat(image, resultImage);

// 轉(zhuǎn)為灰度圖

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));


// 取出身份證號碼區(qū)域

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;

    }

}

這種方式的優(yōu)點是不需要用戶定點描框,只要用戶將身份證放到攝像頭內(nèi)便可自動處理,代碼也相對簡單,能夠很大程度上提升用戶體驗。缺點是引入OpenCV庫會增加了4M包的大小。

坐標(biāo)計算處理

坐標(biāo)計算處理原理是在手機上給定一個身份證區(qū)域讓用戶將身份證放入該區(qū)域內(nèi),通過計算坐標(biāo)獲取指定的區(qū)域(如我Demo的效果是這樣):

正面
反面

這種方式的優(yōu)點是不需要引入OpenCV庫而導(dǎo)致又一程度上增加包的大小。缺點也是顯而易見的,那就是需要用戶對準(zhǔn)指定區(qū)域進(jìn)行定點描框,要不坐標(biāo)計算后獲取到的錯誤區(qū)域拋給Tesseract框架是識別不出的,用戶體驗不是很好。

綜上所述,在實現(xiàn)方式的選擇上可以根據(jù)自己的項目具體情況具體分析,由于我司項目是一個金融APP,引入TesseractOCRiOS框架已經(jīng)增加5.1M包大小,若再引入OpenCV,那么為了一個OCR優(yōu)化功能增加9M的包大小這是不能夠接受的。若是類似于美圖秀秀那類型的APP,使用圖像處理的地方比較多則比較適合引入OpenCV庫。所以下面將著重對坐標(biāo)計算處理這種方式進(jìn)行介紹。

實現(xiàn)步驟

導(dǎo)入Tesseract的iOS庫

這里通過CocoaPods的方式引入第三方庫:


pod 'TesseractOCRiOS'

導(dǎo)入字庫

將創(chuàng)建好的字庫放入tessdata文件夾并拖進(jìn)工程,這里要特別注意,因為TesseractOCRiOS這個庫尋找字庫時不支持路徑傳遞,并且找尋的路徑是主Bundle路徑下的tessdata文件夾里面的字庫。所以一定要使用Create folder references這個選項在主Bundle下創(chuàng)建tessdata文件夾才能夠獲取到里面的字庫。

屬性選擇

創(chuàng)建OcrDetectView

實時顯示攝像頭成像,并提供截取圖片API供外部調(diào)用,它是照片數(shù)據(jù)源的來源。

布局身份證區(qū)域框

這里通過重力感應(yīng)支持橫豎屏切換。這里要特別注意當(dāng)身份證區(qū)域框進(jìn)行橫豎屏切換時截取的文字區(qū)域也同步需要進(jìn)行坐標(biāo)轉(zhuǎn)換,后面再詳細(xì)介紹。

截取圖片

每隔一定的時間(Demo里為1秒)定時截取圖片,由于得到的圖片是整個手機屏幕的圖片,所以這里需要根據(jù)坐標(biāo)及橫豎屏進(jìn)行圖片處理。

坐標(biāo)計算

由于限定了身份證區(qū)域框讓用戶將身份證放入該區(qū)域內(nèi)進(jìn)行識別,所以這些坐標(biāo)是可以獲取到的。

如獲取身份證區(qū)域:


// 獲取屏幕縮放比例(我是在6s機型上做的,當(dāng)時設(shè)定的寬度為347.0,屏幕寬度為375.0,所以屏幕縮放比為347.0 / 375.0)

CGFloat scale  = 347.0 / 375.0;

// 獲取身份證區(qū)域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;

    // 根據(jù)橫豎屏計算x,y,w,h

    float x, y, w, h;

    if (isLandscape)

    {

    // 由于切換為橫屏實際上是以豎屏的身份證區(qū)域框的中心為基點旋轉(zhuǎn)90°

    // 所以橫屏下的x實際上是身份證區(qū)域框高度的一半加上豎屏狀態(tài)下距離屏幕頂部的距離

    // 再減掉橫屏狀態(tài)下的寬度的一半

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

        // 同理橫屏狀態(tài)下的y實際上是屏幕寬度減身份證區(qū)域高度的一半

        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;

    }

// 身份證區(qū)域

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

// 根據(jù)傳入身份證區(qū)域獲取相應(yīng)的image

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

    croppedImage = [JKOcrDetectUtils adjustImageOrientation:croppedImage];

    return croppedImage;

}

獲取身份證號區(qū)域:


- (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;

}

識別

通過坐標(biāo)計算這個步驟可以獲得可供識別的文本塊圖片樣本,在這里我獲取了5個區(qū)塊,分別是性別、出生日期、身份證號、有效日期、人像。

同之前在Mac上識別步驟一樣,初始化字庫->設(shè)置psm等參數(shù)->傳入待識別的圖片->得到識別后的文本->校驗文本。

識別身份證號代碼:


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

{

    // 創(chuàng)建`G8RecognitionOperation`對象異步執(zhí)行OCR識別并初始化字庫

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

    // 設(shè)置psm參數(shù)

    operation.tesseract.pageSegmentationMode = G8PageSegmentationModeSingleBlock;

    // 設(shè)置最大識別時間

    operation.tesseract.maximumRecognitionTime = 1.0;

    // 設(shè)置識別圖片

    operation.tesseract.image = image;

    __weak JKOcrService *wself = self;

    operation.recognitionCompleteBlock = ^(G8Tesseract *tesseract) {

        // 識別后的文本

        NSString *recognizedText = tesseract.recognizedText;

        __strong JKOcrService *sself = wself;

// 校驗文本

        if ([JKOcrDetectUtils accurateVerifyIDCardNumber:recognizedText])

        {

            // 識別成功回調(diào)

            if (completionBlock) completionBlock(YES, recognizedText);

        }

        else

        {

// 識別失敗回調(diào)

            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并發(fā)分別進(jìn)行識別,得到一個結(jié)果集后再統(tǒng)一進(jìn)行驗證,如果都識別通過的話則表示這是一張高質(zhì)量的身份證圖片,就可以把這張身份證圖片上傳到服務(wù)器進(jìn)行識別了。


__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)

        {

        // 高質(zhì)量的身份證圖片

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

// TODO: 識別成功

        }

    });

}

else

{

    // TODO: 識別失敗

}

據(jù)我統(tǒng)計,每個樣本識別時間間隔為20ms左右,也就是說,只要樣本沒問題,逐個識別所用的時間也不到100ms,相對于設(shè)定1秒的時間間隔來說是綽綽有余,但是使用異步也沒毛病。

總結(jié)

通過前面的介紹了解到了Tesseract是什么,可以用于什么樣業(yè)務(wù)場景,如何進(jìn)行樣本訓(xùn)練生成字庫。還介紹了如何在Mac OS X上使用Tesseract進(jìn)行圖片識別生成文本,并在這基礎(chǔ)之上引入了基于iOS的Tesseract庫TesseractOCRiOS,使其能夠為移動端服務(wù)。

但是引入Tesseract這個庫打包出來的APP會增加5.1M包大小,如果再加上系統(tǒng)字庫,包大小更是會顯著增加。雖然自己訓(xùn)練樣本生成字庫可以解決這一問題,但是單單為了身份證流程優(yōu)化這一功能,這樣的結(jié)果往往還是難以接受的。

既然這樣,是否能將Tesseract的作用發(fā)揮到極致,例如銀行卡自動識別也使用它進(jìn)行流程優(yōu)化等等。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容