http://blog.csdn.net/jinshengtao/article/details/17954427   《Mastering Opencv ...读书笔记系列》车牌识别(II)

http://blog.csdn.net/jinshengtao/article/details/17883075/   《Mastering Opencv ...读书笔记系列》车牌识别(I)

2014-01-07 12:08 9920人阅读 评论(5)  举报

版权声明:本文为博主原创文章,未经博主允许不得转载。

继上一篇文章后,现在要做的就是从车牌图像上使用optical character recognition算法将字符提取出来。对于每一块被检测的车牌,使用带监督的神经网络机器学习算法来识别字符。

本文内容:

1.字符分割

2.神经网络训练方法

3.使用神经网络预测字符

一、字符分割【OCR Segment】

在使用神经网络对每个字符进行预测之前,我们必须从车牌图像中扣取改字符图片,因此有如下步骤:

本文的输入图像为上一篇文章的车牌:

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

a.二值化车牌

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

b.求轮廓

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

c.求最小外接矩形

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

d.用纵横比及面积,筛选外接矩形

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP
e.调整统一矩形大小并保存每个字符的图片【注意:分割得到顺序和车牌字符顺序无关,可能不同】

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

代码:

[cpp] view plain copy

 

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

  1. // car_plate_ann.cpp : 定义控制台应用程序的入口点。
  2. //
  3. #include "stdafx.h"
  4. #include <cv.h>
  5. #include <highgui.h>
  6. #include <cvaux.h>
  7. #include <ml.h>
  8. #define HORIZONTAL    1
  9. #define VERTICAL    0
  10. using namespace std;
  11. using namespace cv;
  12. //typedef struct CharSegment{
  13. //  Mat img;
  14. //  Rect mr;
  15. //  CharSegment(Mat a,Rect b){
  16. //      img=a;
  17. //      mr=b;
  18. //  }
  19. //};
  20. bool verifySizes(Mat r){
  21. //Char sizes 45x77
  22. float aspect=45.0f/77.0f;
  23. float charAspect= (float)r.cols/(float)r.rows;
  24. float error=0.35;
  25. float minHeight=15;
  26. float maxHeight=28;
  27. //We have a different aspect ratio for number 1, and it can be ~0.2
  28. float minAspect=0.2;
  29. float maxAspect=aspect+aspect*error;
  30. //area of pixels
  31. float area=countNonZero(r);
  32. //bb area
  33. float bbArea=r.cols*r.rows;
  34. //% of pixel in area
  35. float percPixels=area/bbArea;
  36. /*if(DEBUG)
  37. cout << "Aspect: "<< aspect << " ["<< minAspect << "," << maxAspect << "] "  << "Area "<< percPixels <<" Char aspect " << charAspect  << " Height char "<< r.rows << "\n";*/
  38. if(percPixels < 0.8 && charAspect > minAspect && charAspect < maxAspect && r.rows >= minHeight && r.rows < maxHeight)
  39. return true;
  40. else
  41. return false;
  42. }
  43. Mat preprocessChar(Mat in){
  44. //Remap image
  45. int h=in.rows;
  46. int w=in.cols;
  47. int charSize=20;    //统一每个字符的大小
  48. Mat transformMat=Mat::eye(2,3,CV_32F);
  49. int m=max(w,h);
  50. transformMat.at<float>(0,2)=m/2 - w/2;
  51. transformMat.at<float>(1,2)=m/2 - h/2;
  52. Mat warpImage(m,m, in.type());
  53. warpAffine(in, warpImage, transformMat, warpImage.size(), INTER_LINEAR, BORDER_CONSTANT, Scalar(0) );
  54. Mat out;
  55. resize(warpImage, out, Size(charSize, charSize) );
  56. return out;
  57. }
  58. //create the accumulation histograms,img is a binary image, t is 水平或垂直
  59. Mat ProjectedHistogram(Mat img, int t)
  60. {
  61. int sz=(t)?img.rows:img.cols;
  62. Mat mhist=Mat::zeros(1,sz,CV_32F);
  63. for(int j=0; j<sz; j++){
  64. Mat data=(t)?img.row(j):img.col(j);
  65. mhist.at<float>(j)=countNonZero(data);    //统计这一行或一列中,非零元素的个数,并保存到mhist中
  66. }
  67. //Normalize histogram
  68. double min, max;
  69. minMaxLoc(mhist, &min, &max);
  70. if(max>0)
  71. mhist.convertTo(mhist,-1 , 1.0f/max, 0);//用mhist直方图中的最大值,归一化直方图
  72. return mhist;
  73. }
  74. Mat getVisualHistogram(Mat *hist, int type)
  75. {
  76. int size=100;
  77. Mat imHist;
  78. if(type==HORIZONTAL){
  79. imHist.create(Size(size,hist->cols), CV_8UC3);
  80. }else{
  81. imHist.create(Size(hist->cols, size), CV_8UC3);
  82. }
  83. imHist=Scalar(55,55,55);
  84. for(int i=0;i<hist->cols;i++){
  85. float value=hist->at<float>(i);
  86. int maxval=(int)(value*size);
  87. Point pt1;
  88. Point pt2, pt3, pt4;
  89. if(type==HORIZONTAL){
  90. pt1.x=pt3.x=0;
  91. pt2.x=pt4.x=maxval;
  92. pt1.y=pt2.y=i;
  93. pt3.y=pt4.y=i+1;
  94. line(imHist, pt1, pt2, CV_RGB(220,220,220),1,8,0);
  95. line(imHist, pt3, pt4, CV_RGB(34,34,34),1,8,0);
  96. pt3.y=pt4.y=i+2;
  97. line(imHist, pt3, pt4, CV_RGB(44,44,44),1,8,0);
  98. pt3.y=pt4.y=i+3;
  99. line(imHist, pt3, pt4, CV_RGB(50,50,50),1,8,0);
  100. }else{
  101. pt1.x=pt2.x=i;
  102. pt3.x=pt4.x=i+1;
  103. pt1.y=pt3.y=100;
  104. pt2.y=pt4.y=100-maxval;
  105. line(imHist, pt1, pt2, CV_RGB(220,220,220),1,8,0);
  106. line(imHist, pt3, pt4, CV_RGB(34,34,34),1,8,0);
  107. pt3.x=pt4.x=i+2;
  108. line(imHist, pt3, pt4, CV_RGB(44,44,44),1,8,0);
  109. pt3.x=pt4.x=i+3;
  110. line(imHist, pt3, pt4, CV_RGB(50,50,50),1,8,0);
  111. }
  112. }
  113. return imHist ;
  114. }
  115. void drawVisualFeatures(Mat character, Mat hhist, Mat vhist, Mat lowData,int count){
  116. Mat img(121, 121, CV_8UC3, Scalar(0,0,0));
  117. Mat ch;
  118. Mat ld;
  119. char res[20];
  120. cvtColor(character, ch, CV_GRAY2RGB);
  121. resize(lowData, ld, Size(100, 100), 0, 0, INTER_NEAREST );//将ld从15*15扩大到100*100
  122. cvtColor(ld,ld,CV_GRAY2RGB);
  123. Mat hh=getVisualHistogram(&hhist, HORIZONTAL);
  124. Mat hv=getVisualHistogram(&vhist, VERTICAL);
  125. //Rect_(_Tp _x, _Tp _y, _Tp _width, _Tp _height)
  126. Mat subImg=img(Rect(0,101,20,20));//ch:20*20
  127. ch.copyTo(subImg);
  128. subImg=img(Rect(21,101,100,20));//hh:100*hist.cols
  129. hh.copyTo(subImg);
  130. subImg=img(Rect(0,0,20,100));//hv:hist.cols*100
  131. hv.copyTo(subImg);
  132. subImg=img(Rect(21,0,100,100));//ld:100*100
  133. ld.copyTo(subImg);
  134. line(img, Point(0,100), Point(121,100), Scalar(0,0,255));
  135. line(img, Point(20,0), Point(20,121), Scalar(0,0,255));
  136. sprintf(res,"hist%d.jpg",count);
  137. imwrite(res,img);
  138. //imshow("Visual Features", img);
  139. cvWaitKey(0);
  140. }
  141. Mat features(Mat in, int sizeData,int count){
  142. //Histogram features
  143. Mat vhist=ProjectedHistogram(in,VERTICAL);
  144. Mat hhist=ProjectedHistogram(in,HORIZONTAL);
  145. //Low data feature
  146. Mat lowData;
  147. resize(in, lowData, Size(sizeData, sizeData) );
  148. //画出直方图
  149. drawVisualFeatures(in, hhist, vhist, lowData,count);
  150. //Last 10 is the number of moments components
  151. int numCols=vhist.cols+hhist.cols+lowData.cols*lowData.cols;
  152. Mat out=Mat::zeros(1,numCols,CV_32F);
  153. //Asign values to feature,ANN的样本特征为水平、垂直直方图和低分辨率图像所组成的矢量
  154. int j=0;
  155. for(int i=0; i<vhist.cols; i++)
  156. {
  157. out.at<float>(j)=vhist.at<float>(i);
  158. j++;
  159. }
  160. for(int i=0; i<hhist.cols; i++)
  161. {
  162. out.at<float>(j)=hhist.at<float>(i);
  163. j++;
  164. }
  165. for(int x=0; x<lowData.cols; x++)
  166. {
  167. for(int y=0; y<lowData.rows; y++){
  168. out.at<float>(j)=(float)lowData.at<unsigned char>(x,y);
  169. j++;
  170. }
  171. }
  172. //if(DEBUG)
  173. //  cout << out << "\n===========================================\n";
  174. return out;
  175. }
  176. int _tmain(int argc, _TCHAR* argv[])
  177. {
  178. Mat input = imread("haha_1.jpg",CV_LOAD_IMAGE_GRAYSCALE);
  179. char res[20];
  180. int i = 0;
  181. //vector<CharSegment> output;
  182. //Threshold input image
  183. Mat img_threshold;
  184. threshold(input, img_threshold, 60, 255, CV_THRESH_BINARY_INV);
  185. Mat img_contours;
  186. img_threshold.copyTo(img_contours);
  187. //Find contours of possibles characters
  188. vector< vector< Point> > contours;
  189. findContours(img_contours,
  190. contours, // a vector of contours
  191. CV_RETR_EXTERNAL, // retrieve the external contours
  192. CV_CHAIN_APPROX_NONE); // all pixels of each contours
  193. // Draw blue contours on a white image
  194. cv::Mat result;
  195. input.copyTo(result);
  196. cvtColor(result, result, CV_GRAY2RGB);
  197. //cv::drawContours(result,contours,
  198. //  -1, // draw all contours
  199. //  cv::Scalar(0,0,255), // in blue
  200. //  1); // with a thickness of 1
  201. //Start to iterate to each contour founded
  202. vector<vector<Point> >::iterator itc= contours.begin();
  203. //Remove patch that are no inside limits of aspect ratio and area.
  204. while (itc!=contours.end()) {
  205. //Create bounding rect of object
  206. Rect mr= boundingRect(Mat(*itc));
  207. //rectangle(result, mr, Scalar(255,0,0),2);
  208. //Crop image
  209. Mat auxRoi(img_threshold, mr);
  210. if(verifySizes(auxRoi)){
  211. auxRoi=preprocessChar(auxRoi);
  212. //output.push_back(CharSegment(auxRoi, mr));
  213. //保存每个字符图片
  214. sprintf(res,"train_data_%d.jpg",i);
  215. i++;
  216. imwrite(res,auxRoi);
  217. rectangle(result, mr, Scalar(0,0,255),2);
  218. //对每一个小方块,提取直方图特征
  219. Mat f=features(auxRoi,15,i);
  220. }
  221. ++itc;
  222. }
  223. imwrite("result1.jpg",result);
  224. imshow("car_plate",result);
  225. waitKey(0);
  226. return 0;
  227. }

