HOG+SVM 实现车辆识别
做了一个车辆检测的小demo,熟悉流程~
说明:最开始使用裁剪好的数据集,训练后发现效果很差,所以自己裁剪数据集进行训练,效果更好一些
1、数据集下载
数据集采用了Visdrone2018数据集(本来是给YOLO3准备的【手动表情】)
数据下载的主页:http://www.aiskyeye.com/my/downloadD
在本次训练中,只用到了Task1:Object Detection in Images的部分实验数据:Trainset(1.44GB)
2、数据裁剪
训练时需要正样本和负样本,因为裁剪效果好一些,所以自己动手吧!检测的尺寸一共有2种,一种是64 * 128,一种是64 * 64,这里我选择64 * 64的尺寸,裁剪正样本为检测目标(这里为车辆),负样本为随机背景
裁剪工具:ImageCropper
裁剪时把裁剪尺寸设为64 * 64,但由于放缩实际裁剪大小并不是64 * 64,所以要对裁剪图片进行统一缩放:
string data_path = "E:/车辆跟随项目/车辆检测/dym_car/hard1/hard1.txt";
ifstream input_data(data_path);
vector<string> data_sample_path;
string str;
int data_num;
while (getline(input_data, str)) data_sample_path.push_back(str);
data_num = data_sample_path.size();
input_data.close();
for (int i = 0; i < data_num; ++i)
{
cout << i << endl;
Mat src = imread(data_sample_path[i], 0);
Mat dst = Mat::zeros(64, 64, CV_8UC3); //8位无符号3通道
resize(src, dst, dst.size());
imwrite(data_sample_path[i], dst);
}
3、批处理小功能
这里补充一下一个批处理小方法,因为要对文件夹中所有图片处理,所以要得到包含所有图片名称的.txt文件
(1)建立一个.txt文件,任意命名,如a.txt;
(2)在其中写入;
@echo off
dir /s/b *.* > b.txt exit
b.txt就是你要生成的所有图片名称的文件;
(3)把a.txt移入图片所在文件夹;
(4)将a.txt另存为a.bat(可以直接改后缀,也可以另存为—a.bat—所有文件—保存);
(5)双击a.bat文件,这时候就可以得到b.txt了,注意删掉不必要的文件名称(如a.txt,a.bat,b.txt);
4、训练样本
最初始的训练非常简单,只有200个正样本和100个负样本(裁数据太麻烦啦),效果非常不好,后来加入了困难样本100个二次训练,目前还没加二次正样本训练,但是效果好了那么一丢丢,困难样本制作就是把误检的框框作为负样本训练:
string pos_path = "E:/车辆跟随项目/车辆检测/dym_car/pos1/pos1.txt";
string neg_path = "E:/车辆跟随项目/车辆检测/dym_car/neg1/neg1.txt";
string hard_path = "E:/车辆跟随项目/车辆检测/dym_car/hard1/hard1.txt";//*
Mat sample_feature_mat, sample_label_mat;
ifstream input_pos_sample(pos_path);
ifstream input_neg_sample(neg_path);
ifstream input_hard_sample(hard_path);//*
vector<string> pos_sample_path, neg_sample_path, hard_sample_path;//*
////HOG描述子的维数,由图片大小、检测窗口大小、块大小、细胞单元中直方图bin个数决定
int feature_dim;
string str;
int pos_sample_num, neg_sample_num, hard_sample_num;//*
while (getline(input_pos_sample, str)) pos_sample_path.push_back(str);
while (getline(input_neg_sample, str)) neg_sample_path.push_back(str);
while (getline(input_hard_sample, str)) hard_sample_path.push_back(str);//*
pos_sample_num = pos_sample_path.size();
neg_sample_num = neg_sample_path.size();
hard_sample_num = hard_sample_path.size();//*
input_pos_sample.close();
input_neg_sample.close();
input_hard_sample.close();//*
//HOG检测器,用来计算HOG描述子的
//检测窗口(64,64),块尺寸(16,16),块步长(8,8),cell尺寸(8,8),直方图bin个数9
HOGDescriptor *hog = new HOGDescriptor(Size(64, 64), Size(16, 16), Size(8, 8), Size(8, 8), 9);
//正样本hog特征提取
cout << "正样本:" << endl;
for (int i = 0; i < pos_sample_num; ++i) {
cout << i << endl;
Mat input_img = imread(pos_sample_path[i], 0);
Mat train_data(64, 64, CV_32FC1);
resize(input_img, train_data, Size(64, 64));
vector<float>descriptor;
hog->compute(train_data, descriptor, Size(8, 8));
if (i == 0) {
feature_dim = descriptor.size();
sample_feature_mat = Mat::zeros(pos_sample_num + neg_sample_num + hard_sample_num, feature_dim, CV_32FC1);//*
sample_label_mat = Mat::zeros(pos_sample_num + neg_sample_num + hard_sample_num, 1, CV_32SC1);//*
}
float *pf = sample_feature_mat.ptr<float>(i);
int *pl = sample_label_mat.ptr<int>(i);
for (int j = 0; j < feature_dim; ++j) {
*pf++ = descriptor[j];
}
*pl++ = 1;
}
//负样本hog特征提取
cout << "负样本:" << endl;
for (int i = 0; i < neg_sample_num; ++i) {
cout << i << endl;
Mat input_img = imread(neg_sample_path[i], 0);
Mat train_data(64, 64, CV_32FC1);
resize(input_img, train_data, Size(64, 64));
vector<float>descriptor;
hog->compute(train_data, descriptor, Size(8, 8));
float *pf = sample_feature_mat.ptr<float>(i + pos_sample_num);
int *pl = sample_label_mat.ptr<int>(i + pos_sample_num);
for (int j = 0; j < feature_dim; ++j) {
*pf++ = descriptor[j];
}
*pl++ = -1;
}
//HardExample负样本//*
cout << "HardExample样本" << endl;
for (int i = 0; i < hard_sample_num ; i++)
{
cout << i << endl;
Mat input_img = imread(hard_sample_path[i], 0);
Mat train_data(64, 64, CV_32FC1);
resize(input_img, train_data, Size(64, 64));
vector<float>descriptor;
hog->compute(train_data, descriptor, Size(8, 8));
float *pf = sample_feature_mat.ptr<float>(i + pos_sample_num + neg_sample_num);
int *pl = sample_label_mat.ptr<int>(i + pos_sample_num + neg_sample_num);
for (int j = 0; j < feature_dim; ++j) {
*pf++ = descriptor[j];
}
*pl++ = -1;
}
//训练数据
Ptr < SVM > svm = SVM::create();
svm->setType(SVM::C_SVC);
svm->setKernel(SVM::LINEAR);
svm->setDegree(0);
svm->setGamma(1);
svm->setCoef0(0);
svm->setC(1);//惩罚参数
svm->setNu(0);
svm->setP(0);
svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 3000, 1e-6));//迭代次数和误差
Ptr<TrainData> tData = TrainData::create(sample_feature_mat, ROW_SAMPLE, sample_label_mat);
cout << "train begin" << endl;
svm->trainAuto(tData);
svm->save("E:/车辆跟随项目/车辆检测/dym_car/SVM_Mode2.xml"); //训练结果保存文件
cout << "train done" << endl;
最终得到的.xml文件就是训练出来的模型
5、模型检测
接下来就是用训练好的模型做检测啦:
//加载SVM模型
Ptr<SVM> svm = SVM::load("E:/车辆跟随项目/车辆检测/dym_car/SVM_Mode2.xml");
if (svm->empty()) { //empty()函数 字符串是空的话返回是true
cout << "读取XML文件失败。" << endl;
return 1;
}
else {
cout << "读取XML文件成功。" << endl;
}
Mat svecsmat = svm->getSupportVectors();//svecsmat元素的数据类型为float
int svdim = svm->getVarCount();
int numofsv = svecsmat.rows;
Mat alphamat = Mat::zeros(numofsv, svdim, CV_32F);//alphamat和svindex必须初始化,否则getDecisionFunction()函数会报错
Mat svindex = Mat::zeros(1, numofsv, CV_64F);
Mat Result;
double rho = svm->getDecisionFunction(0, alphamat, svindex);
alphamat.convertTo(alphamat, CV_32F);//将alphamat元素的数据类型重新转成CV_32F
cout << "1" << endl;
Result = -1 * alphamat * svecsmat;//float
cout << "2" << endl;
vector<float> vec;
for (int i = 0; i < svdim; ++i)
{
vec.push_back(Result.at<float>(0, i));
}
vec.push_back(rho);
//保存HOG检测的文件
ofstream fout("HOGDetectorForOpenCV.txt");
for (int i = 0; i < vec.size(); ++i)
{
fout << vec[i] << endl;
}
cout << "保存完毕" << endl;
//----------读取图片进行检测----------------------------
// HOGDescriptor hog_test;
HOGDescriptor hog_test(Size(64, 64), Size(16, 16), Size(8, 8), Size(8, 8), 9);
hog_test.setSVMDetector(vec);
Mat src = imread("E:/车辆跟随项目/车辆检测/dym_car/1.jpg", 0);
if (!src.data) {
cout << "测试图片读取失败" << endl;
return 1;
}
vector<Rect> found, found_filtered;
int p = 1;
resize(src, src, Size(src.cols / p, src.rows / p));
clock_t startTime, finishTime;
cout << "开始检测" << endl;
startTime = clock(); //1.05
hog_test.detectMultiScale(src, found, 0, Size(8, 8), Size(32, 32), 1.05, 2); //多尺度检测
finishTime = clock();
cout << "检测所用时间为" << (finishTime - startTime)*1.0 / CLOCKS_PER_SEC << " 秒 " << endl;
cout << endl << "矩形框的尺寸为 : " << found.size() << endl;
//找出所有没有嵌套的矩形,并放入found_filtered中,如果有嵌套的话,则取外面最大的那个矩形放入found_filtered中
for (int i = 0; i < found.size(); i++)
{
Rect r = found[i];
int j = 0;
for (; j < found.size(); j++)
if (j != i && (r & found[j]) == r)
break;
if (j == found.size())
found_filtered.push_back(r);
}
cout << endl << "嵌套矩形框合并完毕" << endl;
//画矩形框,因为hog检测出的矩形框比实际的框要稍微大些,所以这里需要做一些调整
for (int i = 0; i<found_filtered.size(); i++)
{
Rect r = found_filtered[i];
r.x += cvRound(r.width*0.1); //int cvRound(double value) 对一个double型的数进行四舍五入,并返回一个整型数!
r.width = cvRound(r.width*0.8);
r.y += cvRound(r.height*0.07);
r.height = cvRound(r.height*0.8);
rectangle(src, r.tl(), r.br(), Scalar(0, 0, 255), 3);
}
imwrite("E:/车辆跟随项目/车辆检测/dym_car/12.jpg", src);
namedWindow("src", 0);
imshow("src", src);
waitKey(0);
初始训练效果(框框满天飞脑阔疼):
增加困难样本(好了那么一丢丢)
6、总结
(1)由于只是熟悉下流程,检测效果一般般,也没有测试;
(2)方向一:增加数据集数量;
(3)方向二:制作二次训练正样本(该检测没检测出来);
7、参考
https://www.jianshu.com/p/49d029f1b489
https://blog.csdn.net/qq_37487118/article/details/83046962
https://blog.csdn.net/yang1688899/article/details/81225668?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task
https://bbs.csdn.net/topics/391950597?list=lz
https://blog.csdn.net/u010869312/article/details/44927721
https://www.cnblogs.com/heleifz/p/train-hog-svm-detector.html
https://www.it610.com/article/5135995.htm
https://blog.csdn.net/aa997749935/article/details/88354355