日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

android opencv 银行卡识别,NDK 开发之使用 OpenCV 实现银行卡号识别

發(fā)布時間:2024/9/27 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 android opencv 银行卡识别,NDK 开发之使用 OpenCV 实现银行卡号识别 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

前言

在日常的開發(fā)中,我們有時會遇到添加銀行卡的需求,這時候,產品可能會讓你仿一下支付寶之類的相機掃描識別銀行卡號。很多時候,做這樣的需求會去找找穩(wěn)定的第三方,本文通過 OpenCV 結合識別的需求帶你分析如何實現(xiàn)銀行卡號的識別。由于作者技術有限,本文僅從如何做到識別的思路上介紹,文中例子不適用于實際開發(fā),也不是所有銀行卡都能識別,但希望讀者可以在實現(xiàn)的思路上給予一些啟發(fā),以及更深入熟悉 OpenCV 的組合使用。

1. 銀行卡識別思路分析

1.1 銀行卡一般具有的特征

銀行卡一般會有 銀行、卡號、銀聯(lián)標識等等,主要的是卡號區(qū)域,大多數銀行卡卡號都是在下方顯示的。那么,在檢索一張圖片的時候,要首先找到卡在哪里,卡一般是長方形,所以我們在背景色不非常接近下可以找到銀行卡的輪廓。

1.2 總體實現(xiàn)思路和步驟

在圖片中找到銀行卡區(qū)域 --> 在銀行卡區(qū)域找到卡號區(qū)域 --> 在卡號區(qū)域中找到卡號每個數字的集合 --> 識別數字

2. 在圖片中找到銀行卡區(qū)域

2.1 實現(xiàn)思路和步驟

高斯模糊降噪處理

邊緣梯度增強

取增強絕對值

合并 Mat

轉灰度

二值化

從二值圖像中檢索輪廓

從集合中找合適的輪廓

2.2 具體實現(xiàn)

/**

* 在圖片中找到銀行卡區(qū)域

* @param mat 圖片

* @param rect 銀行卡區(qū)域

* @return 是否成功

*/

