android下使用OpenCV實(shí)現(xiàn)身份證號(hào)碼的識(shí)別

身份證識(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

image.png

接下來開始具體的實(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的安裝路徑,如下:
image.png

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

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


image.png

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


image.png

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


image.png

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

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


image.png

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


image.png

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

例如我定義的名字:zh.song.exp1.tif 其中exp一定不能隨便寫,其他可以自己定義,然后點(diǎn)擊保存。我們就會(huì)看到有一個(gè)文件生成了
image.png

強(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安裝目錄。


image.png

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


image.png

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

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


image.png

8、開始校驗(yàn)
image.png

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

解釋一下: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.

image.png

執(zhí)行完之后就得到了一個(gè)zh.traineddata的文件了,這就是庫文件了、、、

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


image.png

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


image.png

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

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

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