如何用opencv做銀行卡號碼識別?
一、首先看下效果:
圖1
圖1
圖2
二、幾何校正:
步驟:
0.背景:純色背景(如果卡面顏色與背景顏色相近,後續的邊緣檢測會有影響,這塊後續可以通過更改邊緣檢測的閥值參數或其他方法提高)
1.邊緣檢測(預處理)
2.直線檢測(設置檢測直線的條件(最小直線長度和最大直接間隔),獲取銀行卡邊,條件:直線數量大於等於3,小於10)
3.根據直線集獲得點集(Vec4i 轉換成vector<vector<Point>>)
4.根據點集獲得最小外接可旋轉矩形minAreaRect();
5.幾何校正(getRotationMatrix2D(),warpAffine(),getRectSubPix();)
三、銀行卡的上卡號定位:
方法1:
根據模板匹配,獲取卡號的位置
已實現,識別率>70%;缺點,對無銀聯標誌或其在右上角的卡,不適用
(此標誌是在測試環境中獲取的)
方法2:
構想 (未實現):1.cropped.rows/5至cropped.rows*4/5之間,以間距cropped.rows/5掃描2.檢測直線,比較直線的長度和數量來確認;另一種方法,二值化圖像,比較數字間的空白數。但因為背景複雜且有些卡相差很大,此種方法成功率較低。
ps:如果需要高效穩定的方法,這一塊還需要繼續找其他方法
四、卡號識別(下一步實現)
圖3 圖4 圖5
難度:
1.同一張卡,背景複雜,如圖3切割於圖2的工商卡,有個白底e。固定化的閥值化操作已不適用於此類型卡。更離譜是有些凹凸卡號,不認真看,連人眼都分不清。
2.數字的字體多,不適用於模板匹配。可使用SVM、KNN等進行識別
3.數字的預測度設置,目前在微信上,我的兩張卡都會識別錯誤(主要原因是背景過於複雜)
暫時先到這,後續需要找到新的方法,解決以上難點,還需要不斷提高。
五、demo代碼
- #include "stdafx.h"
- #include <windows.h>
- #include <opencv2\opencv.hpp>
- #include <iostream>
- using namespace cv;
- using namespace std;
- void drawDetectLines(Mat& image,const vector<Vec4i>& lines,Scalar & color);
- void on_canny( int, void* );
- //void on_Threshold( int, void* );以備後面使用
- void on_line( int, void* );
- void vec4i2vecPoint();
- bool findUPflag();
- void cropBankNoByUPflag();
- Mat frame;
- Mat grayImg;
- Mat contours;
- Mat threImg;
- int i_canThres=50;
- int g_nThresholdValue=100;
- int g_nThresholdType=1;
- int minLineLength=170;
- int maxLineGap=10;
- vector<Vec4i> lines;
- vector<Point> co;
- vector <vector<Point>> conn;
- Mat UPImg,cropped,noImg;
- Point minLoc;
- void main()
- {
- Mat imgROI;
- bool stop = false;
- char c;
- //VideoCapture capture(0);
- VideoCapture capture("D://img//bankcard//1.wmv");
- namedWindow( "canny");
- //namedWindow( "Threshold");
- namedWindow( "Bob_BankCard_Num_Reader");
- //cvtColor(frame,grayImg,CV_BGR2GRAY);
- // createTrackbar( "模式","Threshold", &g_nThresholdType,4, on_Threshold );
- // createTrackbar( "參數值","Threshold", &g_nThresholdValue,255, on_Threshold );
- createTrackbar( "canThres","canny", &i_canThres,100, on_canny );
- createTrackbar( "最小直線長度","Bob_BankCard_Num_Reader", &minLineLength,300, on_line);
- createTrackbar( "最大直線間隔度","Bob_BankCard_Num_Reader", &maxLineGap,300, on_line);
- Mat M, rotated;
- float angle;
- Size rect_size;
- float ratio;
- int lineSize=0;
- UPImg=imread("D://img//bankcard//UPflag.jpg");
- while(!stop)
- {
- capture>>frame;
- cvtColor(frame,grayImg,CV_BGR2GRAY);
- //on_Threshold(0,0);
- //imshow("canny",contours);
- on_canny(0,0);
- imshow("canny",contours);
- on_line(0,0);
- lineSize=lines.size();
- if(lineSize>=3&&lineSize<=10)//線條的數量,可根據亮度去調節canny的閥值
- {
- drawDetectLines(frame,lines,Scalar(0,255,0));
- vec4i2vecPoint();
- Rect rc=boundingRect(conn.at(0));
- RotatedRect rect;
- rect=minAreaRect(conn.at(0));
- imgROI=frame(rc);
- imshow("outRect",imgROI);
- //後期如果需要提高卡的輪廓識別率,可以判斷卡的height/width比,大約0.64(實際量過)
- ratio=rect.size.height/rect.size.width;
- cout<<"rotation:"<<ratio<<endl;
- if(ratio>0.6&&ratio<0.7)
- {
- angle = rect.angle;
- rect_size = rect.size;
- if (rect.angle < -45.)
- {
- angle += 90.0;
- swap(rect_size.width, rect_size.height);
- }
- M = getRotationMatrix2D(rect.center, angle, 1.0);
- warpAffine(frame, rotated, M, frame.size(), INTER_CUBIC);
- getRectSubPix(rotated, rect_size, rect.center, cropped);
- imshow("cropped",cropped);
- if(findUPflag())
- cropBankNoByUPflag();
- //else
- //執行第二種方法定位銀行卡號的y值,方法構想
- //1.cropped.rows/5至cropped.rows*4/5之間,以間距cropped.rows/5掃描
- //2.檢測直線,比較直線的長度和數量來確認;另一種方法,二值化圖像,比較數字間的空白數。但因為背景複雜且有時相差很大,此種方法成功率較低。
- //如果需要高效穩定的方法,這一塊還需要繼續找其他方法
- }
- }
- co.clear();
- conn.clear();
- lines.clear();
- imshow("Bob_BankCard_Num_Reader",frame);
- c=waitKey(24);;
- if(c==27)
- stop=true;
- }
- }
- void drawDetectLines(Mat& image,const vector<Vec4i>& lines,Scalar & color)
- {
- vector<Vec4i>::const_iterator it=lines.begin();
- while(it!=lines.end()&&lines.size()>=3)
- {
- Point pt1((*it)[0],(*it)[1]);
- Point pt2((*it)[2],(*it)[3]);
- line(image,pt1,pt2,color,1); // 線條寬度設置為1
- ++it;
- }
- }
- void vec4i2vecPoint()
- {
- vector<Vec4i>::const_iterator it=lines.begin();
- while(it!=lines.end())
- {
- Point pt1((*it)[0],(*it)[1]);
- Point pt2((*it)[2],(*it)[3]);
- co.push_back(pt1);
- co.push_back(pt2);
- ++it;
- }
- conn.push_back(co);
- }
- void on_canny( int, void* )
- {
- Canny(grayImg,contours,i_canThres,i_canThres*3);
- }
- //void on_Threshold( int, void* )
- //{
- //threshold(contours ,contours ,g_nThresholdValue,255,g_nThresholdType);
- //imshow( "Threshold", contours );
- //}
- void on_line( int, void* )
- {
- HoughLinesP(contours,lines,1,CV_PI/180,80,minLineLength,maxLineGap);//maxLineGap);
- }
- bool findUPflag()
- {
- int result_cols = cropped.cols - UPImg.cols + 1;
- int result_rows = cropped.rows - UPImg.cols + 1;
- Mat result = Mat( result_cols, result_rows, CV_32FC1 );
- matchTemplate( cropped, UPImg, result, CV_TM_SQDIFF );
- normalize(result,result,0,1,NORM_MINMAX,-1,Mat());
- minMaxLoc( result, NULL, NULL, &minLoc, NULL, Mat() );
- rectangle(cropped, Rect(minLoc,UPImg.size()),Scalar(255,0,0), 1, 8, 0);
- cout<<minLoc<<endl;
- if(minLoc.x<cropped.cols/2 ||minLoc.y<cropped.rows/2)//左邊、右上角都會返回假
- return false;
- else
- return true;//在右下角的情況才會返回,因為大部分卡如此
- }
- void cropBankNoByUPflag()
- {
- Rect rc;
- rc.x=10;//左起
- rc.width=cropped.cols-10;//右結束
- rc.y=minLoc.y-60;
- rc.height=60;
- noImg=cropped(rc);
- imshow("noImg",noImg);
- }