int ocr::find_card_area(const Mat &mat, Rect &card_area) {

// 1. 高斯模糊降噪處理

Mat blurMat;

GaussianBlur(mat, blurMat, Size(5, 5), BORDER_DEFAULT, BORDER_DEFAULT);

// 2. 邊緣梯度增強

// Scharr( InputArray src, OutputArray dst, int ddepth, int dx, int dy, double scale = 1, double delta = 0, int borderType = BORDER_DEFAULT )

// ddepth:輸出圖像的深度

// dx:x方向上的差分階數

// dy:y方向上的差分階數

// 對 x 增強

Mat grade_x;

Scharr(blurMat, grade_x, CV_32F, 1, 0);

// 對 y 增強

Mat grade_y;

Scharr(blurMat, grade_y, CV_32F, 0, 1);

// 3. 取增強絕對值

Mat abs_grade_x;

convertScaleAbs(grade_x, abs_grade_x);

Mat abs_grade_y;

convertScaleAbs(grade_y, abs_grade_y);

// 4. 合并 Mat

Mat gradeMat;

addWeighted(abs_grade_x, 0.5, abs_grade_y, 0.5, 0, gradeMat);

//imwrite("/storage/emulated/0/ocr/find_card_area_gradeMat.jpg", gradeMat);

// 5. 轉灰度

Mat grayMat;

cvtColor(gradeMat, grayMat, COLOR_BGRA2GRAY);

//imwrite("/storage/emulated/0/ocr/find_card_area_grayMat.jpg", grayMat);

// 6. 二值化

// threshold( InputArray src, OutputArray dst, double thresh, double maxval, int type )

// thresh:設定的閾值

// maxval;當灰度值大于(或小于)閾值時將該灰度值賦成的值

// type:當前二值化的方式 THRESH_BINARY 大于閾值的部分被置為255,小于部分被置為0

// 此處其實是將 白變成黑,黑變成白

Mat binaryMat;

threshold(grayMat, binaryMat, 40, 255, THRESH_BINARY);

//imwrite("/storage/emulated/0/ocr/find_card_area_binaryMat.jpg", binaryMat);

// 7. 從二值圖像中檢索輪廓

// findContours( InputOutputArray image, OutputArrayOfArrays contours, int mode, int method, Point offset = Point())

// contours:雙重向量,向量內每個元素保存了一組由連續(xù)的 Point 點構成的點的集合的向量,每一組Point點集就是一個輪廓

// mode:定義輪廓的檢索模式,其中 RETR_EXTERNAL 表示只檢測最外圍輪廓,包含在外圍輪廓內的內圍輪廓被忽略

// method:定義輪廓的近似方法,其中 CHAIN_APPROX_SIMPLE 表示 僅保存輪廓的拐點信息,把所有輪廓拐點處的點保存入contours 向量內,拐點與拐點之間直線段上的信息點不予保留

vector > contours;

findContours(binaryMat, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

LOGE("find_card_area contours.size():%d", contours.size());

// 8. 從集合中找合適的輪廓

for (int i = 0; i < contours.size(); i++) {

// boundingRect 函數計算并返回指定點集或非零像素的灰度圖像

Rect rect = boundingRect(contours[i]);

//LOGE("從集合中找合適的輪廓 i=%d rect.width=%d mat.cols=%d", i, rect.width, mat.cols);

if (rect.width > mat.cols / 2 /*&& rect.width != mat.cols*/ && rect.height > mat.rows / 2) {

// 銀行卡區(qū)域的寬高必須大于圖片的一半

LOGE("find_card_area 哈哈哈,找到啦");

card_area = rect;

break;

}

}

//LOGE("card_area h:%d", card_area.height);

// 9. 釋放資源

blurMat.release();

grade_x.release();

grade_y.release();

abs_grade_x.release();

abs_grade_y.release();

gradeMat.release();

binaryMat.release();

// 沒有找到合適的輪廓 返回失敗錯誤碼

if (card_area.empty()) {

LOGE("find_card_area 啊啊啊,找不到");

return -1;

}

return 0;

}

復制代碼

3. 在銀行卡區(qū)域找到卡號區(qū)域

3.1 實現(xiàn)思路和步驟

此處僅做一個粗略的位置計算,實際開發(fā)時,一般和相機搭配掃描一個框的內容,也可以按框的比例來計算。

此方法有些銀行卡會有誤差,有誤差時,實際開發(fā)應該有輸入框給用戶手動補充識別失敗的數字。

卡號區(qū)域 起點 (x, y) = (銀行卡區(qū)域寬度的 1/12, 銀行卡區(qū)域高度的 1/2)

卡號區(qū)域的寬高 width = 銀行卡區(qū)域寬度的 5/6, height = 銀行卡區(qū)域高度的 1/4

3.2 具體實現(xiàn)

/**

* 在銀行卡區(qū)域找到卡號區(qū)域

* @param mat 圖片

* @param card_area 存放卡號區(qū)域

* @return 是否成功

*/

int ocr::find_card_number_area(const Mat &mat, Rect &card_area) {

// 此處僅做一個粗略的位置計算,實際開發(fā)時,一般和相機搭配掃描一個框的內容,也可以按框的比例來計算。

// 此方法有些銀行卡會有誤差,有誤差時,實際開發(fā)應該有輸入框給用戶手動補充識別失敗的數字。

// 卡號區(qū)域 起點 (x, y) = (銀行卡區(qū)域寬度的 1/12, 銀行卡區(qū)域高度的 1/2)

// 卡號區(qū)域的寬高 width = 銀行卡區(qū)域寬度的 5/6, height = 銀行卡區(qū)域高度的 1/4

card_area.x = mat.cols / 12;

card_area.y = mat.rows / 2;

card_area.width = mat.cols * 5 / 6;

card_area.height = mat.rows / 4;

return 0;

}

復制代碼

4. 在卡號區(qū)域中找到卡號每個數字的集合

4.1 實現(xiàn)思路和步驟

轉灰度圖

二值化

降噪過濾

取反 黑變白,白變黑

從二值圖像中檢索輪廓,黑色的背景,白色的數字,可以檢測出噪點

將高度小于我們設定的最小值的輪廓區(qū)域過濾掉

過濾掉再合起來再取反恢復

過濾后再次檢索輪廓

將輪廓提取成矩形輪廓集合

對矩形輪廓集合進行排序

裁剪出單個數字,保存數字

4.2 具體實現(xiàn)

/**

* 在卡號區(qū)域中找到卡號每個數字的集合

* @param mat 圖片

* @param numbers 存放卡號每個數字的集合

* @return 是否成功

*/

int ocr::find_card_numbers(const Mat &mat, vector &numbers) {

// 1. 轉灰度圖

Mat grayMat;

cvtColor(mat, grayMat, COLOR_BGRA2GRAY);

// 2. 二值化

Mat binaryMat;

// THRESH_BINARY 大于閾值的部分被置為255,小于部分被置為 0

threshold(grayMat, binaryMat, 39, 255, THRESH_BINARY);

// 3. 降噪過濾

Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));

// 形態(tài)學操作

// morphologyEx( InputArray src, OutputArray dst, int op, InputArray kernel,

// Point anchor = Point(-1,-1), int iterations = 1,

// int borderType = BORDER_CONSTANT,

// const Scalar& borderValue = morphologyDefaultBorderValue() );

// op:操作的類型 MORPH_CLOSE 表示閉操作,先膨脹后腐蝕

// kernel:用于膨脹操作的結構元素 如果取值為 Mat, 那么默認使用一個 3 x 3 的方形結構元素,可以使用 getStructuringElement() 來創(chuàng)建結構元素

// anchor:參考點,其默認值為(-1,-1)說明位于kernel的中心位置

// borderType:邊緣類型

// borderValue:邊緣值

morphologyEx(binaryMat, binaryMat, MORPH_CLOSE, kernel);

// 4. 取反 黑變白,白變黑

