身份證識(shí)別!聽起來好難,不會(huì)做。。。,又要實(shí)現(xiàn)這個(gè)功能,跟領(lǐng)導(dǎo)說使用收費(fèi)的吧,又不好意思!自己動(dòng)手實(shí)現(xiàn)才是王道。摸索+百度,暫時(shí)實(shí)現(xiàn)了一個(gè)身份證號(hào)碼的識(shí)別。記錄一下。
首先準(zhǔn)備工作:都知道要使用開源庫OpenCV,可以去官網(wǎng)下載或者git上都有,OpenCV很友好,已經(jīng)為我們Android編譯好了動(dòng)態(tài)庫和include供我們使用,我自己的環(huán)境:AndroidStudio3.6+NDK_r15,還需要文字識(shí)別庫OCR,不想去下載可以使用我分享的網(wǎng)盤,里面包含了需要的所有文件:鏈接: https://pan.baidu.com/s/1LdF2Zgl4FChNiMCLlNWjSg 提取碼: z73h

接下來開始具體的實(shí)現(xiàn)過程:
一、先自己實(shí)現(xiàn)一個(gè)ocr的語言訓(xùn)練庫
1、將下載下來的tesseract-ocr-w64-setup-v5.0.0.20190623.exe安轉(zhuǎn)到電腦上,很簡單一直next就行了,安裝好只有需要配置兩個(gè)環(huán)境變量,給Path里面添加tesseract的安裝路徑,如下:

這是一個(gè)檢測工具,它已經(jīng)自帶的給我們生成了一個(gè)eng.traineddata訓(xùn)練數(shù)據(jù),一會(huì)可以檢測識(shí)別一張圖片,就是網(wǎng)盤里面的圖片id7.tif .

2、還需要新建一個(gè)TESSDATA_PREFIX路徑,就是訓(xùn)練data的路徑,例如我的是這樣的:

配置成功后可以檢測一下,打開cmd命令輸入:tesseract -v

接著我們來檢測識(shí)別一下圖片:輸入命令:tesseract F:\study\Tesseract-OCR\tessdata\id7.tif 22 回車,我們得到一個(gè)22.txt的文件 就是識(shí)別的結(jié)果文件 如下圖

3ok了,加測工具配置好了,我們需要訓(xùn)練工具,網(wǎng)盤下載下來的jTessBoxEditor.zip我們解壓后有一個(gè).jar文件

通過這個(gè)工具我們合并圖片以及識(shí)別校驗(yàn)圖片內(nèi)容,在cmd命令中輸入java -jar jTessBoxEditor.jar 就啟動(dòng)了這個(gè)工具

4、點(diǎn)擊 Tools->Merge TIFF 找到訓(xùn)練樣本(樣本自己找哦,這里提供一張),全選中,打開,出現(xiàn)界面

這是要保存合并的圖片,名字命名一定要規(guī)范,命名的格式如下(很重要):[語言].[字體].exp[num].tif

強(qiáng)調(diào)一下,使用jTessBoxEditor工具合并圖片的時(shí)候,我遇到了一個(gè)問題,20張圖片合并的時(shí)候報(bào)錯(cuò)了,提示“couldn't seek!” 莫慌! 使用網(wǎng)盤提供的TiffToy工具去合成,是一樣的~~~ 工具打開就會(huì)用。
5、將zh.song.exp1.tif 文件復(fù)制到Tesseract-OCR安裝目錄。

在Tesseract-OCR安裝目錄下執(zhí)行命令:
tesseract zh.song.exp1.tif zh.song.exp1 batch.nochop makebox
生成box文件

6、將生成的這兩個(gè)文件放入同一個(gè)目錄下,可以自己新建一個(gè)目錄

7、運(yùn)行jTessBoxEditor工具,點(diǎn)擊Box Editor->open,打開合并的tif文件。

8、開始校驗(yàn)

將不對數(shù)據(jù)改回來就ok了。
9、定義字體特征文件。創(chuàng)建一個(gè)名稱為font_properties的字體特征文件。內(nèi)容如下:

解釋一下:song就是我們合并圖片時(shí)起的名字,后面5個(gè)0要有空格,分別代表的意思是 <italic> 、<bold> 、<fixed> 、<serif>、 <fraktur>的取值為1或0,表示字體是否具有這些屬性。
10、生成語言文件。在樣本圖片所在目錄下創(chuàng)建一個(gè)批處理文件,輸入如下內(nèi)容:
rem 執(zhí)行改批處理前先要目錄下創(chuàng)建font_properties文件
echo Run Tesseract for Training..
tesseract zh.song.exp1.tif zh.song.exp1 nobatch box.train
echo Compute the Character Set..
unicharset_extractor zh.song.exp1.box
mftraining -F font_properties -U unicharset -O zh.unicharset zh.song.exp1.tr
echo Clustering..
cntraining zh.song.exp1.tr
echo Rename Files..
ren normproto zh.normproto
ren inttemp zh.inttemp
ren pffmtable zh.pffmtable
ren shapetable zh.shapetable
echo Create Tessdata..
combine_tessdata zh.

執(zhí)行完之后就得到了一個(gè)zh.traineddata的文件了,這就是庫文件了、、、
二、第一步完成了,接下來就是我們熟悉的Android開發(fā)了。
1、新建一個(gè)C++項(xiàng)目,不用多說,都懂得。直接把訓(xùn)練的文件放入main中

2、將網(wǎng)盤下載下來的opencv的zip解壓,將include文件夾拷貝到項(xiàng)目的cpp文件夾下,我們需要的so拷貝到對應(yīng)的jniLibs

