1. 程式人生 > >OpenCV 數字驗證碼識別

OpenCV 數字驗證碼識別

更新後代碼下載連結在此!!!

點我下載
本文針對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;  
}  

為了方便童鞋們參考,提供了下載連結,不過要花一點積分的哦!(LZ下別的也是要積分的啊!)

戳我戳我