之前寫過關於車牌識別的項目,銀行卡識別和車牌識別類似,也是先從待檢測圖片中找到銀行卡號碼的區域,再將號碼提取出來。
銀行卡識別的難點在於:
1.銀行卡種類繁多,不能使用一類固定的算法識別所有的銀行卡。
2.銀行卡固定和銀行卡號碼區域的固定比較復雜,因為主要應用於手機拍攝識別,所以像素光線都會產生很大影響。
針對手機拍攝有兩套方案:
1.拍攝時固定好銀行卡的位置,這樣我們就不需要先進行銀行卡區域的提取,直接提取銀行卡號碼區域。
2.對於普通拍攝照片,先進行銀行卡區域提取,再提取銀行卡號碼區域。
版本更新:
CardOCR1.0
——銀行卡輪廓和數字輪廓均采用canny邊緣檢測算法。
CardOCR1.1
——銀行卡輪廓定位利用sobel算法替代canny算法。獲取數字區域過程中采用增強對比度再利用特定閾值獲取數字區域替代了之前canny算法的邊緣檢測。識別效果較之前有比較大的提升,魯棒性也更好了。下一階段是關於銀行卡輪廓的偏斜扭正,和利用人工神經網絡識別數字。
這里我們先采用第二種方案,如果后續識別效果不理想我們再采用第一種。
大概流程:
1.提取銀行卡輪廓
2.偏斜扭正
3.獲取數字區域
4.數字識別
我這里剛開始采用農業銀行的銀行卡做為研究,后期再推及其他銀行卡。
原圖:(來自百度)

(1)提取銀行卡輪廓
高斯模糊-》灰度化-》Canny邊緣檢測-》二值化-》找輪廓-》輪廓判斷
高斯模糊:去除小的噪聲影響,平滑圖像。
GaussianBlur(inimg, inimg, Size(GAUSSIANBLUR, GAUSSIANBLUR),0, 0, BORDER_DEFAULT);
灰度化:后續部分操作僅支持灰度圖像
cvtColor(inimg, inimg, CV_BGR2GRAY);
Canny邊緣檢測:檢測整個銀行卡的邊緣
Canny(inimg, inimg, CARDCANNY_1, CARDCANNY_2);