图片显示可以自己边注释边显示,另外提前给出了第二部分的累计水平垂直直方图和低分辨率采样的方法,并把这种特征的图像保存下来了。

二、神经网络训练

1.多层感知机简介:

多层感知机结构:【隐层数量为1层或多层,实际上自从引入了深度学习后,才有多层】

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

其中,每个神经元结构如下:

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

每个神经元都是相似的且每个神经元都有自己的判定边界,有多个输入和多个输出。不同权重的输入结合激励函数得到不同的输出。常见的激励函数有S型、高斯型、上图的hadrlim型。单层的单个神经元可以将输入向量分为两类,而一个有S个神经元的感知机,可以将输入向量分为2^S类

2.获取训练数据

和上一篇训练SVM所使用的特征不同,现在使用每个字符的累计直方图和低分辨率采样图像构成的高维向量作为训练神经网络的特征。训练的样本矩阵P为N*M,其中N(行)代表各个样本图片的融合特征,M(列)为类别。从书中给的已经训练好的orc.xml看,N有675行,M有30列,30列代表西班牙车牌有30种字符0-9和20个英文字母组成,675是这么来的,比如字符0有35张图片样本,对应产生35行高维向量,字符1有40张样本图片,对应产生40行高维向量,然后按照不同分辨率5*5、10*10、15*15、20*20采样【书中ocr.xml只有675,只采用5*5分辨率】。矩阵P实际上是对每一种高维向量的类别标注:

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

在Opencv中使用多层感知机需要配置training data矩阵、classes矩阵、隐层神经元数量。其中,训练数据矩阵和列别标识矩阵均从ocr.xml文件获取【下文会介绍】,这里只采用单隐层,包含10个神经元,输入层为675行,输出层为30行。

计算ocr.xml文件具体步骤:

a.将上一步分割得到的每个字符进行人工分类【可放在不同目录下】,比如最终字符0有35张图片,字符a有30张图片并定义数组【这些数字之和为675】:

[cpp] view plain copy

 

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

  1. const int numFilesChars[]={35, 40, 42, 41, 42, 33, 30, 31, 49, 44, 30, 24, 21, 20, 34, 9, 10, 3, 11, 3, 15, 4, 9, 12, 10, 21, 18, 8, 15, 7};

b.读取某个字符目录下的一张图片,提取累计直方图特征和不同低分辨率图像,具体如下:
(1)统计水平、垂直方向直方图,比如水平直方图,扫描图像每一行,统计每行非零元素的个数,这样就构成1*row矩阵,然后用该矩阵的最大值,规范化该矩阵。
(2)使用resize函数,按照不同分辨率得到图像矩阵
(3)将垂直直方图,水平直方图,低分辨率图像【按行读取】,都存进一个1*(vcol+hcol+h*w)的矩阵
(4)将上述矩阵连同标记一起写入xml文件中
累计直方图及低分辨率图像效果如下:【左下角为字符图像原始大小20*20,由上角为低分辨率采样后放大的图像100*100,右下角为水平直方图,左上角为垂直直方图】

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