3、編輯CMakeLists.txt文件
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.10.2)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
shensuCid
SHARED
CidOcr.cpp)
find_library( # Sets the name of the path variable.
log-lib
log)
# -L 指定庫的查找路徑
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/../jniLibs/${CMAKE_ANDROID_ARCH_ABI}")
#設(shè)置頭文件查找路徑
include_directories(include)
target_link_libraries( # Specifies the target library.
shensuCid
opencv_java4
# jnigraphics
${log-lib})
一切準(zhǔn)備就緒了,編寫我們的C++文件,就是將圖片數(shù)據(jù)傳給jni,經(jīng)過一系列處理得到一個(gè)bitmap?;舅悸肪褪菍⑾鄼C(jī)的yuv格式的data數(shù)據(jù)傳給opencv。
主函數(shù)內(nèi)容如下:
#define DEFAULT_CARD_WIDTH 640
#define DEFAULT_CARD_HEIGHT 400
#define FIX_IDCARD_SIZE Size(DEFAULT_CARD_WIDTH,DEFAULT_CARD_HEIGHT)
using namespace cv;
using namespace std;
extern "C" JNIEXPORT void JNICALL Java_org_opencv_android_Utils_nBitmapToMat2
(JNIEnv *env, jclass, jobject bitmap, jlong m_addr, jboolean needUnPremultiplyAlpha);
extern "C" JNIEXPORT void JNICALL Java_org_opencv_android_Utils_nMatToBitmap
(JNIEnv *env, jclass, jlong m_addr, jobject bitmap);
jobject createBitmap(JNIEnv *env, Mat srcData, jobject config) {
int imgWidth = srcData.cols;
int imgHeight = srcData.rows;
int numPix = imgWidth * imgHeight;
jclass bmpCls = env->FindClass("android/graphics/Bitmap");
jmethodID createBitmapMid = env->GetStaticMethodID(bmpCls, "createBitmap",
"(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
jobject jBmpObj = env->CallStaticObjectMethod(bmpCls, createBitmapMid, imgWidth, imgHeight,
config);
Java_org_opencv_android_Utils_nMatToBitmap(env, 0, (jlong) &srcData, jBmpObj);
return jBmpObj;
}
extern "C"
JNIEXPORT jobject JNICALL
Java_com_XXX_getIdBitmapWithYUVData(JNIEnv *env, jclass type,
jbyteArray yuvData_,
jint width, jint height,
jintArray picRect_,jobject config) {
jbyte *yuvData = env->GetByteArrayElements(yuvData_, NULL);
jint *picRect = env->GetIntArrayElements(picRect_, NULL);
// TODO 先將yuvdata轉(zhuǎn)化為opencv的mat格式數(shù)據(jù)
Mat src_img;
Mat image(height + height/2,width,CV_8UC1,(unsigned char *)yuvData);
Mat mBgr;
cvtColor(image, mBgr, CV_YUV2BGR_NV21);
if(mBgr.empty()){
return NULL;
}
//picRect 就是手機(jī)屏幕繪制的方框的區(qū)域
if(picRect == NULL){
return NULL;
}
int left = picRect[0];
int top = picRect[1];
int right = picRect[2];
int bottom = picRect[3];
Rect finalRect(left,top,right - left,bottom);
src_img = mBgr(finalRect);
if(src_img.empty()){
return NULL;
}
Mat dst_img;
Mat dst;
//無損壓縮//640*400
resize(src_img, src_img,FIX_IDCARD_SIZE);
//灰度化
cvtColor(src_img, dst, COLOR_BGR2GRAY);
//二值化
threshold(dst, dst, 115, 255, CV_THRESH_BINARY);
//膨脹,發(fā)酵,可以使黑色區(qū)域連接到一起
Mat erodeElement = getStructuringElement(MORPH_RECT, Size(20, 10));
erode(dst, dst, erodeElement);
//輪廓檢測
vector< vector<Point> > contoursList;
vector<Rect> rects;
findContours(dst, contoursList, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
for (int i = 0; i < contoursList.size(); i++){
Rect rect = boundingRect(contoursList.at(i));
//rectangle(dst, rect, Scalar(0, 0, 255)); // 在dst 圖片上顯示 rect 矩形
if (rect.width > rect.height * 9) {
rects.push_back(rect);
rectangle(dst, rect, Scalar(0,255,255));
dst_img = src_img(rect);
}
}
if (rects.size() == 1) {
Rect rect = rects.at(0);
dst_img = src_img(rect);
}else {
int lowPoint = 0;
Rect finalContourRect;
for (int i = 0; i < rects.size(); i++){
Rect rect = rects.at(i);
Point point = rect.tl();
if (rect.tl().y > lowPoint) {
lowPoint = point.y;
finalContourRect = rect;
}
}
rectangle(dst, finalContourRect, Scalar(255, 255, 0));
dst_img = src_img(finalContourRect);
}
if(dst_img.empty()){
return NULL;
}
jobject bitmap = createBitmap(env,dst_img,config);
src_img.release();
dst_img.release();
dst.release();
env->ReleaseByteArrayElements(yuvData_, yuvData, 0);
env->ReleaseIntArrayElements(picRect_, picRect, 0);
return bitmap;
}
在java層:項(xiàng)目進(jìn)入后需要有兩件事情:將我們訓(xùn)練的文件拷貝到手機(jī)本地并且初始化TessBaseAPI:
private class TessAsyncTask extends AsyncTask<Void, Void, Boolean> {
private String language = "zh";
@Override
protected Boolean doInBackground(Void... voids) {
String tessPath = IdCardCaptureActivity.this.getApplicationContext().getCacheDir().getAbsolutePath() + "/tess/";
baseApi = new TessBaseAPI();
try {
InputStream is = null;
is = getAssets().open(language + ".traineddata");
File file = new File(tessPath + "tessdata/" + language + ".traineddata");
if (!file.exists()) {
file.getParentFile().mkdirs();
FileOutputStream fos = new FileOutputStream(file);
byte[] buffer = new byte[2048];
int len;
while ((len = is.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
fos.close();
}
is.close();
boolean result = baseApi.init(tessPath, language);
return result;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(Boolean result) {
if (result) {
Log.e("init ocr:","init tess success!");
} else {
AlertDialog.Builder builder = new AlertDialog.Builder(IdCardCaptureActivity.this);
builder.setTitle("初始化身份證識(shí)別庫失敗!\n");
builder.setMessage("找不到庫文件!");
builder.setCancelable(true);
builder.create().show();
}
}
}
項(xiàng)目需要依賴tess庫
implementation 'com.rmtheis:tess-two:9.1.0'
將得到的bitmap傳給TessBaseAPI
tessBaseAPI.setImage(resultBitmap);
String cid = tessBaseAPI.getUTF8Text();