OpenCV 數字驗證碼識別
阿新 • • 發佈:2019-02-12
更新後代碼下載連結在此!!!
點我下載
本文針對OpenCv入門人士,因為我也不是專門做影象的,只是為了完成一次模式識別的小作業。
主要完成的功能就是自動識別圖片中的數字,圖片包括正常圖片,有劃痕影象和有噪點影象。分別如下
先上圖,看識別效果!
接下來開始來點乾貨了:
- opencv的安裝與配置:這個要是展開講可以再寫一篇博文了,我當時什麼都不會配個opencv麻煩死了,最後參考網上studio2012的配置方法成功了,在此略過。看到這裡你的opencv還不能用的話,趕緊別往下看了,先把opencv配好再來吧!
- opencv基本圖片操作:
- 因為opencv有2.0 和 3.0 的版本區別,所以網上搜到的函式或型別都是兩種格式,建議用新版的,什麼impImage* 型別的都是2.0版本的寫法,我全部使用的是Mat。一定要統一好,不要一會新的一會舊的,會報錯的。
- 讀圖片imread,顯示imshow,等待waitKey等等,這些要先熟悉
- opencv的強大之處在於幾乎所有的影象操作它都有現成的函式可供呼叫,非常方便。多谷歌,一定會有函式已經實現了你想完成的功能。
- 二值化:不論是原圖還是有劃痕或噪點的圖,背景都不乾淨,這對識別的影響還是挺不好的,所以要先二值化,把黑白畫素點區分的開一些。但是圖片右側明顯要比左側更暗,所以在閾值選取的時候比較難辦,很難用一個固定的值將兩部分影象都二值化得很理想,所以就用到了逼格更高的自適應二值化(adaptiveThreshold),tips:二值化前先直方圖均衡一下效果會更好。
- 中值濾波:針對有噪點和有劃痕的影象,中值濾波是非常好的處理方案,中值的引數可調,可以很好的消除噪音的影響。缺點就是引數不好調啊,調的想死。。
- 模板匹配:模板的來源可以是自己從待識別的圖片中摳圖,不過我們作業提供了模板圖片,所以這一步就可以省掉了。opencv提供了非常強大的matchTemplate函式,可以將給定圖片與模板按照你規定的計算方法計算一個相似度的值,並將對應的座標儲存下來,你需要做的只是將值比較大(或小,與你規定計算相似度的函式有關)的影象框出來即可
- 視窗掃描:為了提高識別率,我設定了一個視窗對原圖進行掃描,掃描視窗的移動設定了一點規則,就是如果前一個視窗沒有匹配到數字就微調視窗位置,如果匹配到數字就將視窗左軸移動到匹配到的數字的右側,再重複掃描。
基本乾貨就這麼多了,剩下的就是不斷的調引數和掃描視窗的位置了,這個方法的缺陷就是針對不同的圖片,引數和掃描窗都要變,比如來一張一行或三行的數字,那就必須修改掃描視窗的函數了,還有每一步的步長之類的,還是相當蛋疼的!
下面是部分核心程式碼
預處理,包括自適應二值化和中值濾波
void preProcess(){ //自適應二值化&中值濾波
Mat out;
//自適應二值化
adaptiveThreshold(source, source, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, adaptiveBiSize, adaptiveBiParam);
//中值濾波
namedWindow("binary");
imshow("binary",source);
waitKey(0);
medianBlur( source, out, medianBlurSize);
namedWindow("medianblur");
imshow("medianblur",out);
waitKey(0);
source = out;
srcResult = out; //用來顯示
}
匹配
bool match(Mat src){
int srcW,srcH,templatW, templatH, curtemplatW,curtemplatH,resultH, resultW;
Mat templat,result;
srcW = src.cols;
srcH = src.rows;
double currentMin = 1;
int currentIndex=0;
double minValue, maxValue;
Point minLoc, maxLoc,matchLoc;
/*
** 相似度計算方法
** 0:CV_TM_SQDIFF 平方差匹配法,最好的匹配值為0;匹配越差,匹配值越大
** 1:CV_TM_SQDIFF_NORMED 歸一化平方差匹配法
** 2:CV_TM_CCORR 相關匹配法:該方法採用乘法操作;數值越大表明匹配程度越好
** 3:CV_TM_CCORR_NORMED 歸一化相關匹配法
** 4:CV_TM_CCOEFF 相關係數匹配法:1表示完美的匹配;-1表示最差的匹配。
** 5:CV_TM_CCOEFF_NORMED 歸一化相關係數匹配法
*/
int methodType=1;
//迴圈判斷8個數字哪個數字模板最為接近被測試影象
for (int i=0;i<8;i++){
templat = templatVec[i];
templatW = templat.cols;
templatH = templat.rows;
if(srcW < templatW || srcH < templatH)
{
cout <<"模板不能比原影象大" << endl;
return 0;
}
resultW = srcW - templatW + 1;
resultH = srcH - templatH + 1;
result = cvCreateImage(cvSize(resultW, resultH), 1, 1);
matchTemplate(src, templat, result, methodType);
minMaxLoc(result, &minValue, &maxValue, &minLoc, &maxLoc,Mat() );
//如果比當前最小還小,則儲存該值,下標和座標
if (minValue<currentMin){
currentMin = minValue;
currentIndex=i;
matchLoc.x=minLoc.x+window_x;
matchLoc.y=minLoc.y+window_y;
curtemplatW = templatW;
curtemplatH = templatH;
}
}
//cout<<"Min:"<<currentMin<<endl;
//最小值比設定閾值小,則判斷識別出這個數字
if (currentMin<threshold){
numresult.push_back(index[currentIndex]);
cout<<"第"<<countnumbers<<"個數字是:"<<index[currentIndex]<<endl;
/*cout<<"左上角座標為:("<<matchLoc.x<<","<<matchLoc.y<<")"<<endl;
cout<<"右上角座標:("<<matchLoc.x+templatW<<","<<matchLoc.y<<")"<<endl;
cout<<"左下角座標:("<<matchLoc.x<<","<<matchLoc.y+templatH<<")"<<endl;*/
countnumbers++;
rectangle(srcResult, matchLoc, cvPoint(matchLoc.x + curtemplatW, matchLoc.y+ curtemplatH), cvScalar(0,0,255));
/*namedWindow("tmpresult");
imshow("tmpresult",srcResult);
waitKey(0);*/
window_x =matchLoc.x+curtemplatW-1;
return true;
}
//比閾值大則判定為非字元,掃描窗右移一個單位
window_x++;
return false;
}
視窗掃描,虛擬函式需要被實現
virtual void processScan(){
sourceW = source.cols;
sourceH = source.rows;
window_x = 0;
window_y = 3;
//加十以提高容錯率
bool last = false;
while(window_x<sourceW-scanWindowW+5){
if (window_x+scanWindowW>sourceW){
window_x = sourceW - scanWindowW;
last = true;
}
Mat tmp = scanWindow(window_x,window_y);
match(tmp);
if (last) break;
}
window_x = 30;
scanWindowH = 35;
window_y=sourceH - scanWindowH;
while (window_x<=sourceW - scanWindowW-10){
Mat tmp = scanWindow(window_x,window_y);
match(tmp);
}
}
針對不同圖片建立了不同的類來實現:
//識別有噪點的影象
class noisyPic:public Picture{
public:
noisyPic(){
Picture();
threshold = 0.5;
path="test\\noisy.bmp";
adaptiveBiSize = 17;
adaptiveBiParam= 19;
medianBlurSize = 5;
scanWindowW = 38;
scanWindowH = 38;
}
void displayResult(){
cout<<"當前識別的是有噪點的影象,識別結果為:"<<endl;
for (unsigned int i=0;i<numresult.size();i++){cout<<numresult[i]<<" ";}
cout<<endl;
cout<<"====================================================="<<endl;
namedWindow("final");
imshow("final", srcResult);
waitKey(0);
}
};
//有劃痕的影象
class dirtyPic:public Picture{
public:
dirtyPic(){
Picture();
threshold = 0.48;
path="test\\dirty.bmp";
adaptiveBiSize = 21;
adaptiveBiParam= 23;
medianBlurSize = 7;
scanWindowW = 36;
scanWindowH = 38;
}
virtual void displayResult(){
cout<<"當前識別的是有劃痕的影象,識別結果為:"<<endl;
for (unsigned int i=0;i<numresult.size();i++){cout<<numresult[i]<<" ";}
cout<<endl;
cout<<"====================================================="<<endl;
namedWindow("final");
imshow("final", srcResult);
waitKey(0);
}
};
主函式
int main()
{
//正常影象,建構函式不指定引數時,預設識別第一張圖
//建構函式可以指定識別第幾張圖,下面以第三張為例
Picture pic = Picture(3);
pic.startRecognize();
//識別有噪聲影象
noisyPic noisyPic;
noisyPic.startRecognize();
//識別有劃痕影象
dirtyPic dirtyPic;
dirtyPic.startRecognize();
//識別放大縮小影象
scalePic scale = scalePic(1);
scale.startRecognize();
return 0;
}