當然像這樣背景較簡單的圖片比較容易判斷。
二值化:將圖像轉化為黑白圖像,便於輪廓提取
threshold(inimg, inimg, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
找輪廓:畫出檢測的輪廓
vector<vector<Point> > contours; cv::findContours(inimg, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); //找輪廓
輪廓判斷:篩選出滿足條件的輪廓
vector<Rect> rect; for (int i = 0; i < contours.size(); ++i) { //cout << contours[i].size() << endl; if (contours[i].size() > CARDCONTOURS)//將比較小的輪廓剔除掉 { rect.push_back( boundingRect(contours[i]) ); } } //cout << rect.size() << endl; for (int j = 0; j < rect.size(); j++) { float r = (float)rect[j].width / (float)rect[j].height; if (r < 1) { r = (float)rect[j].height / (float)rect[j].width; } //cout << r << ",,," << rect[j].width << ",,," << rect[j].height << endl; if (r <= CARDRATIO_MAX && r >= CARDRATIO_MIN){ original(rect[j]).copyTo(outimg); // copy the region rect1 from the image to roi1 //imshow("re", outimg); } }

這個過程我們可能獲取到多個滿足條件的銀行卡區域,這里我僅提取獲取的第一張圖片。如果想要更精准的話,需要我們像車牌識別過程中一樣用機器學習進行分類。
1.1版本中采用xy軸的Sobel算法進行輪廓提取,由於我們僅需要提取出銀行卡區域,不需要十分精確的邊緣,我們采用Sobel算法更滿足我們的要求。
canny邊緣檢測采用雙閾值值法,高閾值用來檢測圖像中重要的、顯著的線條、輪廓等,而低閾值用來保證不丟失細節部分,低閾值檢測出來的邊緣更豐富,但是很多邊緣並不是我們關心的。最后采用一種查找算法,將低閾值中與高閾值的邊緣有重疊的線條保留,其他的線條都刪除。canny可以獲取很精確的邊緣。


(2)偏斜扭正
此部分待后續開發
(3)獲取數字區域
提取銀行卡號碼區域-》消除多余像素-》畫輪廓-》提取輪廓
提取銀行卡號碼區域:對獲取的銀行卡區域我們進行號碼區域提取時,由於銀行卡號碼位於整個銀行卡的區域的固定的,我們可以直接按固定區域提取銀行卡號碼所在的位置。
Rect rect(CHARRECT_X, CHARRECT_Y, CHARRECT_WIDTH, CHARRECT_HEIGHT);
original(rect).copyTo(inimg);

消除多余像素:灰度化-》Canny檢測-》消除多余行和列(按非零像素的數量進行判斷)
cvtColor(inimg, inimg, CV_BGR2GRAY); //邊緣檢測 Canny(inimg, inimg, CHARCANNY_1, CHARCANNY_2); threshold(inimg, inimg, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); imshow(" threshold",inimg); //inimg.copyTo(outimg); resize(inimg, outimg, Size(CHARBIGSIZE_WIDTH, CHARBIGSIZE_HEIGHT), 0, 0, INTER_LINEAR); for (int row = 0; row < inimg.rows; row++) { //消除多余行 Rect rectNum(0, row, inimg.cols, 1); Mat temp; inimg(rectNum).copyTo(temp); int n = countNonZero(temp); //cout << "Zerorow" << n << ",,," << inimg.rows << endl; if (n < COUNTNONZERO_ROW) { inimg.row(row).setTo(Scalar(0)); } } for (int col = 0; col < inimg.cols; col++) { //消除多余列 Rect rectNum(col, 0, 1, inimg.rows); Mat temp; inimg(rectNum).copyTo(temp); int n = countNonZero(temp); //cout << "Zerocol: " << n << endl; if (n < COUNTNONZERO_COL) { inimg.col(col).setTo(Scalar(0)); } }


畫輪廓:將銀行卡號碼區域按一定比例放大,這樣可以保證完整的號碼被選取出來。但是放大之后也會有弊端,就是獲取的區域但是多個數字連在一起的區域,需要我們進一步進行判斷。
cvtColor(inimg, inimg, CV_BGR2GRAY); //邊緣檢測 Canny(inimg, inimg, CHARCANNY_1, CHARCANNY_2); threshold(inimg, inimg, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); imshow(" threshold",inimg); //inimg.copyTo(outimg); resize(inimg, outimg, Size(CHARBIGSIZE_WIDTH, CHARBIGSIZE_HEIGHT), 0, 0, INTER_LINEAR); for (int row = 0; row < inimg.rows; row++) { //消除多余行 Rect rectNum(0, row, inimg.cols, 1); Mat temp; inimg(rectNum).copyTo(temp); int n = countNonZero(temp); //cout << "Zerorow" << n << ",,," << inimg.rows << endl; if (n < COUNTNONZERO_ROW) { inimg.row(row).setTo(Scalar(0)); } } for (int col = 0; col < inimg.cols; col++) { //消除多余列 Rect rectNum(col, 0, 1, inimg.rows); Mat temp; inimg(rectNum).copyTo(temp); int n = countNonZero(temp); //cout << "Zerocol: " << n << endl; if (n < COUNTNONZERO_COL) { inimg.col(col).setTo(Scalar(0)); } }









提取輪廓:上一步說到多數字連接的問題,這里我們進行多數字分割處理。
for (int j = 0; j < charRect.size(); j++) { if (charRect[j].height > ONECHARRECT_HEIGTH) { cout << charRect[j].height << ",,," << j << endl; if (charRect[j].width <= ONECHARRECT_WIDTH) { rectangle(outTemp, charRect[j], Scalar(100), 1); Mat charTempImg; outimg(charRect[j]).copyTo(charTempImg); //cout << "width" << charRect[j] << endl; stringstream ss(stringstream::in | stringstream::out); ss << "source/char/char" << "_" << j << ".jpg"; imwrite(ss.str(), charTempImg); }else { int n = charRect[j].width / ONECHARRECT_WIDTH_NUM; for (int k = 0; k < n; k++) { float nt = charRect[j].width / n; Rect recttemp(charRect[j].x + n*nt - (k+1)*nt, charRect[j].y, nt, charRect[j].height); Mat charTempImg; outimg(recttemp).copyTo(charTempImg); //cout << "width" << recttemp << endl; stringstream ss(stringstream::in | stringstream::out); ss << "source/char/char" << "_" << j << "_" << k << ".jpg"; imwrite(ss.str(), charTempImg); } } } }

1.1版本
灰度化-》直方圖增強對比度-》特定閾值二值化
去掉了canny邊緣檢測。

增強對比度:
vector< Mat > splitBGR(inimg.channels()); //分割通道,存儲到splitBGR中 split(inimg, splitBGR); //對各個通道分別進行直方圖均衡化 for (int i = 0; i<inimg.channels(); i++) equalizeHist(splitBGR[i], splitBGR[i]); //合並通道 merge(splitBGR, inimg); //imshow("imageAdjust", inimg);

threshold(inimg, inimg, 25, 255, THRESH_BINARY_INV);



(4)數字識別
這部分擬采用深度神經網絡實現,現階段還未實現。待后續更新。
參考——
http://www.cnblogs.com/ronny/p/opencv_road_8.html