具体训练代码为:

[cpp] view plain copy

 

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

  1. // Main entry code OpenCV
  2. #include <cv.h>
  3. #include <highgui.h>
  4. #include <cvaux.h>
  5. #include <iostream>
  6. #include <vector>
  7. #define HORIZONTAL    1
  8. #define VERTICAL    0
  9. using namespace std;
  10. using namespace cv;
  11. //西班牙车牌共30种字符,下面为每个字符的图片个数【没给,需人工挑选】
  12. const int numFilesChars[]={35, 40, 42, 41, 42, 33, 30, 31, 49, 44, 30, 24, 21, 20, 34, 9, 10, 3, 11, 3, 15, 4, 9, 12, 10, 21, 18, 8, 15, 7};
  13. const char strCharacters[] = {'0','1','2','3','4','5','6','7','8','9','B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z'};
  14. const int numCharacters=30;
  15. Mat features(Mat in, int sizeData,int count){
  16. //Histogram features
  17. Mat vhist=ProjectedHistogram(in,VERTICAL);
  18. Mat hhist=ProjectedHistogram(in,HORIZONTAL);
  19. //Low data feature
  20. Mat lowData;
  21. resize(in, lowData, Size(sizeData, sizeData) );
  22. //Last 10 is the number of moments components
  23. int numCols=vhist.cols+hhist.cols+lowData.cols*lowData.cols;
  24. Mat out=Mat::zeros(1,numCols,CV_32F);
  25. //Asign values to feature,ANN的样本特征为水平、垂直直方图和低分辨率图像所组成的矢量
  26. int j=0;
  27. for(int i=0; i<vhist.cols; i++)
  28. {
  29. out.at<float>(j)=vhist.at<float>(i);
  30. j++;
  31. }
  32. for(int i=0; i<hhist.cols; i++)
  33. {
  34. out.at<float>(j)=hhist.at<float>(i);
  35. j++;
  36. }
  37. for(int x=0; x<lowData.cols; x++)
  38. {
  39. for(int y=0; y<lowData.rows; y++){
  40. out.at<float>(j)=(float)lowData.at<unsigned char>(x,y);
  41. j++;
  42. }
  43. }
  44. //if(DEBUG)
  45. //  cout << out << "\n===========================================\n";
  46. return out;
  47. }
  48. int main ( int argc, char** argv )
  49. {
  50. cout << "OpenCV Training OCR Automatic Number Plate Recognition\n";
  51. cout << "\n";
  52. char* path;
  53. //Check if user specify image to process
  54. if(argc >= 1 )
  55. {
  56. path= argv[1];
  57. }else{
  58. cout << "Usage:\n" << argv[0] << " <path to chars folders files> \n";
  59. return 0;
  60. }
  61. Mat classes;
  62. Mat trainingDataf5;
  63. Mat trainingDataf10;
  64. Mat trainingDataf15;
  65. Mat trainingDataf20;
  66. vector<int> trainingLabels;
  67. OCR ocr;
  68. for(int i=0; i< numCharacters; i++)
  69. {
  70. int numFiles=numFilesChars[i];
  71. for(int j=0; j< numFiles; j++){
  72. cout << "Character "<< strCharacters[i] << " file: " << j << "\n";
  73. stringstream ss(stringstream::in | stringstream::out);
  74. ss << path << strCharacters[i] << "/" << j << ".jpg";
  75. Mat img=imread(ss.str(), 0);
  76. Mat f5=features(img, 5);
  77. Mat f10=features(img, 10);
  78. Mat f15=features(img, 15);
  79. Mat f20=features(img, 20);
  80. trainingDataf5.push_back(f5);
  81. trainingDataf10.push_back(f10);
  82. trainingDataf15.push_back(f15);
  83. trainingDataf20.push_back(f20);
  84. trainingLabels.push_back(i);            //每一幅字符图片所对应的字符类别索引下标
  85. }
  86. }
  87. trainingDataf5.convertTo(trainingDataf5, CV_32FC1);
  88. trainingDataf10.convertTo(trainingDataf10, CV_32FC1);
  89. trainingDataf15.convertTo(trainingDataf15, CV_32FC1);
  90. trainingDataf20.convertTo(trainingDataf20, CV_32FC1);
  91. Mat(trainingLabels).copyTo(classes);
  92. FileStorage fs("OCR.xml", FileStorage::WRITE);
  93. fs << "TrainingDataF5" << trainingDataf5;
  94. fs << "TrainingDataF10" << trainingDataf10;
  95. fs << "TrainingDataF15" << trainingDataf15;
  96. fs << "TrainingDataF20" << trainingDataf20;
  97. fs << "classes" << classes;
  98. fs.release();
  99. return 0;
  100. }

三、使用神经网络检测字符
a.读取一张车牌图像
b.配置神经网络参数,并使用xml文件训练神经网络【参数配置上述已经说过了】
c.提取该车牌图像的累计直方图和低分辨率图像特征矩阵
d.将该特征矩阵作为神经网络输入,经网络计算,得到预测结果【字符索引】
e.按照每个字符图像的相对位置,进行字符重新排序
f.得到最终字符【和书中不同的是,我的输入图像是车牌而不是整幅图像,因此绝对坐标是不同的,但字符间的相对位置还是对的,只是不能在车牌照片上显示数字而已,我直接答应到控制台上】

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

具体代码:

又用到了车牌类,这里面有车牌字符相对位置调整的函数,都给出来吧:

Plate.h:

[cpp] view plain copy

 

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

  1. #ifndef Plate_h
  2. #define Plate_h
  3. #include <string.h>
  4. #include <vector>
  5. #include <cv.h>
  6. #include <highgui.h>
  7. #include <cvaux.h>
  8. using namespace std;
  9. using namespace cv;
  10. class Plate{
  11. public:
  12. Plate();
  13. Plate(Mat img, Rect pos);
  14. string str();
  15. Rect position;
  16. Mat plateImg;
  17. vector<char> chars;
  18. vector<Rect> charsPos;
  19. };
  20. #endif

Plate.cpp:

[cpp] view plain copy

 

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

  1. /*****************************************************************************
  2. *   Number Plate Recognition using SVM and Neural Networks
  3. ******************************************************************************
  4. *   by David Mill醤 Escriv? 5th Dec 2012
  5. *   http://blog.damiles.com
  6. ******************************************************************************
  7. *   Ch5 of the book "Mastering OpenCV with Practical Computer Vision Projects"
  8. *   Copyright Packt Publishing 2012.
  9. *   http://www.packtpub.com/cool-projects-with-opencv/book
  10. *****************************************************************************/
  11. #include "Plate.h"
  12. Plate::Plate(){
  13. }
  14. Plate::Plate(Mat img, Rect pos){
  15. plateImg=img;
  16. position=pos;
  17. }
  18. string Plate::str(){
  19. string result="";
  20. //Order numbers
  21. vector<int> orderIndex;
  22. vector<int> xpositions;
  23. for(int i=0; i< charsPos.size(); i++){
  24. orderIndex.push_back(i);
  25. xpositions.push_back(charsPos[i].x);
  26. }
  27. float min=xpositions[0];
  28. int minIdx=0;
  29. for(int i=0; i< xpositions.size(); i++){
  30. min=xpositions[i];
  31. minIdx=i;
  32. for(int j=i; j<xpositions.size(); j++){
  33. if(xpositions[j]<min){
  34. min=xpositions[j];
  35. minIdx=j;
  36. }
  37. }
  38. int aux_i=orderIndex[i];
  39. int aux_min=orderIndex[minIdx];
  40. orderIndex[i]=aux_min;
  41. orderIndex[minIdx]=aux_i;
  42. float aux_xi=xpositions[i];
  43. float aux_xmin=xpositions[minIdx];
  44. xpositions[i]=aux_xmin;
  45. xpositions[minIdx]=aux_xi;
  46. }
  47. for(int i=0; i<orderIndex.size(); i++){
  48. result=result+chars[orderIndex[i]];
  49. }
  50. return result;
  51. }

