圖像驗證碼識別(九)——訓練和識別

前面講到已經把所有的字符經過去幹擾、分割和歸一化得到標準大小的單個字符,接下來要做的就是識別驗證碼了。現在要做的基本上也就和OCR沒什麼區別了,因爲得到的字符已經是儘可能標準的了。下面的識別分爲兩個步驟,第一步先是特徵值的提取,第二步是SVM訓練。

一、特徵值提取

首先要說的是我當時在做這個的時候,還沒有了解“主成分分析”,所以在提取特徵值的時候用的是比較簡單的方法,就是簡單的提取像素值來解決的。具體來說,由於前面歸一化的字符每個都是16*16大小的,可以將字符圖片等分爲16個子區域,每個區域是4*4的,然後統計每個區域內部黑色像素(字符像素)的個數,這樣可以得到16個數值,然後按照從左到右,從上到下來的順序進行排列,可以得到一個16維的數據,這樣依賴就將256維的原數據降到了16維。

現在要做的就是如果想驗證哪個網站的驗證碼,就寫個爬蟲爬該網站的驗證碼,爬個幾百張然後對每一張驗證碼上的字符進行標記,然後按照前面的步驟一步一步預處理然後提取特徵值,將每個字符的特徵值和其標記的字符寫入到數據文件中,在這裏我取了某網站的驗證碼一共250張,每張有4個字符,字符集只有大寫字母26個和0-9十個數字,這樣得到了1000條數據,由於字符存在粘連狀況,因此在字符分割那一部分並不是100%成功,最後有十幾張驗證碼圖片分割失敗,所以最終得到的數據集個數只有900多個。部分數據如下:

[cpp] view plain copy
  1. D,0,4,7,12,9,9,4,12,7,8,4,12,0,8,8,2  
  2. N,0,1,5,6,9,15,7,2,0,5,14,7,6,9,7,3  
  3. Y,3,1,0,0,5,12,9,8,3,12,4,1,5,0,0,0  
  4. 2,0,0,0,1,7,2,7,12,8,9,8,8,0,3,0,0  
  5. Z,0,0,1,8,13,1,10,12,12,11,1,12,5,1,0,2  
  6. I,0,0,0,0,0,1,4,6,7,11,7,3,0,0,0,0  
  7. Z,0,0,1,6,12,1,11,12,12,12,2,12,6,1,0,0  
  8. 5,0,0,1,0,6,12,4,9,8,7,9,8,2,0,0,0  
  9. G,0,9,8,3,8,7,5,11,12,1,10,11,3,6,9,1  
  10. 7,0,0,0,0,8,1,6,11,9,10,6,0,2,1,0,0  
  11. M,0,4,7,10,8,16,11,9,0,4,12,7,9,14,13,8  
  12. D,0,1,4,5,11,10,9,12,12,1,3,10,5,11,11,1  
  13. 3,0,0,1,1,6,2,2,10,10,9,12,8,0,2,0,0  
  14. F,0,0,4,6,7,13,12,4,8,8,8,0,5,3,2,0  
  15. N,0,0,5,6,9,15,6,2,0,5,11,7,7,10,5,3  
  16. X,1,0,0,7,7,11,12,4,3,13,10,8,9,2,0,1  
  17. 2,0,0,0,2,8,4,6,13,9,11,9,7,2,3,0,0  
  18. P,1,0,4,5,11,12,11,4,12,6,8,0,4,10,1,0  
  19. J,0,0,2,2,0,0,3,13,4,10,11,6,3,2,0,0  
  20. V,4,4,3,0,2,6,9,16,0,7,12,4,6,6,0,0  
  21. 7,1,0,0,0,8,5,10,9,12,8,0,0,1,0,0,0  
  22. W,9,12,12,9,4,8,11,1,9,10,11,9,4,9,9,2  

數據集每行代表一條數據,第一個字母或數字是該字符的標記結果,後面緊跟着16個數字是其特徵值。

二、機器學習識別

現在終於到了驗證碼識別的最後一步了,有了前面的數據集,就可以進行訓練了。我在這裏使用的分類器是SVM,由於整個項目都是用OpenCV做的,而OpenCV正好提供SVM的庫,因此就直接拿來用了。OpenCV的SVM是基於libSVM的,有關SVM(支持向量機)的知識我也瞭解的不是太多,這裏不再贅述,有興趣的可以去找找資料看看。在OpenCV的源代碼工程裏,可以找到怎麼使用OpenCV SVM的demo,這裏就直接拿來用了,代碼如下:

