1. 程式人生 > >Android智慧識別

Android智慧識別

在真正開始看這篇文章之前,希望我們能先去了解一下這篇文章《NDK開發前奏 - 實現支付寶人臉識別功能》,此篇文章閱讀起來可能會有些許難度,因此我們只要能看懂 c/c++ 語法,能跟上我分析問題的思路就可以了。後面我們會講一些演算法,會去介紹 opencv 的常用函式。當然我們學習 Ndk 主要還是為我們的 Android 來服務的,以便我們能讀懂 android 底層原始碼,能做一些 Ndk 專案開發。

今天下班回家特意試了一下支付寶,用支付寶掃描了一下我的銀行卡效果不是很佳,當然我相信大多數情況下是正常的。我這麼講只是想說明智慧識別本就用一定的侷限性,並不是 100% 非常正確。下面是支付寶的識別效果,上個圖 (少了一位,錯了兩位):

像身份證、銀行卡和人臉識別這些,一般網上都有技術支援的(部分需要付費),我們能不自己折騰的儘量不要自己折騰,但瞭解其原理,知道修改其原始碼還是有必要的。接下來我們來分析一下,且看我下面這兩張銀行卡,卡是真不少就是沒錢。

農業銀行

長沙銀行

我們想要去識別銀行卡其實思路也不難,只要找到他們的共性和特色就可以了。且看我下面畫的這張圖:

有幾個特徵,最外層是銀行卡區域,左上角一般是 logo 和銀行名稱標識,右下角是銀聯區域,中間是我們要識別的卡號區域。有了這幾個特徵我們就有了思路了:
1. 篩選過濾擷取銀行卡區域;
2. 根據標識篩選擷取銀行卡號區域;
3. 二值分析與特徵提取;
4. 數字分割識別顯示。

1.篩選過濾擷取銀行卡區域

opencv 操作的是 mat 型別,我們拍照獲取的是 Bitmap 型別,後面我們會獲取每一幀進行處理,原理和套路都是一致的,由易到難,先來簡單點的。所以第一個要寫的方法就是 Bitmap 和 Mat 相互轉換:

void MatBitmapUtil::bitmap2mat(JNIEnv *env, Mat &mat, jobject &bitmap) {
    // 鎖定畫布 獲取首地址畫素
    void* pixels = 0;
    AndroidBitmap_lockPixels(env,bitmap,&pixels);

    // 獲取資訊判斷格式
    AndroidBitmapInfo
*bitmapInfo = new AndroidBitmapInfo(); AndroidBitmap_getInfo(env,bitmap,bitmapInfo); mat.create(bitmapInfo->height,bitmapInfo->width,CV_8UC4); if(bitmapInfo->format == ANDROID_BITMAP_FORMAT_RGBA_8888){ // argb Mat temp(bitmapInfo->height,bitmapInfo->width,CV_8UC4,pixels); temp.copyTo(mat); } else if(bitmapInfo->format == ANDROID_BITMAP_FORMAT_RGB_565){ // rgb Mat temp(bitmapInfo->height,bitmapInfo->width,CV_8UC2,pixels); temp.copyTo(mat,COLOR_BGR5652BGRA); } // 其他要自己去轉 // 解鎖畫布 AndroidBitmap_unlockPixels(env,bitmap); delete(bitmapInfo); } void MatBitmapUtil::mat2Bitmap(JNIEnv *env, Mat &mat, jobject &bitmap) { // 1. 獲取 bitmap 資訊 AndroidBitmapInfo info; void* pixels; AndroidBitmap_getInfo(env,bitmap,&info); // 鎖定 Bitmap 畫布 AndroidBitmap_lockPixels(env,bitmap,&pixels); if(info.format == ANDROID_BITMAP_FORMAT_RGBA_8888){// C4 Mat temp(info.height,info.width,CV_8UC4,pixels); if(mat.type() == CV_8UC4){ mat.copyTo(temp); } else if(mat.type() == CV_8UC2){ cvtColor(mat,temp,COLOR_BGR5652BGRA); } else if(mat.type() == CV_8UC1){// 灰度 mat cvtColor(mat,temp,COLOR_GRAY2BGRA); } } else if(info.format == ANDROID_BITMAP_FORMAT_RGB_565){// C2 Mat temp(info.height,info.width,CV_8UC2,pixels); if(mat.type() == CV_8UC4){ cvtColor(mat,temp,COLOR_BGRA2BGR565); } else if(mat.type() == CV_8UC2){ mat.copyTo(temp); } else if(mat.type() == CV_8UC1){// 灰度 mat cvtColor(mat,temp,COLOR_GRAY2BGR565); } } // 其他要自己去轉 // 解鎖 Bitmap 畫布 AndroidBitmap_unlockPixels(env,bitmap); }

Bitmap 轉 Mat 之後我們就可以用 opencv 去操作了,我們第一步是要找到銀行卡區域,然後進行擷取儲存。所以我們首先需要對輪廓進行梯度增強,然後對圖片進行二值化輪廓查詢,篩選出最符合的區域。

Rect co1::find_card_area(const Mat &mat) {
    // 首先降噪
    Mat blur;
    GaussianBlur(mat, blur, Size(5, 5), BORDER_DEFAULT, BORDER_DEFAULT);

    // 梯度增強 , x 軸和 y 軸
    Mat grad_x, grad_y;
    Scharr(blur, grad_x, CV_32F, 1, 0);
    Scharr(blur, grad_y, CV_32F, 0, 1);
    Mat grad_abs_x, grad_abs_y;
    convertScaleAbs(grad_x, grad_abs_x);
    convertScaleAbs(grad_y, grad_abs_y);
    Mat grad;
    addWeighted(grad_abs_x, 0.5, grad_abs_y, 0.5, 0, grad);

    imwrite("/storage/emulated/0/ocr/grad_n.jpg",grad);

    // 二值化,進行輪廓查詢
    Mat gray;
    cvtColor(grad, gray, COLOR_BGRA2GRAY);
    Mat binary;
    threshold(gray, binary, 40, 255, THRESH_BINARY);

    imwrite("/storage/emulated/0/ocr/binary_n.jpg",binary);

    // 輪廓查詢
    vector<vector<Point> > contours;
    findContours(binary, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    Rect card_rect;
    for (int i = 0; i < contours.size(); ++i) {
        Rect rect = boundingRect(contours[i]);
        // 是不是符合規則
        if (rect.width > mat.cols / 2 && rect.width != mat.cols && rect.height > mat.rows / 2) {
            card_rect = rect;
            break;
        }
    }
    // release source
    blur.release();
    grad_x.release();
    grad_y.release();
    grad_abs_x.release();
    grad_abs_y.release();
    grad.release();
    gray.release();
    binary.release();
    // return card rect
    return card_rect;
}
2. 根據標識篩選擷取銀行卡號區域

擷取到銀行卡區域後,我們就可以根據我們的銀行卡標識,去篩選擷取我們的卡號區域。

JNIEXPORT jint JNICALL
Java_com_darren_ndk_day05_FaceDetection_bankOcr(JNIEnv *env, jobject instance,
                                                              jobject bitmap) {
    // Bitmap 轉成 opencv 能操作的 C++ 物件 Mat , Mat 是一個矩陣
    Mat mat;
    MatBitmapUtil::bitmap2mat(env, mat, bitmap);
    // 1.篩選過濾擷取銀行卡區域
    Rect card_rect = co1::find_card_area(mat);
    Mat card_mat(mat,card_rect);
    //  2. 根據標識篩選擷取銀行卡號區域;
    Rect card_number_rect = co1::find_card_number_area(mat);
    Mat card_number_mat(card_mat,card_number_mat);
    //  3. 寫入檔案看一看
    imwrite("/storage/emulated/0/ocr/card_number.jpg",card_mat);
    LOGE("處理完畢");
    return 0;
}

card_number

這其實才剛剛開始,假設光線不強呢?很多銀行卡沒有銀聯的標識,又或者某些銀行卡的干擾太多。後面的文章我們將陸續去完善,直到可以獲取相機的每一幀進行處理。