主要处理的函数:

[cpp] view plain copy

 

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

  1. // car_plate_classify.cpp : 定义控制台应用程序的入口点。
  2. //
  3. #include "stdafx.h"
  4. #include <cv.h>
  5. #include <highgui.h>
  6. #include <cvaux.h>
  7. #include <ml.h>
  8. #include <iostream>
  9. #include <vector>
  10. #include "Plate.h"
  11. #define HORIZONTAL    1
  12. #define VERTICAL    0
  13. using namespace std;
  14. using namespace cv;
  15. CvANN_MLP  ann;
  16. const char strCharacters[] = {'0','1','2','3','4','5','6','7','8','9','B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z'};
  17. const int numCharacters=30;
  18. bool verifySizes(Mat r){
  19. //Char sizes 45x77
  20. float aspect=45.0f/77.0f;
  21. float charAspect= (float)r.cols/(float)r.rows;
  22. float error=0.35;
  23. float minHeight=15;
  24. float maxHeight=28;
  25. //We have a different aspect ratio for number 1, and it can be ~0.2
  26. float minAspect=0.2;
  27. float maxAspect=aspect+aspect*error;
  28. //area of pixels
  29. float area=countNonZero(r);
  30. //bb area
  31. float bbArea=r.cols*r.rows;
  32. //% of pixel in area
  33. float percPixels=area/bbArea;
  34. /*if(DEBUG)
  35. cout << "Aspect: "<< aspect << " ["<< minAspect << "," << maxAspect << "] "  << "Area "<< percPixels <<" Char aspect " << charAspect  << " Height char "<< r.rows << "\n";*/
  36. if(percPixels < 0.8 && charAspect > minAspect && charAspect < maxAspect && r.rows >= minHeight && r.rows < maxHeight)
  37. return true;
  38. else
  39. return false;
  40. }
  41. Mat preprocessChar(Mat in){
  42. //Remap image
  43. int h=in.rows;
  44. int w=in.cols;
  45. int charSize=20;    //统一每个字符的大小
  46. Mat transformMat=Mat::eye(2,3,CV_32F);
  47. int m=max(w,h);
  48. transformMat.at<float>(0,2)=m/2 - w/2;
  49. transformMat.at<float>(1,2)=m/2 - h/2;
  50. Mat warpImage(m,m, in.type());
  51. warpAffine(in, warpImage, transformMat, warpImage.size(), INTER_LINEAR, BORDER_CONSTANT, Scalar(0) );
  52. Mat out;
  53. resize(warpImage, out, Size(charSize, charSize) );
  54. return out;
  55. }
  56. //create the accumulation histograms,img is a binary image, t is 水平或垂直
  57. Mat ProjectedHistogram(Mat img, int t)
  58. {
  59. int sz=(t)?img.rows:img.cols;
  60. Mat mhist=Mat::zeros(1,sz,CV_32F);
  61. for(int j=0; j<sz; j++){
  62. Mat data=(t)?img.row(j):img.col(j);
  63. mhist.at<float>(j)=countNonZero(data);    //统计这一行或一列中,非零元素的个数,并保存到mhist中
  64. }
  65. //Normalize histogram
  66. double min, max;
  67. minMaxLoc(mhist, &min, &max);
  68. if(max>0)
  69. mhist.convertTo(mhist,-1 , 1.0f/max, 0);//用mhist直方图中的最大值,归一化直方图
  70. return mhist;
  71. }
  72. Mat features(Mat in, int sizeData){
  73. //Histogram features
  74. Mat vhist=ProjectedHistogram(in,VERTICAL);
  75. Mat hhist=ProjectedHistogram(in,HORIZONTAL);
  76. //Low data feature
  77. Mat lowData;
  78. resize(in, lowData, Size(sizeData, sizeData) );
  79. //Last 10 is the number of moments components
  80. int numCols=vhist.cols+hhist.cols+lowData.cols*lowData.cols;
  81. Mat out=Mat::zeros(1,numCols,CV_32F);
  82. //Asign values to feature,ANN的样本特征为水平、垂直直方图和低分辨率图像所组成的矢量
  83. int j=0;
  84. for(int i=0; i<vhist.cols; i++)
  85. {
  86. out.at<float>(j)=vhist.at<float>(i);
  87. j++;
  88. }
  89. for(int i=0; i<hhist.cols; i++)
  90. {
  91. out.at<float>(j)=hhist.at<float>(i);
  92. j++;
  93. }
  94. for(int x=0; x<lowData.cols; x++)
  95. {
  96. for(int y=0; y<lowData.rows; y++){
  97. out.at<float>(j)=(float)lowData.at<unsigned char>(x,y);
  98. j++;
  99. }
  100. }
  101. return out;
  102. }
  103. int classify(Mat f){
  104. int result=-1;
  105. Mat output(1, 30, CV_32FC1); //西班牙车牌只有30种字符
  106. ann.predict(f, output);
  107. Point maxLoc;
  108. double maxVal;
  109. minMaxLoc(output, 0, &maxVal, 0, &maxLoc);
  110. //We need know where in output is the max val, the x (cols) is the class.
  111. return maxLoc.x;
  112. }
  113. void train(Mat TrainData, Mat classes, int nlayers){
  114. Mat layers(1,3,CV_32SC1);
  115. layers.at<int>(0)= TrainData.cols;
  116. layers.at<int>(1)= nlayers;
  117. layers.at<int>(2)= 30;
  118. ann.create(layers, CvANN_MLP::SIGMOID_SYM, 1, 1);
  119. //Prepare trainClases
  120. //Create a mat with n trained data by m classes
  121. Mat trainClasses;
  122. trainClasses.create( TrainData.rows, 30, CV_32FC1 );
  123. for( int i = 0; i <  trainClasses.rows; i++ )
  124. {
  125. for( int k = 0; k < trainClasses.cols; k++ )
  126. {
  127. //If class of data i is same than a k class
  128. if( k == classes.at<int>(i) )
  129. trainClasses.at<float>(i,k) = 1;
  130. else
  131. trainClasses.at<float>(i,k) = 0;
  132. }
  133. }
  134. Mat weights( 1, TrainData.rows, CV_32FC1, Scalar::all(1) );
  135. //Learn classifier
  136. ann.train( TrainData, trainClasses, weights );
  137. }
  138. int _tmain(int argc, _TCHAR* argv[])
  139. {
  140. Mat input = imread("test.jpg",CV_LOAD_IMAGE_GRAYSCALE);
  141. Plate mplate;
  142. //Read file storage.
  143. FileStorage fs;
  144. fs.open("OCR.xml", FileStorage::READ);
  145. Mat TrainingData;
  146. Mat Classes;
  147. fs["TrainingDataF15"] >> TrainingData;
  148. fs["classes"] >> Classes;
  149. //训练神经网络
  150. train(TrainingData, Classes, 10);
  151. //dealing image and save each character image into vector<CharSegment>
  152. //Threshold input image
  153. Mat img_threshold;
  154. threshold(input, img_threshold, 60, 255, CV_THRESH_BINARY_INV);
  155. Mat img_contours;
  156. img_threshold.copyTo(img_contours);
  157. //Find contours of possibles characters
  158. vector< vector< Point> > contours;
  159. findContours(img_contours,
  160. contours, // a vector of contours
  161. CV_RETR_EXTERNAL, // retrieve the external contours
  162. CV_CHAIN_APPROX_NONE); // all pixels of each contours
  163. //Start to iterate to each contour founded
  164. vector<vector<Point> >::iterator itc= contours.begin();
  165. //Remove patch that are no inside limits of aspect ratio and area.
  166. while (itc!=contours.end()) {
  167. //Create bounding rect of object
  168. Rect mr= boundingRect(Mat(*itc));
  169. //rectangle(result, mr, Scalar(255,0,0),2);
  170. //Crop image
  171. Mat auxRoi(img_threshold, mr);
  172. if(verifySizes(auxRoi)){
  173. auxRoi=preprocessChar(auxRoi);
  174. //对每一个小方块,提取直方图特征
  175. Mat f=features(auxRoi,15);
  176. //For each segment feature Classify
  177. int character=classify(f);
  178. mplate.chars.push_back(strCharacters[character]);
  179. mplate.charsPos.push_back(mr);
  180. //printf("%c ",strCharacters[character]);
  181. }
  182. ++itc;
  183. }
  184. string licensePlate=mplate.str();
  185. cout<<licensePlate<<endl;
  186. return 0;
  187. }

