前面的文章中我们主要介绍了车牌定位的相关技术,但是定位出来的相关区域可能并非是真实的车牌区域,EasyPR通过SVM支持向量机,一种机器学习算法来判定截取的图块是否是真的“车牌”,本节主要对相关的技术做详细的介绍。
注:SVM相关内容可以详细参考周志华老师的《机器学习》和一篇名为《支持向量机通俗导论(理解SVM的三层境界)》的文章。
一、SVM简介
支持向量机,其英文名为 support vector machine,故一般简称SVM,通俗来讲,它是一种二类分类模型,其基本模型定义为特征空间上的间隔最大的线性分类器,其学习策略便是间隔最大化,最终可转化为一个凸二次规划问题的求解。
如上图所示: 距离超平面最近的几个训练样本点被称之为支持向量(support vector),两个异类支持向量到超平面的距离之和被称之为间隔。对应的SVM的基本型如下:
上述问题本身是一个凸二次规划问题,通过拉格朗日乘子法可得到其对偶问题。
上述两个公式非常重要,简直是核心公式。
得到上面的两个公式,再带回L中把去w和b消掉,得到如下公式:
从对偶问题解出的拉格朗日乘子对应着训练样本(xi,yi)。注意到上式中有不等式约束,因此上述过程需满足KKT条件,即要求
支持向量机有一个重要的性质,训练完成后,大部分的训练样本都不需要保留,最终模型仅与支持向量有关。
接下来就是如何求解这个二次规划问题,可使用通用的二次规划算法来求解,也可以利用问题本身的特性,采用SMO算法来求解。
上面是对SVM的理论做了极其简单的介绍,对支持向量机理论感兴趣的童鞋可以做进一步的研究。
二、车牌判别
opencv中对SVM进行了集成,调用opencv的SVM接口是十分方便的,对于定位的车牌区域,首先获取其车牌特征,这边采用的是LBP算子,具体内容上一节做了详细的介绍。SVM调用的类是opencv 机器学习模块中的SVM类,具体的代码如下所示:
1 typedef void (*svmCallback)(const cv::Mat& image, cv::Mat& features); 2 cv::Ptr<ml::SVM> svm_; 3 svmCallback extractFeature; 4 5 PlateJudge::PlateJudge() { 6 svm_ = ml::SVM::load<ml::SVM>(kDefaultSvmPath); 7 extractFeature = getLBPFeatures; 8 } 9 10 void PlateJudge::LoadModel(std::string path) { 11 if (path != std::string(kDefaultSvmPath)) { 12 if (!svm_->empty()) 13 svm_->clear(); 14 15 svm_ = ml::SVM::load<ml::SVM>(path); 16 } 17 } 18 19 int PlateJudge::plateJudge(const Mat &inMat, int &result) { 20 Mat features; 21 extractFeature(inMat, features); 22 float response = svm_->predict(features); 23 result = (int)response; 24 25 return 0; 26 }
通过 SVM::load() 函数加载训练好的SVM参数文件,然后调用 predict()函数对当前区域进行判定,是否为车牌。
三、SVM训练
svm训练通过类SvmTrain 来进行,具体的训练函数如下所示:
1 void SvmTrain::train() { 2 svm_ = cv::ml::SVM::create(); 3 svm_->setType(cv::ml::SVM::C_SVC); 4 svm_->setKernel(cv::ml::SVM::RBF); 5 svm_->setDegree(0.1); 6 svm_->setGamma(0.1); 7 svm_->setCoef0(0.1); 8 svm_->setC(1); 9 svm_->setNu(0.1); 10 svm_->setP(0.1); 11 svm_->setTermCriteria(cvTermCriteria(CV_TERMCRIT_ITER, 20000, 0.0001)); 12 13 auto train_data = tdata(); 14 15 fprintf(stdout, ">> Training SVM model, please wait... "); 16 long start = utils::getTimestamp(); 17 svm_->train(train_data); 18 19 long end = utils::getTimestamp(); 20 fprintf(stdout, ">> Training done. Time elapse: %ldms ", end - start); 21 fprintf(stdout, ">> Saving model file... "); 22 svm_->save(svm_xml_); 23 24 fprintf(stdout, ">> Your SVM Model was saved to %s ", svm_xml_); 25 fprintf(stdout, ">> Testing... "); 26 27 this->test(); 28 29 }
kernel_type:SVM的内核类型(4种):
CvSVM::POLY : 多项式内核:-- polynomial: (gamma*u'*v + coef0)^degree
CvSVM::RBF : 高斯核-- radial basis function: exp(-gamma*|u-v|^2)
CvSVM::SIGMOID:Sigmoid函数内核-- sigmoid: tanh(gamma*u'*v + coef0)
svm_type:指定SVM的类型(5种):
1、CvSVM::C_SVC : C类支撑向量分类机。 n类分组 (n≥2),容许用异常值处罚因子C进行不完全分类。
2、CvSVM::NU_SVC : 类支撑向量分类机。n类似然不完全分类的分类器。参数为庖代C(其值在区间【0,1】中,nu越大,决定计划鸿沟越腻滑)。
3、CvSVM::ONE_CLASS : 单分类器,所有的练习数据提取自同一个类里,然后SVM建树了一个分界线以分别该类在特点空间中所占区域和其它类在特点空间中所占区域。
4、CvSVM::EPS_SVR : 类支撑向量回归机。练习集中的特点向量和拟合出来的超平面的间隔须要小于p。异常值处罚因子C被采取。
5、CvSVM::NU_SVR : 类支撑向量回归机。 庖代了 p。
degree:内核函数(POLY)的参数degree。
gamma:内核函数(POLY/ RBF/ SIGMOID)的参数。
coef0:内核函数(POLY/ SIGMOID)的参数coef0。
Cvalue:SVM类型(C_SVC/ EPS_SVR/ NU_SVR)的参数C。
nu:SVM类型(NU_SVC/ ONE_CLASS/ NU_SVR)的参数 。
p:SVM类型(EPS_SVR)的参数。
class_weights:C_SVC中的可选权重,赋给指定的类,乘以C今后变成 class_weights*C 。所以这些权重影响不合类此外错误分类处罚项。权重越大,某一类此外误分类数据的处罚项就越大。
term_crit:SVM的迭代练习过程的中断前提,解决项目组受束缚二次最优题目。您可以指定的公差和或最大迭代次数。
对于训练数据 tdata()的获取,如下所示:
1 cv::Ptr<cv::ml::TrainData> SvmTrain::tdata() { 2 this->prepare(); 3 4 cv::Mat samples; 5 std::vector<int> responses; 6 7 for (auto f : train_file_list_) { 8 auto image = cv::imread(f.file); 9 if (!image.data) { 10 fprintf(stdout, ">> Invalid image: %s ignore. ", f.file.c_str()); 11 continue; 12 } 13 cv::Mat feature; 14 getLBPFeatures(image, feature); 15 feature = feature.reshape(1, 1); 16 17 samples.push_back(feature); 18 responses.push_back(int(f.label)); 19 } 20 21 cv::Mat samples_, responses_; 22 samples.convertTo(samples_, CV_32FC1); 23 cv::Mat(responses).copyTo(responses_); 24 25 return cv::ml::TrainData::create(samples_, cv::ml::SampleTypes::ROW_SAMPLE, 26 responses_); 27 }
对于训练得到的结果,将保存在 svm_xml_文件中。