[cpp] view plain copy
  1. const char out_file[] = "recognition.data";  
  2. const char xml_file[] = "train_out.xml";  
  3. const int OFFSET = 7;  
  4. const int VECTOR_SIZE = 16;  
  5.   
  6. bool read_num_class_data( const string& filename, int var_count,  
  7.                      Mat* _data, Mat* _responses )  
  8. {  
  9.     const int M = 1024;  
  10.     char buf[M+2];  
  11.   
  12.     Mat el_ptr(1, var_count, CV_32F);  
  13.     int i;  
  14.     vector<int> responses;  
  15.   
  16.     _data->release();  
  17.     _responses->release();  
  18.   
  19.     FILE* f = fopen( filename.c_str(), "rt" );  
  20.     if( !f )  
  21.     {  
  22.         cout << "Could not read the database " << filename << endl;  
  23.         return false;  
  24.     }  
  25.   
  26.     for(;;)  
  27.     {  
  28.         char* ptr;  
  29.         if( !fgets( buf, M, f ) || !strchr( buf, ',' ) )  
  30.             break;  
  31.         responses.push_back((int)buf[0]);  
  32.         ptr = buf+2;  
  33.         for( i = 0; i < var_count; i++ )  
  34.         {  
  35.             int n = 0;  
  36.             sscanf( ptr, "%f%n", &el_ptr.at<float>(i), &n );  
  37.             ptr += n + 1;  
  38.         }  
  39.         if( i < var_count )  
  40.             break;  
  41.         _data->push_back(el_ptr);  
  42.     }  
  43.     fclose(f);  
  44.     Mat(responses).copyTo(*_responses);  
  45.   
  46.     cout << "The database " << filename << " is loaded.\n";  
  47.   
  48.     return true;  
  49. }  
  50.   
  51. bool build_svm_classifier( const string& data_filename,  
  52.                       const string& filename_to_save)  
  53. {  
  54.     int i;  
  55.     Mat data;  
  56.     Mat responses;  
  57.     bool ok = read_num_class_data( data_filename, VECTOR_SIZE, &data, &responses );  
  58.     if( !ok )  
  59.         return ok;  
  60.     int nsamples_all = data.rows;  
  61.     int ntrain_samples = (int)(nsamples_all*0.8);  
  62.   
  63.     Mat train_data = data.rowRange(0,ntrain_samples);  
  64.     Mat test_data  = data.rowRange(ntrain_samples,nsamples_all);  
  65.     Mat train_response = responses.rowRange(0,ntrain_samples);  
  66.     Mat test_response = responses.rowRange(ntrain_samples,nsamples_all);  
  67.   
  68.     cout << "Training the classifier ...\n";  
  69.     // Set up SVM's parameters  
  70.     CvSVMParams params;  
  71.     params.svm_type    = CvSVM::C_SVC;  
  72.     params.kernel_type = CvSVM::LINEAR;  
  73.     params.term_crit   = cvTermCriteria(CV_TERMCRIT_ITER, 100, 1e-6);  
  74.   
  75.      // Train the SVM  
  76.     CvSVM SVM;  
  77.     SVM.train(train_data, train_response, Mat(), Mat(), params);  
  78.     SVM.save(filename_to_save.c_str());  
  79.   
  80.     cout << "Begin to test the classifier ..." << endl;  
  81.     int right = 0;  
  82.     for(i=0;i<nsamples_all - ntrain_samples;i++)  
  83.     {  
  84.         Mat sample = test_data.row(i);  
  85.         if(SVM.predict(sample)  == test_response.at<int>(i) )  
  86.             right++;  
  87.     }  
  88.     cout << "The correct rate of the " << nsamples_all - ntrain_samples << " test cases is: " << right*100.0 / (nsamples_all-ntrain_samples)  << "%"<< endl;  
  89.   
  90.         return true;  
  91. }  
  92.   
  93. int predict(const string& sample)  
  94. {  
  95.     int i;  
  96.     char buf[80],*ptr;  
  97.     CvSVM SVM;  
  98.     SVM.load(xml_file);  
  99.   
  100.     Mat sample_mat = Mat(1,VECTOR_SIZE,CV_32F);  
  101.     strcpy(buf,sample.c_str());  
  102.     ptr = buf;  
  103.     for (i = 0; i < VECTOR_SIZE; ++i)  
  104.     {  
  105.         int n = 0;  
  106.         sscanf( ptr, "%f%n", &sample_mat.at<float>(i), &n );  
  107.         ptr += n + 1;  
  108.     }  
  109.   
  110.     return SVM.predict(sample_mat);  
  111. }  
這裏recognition.data文件是前面提取得到的數據集,read_num_class_data函數讀取數據集文件,對數據集裏的每一條數據提取出來並且存儲到相應的數據結構中,接下來會調用build_svm_classifier函數來進行訓練,訓練完成後會生成一個train_out.xml的文件,這個文件就是訓練後的輸出模板,這樣在接下來每次識別的時候,每次對驗證碼圖片進行預處理並且提取特徵值,將得到的16個特徵值與這個模板進行匹配,就可以得到識別的字符了。讀取模板文件並且識別是由predict函數完成的。

下面給出我的一些結果,首先前面得到了900多條數據,我將這些數據分成訓練集和測試集,測試集分了180條數據,剩下的都當做訓練集了,訓練的時候由於數據集非常小,所以訓練的過程在一瞬間就完成了,然後自動讀取模板對180測試數據進行識別和校準,最後結果是180條測試集數據正確率100%。


可能是由於我的驗證碼圖片取得不是很難攻破,所以這裏在數據集很小的情況下還能保證識別率。最後給出識別一張完成圖片的結果圖:



至此,整個項目的介紹就完成了,我本人能力有限,這也僅僅是一個碩士生的課程作業,我也只是當時臨時起意,覺得做這個有點意思,可以嘗試一下。所以我將自己的經驗寫在這裏也只是做一個分享,把自己的思考過程展現給大家,給那些想做驗證碼識別但是沒有什麼經驗的人簡單的做入門介紹,權當拋磚引玉。如果有做的不好的地方,還望您能諒解。

整個代碼工程我都放在GitHub上了,鏈接如下:https://github.com/ysc6688/rcgn,僅供大家參考學習,如果有人拿來做不法的事情,一概與本人無關。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章