目錄
需求背景
Tesseract簡介及環(huán)境搭建
字庫訓(xùn)練
Tesseract for iOS
總結(jié)
需求背景
由于客戶端內(nèi)核的限制,市場(chǎng)上大多數(shù)身份證識(shí)別都會(huì)放在服務(wù)器校驗(yàn)乎完,客戶端一般只是負(fù)責(zé)抓取圖片,將抓取到的圖片上送到服務(wù)器識(shí)別秘狞。這樣一來如果客戶端抓取到的身份證圖片的質(zhì)量無法保障,服務(wù)器也很難識(shí)別得出來蹈集,會(huì)拖慢身份證識(shí)別進(jìn)程烁试,造成用戶體驗(yàn)不好的情況。(如我項(xiàng)目的流程是只要攝像頭一打開就開始抓取圖片上傳到服務(wù)器去識(shí)別拢肆,不管抓取到的圖片是否是身份證圖片减响,客戶端等待服務(wù)端返回結(jié)果,如果沒解析出來則繼續(xù)抓取圖片上傳郭怪,直至識(shí)別出來為止)支示。
針對(duì)客戶端抓取身份證圖片質(zhì)量的情況,客戶端應(yīng)先對(duì)身份證所必須的字庫進(jìn)行訓(xùn)練(在Tesseract提供的字庫中英文庫21.9M鄙才,中文庫52.7M颂鸿,如果直接使用大大增加APP包大小)。然后將訓(xùn)練好的字庫集成進(jìn)Tesseract框架分別對(duì)性別攒庵、出生日期嘴纺、身份證號(hào)、有效日期浓冒、人像等區(qū)域識(shí)別并進(jìn)行校驗(yàn)颖医,都通過校驗(yàn)之后再把得到的高質(zhì)量圖片上傳到服務(wù)器去識(shí)別。
如此一來裆蒸,大大減少客戶端與服務(wù)器交互的同時(shí)熔萧,把高質(zhì)量圖片上傳到服務(wù)器識(shí)別可以增加身份證識(shí)別成功率,減少身份證識(shí)別時(shí)間僚祷,提升用戶體驗(yàn)佛致。
Tesseract簡介及環(huán)境搭建
簡介
Tesseract的OCR引擎最先由HP實(shí)驗(yàn)室于1985年開始研發(fā),至1995年時(shí)已經(jīng)成為OCR業(yè)內(nèi)最準(zhǔn)確的三款識(shí)別引擎之一辙谜。然而俺榆,HP不久便決定放棄OCR業(yè)務(wù),Tesseract也從此塵封装哆。數(shù)年以后罐脊,HP意識(shí)到,與其將Tesseract束之高閣蜕琴,不如貢獻(xiàn)給開源軟件業(yè)萍桌,讓其重?zé)ㄐ律T?005年凌简,Tesseract由美國內(nèi)華達(dá)州信息技術(shù)研究所獲得上炎,并委托Google對(duì)其進(jìn)行改進(jìn)、優(yōu)化工作雏搂。
Tesseract目前已作為開源項(xiàng)目發(fā)布在Google Project藕施,它與Leptonica圖片處理庫結(jié)合寇损,可以讀取各種格式的圖像并將它們轉(zhuǎn)化成超過60種語言的文本,我們還可以不斷訓(xùn)練自己的庫裳食,使圖像轉(zhuǎn)換文本的能力不斷增強(qiáng)矛市。如果團(tuán)隊(duì)深度需要,還可以以它為模板诲祸,開發(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的同時(shí)安裝訓(xùn)練工具
brew install --with-training-tools tesseract
// 安裝tesseract的同時(shí)安裝所有語言,語言包比較大配紫,如果安裝的話時(shí)間較長径密,建議不安裝,按需選擇
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版本號(hào))/share/tessdata目錄下惧眠。
使用
使用如下指令進(jìn)行圖片文字識(shí)別:
// 默認(rèn)使用eng字庫,imageName是圖片絕對(duì)路徑于个,result是識(shí)別結(jié)果
tesseract imageName result
// 指定使用簡體中文
tesseract -l chi_sim imageName result
// 指定多語言氛魁,用+號(hào)相連
tesseract -l chi_sim+eng imageName result
// 查看本地存在的語言庫
tesseract --list-langs
有個(gè)地方需要特別注意,參數(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)測(cè)(OSD)
1 使用OSD自動(dòng)分頁
2 自動(dòng)分頁秀存,但是不使用OSD或OCR(Optical Character Recognition,光學(xué)字符識(shí)別)
3 全自動(dòng)分頁羽氮,但是沒有使用OSD(默認(rèn))
4 假設(shè)可變大小的一個(gè)文本列或链。
5 假設(shè)垂直對(duì)齊文本的單個(gè)統(tǒng)一塊。
6 假設(shè)一個(gè)統(tǒng)一的文本塊档押。
7 將圖像視為單個(gè)文本行澳盐。
8 將圖像視為單個(gè)詞。
9 將圖像視為圓中的單個(gè)詞令宿。
10 將圖像視為單個(gè)字符
根據(jù)情況選擇不同的psm值叼耙,這很重要,如果選擇到不恰當(dāng)?shù)闹禃?huì)導(dǎo)致識(shí)別失敗粒没。
例如下面這張圖應(yīng)假設(shè)為一個(gè)統(tǒng)一的文本塊:
使用命令:
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文件,成功識(shí)別:
一二三四
一二三四
字庫訓(xùn)練
安裝jTessBoxEditor
翻墻后革娄,在這里下載 jTessBoxEditorFX-2.0-Beta.zip倾贰,解壓后得到j(luò)TessBoxEditorFX文件夾冕碟,由于這是由Java開發(fā)的,所以我們應(yīng)確保運(yùn)行jTessBoxEditor前先安裝JRE(Java Runtime Environment匆浙,Java運(yùn)行環(huán)境)安寺,由于JRE的安裝教程很多,這里就不做過多介紹了。
獲取樣本文件
由于身份證的字體是比較固定的,所以不需要做太多樣本進(jìn)行訓(xùn)練批销。像身份證號(hào)的數(shù)字和X是黑體舔示,性別、生日绽淘、有效期等字體是方正黑體簡體,所以我們只需要在word上輸入身份證上對(duì)應(yīng)字體的文字,然后用切圖工具把文字切出來凳枝,這樣樣本就可以獲取到了。下面是我獲取身份證文字的樣本圖片:
黑體數(shù)字:
黑體X:
華文黑體簡體漢字:
華文黑體簡體數(shù)字:
【注意】:樣本圖像文件格式必須為tif/tiff格式
Merge樣本文件
進(jìn)入jTessBoxEditor目錄跋核,在終端執(zhí)行java -Xms128m -Xmx1024m -jar jTessBoxEditorFX.jar命令岖瑰,會(huì)出現(xiàn)如下操作界面:
Tools->Merge TIFF,將樣本文件全部選上砂代,并將合并文件保存為font.tif蹋订,進(jìn)入該文件所在目錄,執(zhí)行指令
tesseract font.tif font batch.nochop makebox
生成文件名為font.box文件
字符矯正
生成font.box文件后刻伊,可以使用jTessBoxEditor對(duì)字符進(jìn)行矯正了露戒。選中Box Editor->Open,打開font.tif捶箱,會(huì)出現(xiàn)如下操作界面:
剛開始對(duì)數(shù)字和字母識(shí)別得比較準(zhǔn)確玫锋,但是識(shí)別的中文應(yīng)該是亂碼,選中文字在Character一欄輸入正確的文字進(jìn)行字符矯正讼呢、保存撩鹿。這樣就可以得到一個(gè)較為準(zhǔn)確的字符集(PS:如果要識(shí)別其他字體和文字得獲取大量樣本進(jìn)行訓(xùn)練矯正,由于身份證字體比較固定悦屏,所以只需識(shí)別固定文字即可节沦,創(chuàng)建出來的字庫在165KB左右)。
執(zhí)行批處理文件
生成字符特征
執(zhí)行指令
tesseract font.tif font nobatch box.train
生成.tr文件础爬,它包含了訓(xùn)練頁的每個(gè)字符的特征甫贯。
計(jì)算字符集
執(zhí)行指令
unicharset_extractor font.box
生成unicharset數(shù)據(jù)文件,它包含了tesseract需要知道可能要輸出的字符集看蚜。
字體屬性
執(zhí)行指令
echo 'font 0 0 0 0 0' > font_properties
可以將提供字體形式信息重定向到font_properties文本文件中叫搁,通過-F filename選項(xiàng)指定來進(jìn)行mftraining.
當(dāng)運(yùn)行mftraining時(shí),每個(gè).tr文件名必須有相關(guān)entry在font_properties文件中,否則將中止mftraining渴逻。
聚合
當(dāng)所有訓(xùn)練頁的字符特征被抽取出來時(shí)疾党,我們需要將它們聚集起來創(chuàng)建prototype文件。這些字符形狀特性可以通過使用shapeclustering惨奕、mftraining和cntraining程序進(jìn)行聚焦雪位。
// shapeclustering通過形狀聚集創(chuàng)建了主形狀表,并將它寫到一個(gè)文件中: shapetable.
shapeclustering -F font_properties -U unicharset font.tr
// mftraining將輸出兩個(gè)其它的數(shù)據(jù)文件: inttemp(形狀原型)和pffmtable(每個(gè)字符所希望的特征)
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)該如下圖所示:
生成的font.traineddata就是我們所需要的字庫港粱。
Tesseract for iOS
通過前面的介紹我們知道了Tesseract框架是根據(jù)我們提供的字庫對(duì)圖片上的文字進(jìn)行識(shí)別螃成,然后轉(zhuǎn)化為文本的形式輸出,并且我們也創(chuàng)建了自己的字庫啥容。但往往一張圖片上不一定只有一個(gè)文本塊锈颗,有可能有多個(gè)文本塊顷霹。例如身份證它就有身份證號(hào)咪惠、性別、民族淋淀、出生年月遥昧、姓名等多個(gè)區(qū)塊,那么如何把它們截取為一個(gè)個(gè)區(qū)塊朵纷,然后將每個(gè)區(qū)塊分別提供給Tesseract框架進(jìn)行識(shí)別呢炭臭?
目前找到了兩種方法對(duì)這些區(qū)塊進(jìn)行分離:
圖像處理技術(shù)
圖像處理技術(shù)是使用OpenCV庫對(duì)圖像進(jìn)行灰度化,二值化袍辞,腐蝕鞋仍,輪廓檢測(cè)等。
1.灰度化處理:圖片灰度化處理就是將指定圖片每個(gè)像素點(diǎn)的RGB三個(gè)分量通過一定的算法計(jì)算出該像素點(diǎn)的灰度值搅吁,使圖像只含亮度而不含色彩信息威创。
2.二值化:二值化處理就是將經(jīng)過灰度化處理的圖片轉(zhuǎn)換為只包含黑色和白色兩種顏色的圖像,他們之間沒有其他灰度的變化谎懦。在二值圖中用255便是白色肚豺,0表示黑色。
3.腐蝕:圖片的腐蝕就是將得到的二值圖中的黑色塊進(jìn)行放大界拦。即連接圖片中相鄰黑色像素點(diǎn)的元素吸申。通過腐蝕可以把身份證上的身份證號(hào)碼連接在一起形成一個(gè)矩形區(qū)域。
4.輪廓檢測(cè):圖片經(jīng)過腐蝕操作后相鄰點(diǎn)會(huì)連接在一起形成一個(gè)大的區(qū)域,這個(gè)時(shí)候通過輪廊檢測(cè)就可以把每個(gè)大的區(qū)域找出來截碴,這樣就可以定位到身份證上面號(hào)碼的區(qū)域梳侨。
代碼處理:
// 將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);
// 腐蝕,填充(腐蝕是讓黑色點(diǎn)變大)
cv::Mat erodeElement = getStructuringElement(cv::MORPH_RECT, cv::Size(26,26));
cv::erode(resultImage, resultImage, erodeElement);
// 輪廊檢測(cè)
std::vector<std::vector<cv::Point>> contours; // 定義一個(gè)容器來存儲(chǔ)所有檢測(cè)到的輪廊
cv::findContours(resultImage, contours, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, cvPoint(0, 0));
// 取出身份證號(hào)碼區(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)點(diǎn)是不需要用戶定點(diǎn)描框隐岛,只要用戶將身份證放到攝像頭內(nèi)便可自動(dòng)處理猫妙,代碼也相對(duì)簡單,能夠很大程度上提升用戶體驗(yàn)聚凹。缺點(diǎn)是引入OpenCV庫會(huì)增加了4M包的大小割坠。
坐標(biāo)計(jì)算處理
坐標(biāo)計(jì)算處理原理是在手機(jī)上給定一個(gè)身份證區(qū)域讓用戶將身份證放入該區(qū)域內(nèi),通過計(jì)算坐標(biāo)獲取指定的區(qū)域(如我Demo的效果是這樣):
這種方式的優(yōu)點(diǎn)是不需要引入OpenCV庫而導(dǎo)致又一程度上增加包的大小妒牙。缺點(diǎn)也是顯而易見的彼哼,那就是需要用戶對(duì)準(zhǔn)指定區(qū)域進(jìn)行定點(diǎn)描框,要不坐標(biāo)計(jì)算后獲取到的錯(cuò)誤區(qū)域拋給Tesseract框架是識(shí)別不出的湘今,用戶體驗(yàn)不是很好敢朱。
綜上所述,在實(shí)現(xiàn)方式的選擇上可以根據(jù)自己的項(xiàng)目具體情況具體分析摩瞎,由于我司項(xiàng)目是一個(gè)金融APP拴签,引入TesseractOCRiOS框架已經(jīng)增加5.1M包大小,若再引入OpenCV旗们,那么為了一個(gè)OCR優(yōu)化功能增加9M的包大小這是不能夠接受的蚓哩。若是類似于美圖秀秀那類型的APP,使用圖像處理的地方比較多則比較適合引入OpenCV庫上渴。所以下面將著重對(duì)坐標(biāo)計(jì)算處理這種方式進(jìn)行介紹岸梨。
實(shí)現(xiàn)步驟
導(dǎo)入Tesseract的iOS庫
這里通過CocoaPods的方式引入第三方庫:
pod 'TesseractOCRiOS'
導(dǎo)入字庫
將創(chuàng)建好的字庫放入tessdata文件夾并拖進(jìn)工程,這里要特別注意稠氮,因?yàn)門esseractOCRiOS這個(gè)庫尋找字庫時(shí)不支持路徑傳遞曹阔,并且找尋的路徑是主Bundle路徑下的tessdata文件夾里面的字庫。所以一定要使用Create folder references這個(gè)選項(xiàng)在主Bundle下創(chuàng)建tessdata文件夾才能夠獲取到里面的字庫隔披。
創(chuàng)建OcrDetectView
實(shí)時(shí)顯示攝像頭成像赃份,并提供截取圖片API供外部調(diào)用,它是照片數(shù)據(jù)源的來源奢米。
布局身份證區(qū)域框
這里通過重力感應(yīng)支持橫豎屏切換抓韩。這里要特別注意當(dāng)身份證區(qū)域框進(jìn)行橫豎屏切換時(shí)截取的文字區(qū)域也同步需要進(jìn)行坐標(biāo)轉(zhuǎn)換,后面再詳細(xì)介紹恃慧。
截取圖片
每隔一定的時(shí)間(Demo里為1秒)定時(shí)截取圖片园蝠,由于得到的圖片是整個(gè)手機(jī)屏幕的圖片,所以這里需要根據(jù)坐標(biāo)及橫豎屏進(jìn)行圖片處理痢士。
坐標(biāo)計(jì)算
由于限定了身份證區(qū)域框讓用戶將身份證放入該區(qū)域內(nèi)進(jìn)行識(shí)別彪薛,所以這些坐標(biāo)是可以獲取到的茂装。
如獲取身份證區(qū)域:
// 獲取屏幕縮放比例(我是在6s機(jī)型上做的,當(dāng)時(shí)設(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;
// 圖片實(shí)際寬高
CGFloat width = screenWidth * scale;
CGFloat height = screenHeight * scale;
// image相對(duì)于屏幕的縮放比
float px = image.size.width / screenWidth;
float py = image.size.height / screenHeight;
// 根據(jù)橫豎屏計(jì)算x,y,w,h
float x, y, w, h;
if (isLandscape)
{
// 由于切換為橫屏實(shí)際上是以豎屏的身份證區(qū)域框的中心為基點(diǎn)旋轉(zhuǎn)90°
// 所以橫屏下的x實(shí)際上是身份證區(qū)域框高度的一半加上豎屏狀態(tài)下距離屏幕頂部的距離
// 再減掉橫屏狀態(tài)下的寬度的一半
x = height / 2.0 + idcardBoxTopOffset - width / 2.0;
// 同理橫屏狀態(tài)下的y實(shí)際上是屏幕寬度減身份證區(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;
}
獲取身份證號(hào)區(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;
}
識(shí)別
通過坐標(biāo)計(jì)算這個(gè)步驟可以獲得可供識(shí)別的文本塊圖片樣本,在這里我獲取了5個(gè)區(qū)塊易遣,分別是性別彼妻、出生日期、身份證號(hào)豆茫、有效日期侨歉、人像。
同之前在Mac上識(shí)別步驟一樣揩魂,初始化字庫->設(shè)置psm等參數(shù)->傳入待識(shí)別的圖片->得到識(shí)別后的文本->校驗(yàn)文本幽邓。
識(shí)別身份證號(hào)代碼:
- (void)recognizeImageWithTesseract:(UIImage *)image mode:(DetectMode)mode completionBlock:(void(^)(BOOL isRecognized, NSString *recognizedText))completionBlock
{
// 創(chuàng)建`G8RecognitionOperation`對(duì)象異步執(zhí)行OCR識(shí)別并初始化字庫
G8RecognitionOperation *operation = [[G8RecognitionOperation alloc] initWithLanguage:@"font"];
// 設(shè)置psm參數(shù)
operation.tesseract.pageSegmentationMode = G8PageSegmentationModeSingleBlock;
// 設(shè)置最大識(shí)別時(shí)間
operation.tesseract.maximumRecognitionTime = 1.0;
// 設(shè)置識(shí)別圖片
operation.tesseract.image = image;
__weak JKOcrService *wself = self;
operation.recognitionCompleteBlock = ^(G8Tesseract *tesseract) {
// 識(shí)別后的文本
NSString *recognizedText = tesseract.recognizedText;
__strong JKOcrService *sself = wself;
// 校驗(yàn)文本
if ([JKOcrDetectUtils accurateVerifyIDCardNumber:recognizedText])
{
// 識(shí)別成功回調(diào)
if (completionBlock) completionBlock(YES, recognizedText);
}
else
{
// 識(shí)別失敗回調(diào)
if (completionBlock) completionBlock(NO, @"");
}
};
// 添加隊(duì)列
[self.operationQueue addOperation:operation];
}
校驗(yàn)身份證號(hào)代碼:
+ (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];//測(cè)試出生日期的合法性
}
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];//測(cè)試出生日期的合法性
}
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];//測(cè)試出生日期的合法性
}
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];//測(cè)試出生日期的合法性
}
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)];// 判斷校驗(yàn)位
if ([M isEqualToString:[value substringWithRange:NSMakeRange(17,1)]])
{
return YES;// 檢測(cè)ID的校驗(yàn)位
}
else
{
return NO;
}
}
else
{
return NO;
}
default:
return NO;
}
}
性別、出生日期火脉、有效日期的識(shí)別步驟大同小異牵舵,這里就不一一列舉了。然后可以通過使用dispatch_group并發(fā)分別進(jìn)行識(shí)別倦挂,得到一個(gè)結(jié)果集后再統(tǒng)一進(jìn)行驗(yàn)證畸颅,如果都識(shí)別通過的話則表示這是一張高質(zhì)量的身份證圖片,就可以把這張身份證圖片上傳到服務(wù)器進(jìn)行識(shí)別了方援。
__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: 識(shí)別成功
}
});
}
else
{
// TODO: 識(shí)別失敗
}
據(jù)我統(tǒng)計(jì)没炒,每個(gè)樣本識(shí)別時(shí)間間隔為20ms左右,也就是說肯骇,只要樣本沒問題窥浪,逐個(gè)識(shí)別所用的時(shí)間也不到100ms祖很,相對(duì)于設(shè)定1秒的時(shí)間間隔來說是綽綽有余笛丙,但是使用異步也沒毛病。
總結(jié)
通過前面的介紹了解到了Tesseract是什么假颇,可以用于什么樣業(yè)務(wù)場(chǎng)景胚鸯,如何進(jìn)行樣本訓(xùn)練生成字庫。還介紹了如何在Mac OS X上使用Tesseract進(jìn)行圖片識(shí)別生成文本笨鸡,并在這基礎(chǔ)之上引入了基于iOS的Tesseract庫TesseractOCRiOS
姜钳,使其能夠?yàn)橐苿?dòng)端服務(wù)。
但是引入Tesseract這個(gè)庫打包出來的APP會(huì)增加5.1M包大小形耗,如果再加上系統(tǒng)字庫哥桥,包大小更是會(huì)顯著增加。雖然自己訓(xùn)練樣本生成字庫可以解決這一問題激涤,但是單單為了身份證流程優(yōu)化這一功能拟糕,這樣的結(jié)果往往還是難以接受的。
既然這樣,是否能將Tesseract的作用發(fā)揮到極致送滞,例如銀行卡自動(dòng)識(shí)別也使用它進(jìn)行流程優(yōu)化等等侠草。