Mat binaryNotMat = binaryMat.clone();

bitwise_not(binaryNotMat, binaryNotMat);

// 5. 從二值圖像中檢索輪廓,黑色的背景,白色的數字,可以檢測出噪點

vector > contours;

findContours(binaryNotMat, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

LOGE("find_card_numbers 1 contours.size():%d", contours.size());

// 6. 將高度小于我們設定的最小值的輪廓區(qū)域過濾掉

int mat_area = mat.rows * mat.cols;

int min_h = mat.rows / 4;

for (int i = 0; i < contours.size(); i++) {

// 所有輪廓進行檢測過濾

Rect rect = boundingRect(contours[i]);

// 面積太小的噪點過濾掉

int area = rect.width * rect.height;

if (area < mat_area / 200) {

// 小面積輪廓填充為 白色背景

drawContours(binaryMat, contours, i, Scalar(255), -1);

} else if (rect.height < min_h) {

// 小面積輪廓填充為 白色背景

drawContours(binaryMat, contours, i, Scalar(255), -1);

}

}

// 7. 過濾掉再合起來再取反恢復

binaryMat.copyTo(binaryNotMat);

bitwise_not(binaryNotMat, binaryNotMat);

// 8. 過濾后再次檢索輪廓

contours.clear();

findContours(binaryNotMat, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

// 9. 將輪廓提取成矩形輪廓集合,找到最小寬度輪廓

Rect rects[contours.size()];

// 白色的圖片,單顏色

Mat contoursMat(binaryMat.size(), CV_8UC1, Scalar(255));

// min_w 存放最小寬度輪廓

int min_w = mat.cols;

for (int i = 0; i < contours.size(); i++) {

rects[i] = boundingRect(contours[i]);

drawContours(contoursMat, contours, i, Scalar(0), 1);

min_w = min(rects[i].width, min_w);

}

// 10. 對矩形輪廓集合進行排序

// 冒泡排序

LOGE("contours.size()=%d", contours.size());

if (contours.size() <= 0) return -1;

for (int i = 0; i < contours.size() - 1; ++i) {

for (int j = 0; j < contours.size() - i - 1; ++j) {

if (rects[j].x > rects[j + 1].x) {

swap(rects[j], rects[j + 1]);

}

}

}

// 11. 裁剪出單個數字,保存數字

numbers.clear();

for (int i = 0; i < contours.size(); i++) {

// >= 最小寬度的兩倍,是粘連的數字

if (rects[i].width >= min_h * 2) {

// 處理粘連字符

Mat mat(contoursMat, rects[i]);

int cols_pos = find_split_number(mat);

// 左右兩個數字都存進去

Rect rect_left(0, 0, cols_pos - 1, mat.rows);

numbers.push_back(Mat(mat, rect_left));

Rect rect_right(cols_pos, 0, mat.cols, mat.rows);

numbers.push_back(Mat(mat, rect_right));

} else {

Mat number(contoursMat, rects[i]);

numbers.push_back(number);

// 保存數字圖片

LOGE("保存數字圖片:%d", i);

char name[50];

sprintf(name, "/storage/emulated/0/CardOCR/number_%d.jpg", i);

imwrite(name, number);

}

}

LOGE("numbers.size:%d", numbers.size());

// 釋放資源

grayMat.release();

binaryMat.release();

kernel.release();

binaryNotMat.release();

contoursMat.release();

return 0;

}

復制代碼

4.3 字符串進行粘連處理

/**

* 字符串進行粘連處理

* @param mat

* @return 粘連的那一列

*/

int ocr::find_split_number(const Mat &mat) {

// 對中心位置的左右 1/4 掃描,記錄最少的黑色像素點的這一列的位置,就當做字符串的粘連位置

int mx = mat.cols / 2;

int height = mat.rows;

// 圍繞中心左右掃描 1/4

int start_x = mx - mx / 2;

int end_x = mx + mx / 2;

// 字符的粘連位置

int cols_pos = mx;

// 獲取像素子

int c = 0;

// 最小的像素值

int min_h_p = mat.rows;

for (int col = start_x; col < end_x; ++col) {

int total = 0;

for (int row = 0; row < height; ++row) {

c = mat.at(row, col)[0];// 單通道

if (c == 0) {

total++;

}

}

if (total < min_h_p) {

min_h_p = total;

cols_pos = col;

}

}

return cols_pos;

}

復制代碼

5. 原圖和識別效果

原圖(圖片來源網絡,侵刪)

識別效果

后話

由于時間等原因,還需對識別出來的單個數字進行樣本對比,識別出最相似的數字。作者也沒有足夠的樣本,所以此步驟留到后續(xù)完善,但本文的初衷是為了分析識別思路和如何使用 OpenCV 進行實現(xiàn),因此,讀者也可結合我的基礎上進行完善,本文完整的源碼地址是: github.com/Vegen/BankC… ,歡迎 star 和 交流。

總結

以上是生活随笔為你收集整理的android opencv 银行卡识别,NDK 开发之使用 OpenCV 实现银行卡号识别的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。