Android智能識別 - 銀行卡區(qū)域裁剪

在真正開始看這篇文章之前,希望我們能先去了解一下這篇文章《NDK開發(fā)前奏 - 實(shí)現(xiàn)支付寶人臉識別功能》,此篇文章閱讀起來可能會有些許難度,因此我們只要能看懂 c/c++ 語法,能跟上我分析問題的思路就可以了。后面我們會講一些算法,會去介紹 opencv 的常用函數(shù)。當(dāng)然我們學(xué)習(xí) Ndk 主要還是為我們的 Android 來服務(wù)的,以便我們能讀懂 android 底層源碼,能做一些 Ndk 項(xiàng)目開發(fā)。

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

像身份證、銀行卡和人臉識別這些,一般網(wǎng)上都有技術(shù)支持的(部分需要付費(fèi)),我們能不自己折騰的盡量不要自己折騰,但了解些原理還是有必要的。接下來我們來分析一下,且看我下面這兩張銀行卡,卡是真不少就是沒錢。

農(nóng)業(yè)銀行
長沙銀行

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



有幾個(gè)特征,最外層是銀行卡區(qū)域,左上角一般是 logo 和銀行名稱標(biāo)識,右下角是銀聯(lián)區(qū)域,中間是我們要識別的卡號區(qū)域。有了這幾個(gè)特征我們就有了思路了:

  1. 篩選過濾截取銀行卡區(qū)域;
  2. 根據(jù)標(biāo)識篩選截取銀行卡號區(qū)域;
  3. 二值分析與特征提取;
  4. 數(shù)字分割識別顯示。
1.篩選過濾截取銀行卡區(qū)域

opencv 操作的是 mat 類型,我們拍照獲取的是 Bitmap 類型,后面我們會獲取每一幀進(jìn)行處理,原理和套路都是一致的,由易到難,先來簡單點(diǎn)的。所以第一個(gè)要寫的方法就是 Bitmap 和 Mat 相互轉(zhuǎn)換:

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);
    }
    // 其他要自己去轉(zhuǎn)
    
    // 解鎖畫布
    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);
        }
    }
    // 其他要自己去轉(zhuǎn)

    // 解鎖 Bitmap 畫布
    AndroidBitmap_unlockPixels(env,bitmap);
}

Bitmap 轉(zhuǎn) Mat 之后我們就可以用 opencv 去操作了,我們第一步是要找到銀行卡區(qū)域,然后進(jìn)行截取保存。所以我們首先需要對輪廓進(jìn)行梯度增強(qiáng),然后對圖片進(jìn)行二值化輪廓查找,篩選出最符合的區(qū)域。

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

    // 梯度增強(qiáng) , 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);

    // 二值化,進(jìn)行輪廓查找
    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]);
        // 是不是符合規(guī)則
        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. 根據(jù)標(biāo)識篩選截取銀行卡號區(qū)域

截取到銀行卡區(qū)域后,我們就可以根據(jù)我們的銀行卡標(biāo)識,去篩選截取我們的卡號區(qū)域。

JNIEXPORT jint JNICALL
Java_com_darren_ndk_day05_FaceDetection_bankOcr(JNIEnv *env, jobject instance,
                                                              jobject bitmap) {
    // Bitmap 轉(zhuǎn)成 opencv 能操作的 C++ 對象 Mat , Mat 是一個(gè)矩陣
    Mat mat;
    MatBitmapUtil::bitmap2mat(env, mat, bitmap);
    // 1.篩選過濾截取銀行卡區(qū)域
    Rect card_rect = co1::find_card_area(mat);
    Mat card_mat(mat,card_rect);
    //  2. 根據(jù)標(biāo)識篩選截取銀行卡號區(qū)域;
    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

這其實(shí)才剛剛開始,假設(shè)光線不強(qiáng)呢?很多銀行卡沒有銀聯(lián)的標(biāo)識,又或者某些銀行卡的干擾太多。后面的文章我們將陸續(xù)去完善,直到可以獲取相機(jī)的每一幀進(jìn)行處理。

視頻鏈接:https://pan.baidu.com/s/10IvWru9d6yhFkNNY7z18Vw
視頻密碼:0n67

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

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