这边运行时间略长,大概10s以下吧。这就是android不能做太多图像处理的原因,运行速度不给力啊。

上上后面做的评估是对隐层神经元数量和不同分辨率的一种统计,没多大花头,以后要用再看吧。而且车牌识别已经做烂了,没什么动力了~~

好吧,下一篇尝试将车牌检测与识别都移植到android上试试。

 
2014-01-05 15:28 18303人阅读 评论(23)  举报
《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP 分类:
图像处理(25) 《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

版权声明:本文为博主原创文章,未经博主允许不得转载。

一、ANPR简介:

Automatic Number Plate Recognition (ANPR),,是一种使用Optical Character Recognition (OCR)和其他分割、检测方法来读取汽车注册牌照的算法。最好的ANPR算法结果是由红外线照相机拍摄图片得到的。因为车牌的特殊材质,夜间会有逆反射效果,看不清车牌。但是现在我们不使用IR图片,我们使用常规图片,这样就增加了我们检测错误和识别错误的等级,以显示我们的算法有多牛逼【老外的意思,有逆反射的图片我没试过】。下面给出,反射、散射、逆反射的示意图:

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

每个国家的车牌规格都不一样,这里使用西班牙的车牌,左边4个为数字,右边2个为字母,车牌以白色为背景。具体字符间隔如下图所示:

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

ANPR算法大体分为两个步骤:

1.车牌检测:检测车牌在图像中的位置

2.车牌识别:使用OCR算法检测车牌上的字母数字字符

这篇博文今天只讲车牌检测【提取车牌、SVM如何训练】,车牌识别为下一篇博文,搬到android系统为下下篇博文

二、车牌检测

大体也分为两个步骤:

1.图像分割:采用一系列不同的滤波器、形态学操作、轮廓算法和验证算法,提取图像中可能包含车牌的区域。

2.图像分类:对每个图像块使用支持向量机SVM分类,并由代码自动创建正负样本【正:有车牌,负:无车牌】(车牌规格统一:800像素宽,拍摄位置大概离车2-4米远)

整个车牌检测部分,会涉及以下内容:

Sobel filter

Threshold operation

Close morphologic operation

Mask of one filled area

Possible detected plates marked in red (features images)

Detected plates after the SVM classifier

假设车牌图片没有旋转和变形,则车牌分割的一个重要特征是车牌中有大量的垂直边缘。这个特征可以通过在第一阶段剔除没有任何垂直边缘的区域来提取。车牌原图:

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

具体算法步骤如下:

1.将彩色图像转化为灰度图,并采用5*5模版对图像进行高斯模糊来退出由照相机或其他环境噪声(如果不这么做,我们会得到很多垂直边缘,导致错误检测。)

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

2.使用Sobel滤波器求一阶水平方向导数,以此寻找垂直边缘

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

3.使用Otsu自适应阈值算法获得图像二值化的阈值,并由此得到一副二值画图片

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

4.采用闭操作,去除每个垂直边缘线之间的空白空格,并连接所有包含 大量边缘的区域(这步过后,我们将有许多包含车牌的候选区域)

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

5.由于大多数区域并不包含车牌,我们使用轮廓外接矩形的纵横比和区域面积,对这些区域进行区分。

a.首先使用findContours找到外部轮廓

b.使用minAreaRect获得这些轮廓的最小外接矩形,存储在vector向量中

c.使用面积和长宽比,作基本的验证【阈值:长宽比为4.727272,允许误差范围正负40%,面积范围15*15至125*125】

经过判断后的轮廓图:

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

6.由于每个车牌都包含白色背景属性。我们为了更精确的裁剪图像,可以使用floodfill算法【用指定颜色填充某一密闭区域,相当于油漆桶的功能】来提取那些旋转的矩形。

不会翻译,不怎么明白,各位这步直接看代码吧

第一步的原文:get several seeds near the last rotated rectangle center. Then get the minimum size of plate between the width and height, and use it to generate random seeds near the patch center.】总之,得到每个矩形的中心,然后求每个矩形各自长宽的较小值,再用随机数和这个较小值得到中心附近的种子点

第二步的原文:for each seed, we use a floodFill function to draw a new mask image to store the new closest cropping region:

第三部的翻译:对这些裁剪区域,再次用纵横比和区域面积进行验证,再去除图像的旋转,并裁剪图像到统一尺寸,均衡化图像的灰度

下面,分别给出这三步的结果图:

第一步的图像,绿色为矩形中心,黄色为种子点,不知道大家是否能看清楚:

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

第二步的图片,上图有5处种子区域,故有5个模版mask图像【代表最近邻接区域】:

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

第三步的结果图,注意:这里的结果就是训练SVM的正负样本,只要人工挑选一下:

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

下面给出以上部分的完整代码【我讨厌一段段的写:)】

