基于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的作用發揮到極致,例如銀行卡自動識別也使用它進行流程優化等等。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容