前面講到已經把所有的字符經過去幹擾、分割和歸一化得到標準大小的單個字符,接下來要做的就是識別驗證碼了。現在要做的基本上也就和OCR沒什麼區別了,因爲得到的字符已經是儘可能標準的了。下面的識別分爲兩個步驟,第一步先是特徵值的提取,第二步是SVM訓練。
一、特徵值提取
首先要說的是我當時在做這個的時候,還沒有了解“主成分分析”,所以在提取特徵值的時候用的是比較簡單的方法,就是簡單的提取像素值來解決的。具體來說,由於前面歸一化的字符每個都是16*16大小的,可以將字符圖片等分爲16個子區域,每個區域是4*4的,然後統計每個區域內部黑色像素(字符像素)的個數,這樣可以得到16個數值,然後按照從左到右,從上到下來的順序進行排列,可以得到一個16維的數據,這樣依賴就將256維的原數據降到了16維。
現在要做的就是如果想驗證哪個網站的驗證碼,就寫個爬蟲爬該網站的驗證碼,爬個幾百張然後對每一張驗證碼上的字符進行標記,然後按照前面的步驟一步一步預處理然後提取特徵值,將每個字符的特徵值和其標記的字符寫入到數據文件中,在這裏我取了某網站的驗證碼一共250張,每張有4個字符,字符集只有大寫字母26個和0-9十個數字,這樣得到了1000條數據,由於字符存在粘連狀況,因此在字符分割那一部分並不是100%成功,最後有十幾張驗證碼圖片分割失敗,所以最終得到的數據集個數只有900多個。部分數據如下:
- D,0,4,7,12,9,9,4,12,7,8,4,12,0,8,8,2
- N,0,1,5,6,9,15,7,2,0,5,14,7,6,9,7,3
- Y,3,1,0,0,5,12,9,8,3,12,4,1,5,0,0,0
- 2,0,0,0,1,7,2,7,12,8,9,8,8,0,3,0,0
- Z,0,0,1,8,13,1,10,12,12,11,1,12,5,1,0,2
- I,0,0,0,0,0,1,4,6,7,11,7,3,0,0,0,0
- Z,0,0,1,6,12,1,11,12,12,12,2,12,6,1,0,0
- 5,0,0,1,0,6,12,4,9,8,7,9,8,2,0,0,0
- G,0,9,8,3,8,7,5,11,12,1,10,11,3,6,9,1
- 7,0,0,0,0,8,1,6,11,9,10,6,0,2,1,0,0
- M,0,4,7,10,8,16,11,9,0,4,12,7,9,14,13,8
- D,0,1,4,5,11,10,9,12,12,1,3,10,5,11,11,1
- 3,0,0,1,1,6,2,2,10,10,9,12,8,0,2,0,0
- F,0,0,4,6,7,13,12,4,8,8,8,0,5,3,2,0
- N,0,0,5,6,9,15,6,2,0,5,11,7,7,10,5,3
- X,1,0,0,7,7,11,12,4,3,13,10,8,9,2,0,1
- 2,0,0,0,2,8,4,6,13,9,11,9,7,2,3,0,0
- P,1,0,4,5,11,12,11,4,12,6,8,0,4,10,1,0
- J,0,0,2,2,0,0,3,13,4,10,11,6,3,2,0,0
- V,4,4,3,0,2,6,9,16,0,7,12,4,6,6,0,0
- 7,1,0,0,0,8,5,10,9,12,8,0,0,1,0,0,0
- 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,這裏就直接拿來用了,代碼如下:
- const char out_file[] = "recognition.data";
- const char xml_file[] = "train_out.xml";
- const int OFFSET = 7;
- const int VECTOR_SIZE = 16;
- bool read_num_class_data( const string& filename, int var_count,
- Mat* _data, Mat* _responses )
- {
- const int M = 1024;
- char buf[M+2];
- Mat el_ptr(1, var_count, CV_32F);
- int i;
- vector<int> responses;
- _data->release();
- _responses->release();
- FILE* f = fopen( filename.c_str(), "rt" );
- if( !f )
- {
- cout << "Could not read the database " << filename << endl;
- return false;
- }
- for(;;)
- {
- char* ptr;
- if( !fgets( buf, M, f ) || !strchr( buf, ',' ) )
- break;
- responses.push_back((int)buf[0]);
- ptr = buf+2;
- for( i = 0; i < var_count; i++ )
- {
- int n = 0;
- sscanf( ptr, "%f%n", &el_ptr.at<float>(i), &n );
- ptr += n + 1;
- }
- if( i < var_count )
- break;
- _data->push_back(el_ptr);
- }
- fclose(f);
- Mat(responses).copyTo(*_responses);
- cout << "The database " << filename << " is loaded.\n";
- return true;
- }
- bool build_svm_classifier( const string& data_filename,
- const string& filename_to_save)
- {
- int i;
- Mat data;
- Mat responses;
- bool ok = read_num_class_data( data_filename, VECTOR_SIZE, &data, &responses );
- if( !ok )
- return ok;
- int nsamples_all = data.rows;
- int ntrain_samples = (int)(nsamples_all*0.8);
- Mat train_data = data.rowRange(0,ntrain_samples);
- Mat test_data = data.rowRange(ntrain_samples,nsamples_all);
- Mat train_response = responses.rowRange(0,ntrain_samples);
- Mat test_response = responses.rowRange(ntrain_samples,nsamples_all);
- cout << "Training the classifier ...\n";
- // Set up SVM's parameters
- CvSVMParams params;
- params.svm_type = CvSVM::C_SVC;
- params.kernel_type = CvSVM::LINEAR;
- params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 100, 1e-6);
- // Train the SVM
- CvSVM SVM;
- SVM.train(train_data, train_response, Mat(), Mat(), params);
- SVM.save(filename_to_save.c_str());
- cout << "Begin to test the classifier ..." << endl;
- int right = 0;
- for(i=0;i<nsamples_all - ntrain_samples;i++)
- {
- Mat sample = test_data.row(i);
- if(SVM.predict(sample) == test_response.at<int>(i) )
- right++;
- }
- cout << "The correct rate of the " << nsamples_all - ntrain_samples << " test cases is: " << right*100.0 / (nsamples_all-ntrain_samples) << "%"<< endl;
- return true;
- }
- int predict(const string& sample)
- {
- int i;
- char buf[80],*ptr;
- CvSVM SVM;
- SVM.load(xml_file);
- Mat sample_mat = Mat(1,VECTOR_SIZE,CV_32F);
- strcpy(buf,sample.c_str());
- ptr = buf;
- for (i = 0; i < VECTOR_SIZE; ++i)
- {
- int n = 0;
- sscanf( ptr, "%f%n", &sample_mat.at<float>(i), &n );
- ptr += n + 1;
- }
- return SVM.predict(sample_mat);
- }
下面給出我的一些結果,首先前面得到了900多條數據,我將這些數據分成訓練集和測試集,測試集分了180條數據,剩下的都當做訓練集了,訓練的時候由於數據集非常小,所以訓練的過程在一瞬間就完成了,然後自動讀取模板對180測試數據進行識別和校準,最後結果是180條測試集數據正確率100%。
可能是由於我的驗證碼圖片取得不是很難攻破,所以這裏在數據集很小的情況下還能保證識別率。最後給出識別一張完成圖片的結果圖:
至此,整個項目的介紹就完成了,我本人能力有限,這也僅僅是一個碩士生的課程作業,我也只是當時臨時起意,覺得做這個有點意思,可以嘗試一下。所以我將自己的經驗寫在這裏也只是做一個分享,把自己的思考過程展現給大家,給那些想做驗證碼識別但是沒有什麼經驗的人簡單的做入門介紹,權當拋磚引玉。如果有做的不好的地方,還望您能諒解。
整個代碼工程我都放在GitHub上了,鏈接如下:https://github.com/ysc6688/rcgn,僅供大家參考學習,如果有人拿來做不法的事情,一概與本人無關。