[cpp] view plain copy

 

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

  1. // Car_plate.cpp : 定义控制台应用程序的入口点。
  2. //
  3. #include "stdafx.h"
  4. #include<iostream>
  5. #include <cv.h>
  6. #include <highgui.h>
  7. #include <cvaux.h>
  8. using namespace std;
  9. using namespace cv;
  10. //对minAreaRect获得的最小外接矩形,用纵横比进行判断
  11. bool verifySizes(RotatedRect mr)
  12. {
  13. float error=0.4;
  14. //Spain car plate size: 52x11 aspect 4,7272
  15. float aspect=4.7272;
  16. //Set a min and max area. All other patchs are discarded
  17. int min= 15*aspect*15; // minimum area
  18. int max= 125*aspect*125; // maximum area
  19. //Get only patchs that match to a respect ratio.
  20. float rmin= aspect-aspect*error;
  21. float rmax= aspect+aspect*error;
  22. int area= mr.size.height * mr.size.width;
  23. float r= (float)mr.size.width / (float)mr.size.height;
  24. if(r<1)
  25. r= (float)mr.size.height / (float)mr.size.width;
  26. if(( area < min || area > max ) || ( r < rmin || r > rmax )){
  27. return false;
  28. }else{
  29. return true;
  30. }
  31. }
  32. //直方图均衡化
  33. Mat histeq(Mat in)
  34. {
  35. Mat out(in.size(), in.type());
  36. if(in.channels()==3){
  37. Mat hsv;
  38. vector<Mat> hsvSplit;
  39. cvtColor(in, hsv, CV_BGR2HSV);
  40. split(hsv, hsvSplit);
  41. equalizeHist(hsvSplit[2], hsvSplit[2]);
  42. merge(hsvSplit, hsv);
  43. cvtColor(hsv, out, CV_HSV2BGR);
  44. }else if(in.channels()==1){
  45. equalizeHist(in, out);
  46. }
  47. return out;
  48. }
  49. int _tmain(int argc, _TCHAR* argv[])
  50. {
  51. Mat img_gray = imread("test.jpg",CV_LOAD_IMAGE_GRAYSCALE);
  52. Mat input = imread("test.jpg");
  53. //char res[20];
  54. //apply a Gaussian blur of 5 x 5 and remove noise
  55. blur(img_gray,img_gray,Size(5,5));
  56. //Finde vertical edges. Car plates have high density of vertical lines
  57. Mat img_sobel;
  58. Sobel(img_gray, img_sobel, CV_8U, 1, 0, 3, 1, 0, BORDER_DEFAULT);//xorder=1,yorder=0,kernelsize=3
  59. //apply a threshold filter to obtain a binary image through Otsu's method
  60. Mat img_threshold;
  61. threshold(img_sobel, img_threshold, 0, 255, CV_THRESH_OTSU+CV_THRESH_BINARY);
  62. //Morphplogic operation close:remove blank spaces and connect all regions that have a high number of edges
  63. Mat element = getStructuringElement(MORPH_RECT, Size(17, 3) );
  64. morphologyEx(img_threshold, img_threshold, CV_MOP_CLOSE, element);
  65. //Find 轮廓 of possibles plates
  66. vector< vector< Point> > contours;
  67. findContours(img_threshold,
  68. contours, // a vector of contours
  69. CV_RETR_EXTERNAL, // 提取外部轮廓
  70. CV_CHAIN_APPROX_NONE); // all pixels of each contours
  71. //Start to iterate to each contour founded
  72. vector<vector<Point> >::iterator itc= contours.begin();
  73. vector<RotatedRect> rects;
  74. //Remove patch that are no inside limits of aspect ratio and area.
  75. while (itc!=contours.end()) {
  76. //Create bounding rect of object
  77. RotatedRect mr= minAreaRect(Mat(*itc));
  78. if( !verifySizes(mr)){
  79. itc= contours.erase(itc);
  80. }else{
  81. ++itc;
  82. rects.push_back(mr);
  83. }
  84. }
  85. // Draw blue contours on a white image
  86. cv::Mat result;
  87. //input.copyTo(result);
  88. //cv::drawContours(result,contours,
  89. //  -1, // draw all contours
  90. //  cv::Scalar(0,0,255), // in blue
  91. //  3); // with a thickness of 1
  92. for(int i=0; i< rects.size(); i++)
  93. {
  94. //For better rect cropping for each posible box
  95. //Make floodfill algorithm because the plate has white background
  96. //And then we can retrieve more clearly the contour box
  97. circle(result, rects[i].center, 3, Scalar(0,255,0), -1);
  98. //get the min size between width and height
  99. float minSize=(rects[i].size.width < rects[i].size.height)?rects[i].size.width:rects[i].size.height;
  100. minSize=minSize-minSize*0.5;
  101. //initialize rand and get 5 points around center for floodfill algorithm
  102. srand ( time(NULL) );
  103. //Initialize floodfill parameters and variables
  104. Mat mask;
  105. mask.create(input.rows + 2, input.cols + 2, CV_8UC1);
  106. mask= Scalar::all(0);
  107. int loDiff = 30;
  108. int upDiff = 30;
  109. int connectivity = 4;
  110. int newMaskVal = 255;
  111. int NumSeeds = 10;
  112. Rect ccomp;
  113. int flags = connectivity + (newMaskVal << 8 ) + CV_FLOODFILL_FIXED_RANGE + CV_FLOODFILL_MASK_ONLY;
  114. for(int j=0; j<NumSeeds; j++){
  115. Point seed;
  116. seed.x=rects[i].center.x+rand()%(int)minSize-(minSize/2);
  117. seed.y=rects[i].center.y+rand()%(int)minSize-(minSize/2);
  118. circle(result, seed, 1, Scalar(0,255,255), -1);
  119. int area = floodFill(input, mask, seed, Scalar(255,0,0), &ccomp, Scalar(loDiff, loDiff, loDiff), Scalar(upDiff, upDiff, upDiff), flags);
  120. }
  121. //sprintf(res,"result%d.jpg",i);
  122. //imwrite(res,mask);
  123. //Check new floodfill mask match for a correct patch.
  124. //Get all points detected for get Minimal rotated Rect
  125. vector<Point> pointsInterest;
  126. Mat_<uchar>::iterator itMask= mask.begin<uchar>();
  127. Mat_<uchar>::iterator end= mask.end<uchar>();
  128. for( ; itMask!=end; ++itMask)
  129. if(*itMask==255)
  130. pointsInterest.push_back(itMask.pos());
  131. RotatedRect minRect = minAreaRect(pointsInterest);
  132. if(verifySizes(minRect)){
  133. // rotated rectangle drawing
  134. Point2f rect_points[4]; minRect.points( rect_points );
  135. for( int j = 0; j < 4; j++ )
  136. line( result, rect_points[j], rect_points[(j+1)%4], Scalar(0,0,255), 1, 8 );
  137. //Get rotation matrix
  138. float r= (float)minRect.size.width / (float)minRect.size.height;
  139. float angle=minRect.angle;
  140. if(r<1)
  141. angle=90+angle;
  142. Mat rotmat= getRotationMatrix2D(minRect.center, angle,1);
  143. //Create and rotate image
  144. Mat img_rotated;
  145. warpAffine(input, img_rotated, rotmat, input.size(), CV_INTER_CUBIC);
  146. //Crop image
  147. Size rect_size=minRect.size;
  148. if(r < 1)
  149. swap(rect_size.width, rect_size.height);
  150. Mat img_crop;
  151. getRectSubPix(img_rotated, rect_size, minRect.center, img_crop);
  152. Mat resultResized;
  153. resultResized.create(33,144, CV_8UC3);
  154. resize(img_crop, resultResized, resultResized.size(), 0, 0, INTER_CUBIC);
  155. //Equalize croped image
  156. Mat grayResult;
  157. cvtColor(resultResized, grayResult, CV_BGR2GRAY);
  158. blur(grayResult, grayResult, Size(3,3));
  159. grayResult=histeq(grayResult);
  160. /*  if(1){
  161. stringstream ss(stringstream::in | stringstream::out);
  162. ss << "haha" << "_" << i << ".jpg";
  163. imwrite(ss.str(), grayResult);
  164. }*/
  165. //output.push_back(Plate(grayResult,minRect.boundingRect()));
  166. }
  167. }
  168. //imshow("car_plate",result);
  169. waitKey(0);
  170. return 0;
  171. }

