图像验证码识别(九)——训练和识别

前面讲到已经把所有的字符经过去干扰、分割和归一化得到标准大小的单个字符,接下来要做的就是识别验证码了。现在要做的基本上也就和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
  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%。


可能是由于我的验证码图片取得不是很难攻破,所以这里在数据集很小的情况下还能保证识别率。最后给出识别一张完成图片的结果图:

图像验证码识别(九)——训练和识别_第1张图片


至此,整个项目的介绍就完成了,我本人能力有限,这也仅仅是一个硕士生的课程作业,我也只是当时临时起意,觉得做这个有点意思,可以尝试一下。所以我将自己的经验写在这里也只是做一个分享,把自己的思考过程展现给大家,给那些想做验证码识别但是没有什么经验的人简单的做入门介绍,权当抛砖引玉。如果有做的不好的地方,还望您能谅解。

整个代码工程我都放在GitHub上了,链接如下:https://github.com/ysc6688/rcgn,仅供大家参考学习,如果有人拿来做不法的事情,一概与本人无关。

你可能感兴趣的:(验证码识别)