最近在研究车牌识别,利用到Opencv的ml.hpp,里面实现了各种机器学习算法,包括harrcascade、traincascade、支持向量机SVM提取HOG梯度向量特征,人工神经网络等等,而车牌识别又分为检测车牌位置和车牌号码识别。
车牌识别的几个步骤:
一、准备样本集(车牌、非车牌)
二、训练样本数据(SVM 支持向量机 提取HOG特征)
三、利用训练得到的分类器去分类车牌(Classify 分类器)
四、准备样本集(车牌号码)
五、训练样本数据(神经网络)
六、利用训练得到的分类器去识别车牌
一、首先要准备样本集
已经上传到CSDN,各位客官可以自行下载。(CSDN版本更新,上传的资源最低设置2分,不能设置为0分的,如果客官没有积分,把邮箱留下,我发给你)
正负样本:http://download.csdn.net/my/uploads
正样本:车牌
负样本:非车牌、干扰因素
样本数量:正负:1401 : 2175
样本大小:136*36 (宽高不固定,但要切合实际比例)
二、训练样本数据(SVM 支持向量机)
注意:样本数据路径不能为中文
这里提供一个SVM在线学习网,是一位台湾的大神写的,不用翻墙,里面介绍了SVM训练的各种参数含义
http://www.csie.ntu.edu.tw/~cjlin/libsvm/
1、环境搭建:
Opencv3.3.0
VS 2017 可以看我前面的博客:VS2017 配置 Opencv3.3.0环境
opencv3.3.0只支持X64位
2、读取样本图片:
用Linux 系统下的dirent.h文件,挺简单的,提供一下学习的链接C++查找文件
如果本地没有这个文件的话,需要去github上找到这个文件,下载clone到项目路径里就可以了。
提供的链接:dirent.h
//获取样本数据路径
vector<string> getFiles(const string folder) {
vector<string> files;
list<string> subfolders;
subfolders.push_back(folder);
#ifdef WIN32
while (!subfolders.empty()) {
std::string current_folder(subfolders.back());
if (*(current_folder.end() - 1) != '/') {
current_folder.append("/*");
}
else {
current_folder.append("*");
}
subfolders.pop_back();
struct _finddata_t file_info;
auto file_handler = _findfirst(current_folder.c_str(), &file_info);
while (file_handler != -1) {
//在Linux系统上文件目录第一、第二个文件都是'.' 和 '..' 省略掉。
if ((!strcmp(file_info.name, ".") || !strcmp(file_info.name, ".."))) {
if (_findnext(file_handler, &file_info) != 0) break;
continue;
}
//判断是否是子目录
if (file_info.attrib & _A_SUBDIR) {
std::string folder(current_folder);
folder.pop_back();
folder.append(file_info.name);
subfolders.push_back(folder.c_str());
}
else {
std::string file_path;
file_path.assign(current_folder.c_str()).pop_back();
file_path.append(file_info.name);
files.push_back(file_path);
}
if (_findnext(file_handler, &file_info) != 0) break;
}
_findclose(file_handler);
}
#else
while (!subfolders.empty()) {
string current_folder(subfolders.back());
if (*(current_folder.end() - 1) != '/') {
current_folder.push_back('/');
}
DIR* pdir = opendir(current_folder.c_str());
subfolders.pop_back();
if (!pdir) {
continue;
}
dirent* dir = NULL;
while ((dir = readdir(pdir)) != NULL) {
struct stat st;
//在Linux系统上文件目录第一、第二个文件都是'.' 和 '..' 省略掉。
if (!strcmp(dir->d_name, ".") || !strcmp(dir->d_name, "..")) {
continue;
}
if (!strcmp(dir->d_name, ".DS_Store")) {
continue;
}
std::string file_path;
file_path.append(current_folder.c_str());
file_path.append(dir->d_name);
if (lstat(file_path.c_str(), &st) < 0) {
continue;
}
if (S_ISDIR(st.st_mode)) {
std::string subfolder(current_folder);
subfolder.append(dir->d_name);
subfolders.push_back(subfolder.c_str());
}
else {
files.push_back(file_path);
}
}
closedir(pdir);
}
#endif
return files;
}
3、创建SVM支持向量机,训练样本数据:
关于什么是HOG,什么又是梯度向量特征提取可以看我博客
深入浅出理解HOG特征—梯度方向直方图
/**--------------SVM(支持向量机)分类器训练与测试--------------------
* SVM:通常用来进行模式识别、分类以及回归分析
* 使用SVM对于查找到的车牌号区域进行判断,进一步确定定位车牌号区域正确
*
* 流程:
* 1、加载全部样本(训练数据) 训练数据有正确的,有错误的。也就是分为车牌区域与非车牌区域
* 2、创建样本数据标签,标记对应样本分类(正确/错误)
* 3、训练、保存
*/
//正样本路径
char * SVM_POS;
//负样本路径
char * SVM_NEG;
//训练得到HOG特征数据保存路径
char * SVM_XML;
typedef struct TrainStruct {
string file; //正负样本路径
int label; //正负样本标识
};
/*
提取HOG特征(像素梯度变化率)
*/
void getSvmHOGFeatures(Mat src, Mat & feature) {
// 参数含义:窗口大小 Size(128, 64) ; Size(16, 16):块大小,目前只支持Size(16, 16) ;
//Size(8, 8):块的滑动步长,大小只支持是单元格cell_size大小的倍数
//cell大小 Size(8, 8)目前只支持Size(8, 8);
//nbins,方向分箱的个数 n表示在一个胞元(cell)中统计梯度的方向数目,例如箱子=9时,在一个胞元内统计9个方向的梯度直方图,每个方向为360/9=40度
HOGDescriptor hog(Size(128, 64), Size(16, 16), Size(8, 8), Size(8, 8), 9);
std::vector<float> descriptor;
// Size dsize = Size(128c,64);
Mat trainImg = Mat(hog.winSize, CV_32S);
resize(src, trainImg, hog.winSize);
//计算读入的图片的Hog特征
hog.compute(trainImg, descriptor, Size(8, 8));
Mat mat_featrue(descriptor);
mat_featrue.copyTo(feature);
}
void tranSVMDATA() {
printf("prepare tain SVM car classify model...\n");
//创建SVM
Ptr<SVM> classifier = SVM::create();
//设置SVM种类
classifier->setType(SVM::C_SVC);//惩罚因子C,默认为0
//设置采用的核函数
//SVM::POLY默认值,当数据量比较大的时候 VM::POLY 是个不错的选择
classifier->setGamma(SVM::SIGMOID);
//设置 C-SVC, epsilon-SVR, and nu-SVR的惩罚因子数(优化),默认值是0
classifier->setC(100);
/*
终止条件类型
TermCriteria::Type 有三个:COUNT, EPS or COUNT + EPS
TermCriteria::Type::COUNT 小于最大迭代次数或者元素数
TermCriteria::Type::MAX_ITER 同上
TermCriteria::Type::EPS 当达到所要求的精度或者参数变化是即停止迭代
//设置终止条件 ,最大迭代10W次
//所要求精度:是0.0001 就是 0.01%
//classifier->setTermCriteria(TermCriteria(TermCriteria::Type::COUNT,10*10000,0.0001));
cvTermCriteria同样设置终止条件
Type::有三个
CV_TERMCRIT_ITER 小于最大迭代次数或者元素数
CV_TERMCRIT_NUMBER 同上
CV_TERMCRIT_EPS 当达到所要求的精度或者参数变化是即停止迭代
*/
// 训练的终止条件,迭代20000次,
//0.00001: 迭代过程中,允许输入数据进行一定的变异,这个变异的比率最高要求的精度是0.0001 ,0.01%
classifier->setTermCriteria(cvTermCriteria(CV_TERMCRIT_ITER, 20000, 0.0001));
//计算训练所用时长
auto time_start = cv::getTickCount();
//存储样本数据的路径和标志(正样本为1,负样本为-1)
vector<TrainStruct> svm_data;
Mat src_mat;
//收集正样本
vector<string> pos_files = getFiles(SVM_POS);
for (auto file : pos_files) {
svm_data.push_back({ file,1 });
}
//收集负样本
vector<string> neg_files = getFiles(SVM_NEG);
for (auto file : neg_files) {
svm_data.push_back({ file,-1 });
}
Mat samples;
vector<int> responses;
//读取数据
for (auto data : svm_data) {
//灰度图方式读取 0灰度 1彩色
Mat img = imread(data.file, 0);
//剔除无用的样本
if (!img.data) {
printf("load imge failed image: %s.\n", data.file.c_str());
continue;
}
//二值化
threshold(img, img, 0, 255, THRESH_BINARY + THRESH_OTSU);
Mat feature;
//获取HOG 特征,
getSvmHOGFeatures(img,feature);
// 修改图片的通道和行列数,其实是将一个图片的矩阵变成一个行向量
//[[1, 2, 3],
// [4, 5, 6],
// [7, 8, 9]]
//转变成[1,2,3,4,5,6,7,8,9]
feature = feature.reshape(1, 1);
// 将一维向量叠加,叠加成一个二维的向量
//param m Added line(Mat).
samples.push_back(feature);
//相应把正负样本标签贴上
responses.push_back(data.label);
}
// 训练数据的格式,OpenCV规定 samples 中的数据都是需要32位浮点型
// 因为TrainData::create 第一个参数是规定死的要cv_32F
samples.convertTo(samples, CV_32FC1);
// samples 将图片和样本标签合并成为一个训练集数据
// 第二个参数的原因是,我们的samples 中的每个图片数据的排列都是一行
auto train_data = TrainData::create(samples, SampleTypes::ROW_SAMPLE, responses);
auto time_end = cv::getTickCount();
auto time_per_frame = (time_end - time_start) / cv::getTickFrequency();
printf("prepare train data time : %3.5f\n", time_per_frame);
time_start = cv::getTickCount();
//线性内核,训练
//参数除了最后一个true,其他均为默认值
//最后一个为true 的原因:则会创建更平衡的验证子集 也就是如果是2类分类的话能得到更准确的结果
classifier->trainAuto(train_data, 10, SVM::getDefaultGrid(SVM::C),
SVM::getDefaultGrid(SVM::GAMMA), SVM::getDefaultGrid(SVM::P),
SVM::getDefaultGrid(SVM::NU), SVM::getDefaultGrid(SVM::COEF),
SVM::getDefaultGrid(SVM::DEGREE), true);
// classifier->train(trainning_mat,ROW_SAMPLE,label); //SVM训练
classifier->save(SVM_XML);
time_end = cv::getTickCount();
time_per_frame = (time_end - time_start) / cv::getTickFrequency();
printf("train finished time: %3.5f ,model save : %s \n", time_per_frame, SVM_XML);
}
三、利用训练得到的分类器去分类车牌(XML) 花了将近一个多小时训练 o(╥﹏╥)o
至此,SVM HOG特征训练完成。先码到这里,剩下的几个步骤看后面再找个时间补上。
四、准备样本集(车牌号码)
五、训练样本数据(神经网络)
六、利用训练得到的分类器去识别车牌
同时将我的资源提供给大家
HOG_SVM_DATA.XML文件
在这段时间的学习里,看到几篇深入学习HOG特征和梯度向量的外国文档,感觉写的不错,分享给各位客官。
https://hal.inria.fr/inria-00548512/document/
https://en.wikipedia.org/wiki/Histogram_of_oriented_gradients
https://software.intel.com/en-us/ipp-dev-reference-histogram-of-oriented-gradients-hog-descriptor
http://www.learnopencv.com/histogram-of-oriented-gradients
http://www.learnopencv.com/handwritten-digits-classification-an-opencv-c-python-tutorial