注意上述代码末尾的注释部分:

[cpp] view plain copy

 

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

  1. <span style="white-space:pre">      </span>if(1){
  2. stringstream ss(stringstream::in | stringstream::out);
  3. ss << "haha" << "_" << i << ".jpg";
  4. imwrite(ss.str(), grayResult);
  5. }

以上部分,就是自动生成正负样本的代码。比人工去QQ截图好多了:)

在介绍SVM车牌分类之前,我介绍怎么训练SVM【注意:SVM的实现是个庞大的工程,我一直没有自己弄过,这里使用的还是opencv封装的SVM】

如何训练:
  正样本75张包含车牌的图像和35张不包含车牌的144*33图像。【还有其他更好的特征来训练SVM,PCA,傅立叶变换,纹理分析等等】。
如何获取样本及存放训练数据。
   通过上述图像分割步骤,我们可以得到车牌及非车牌图像,我们把二者都执行reshaple(1,1),再存放到trainImage的矩阵中,并修改对应trainLables矩阵的0-1值,然后把trainData改为32为浮点数系,再把trainData和trainLabel直接写进xml文件【也就是说xml中包含了样本图像的像素值和样本分类标记】

具体代码:

[cpp] view plain copy

 

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

  1. Mat classes;//(numPlates+numNoPlates, 1, CV_32FC1);
  2. Mat trainingData;//(numPlates+numNoPlates, imageWidth*imageHeight, CV_32FC1 );
  3. Mat trainingImages;
  4. vector<int> trainingLabels;
  5. for(int i=0; i< numPlates; i++)
  6. {
  7. stringstream ss(stringstream::in | stringstream::out);
  8. ss << path_Plates << i << ".jpg";
  9. Mat img=imread(ss.str(), 0);
  10. img= img.reshape(1, 1);
  11. trainingImages.push_back(img);
  12. trainingLabels.push_back(1);
  13. }
  14. for(int i=0; i< numNoPlates; i++)
  15. {
  16. stringstream ss(stringstream::in | stringstream::out);
  17. ss << path_NoPlates << i << ".jpg";
  18. Mat img=imread(ss.str(), 0);
  19. img= img.reshape(1, 1);
  20. trainingImages.push_back(img);
  21. trainingLabels.push_back(0);
  22. }
  23. Mat(trainingImages).copyTo(trainingData);
  24. //trainingData = trainingData.reshape(1,trainingData.rows);
  25. trainingData.convertTo(trainingData, CV_32FC1);
  26. Mat(trainingLabels).copyTo(classes);
  27. FileStorage fs("SVM.xml", FileStorage::WRITE);
  28. fs << "TrainingData" << trainingData;
  29. fs << "classes" << classes;
  30. fs.release();

以上代码,可以自己另外建一个工程,认为设置一下正负样本的数量numPlates和numNoPlates,正负样本存储的路径path_Plates和path_NoPlates。这样我们就得到了存放正负样本的SVM.XML文件了。

最后,给出使用Opencv提供的SVM分类器,对图像进行分了的完整代码【对一副图像判断其中是否含有西班牙车牌】:

劳什子外国人搞了车牌类,好吧,我挑和本文有关的都贴出来吧

[cpp] view plain copy

 

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

  1. #ifndef Plate_h
  2. #define Plate_h
  3. #include <string.h>
  4. #include <vector>
  5. #include <cv.h>
  6. #include <highgui.h>
  7. #include <cvaux.h>
  8. using namespace std;
  9. using namespace cv;
  10. class Plate{
  11. public:
  12. Plate();
  13. Plate(Mat img, Rect pos);
  14. string str();
  15. Rect position;
  16. Mat plateImg;
  17. vector<char> chars;
  18. vector<Rect> charsPos;
  19. };
  20. #endif

这里,我们只要实现上述Plate类的构造函数就行了

[cpp] view plain copy

 

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

  1. Plate::Plate(Mat img, Rect pos){
  2. plateImg=img;
  3. position=pos;
  4. }

下面我再次给出完整代码,不过大家重点关注如何配置SVM就行了:

