本文接着上一篇 《自动车牌识别(ANPR)练习项目学习笔记1(基于opencv)》 继续做笔记
C. SVM分类算法的原始数据准备、训练和判别
上一篇结束的时候获得了 vector<Plate> 类型的 posible_regions , 里面存放了被认为有可能是牌照区域的Plate。Plate类没有搞明白,但是这里要用到的 posible_regions[i].plateImg 是一个Mat 类型的灰度图。
这一部分有两步,第一是准备原始数据,得到SVM.xml 。第二是用这些数据进行训练和判别。最终目的是把posible_regions分成两类,牌照区域和无效区域。
1. SVM.xml 是由 trainSVM 这个工程实现的。trainSVM.cpp代码如下:
#include <cv.h> #include <highgui.h> #include <cvaux.h> #include <iostream> #include <vector> using namespace std; using namespace cv; int main ( int argc, char** argv ) { cout << "OpenCV Training SVM Automatic Number Plate Recognition\n"; cout << "\n"; char* path_Plates; //是牌照图像的存放路径 char* path_NoPlates; //非牌照图像的存放路径 int numPlates; //是牌照图像的个数(75) int numNoPlates; //非牌照图像的个数(36) int imageWidth=144; int imageHeight=33; //Check if user specify image to process if(argc >= 5 ) { numPlates= atoi(argv[1]); //atoi:alphanumeric to integer 把字符串转换成整形 numNoPlates= atoi(argv[2]); path_Plates= argv[3]; path_NoPlates= argv[4]; }else{ cout << "Usage:\n" << argv[0] << " <num Plate Files> <num Non Plate Files> <path to plate folder files> <path to non plate files> \n"; return 0; } Mat classes;//(numPlates+numNoPlates, 1, CV_32FC1); Mat trainingData;//(numPlates+numNoPlates, imageWidth*imageHeight, CV_32FC1 ); Mat trainingImages; vector<int> trainingLabels; for(int i=0; i< numPlates; i++) { stringstream ss(stringstream::in | stringstream::out); ss << path_Plates << i << ".jpg"; Mat img=imread(ss.str(), 0); img= img.reshape(1, 1); //reshape成1 channel,1 rows, x cols trainingImages.push_back(img); trainingLabels.push_back(1); } for(int i=0; i< numNoPlates; i++) { stringstream ss(stringstream::in | stringstream::out); ss << path_NoPlates << i << ".jpg"; Mat img=imread(ss.str(), 0); img= img.reshape(1, 1); trainingImages.push_back(img); trainingLabels.push_back(0); } Mat(trainingImages).copyTo(trainingData); //trainingData = trainingData.reshape(1,trainingData.rows); trainingData.convertTo(trainingData, CV_32FC1); Mat(trainingLabels).copyTo(classes); FileStorage fs("SVM.xml", FileStorage::WRITE); fs << "TrainingData" << trainingData; fs << "classes" << classes; fs.release(); return 0; }原资料包中没有提供原始数据图片,所以一开始有点困惑。但是PDF文档中说明了情况,原作者采集了75张牌照图片和36张非牌照图片的信息。大体上这些图像是这样子的:
(上一篇中有讲到保存这样的图片,但是我找不到保存到哪里了,否则可以自己产生这样的样本图片。这个问题先放着。)
if(saveRegions){ stringstream ss(stringstream::in | stringstream::out); ss << "tmp/" << filename << "_" << i << ".jpg"; imwrite(ss.str(), grayResult); //imwrite()中用string保存文件名的方法 }根据作者定义的几个变量:char* path_Plates; char* path_NoPlates; 推测作者把这两类图片放在两个文件夹中,路径地址是在DOS命令中输入的时候给出的: path_plate = argv[3]; path_NoPlate = argv[4]; 并且每个文件夹中的图片命名方式是从数字“0”开始,往上递增。
图片的数量也是这样给出的:numPlates = atoi(argv[1]); 其中atoi() 函数将字符串型转换成整型。根据main()函数的第二种定义形式:int main(int argc, char* argv[])可知argv[1] 的类型为char*, 即指向字符串的指针;而atoi( argv[1] ) 就是通过函数 atoi (其声明为:int atoi( const char* nptr ); ) 将argv[1] 指向的字符串转换为整型,因为numPlates是整型。
(我一直用的是控制台应用程序,不知道在什么样的环境下输入argc, argv。)
接下来分别将两个文件夹中的图片信息采集到 trainingData 和 class 中。注意这两个都是 Mat. 如果需要初始化的话,应该是这样:
Mat class( 1, numPlates+numNoPlates, CV_32FC1 );
Mat trainingData( numPlates+numNoPlates, imgWidth*imgHeight , CV_32FC1 );
reshape() 函数是通过改变矩阵的通道数和行数,自动计算列数,从而改变矩阵的形状。 img = img.reshape(1,1) 就是把img 变成单通道的行矩阵。再通过push_back 一行一行压进Mat trainingImges中。(由此看出Mat 类型也是可以push_back的,和 vector一样)。
最终 trainingImages 中装了是牌照图及非牌照图的所有像素灰度值(一个图片一行); trainingLabels中装的是对应的判断值,及“0”或“1”。
接下来做一下格式转换,复制变量: trainingImage -> trainingData traniningLabels -> classes;
发现vector<int> 类型的 trainingLabels 可以直接复制给Mat类型的行矩阵classes.
接下来将 trainingData 和 classes 中的数据写到文件 "SVM.xml"中。 用到了 FileStorage 类。SVM.xml文件大概是这样子的。其中TrainingData 数据太多,把它折叠了。
2. SVM训练和判别
在main函数中,先设置SVM参数,然后调用svmClassifier()函数就完成了训练。按照我仅有的一点印象,应该是写成这样:
CvSVM svmClassifier; svmClassifier.train(SVM_TrainingData, SVM_Classes, Mat(), Mat(), SVM_params);但实际上作者是这样写的:
CvSVM svmClassifier(SVM_TrainingData, SVM_Classes, Mat(), Mat(), SVM_params);不知道两者是否一样。
关于参数该怎么写,opencv手册上没有详细介绍,估计是要根据选择的类型和核函数具体来定。这里确定了SVM类型:使用C支持向量机;核函数类型:线性;终止条件:达到最大迭代次数后终止。
下一步是判别。依次取出possible_regions里面的Plate. 调用svmClassifier.predict() 返回值有1和-1.
每一个样本都是一个行矩阵,矩阵值是像素灰度值。原作者推荐了更好的方法,比如主成分分析。
关于SVM的原理我还不理解,推荐几个链接:
http://www.cnblogs.com/jerrylead/archive/2011/03/13/1982639.html
作者写了SVM系列,全是公式推导。
http://blog.csdn.net/v_july_v/article/details/7624837
长文,很详细。
http://www.zhihu.com/question/21094489
知乎,有立体图未完待续。