自动车牌识别(ANPR)练习项目学习笔记2(基于opencv)

本文接着上一篇 《自动车牌识别(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

知乎,有立体图

到这里已经完成了车牌区域的识别,把不是车牌的区域去除了。下一步将是车牌字符识别。

未完待续。



你可能感兴趣的:(C++,xml,SVM,opencv,ANPR)