[cpp] view plain copy

 

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

  1. // car_plate_svm.cpp : 定义控制台应用程序的入口点。
  2. //
  3. #include "stdafx.h"
  4. #include<iostream>
  5. #include <cv.h>
  6. #include <highgui.h>
  7. #include <cvaux.h>
  8. #include "Plate.h"
  9. using namespace std;
  10. using namespace cv;
  11. //对minAreaRect获得的最小外接矩形,用纵横比进行判断
  12. bool verifySizes(RotatedRect mr)
  13. {
  14. float error=0.4;
  15. //Spain car plate size: 52x11 aspect 4,7272
  16. float aspect=4.7272;
  17. //Set a min and max area. All other patchs are discarded
  18. int min= 15*aspect*15; // minimum area
  19. int max= 125*aspect*125; // maximum area
  20. //Get only patchs that match to a respect ratio.
  21. float rmin= aspect-aspect*error;
  22. float rmax= aspect+aspect*error;
  23. int area= mr.size.height * mr.size.width;
  24. float r= (float)mr.size.width / (float)mr.size.height;
  25. if(r<1)
  26. r= (float)mr.size.height / (float)mr.size.width;
  27. if(( area < min || area > max ) || ( r < rmin || r > rmax )){
  28. return false;
  29. }else{
  30. return true;
  31. }
  32. }
  33. Mat histeq(Mat in)
  34. {
  35. Mat out(in.size(), in.type());
  36. if(in.channels()==3){
  37. Mat hsv;
  38. vector<Mat> hsvSplit;
  39. cvtColor(in, hsv, CV_BGR2HSV);
  40. split(hsv, hsvSplit);
  41. equalizeHist(hsvSplit[2], hsvSplit[2]);
  42. merge(hsvSplit, hsv);
  43. cvtColor(hsv, out, CV_HSV2BGR);
  44. }else if(in.channels()==1){
  45. equalizeHist(in, out);
  46. }
  47. return out;
  48. }
  49. vector<Plate> segment(Mat input){
  50. vector<Plate> output;
  51. //char res[20];
  52. //apply a Gaussian blur of 5 x 5 and remove noise
  53. Mat img_gray;
  54. cvtColor(input, img_gray, CV_BGR2GRAY);
  55. blur(img_gray, img_gray, Size(5,5));
  56. //Finde vertical edges. Car plates have high density of vertical lines
  57. Mat img_sobel;
  58. Sobel(img_gray, img_sobel, CV_8U, 1, 0, 3, 1, 0, BORDER_DEFAULT);//xorder=1,yorder=0,kernelsize=3
  59. //apply a threshold filter to obtain a binary image through Otsu's method
  60. Mat img_threshold;
  61. threshold(img_sobel, img_threshold, 0, 255, CV_THRESH_OTSU+CV_THRESH_BINARY);
  62. //Morphplogic operation close:remove blank spaces and connect all regions that have a high number of edges
  63. Mat element = getStructuringElement(MORPH_RECT, Size(17, 3) );
  64. morphologyEx(img_threshold, img_threshold, CV_MOP_CLOSE, element);
  65. //Find 轮廓 of possibles plates
  66. vector< vector< Point> > contours;
  67. findContours(img_threshold,
  68. contours, // a vector of contours
  69. CV_RETR_EXTERNAL, // 提取外部轮廓
  70. CV_CHAIN_APPROX_NONE); // all pixels of each contours
  71. //Start to iterate to each contour founded
  72. vector<vector<Point> >::iterator itc= contours.begin();
  73. vector<RotatedRect> rects;
  74. //Remove patch that are no inside limits of aspect ratio and area.
  75. while (itc!=contours.end()) {
  76. //Create bounding rect of object
  77. RotatedRect mr= minAreaRect(Mat(*itc));
  78. if( !verifySizes(mr)){
  79. itc= contours.erase(itc);
  80. }else{
  81. ++itc;
  82. rects.push_back(mr);
  83. }
  84. }
  85. //// Draw blue contours on a white image
  86. cv::Mat result;
  87. input.copyTo(result);
  88. //cv::drawContours(result,contours,
  89. //  -1, // draw all contours
  90. //  cv::Scalar(255,0,0), // in blue
  91. //  1); // with a thickness of 1
  92. for(int i=0; i< rects.size(); i++)
  93. {
  94. //For better rect cropping for each posible box
  95. //Make floodfill algorithm because the plate has white background
  96. //And then we can retrieve more clearly the contour box
  97. circle(result, rects[i].center, 3, Scalar(0,255,0), -1);
  98. //get the min size between width and height
  99. float minSize=(rects[i].size.width < rects[i].size.height)?rects[i].size.width:rects[i].size.height;
  100. minSize=minSize-minSize*0.5;
  101. //initialize rand and get 5 points around center for floodfill algorithm
  102. srand ( time(NULL) );
  103. //Initialize floodfill parameters and variables
  104. Mat mask;
  105. mask.create(input.rows + 2, input.cols + 2, CV_8UC1);
  106. mask= Scalar::all(0);
  107. int loDiff = 30;
  108. int upDiff = 30;
  109. int connectivity = 4;
  110. int newMaskVal = 255;
  111. int NumSeeds = 10;
  112. Rect ccomp;
  113. int flags = connectivity + (newMaskVal << 8 ) + CV_FLOODFILL_FIXED_RANGE + CV_FLOODFILL_MASK_ONLY;
  114. for(int j=0; j<NumSeeds; j++){
  115. Point seed;
  116. seed.x=rects[i].center.x+rand()%(int)minSize-(minSize/2);
  117. seed.y=rects[i].center.y+rand()%(int)minSize-(minSize/2);
  118. circle(result, seed, 1, Scalar(0,255,255), -1);
  119. int area = floodFill(input, mask, seed, Scalar(255,0,0), &ccomp, Scalar(loDiff, loDiff, loDiff), Scalar(upDiff, upDiff, upDiff), flags);
  120. }
  121. //sprintf(res,"result%d.jpg",i);
  122. //imwrite(res,mask);
  123. //Check new floodfill mask match for a correct patch.
  124. //Get all points detected for get Minimal rotated Rect
  125. vector<Point> pointsInterest;
  126. Mat_<uchar>::iterator itMask= mask.begin<uchar>();
  127. Mat_<uchar>::iterator end= mask.end<uchar>();
  128. for( ; itMask!=end; ++itMask)
  129. if(*itMask==255)
  130. pointsInterest.push_back(itMask.pos());
  131. RotatedRect minRect = minAreaRect(pointsInterest);
  132. if(verifySizes(minRect)){
  133. // rotated rectangle drawing
  134. Point2f rect_points[4]; minRect.points( rect_points );
  135. for( int j = 0; j < 4; j++ )
  136. line( result, rect_points[j], rect_points[(j+1)%4], Scalar(0,0,255), 1, 8 );
  137. //Get rotation matrix
  138. float r= (float)minRect.size.width / (float)minRect.size.height;
  139. float angle=minRect.angle;
  140. if(r<1)
  141. angle=90+angle;
  142. Mat rotmat= getRotationMatrix2D(minRect.center, angle,1);
  143. //Create and rotate image
  144. Mat img_rotated;
  145. warpAffine(input, img_rotated, rotmat, input.size(), CV_INTER_CUBIC);
  146. //Crop image
  147. Size rect_size=minRect.size;
  148. if(r < 1)
  149. swap(rect_size.width, rect_size.height);
  150. Mat img_crop;
  151. getRectSubPix(img_rotated, rect_size, minRect.center, img_crop);
  152. Mat resultResized;
  153. resultResized.create(33,144, CV_8UC3);
  154. resize(img_crop, resultResized, resultResized.size(), 0, 0, INTER_CUBIC);
  155. //Equalize croped image
  156. Mat grayResult;
  157. cvtColor(resultResized, grayResult, CV_BGR2GRAY);
  158. blur(grayResult, grayResult, Size(3,3));
  159. grayResult=histeq(grayResult);
  160. /*  if(1){
  161. stringstream ss(stringstream::in | stringstream::out);
  162. ss << "haha" << "_" << i << ".jpg";
  163. imwrite(ss.str(), grayResult);
  164. }*/
  165. output.push_back(Plate(grayResult,minRect.boundingRect()));
  166. }
  167. }
  168. //imshow("car_plate",result);
  169. //waitKey(0);
  170. return output;
  171. }
  172. int _tmain(int argc, _TCHAR* argv[])
  173. {
  174. Mat input = imread("test.jpg");
  175. vector<Plate> posible_regions = segment(input);
  176. //SVM for each plate region to get valid car plates
  177. //Read file storage.
  178. FileStorage fs;
  179. fs.open("SVM.xml", FileStorage::READ);
  180. Mat SVM_TrainingData;
  181. Mat SVM_Classes;
  182. fs["TrainingData"] >> SVM_TrainingData;
  183. fs["classes"] >> SVM_Classes;
  184. //Set SVM params
  185. CvSVMParams SVM_params;
  186. SVM_params.svm_type = CvSVM::C_SVC;
  187. SVM_params.kernel_type = CvSVM::LINEAR; //CvSVM::LINEAR;
  188. SVM_params.degree = 0;
  189. SVM_params.gamma = 1;
  190. SVM_params.coef0 = 0;
  191. SVM_params.C = 1;
  192. SVM_params.nu = 0;
  193. SVM_params.p = 0;
  194. SVM_params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 1000, 0.01);
  195. //Train SVM
  196. CvSVM svmClassifier(SVM_TrainingData, SVM_Classes, Mat(), Mat(), SVM_params);
  197. //For each possible plate, classify with svm if it's a plate or no
  198. vector<Plate> plates;
  199. for(int i=0; i< posible_regions.size(); i++)
  200. {
  201. Mat img=posible_regions[i].plateImg;
  202. Mat p= img.reshape(1, 1);
  203. p.convertTo(p, CV_32FC1);
  204. int response = (int)svmClassifier.predict( p );
  205. /*if(response==1)
  206. plates.push_back(posible_regions[i]);*/
  207. printf("%d.jpg分类结果:%d\n",i,response);
  208. }
  209. return 0;
  210. }

好吧,今天到此为止了。还是那张原图,由于图像分割后产生3个候选车牌,所以SVM分类结果为:

《Mastering Opencv ...读书笔记系列》车牌识别(II)-LMLPHP

这里关于OPENCV的各种函数配置,我一点没提,因为如果不懂原理,就不要用人家成熟的东西,否则永远被动,被opencv牵着走。

05-02 21:20