3 代码实现
本项目主要借助于OpenCV库,在Visual Studio 2017平台开发,用C++语言编写代码。代码主要分为四个模块,分别是特征提取,数据加载,模型训练以及模型测试。下面对这四个模块的代码进行一一解析。
3.1 特征提取
本项目在开发过程中,一共设计了两种特征提取方式,分别是自拟的特征提取和HOG特征提取。下面结合代码对这两种特征提取方式一一介绍。
自拟特征提取的思路是提取每行每列中二值化结果为255的个数。首先,将输入图像经过灰度化转化为灰度图,再经过自适应二值化转化为二值图,最后创建一个一维的矩阵用于存放统计的数据作为特征向量,如下所示。
Mat getFeatures(Mat src,int rows,int cols) {
Mat grayImage;
cvtColor(src, grayImage, COLOR_RGB2GRAY);
Mat img_threshold;
threshold(grayImage, img_threshold, 0, 255, THRESH_OTSU + THRESH_BINARY);
Mat out = Mat::zeros(1, rows+cols, CV_8UC1);
for (int h = 0; h < rows; h++) {
for (int l = 0; l < cols; l++) {
if (img_threshold.at<uchar>(h, l) == 255) {
out.at<uchar>(h)++;
}
}
}
for (int l = 0; l < cols; l++) {
for (int h = 0; h < rows; h++) {
if (img_threshold.at<uchar>(h, l) == 255) {
out.at<uchar>(rows + l)++;
}
}
}
return out;
}
HOG特征提取先要对图像大小进行裁剪,有几种效果较好的图像尺寸,本文选用的是128×64。接着,选取8×8的滑动窗口对图像计算特征,这里直接调用OpenCV中的内置函数。最后将提取的特征保存到矩阵out中,过程如下所示。
Mat HOGgetFeatures(Mat src){
//these parameters work well
HOGDescriptor hog(Size(128, 64), Size(16, 16), Size(8, 8), Size(8, 8), 3);
std::vector<float> descriptor;
// resize input image to (128,64) for compute
Size dsize = Size(128, 64);
Mat trainImg = Mat(dsize, CV_32S);
resize(src, trainImg, dsize);
// compute descripter
hog.compute(trainImg, descriptor, Size(8, 8));
// copy the result
Mat features(descriptor);
Mat out;
transpose(features, out);
return out;
}
经过测试,发现用HOG特征提取的方式,提取到的特征输入SVM中训练,得到的模型准确率更高。因此,本项目最终采用的是HOG特征提取。
3.2 数据加载
数据加载包括从车牌数据加载和非车牌数据加载。本项目将车牌数据和非车牌数据分开保存在特定文件夹中,便于给数据打上标签。通过文件路径访问,从相应文件夹中依次读取数据,通过特征提取生成一维特征向量,同时根据数据所属类别生成一个数据标签,最后将这个一维特征和数据标签分别append()到两个大矩阵里。
void getPlate(Mat &trainingImages, vector<int>& trainingLabels, int getfeatureway) {
string pattern_jpg = "C:\\Users\\nicor\\Desktop\\test_has\\*.jpg"; //从一个文件夹中读取所有图片
vector<cv::String> image_files;
cv::glob(pattern_jpg, image_files);
if (image_files.size() == 0) { //文件夹中没有车牌图片
cout << "No image files[jpg]" << endl;
}
else {
switch (getfeatureway)
{
case Binary:
for (unsigned int frame = 0; frame < image_files.size(); ++frame) {//image_file.size()代表文件中总共的图片个数
Mat src = imread(image_files[frame]);
Mat out = getFeatures(src, src.rows, src.cols);
out = out.reshape(1, 1);
trainingImages.push_back(out);
trainingLabels.push_back(1);
}
break;
case HOG:
for (unsigned int frame = 0; frame < image_files.size(); ++frame) {//image_file.size()代表文件中总共的图片个数
Mat src = imread(image_files[frame]);
Mat out = HOGgetFeatures(src);
out = out.reshape(1, 1);
trainingImages.push_back(out);
trainingLabels.push_back(1);
}
break;
case SIFT:
break;
case LBP:
break;
default:
break;
}
}
}
这段代码主要利用先前定义的HOG特征提取函数,提取每张图像的特征,并reshape成一维向量,最后压栈到特征矩阵中。
这样,我们就完成了训练数据的加载,并生成了输入到模型中训练的特征矩阵和标签矩阵。
3.3 模型训练
训练时首先通过调用OpenCV中的一个机器学习库生成一个SVM对象,接着设置参数初始化这个SVM模型,参数的设置参照一些网上其他人的经验值。具体过程如下所示。
//训练模型
cv::Ptr<cv::ml::SVM> svm_ = cv::ml::SVM::create();
//SVM模型参数设置
svm_->setType(ml::SVM::C_SVC);
svm_->setKernel(cv::ml::SVM::RBF);
svm_->setDegree(0.1);
svm_->setGamma(0.1);
svm_->setCoef0(0.1);
svm_->setC(1);
svm_->setNu(0.1);
svm_->setP(0.1);
svm_->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 20000, 0.0001));
接着,加载数据,并将特征矩阵和标签矩阵转换格式后,生成一个训练数据集,最终输入到SVM中进行训练,过程如下所示。
Mat classes; //标签
Mat trainingData; //数据
Mat trainingImages; //训练数据
vector<int> trainingLabels; //数据标签
//特征提取方式
getPlate(trainingImages, trainingLabels, HOG);
getNoPlate(trainingImages, trainingLabels, HOG);
Mat(trainingImages).copyTo(trainingData);
trainingData.convertTo(trainingData, CV_32FC1); //格式转化后的数据
Mat(trainingLabels).copyTo(classes);
classes.convertTo(classes, CV_32SC1); //格式转换后的标签
Ptr<ml::TrainData> trainData=ml::TrainData::create(trainingData, ml::SampleTypes::ROW_SAMPLE, classes);
最终训练模型时,调用的是CvSVM中的trainAuto函数,这个函数会根据输入的数据自适应训练,自动调整参数,效果好,但耗费时间较长。
svm_->trainAuto(trainData, 10, //自适应训练,自动调整参数,效果好,耗费时间大
ml::SVM::getDefaultGrid(ml::SVM::C),ml::SVM::getDefaultGrid(ml::SVM::GAMMA),
ml::SVM::getDefaultGrid(ml::SVM::P),ml::SVM::getDefaultGrid(ml::SVM::NU),
ml::SVM::getDefaultGrid(ml::SVM::COEF),ml::SVM::getDefaultGrid(ml::SVM::DEGREE), true);
3.4 模型测试
Ptr<ml::SVM> svm_ = cv::ml::StatModel::load<cv::ml::SVM>("C:\\Users\\nicor\\Desktop\\svm_HOG.xml"); //加载训练好的模型
int response = 0;
for (unsigned int frame = 0; frame < image_files.size(); ++frame) {//image_file.size()代表文件中总共的图片个数
Mat src = imread(image_files[frame]);
Mat test = HOGgetFeatures(src);
Mat test_;
test.convertTo(test_, CV_32FC1);
response = 0;
response = (int)(svm_->predict(test_));
if (isplate) {
if (response) {
correct_cnt++;
}
}
else {
if (!response) {
correct_cnt++;
}
}
模型训练结束后,会生成一个.xml文件。测试时,我们首先要将该权重模型加载到程序中,之后从测试集里依次读取图像,通过HOG特征提取生成一维特征向量,最后将该一维特征向量输入到模型中,做一次前向传播得到输出。输出会有两个结果,0或1,分别代表预测的类别为车牌和非车牌。经过统计,我们就可以大概得出模型预测的准确率。
4 测试结果
4.1 SVM模型准确率测试
训练结束后,我们调用代码中的模型测试模块,对训练得到的SVM权重模型的准确率进行测试。跟训练集一样,测试集同样分为车牌测试集和非车牌测试集。在代码中加载该权重模型,分别对两个测试集进行判别,得出如图4-1所示结果。可以看到,训练得到的SVM权重模型对车牌数据和非车牌数据的误判都很少,其中利用HOG特征提取训练所得的模型准确率达到了98%以上。
从测试集中另取一张原始车辆图像,按照车牌识别流程对图像中的车牌进行提取。经过高斯模糊,灰度化,边缘检测,二值化,闭操作,取轮廓,轮廓外接矩形框,按车牌尺寸筛选车牌区域等一系列操作后,得到如图4-2所示结果。
此时,提取的区域包含车牌区域和非车牌区域,利用常规方法已很难将它们分离开来,此时就用到了我们上面训练所得的SVM模型进行判别。将这些疑似区域旋转仿射至水平后,尺寸统一调整为136x36,经过HOG特征提取后生成相应一维特征向量,输入SVM模型中。根据模型输出结果,若为1,则表示为车牌;若为0,则是非车牌。最终从图像中提取的车牌如图4-3所示。
此外,本车牌定位算法配合SVM分类模型有较好的适用性,在多车辆复杂图像中也能有效地提取到车牌区域,结果如图4-4所示。