任务:做一个简单的分类模型 用HOG+SVM 行人检测 网上自己找开源代码 这个代码CPU可以训练 掌握数据集制作的过程 训练的过程 先不深究算法原理
一、基本介绍
基本思路就是从数据集读入正负样本,提取HOG特征,送入SVM训练。
对数据集的处理包括负样本的提取,正样本的缩放,以及难例的获取。
二、成果展示
刚开始样本数量比较少且没有加入难例训练时,训练效果不是很好。
增加了一些数据后,识别效果up!
加入难例训练之后,效果更好了
三、基本原理
行人检测: HOG + SVM
HOG:一种用来进行物体检测的特征描述子。HOG特征通过计算和统计图像局部区域的梯度方向直方图来构成特征.
SVM: (Support Vector Machine)指的是支持向量机,是常见的一种判别方法。在行人检测中可以用作区分行人和非行人的分类器。
hog的使用:https://blog.csdn.net/zhazhiqiang/article/details/21047207
没有深究原理,大概知道要干嘛,以后懂原理再来补充吧
四、问题总结
1.opencv的安装,自己的vs是2019版本的,搜教程刚开始没有注意版本,新版本去掉了一个文件,自己怎么也找不到。还要注意每次使用opencv时要注意增加属性页。
2.hog.compute()引发异常,这个bug在网上搜了许多,包括没有附加依赖项,释放hog各种各样的说法,都不对。还是要仔细分析自己的代码,这个其实是负样本的读取路径问题。
3.Result = -1 * alphamat * svecsmat;引发异常,这个是因为矩阵相乘,但是维度不匹配,所以报错。
再往上可以推到double rho = svm->getDecisionFunction(0, alphamat, svindex);这句引起了alpha的问题,网上有建议是代码样本数量和实际读取的样本数量不一样。重新检查了正负样本数,还是没有解决。
直接搜不到了,就去看别人的代码,偶然看到一条注释解决了问题!
4.还有一个小知识点:如何获取文件夹内文件名保存到txt文件中
五、代码展示
运行环境:win+opencv3.4.6
运行过程:
1.运行neg.cpp 剪裁负样本的图片
2.运行train.cpp 进行HOG的提取和SVM的训练
3.运行hardexample.cpp训练hardneg
4.运行train.cpp 生成最终的SVM模型
neg.cpp:对负样本进行处理,对每张原始负样本图片随机切十张,得到训练负样本。
#include
#include
#include
#include //srand()和rand()函数
#include //time()函数
#include
#include
#include
#include
#include
#define INRIANegativeImageList "D:/Pedestrain_Detection-master/Pedestrain_Detection-master/img_dir/sample_neg.txt" //原始负样本图片文件列表
int cropNegNum = 12140;
using namespace std;
using namespace cv;
int CropImageCount = 0; //裁剪出来的负样本图片个数
void neg()
//int main()
{
Mat src;
string ImgName;
char saveName[600];//裁剪出来的负样本图片文件名
ifstream fin(INRIANegativeImageList);//打开原始负样本图片文件列表
int num = 0;
ofstream fout("D:/Pedestrain_Detection-master/Pedestrain_Detection-master/img_dir/sample_new_neg.txt", ios::trunc);
for (int j = 0; j < cropNegNum && getline(fin, ImgName); j++)
{
cout << "处理:" << ImgName << endl;
ImgName = "D:/Pedestrain_Detection-master/Pedestrain_Detection-master/normalized_images/train/neg/" + ImgName;
src = imread(ImgName, 1);
//图片大小应该能至少包含一个64*128的窗口
if (src.cols >= 64 && src.rows >= 128)
{
srand(time(NULL));//设置随机数种子
for (int i = 0; i < 10; i++)
{
int x = (rand() % (src.cols - 64)); //左上角x坐标
int y = (rand() % (src.rows - 128)); //左上角y坐标
//cout<
train.h
#ifndef PEDESTRIAN_DETECTION_DETECTION_H
#define PEDESTRIAN_DETECTION_DETECTION_H
//训练开关
#define TRAIN false//是否进行训练,true表示重新训练,false表示读取xml文件中的SVM模型
#define CENTRAL_CROP true //true:训练时,对96*160的INRIA正样本图片剪裁出中间的64*128大小人体
#define HARDNEG true //是否使用hardneg,true表示使用
//样本数量
#define PosSamNO 2416 //原始正样本数 2416 , 3542
#define NegSamNO 5980 // 剪裁后的负样本数 6070
#define cropNegNum 600 //原始负样本数
#define HardExampleNO 3004// hardneg的样本数量 //10896 不用难例时要设置为0 4081 //
#define AugPosSamNO 0 //Aug positive num#pragma once
//正样本图片的文件名列表
#define PosSamListFile "D:/Pedestrain_Detection-master/Pedestrain_Detection-master/img_dir/pos1.txt"
//负样本图片的文件名列表
#define NegSamListFile "D:/Pedestrain_Detection-master/Pedestrain_Detection-master/img_dir/sample_new_neg.txt"
//hard负样本图片的文件列表
#define NegHardListFile "D:/Pedestrain_Detection-master/Pedestrain_Detection-master/img_dir/hard_neg.txt"
//训练的HOG特征,svm分类时使用
#define SvmListFile "D:/Pedestrain_Detection-master/Pedestrain_Detection-master/data/SVM_HOG8.xml"
//训练的HOG特征,resultMat结果
#define HogDetectorListFile "D:/Pedestrain_Detection-master/Pedestrain_Detection-master/data/HOGDetectorForOpenCV7.txt"
//读入的pos图片路径
#define PosImageFile "D:/Pedestrain_Detection-master/Pedestrain_Detection-master/normalized_images/train/pos/"
//读入的neg图片路径
#define NegImageFile "D:/Pedestrain_Detection-master/Pedestrain_Detection-master/normalized_images/train/new_neg/"
//读入的hardneg图片路径
#define HardNegImageFile "D:/Pedestrain_Detection-master/Pedestrain_Detection-master/normalized_images/train/hard_neg/"
#endif //PEDESTRIAN_DETECTION_DETECTION_H
train.h
#include
#include
#include
#include
#include
#include
#include
#include
#include "train.h"
using namespace std;
using namespace cv;
using namespace cv::ml;
int main()
{
//void neg();
//检测窗口(64,128),块尺寸(16,16),块步长(8,8),cell尺寸(8,8),直方图bin个数9
HOGDescriptor hog(Size(64, 128), Size(16, 16), Size(8, 8), Size(8, 8), 9);
int DescriptorDim;//HOG描述子的维数,由图片大小、检测窗口大小、块大小、细胞单元中直方图bin个数决定
Ptr svm = SVM::create();// 创建分类器
if (TRAIN)//若TRAIN为true,重新训练分类器
{
string ImgName;//图片名(绝对路径)
//正样本图片的文件名列表
ifstream finPos(PosSamListFile);
// ifstream finNeg("../sample_neg.txt");
//负样本图片的文件名列表
ifstream finNeg(NegSamListFile);
//HardExample负样本的文件名列表
ifstream finHardNeg(NegHardListFile);
if (HARDNEG) {
if (!finPos || !finNeg || !finHardNeg) {
cout << "Pos/Neg/hardNeg imglist reading failed..." << endl;
return 1;
}
}
else {
if (!finPos || !finNeg)
{
cout << "Pos/Neg/hardNeg imglist reading failed..." << endl;
return 1;
}
}
Mat sampleFeatureMat;
Mat sampleLabelMat;
//loading original positive examples...
for (int num = 0; num < PosSamNO && getline(finPos, ImgName); num++)
{
cout << "Now processing original positive image: " << ImgName << endl;
//cout << "pos num" << num << endl;
ImgName = PosImageFile + ImgName;
Mat src = imread(ImgName, 1);//读取图片
if (CENTRAL_CROP)//true:训练时,对96*160的INRIA正样本图片剪裁出中间的64*128大小人体
if (src.cols >= 96 && src.rows >= 160)
//resize(src, src, Size(64, 128));
src = src(Rect(16, 16, 64, 128));
else cout << "error" << endl; //测试
vector descriptors;//HOG描述子向量
hog.compute(src, descriptors, Size(8, 8));//计算HOG描述子,检测窗口移动步长(8,8)
//cout<<"描述子维数:"<(num, i) = descriptors[i];//第num个样本的特征向量中的第i个元素
sampleLabelMat.at(num, 0) = 1;//正样本类别为1,有人
}
finPos.close();
//依次读取负样本图片,生成HOG描述子
for (int num = 0; num < NegSamNO && getline(finNeg, ImgName); num++)
{
cout << "Now processing original negative image: " << ImgName << endl;
//cout << "neg num" << num << endl;
//ImgName = "D:/Pedestrain_Detection-master/Pedestrain_Detection-master/normalized_images/train/new_neg/" + ImgName;
//加上负样本的路径名
ImgName = NegImageFile + ImgName;
Mat src = imread(ImgName, 1);//读取图片
vector descriptors;//HOG描述子向量
hog.compute(src, descriptors, Size(8, 8));//计算HOG描述子,检测窗口移动步长(8,8)
//cout << "描述子维数:" << descriptors.size() << endl;
//将计算好的HOG描述子复制到样本特征矩阵sampleFeatureMat
for (int i = 0; i < DescriptorDim; i++)
sampleFeatureMat.at(num + PosSamNO + AugPosSamNO, i) = descriptors[i];//第PosSamNO+num个样本的特征向量中的第i个元素
sampleLabelMat.at(num + PosSamNO + AugPosSamNO, 0) = -1;//负样本类别为-1,无人
}
finNeg.close();
//依次读取HardExample负样本图片,生成HOG描述子
if (HARDNEG) {
for (int num = 0; num < HardExampleNO && getline(finHardNeg, ImgName); num++)
{
cout << "Now processing original hard negative image: " << ImgName << endl;
// ImgName = "../normalized_images/train/neg/" + ImgName;
//加上负样本的路径名
ImgName = HardNegImageFile + ImgName;
Mat src = imread(ImgName);//读取图片
vector descriptors;//HOG描述子向量
hog.compute(src, descriptors, Size(8, 8));//计算HOG描述子,检测窗口移动步长(8,8)
//cout<<"描述子维数:"<(num + PosSamNO + NegSamNO + AugPosSamNO, i) = descriptors[i];//第PosSamNO+num个样本的特征向量中的第i个元素
sampleLabelMat.at(num + PosSamNO + NegSamNO + AugPosSamNO, 0) = -1;//负样本类别为-1,无人
}
}
finHardNeg.close();
//指定svm种类
svm->setType(SVM::C_SVC);
//CvSVM::C_SVC : C类支持向量分类机。 n类分组 (n≥2),允许用异常值惩罚因子C进行不完全分类。
//CvSVM::NU_SVC : 类支持向量分类机。n类似然不完全分类的分类器。
// 参数为取代C(其值在区间【0,1】中,nu越大,决策边界越平滑)
//CvSVM::ONE_CLASS : 单分类器,所有的训练数据提取自同一个类里,
// 然后SVM建立了一个分界线以分割该类在特征空间中所占区域和其它类在特征空间中所占区域。
//CvSVM::EPS_SVR : 类支持向量回归机。
// 训练集中的特征向量和拟合出来的超平面的距离需要小于p。异常值惩罚因子C被采用
//CvSVM::NU_SVR : 类支持向量回归机。 代替了p。
svm->setC(0.01);//惩罚因子
svm->setGamma(1.0);//gamma参数
//C是惩罚系数,即对误差的宽容度。
// c越高,说明越不能容忍出现误差,容易过拟合。
// C越小,容易欠拟合。C过大或过小,泛化能力变差
svm->setKernel(SVM::LINEAR);//设置核函数
// LINEAR:线性核函数;
// POLY:多项式核函数;
/// -d用来设置多项式核函数的最高此项次数;默认值是3
/// -r用来设置核函数中的coef0,也就是公式中的第二个r,默认值是0。
// 一般选择1-11:1 3 5 7 9 11,也可以选择2,4,6…
// RBF:径向机核函数【高斯核函数】;
/// -g用来设置核函数中的gamma参数设置,默认值是1/k(k是类别数)
//gamma是选择RBF函数作为kernel后,该函数自带的一个参数。
// 隐含地决定了数据映射到新的特征空间后的分布,
// gamma越大,支持向量越少,gamma值越小,支持向量越多。
// 支持向量的个数影响训练与预测的速度。
// SIGMOID:神经元的非线性作用函数核函数;
/// -g用来设置核函数中的gamma参数设置,默认值是1/k(k是类别数)
/// -r用来设置核函数中的coef0,也就是公式中的第二个r,默认值是0
// PRECOMPUTED:用户自定义核函数
//SVM的迭代训练过程的中止条件
svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 50000, FLT_EPSILON));
cout << "Starting training..." << endl;
//svm->train(trainingDataMat, cv::ml::SampleTypes::ROW_SAMPLE, labelsMat);
svm->train(sampleFeatureMat, ROW_SAMPLE, sampleLabelMat);
// svm ->trainAuto(); //svm自动优化参数
cout << "Finishing training..." << endl;
//储存 SVM 分类器
svm->save(SvmListFile);
//SVM的改进算法
//J.Platt的SMO算法、
//T.Joachims的SVM、
//C.J.C.Burges等的PCGC、
//张学工的CSVM
//以及O.L.Mangasarian等的SOR算法
}
else
{
svm = SVM::load(SvmListFile);
}
cout << "loaded SVM_HOG.xml file" << endl;
int svdim = svm->getVarCount();//特征向量的维数,即HOG描述子的维数
//支持向量的个数
Mat svecsmat = svm->getSupportVectors();//svecsmat元素的数据类型为float
int numofsv = svecsmat.rows;
//alphamat和svindex必须初始化,否则getDecisionFunction()函数会报错
Mat alphamat = Mat::zeros(numofsv, svdim, CV_32F);
//Mat alphamat = Mat::zeros(1, numofsv, CV_64F);
Mat svindex = Mat::zeros(1, numofsv, CV_64F);
Mat Result= Mat::zeros(1, svdim, CV_32FC1);
double rho = svm->getDecisionFunction(0, alphamat, svindex);
cout << "the value of rho is " << rho << endl;
alphamat.convertTo(alphamat, CV_32F);//将alphamat元素的数据类型重新转成CV_32F
cout << "the value of alphamat is " << alphamat << endl;
cout << "the size of alphamat is " << alphamat.size() << endl;
cout << "the size of svecsmat is " << svecsmat.size() << endl;
//cout << svecsmat << endl;
//计算-(alphaMat * supportVectorMat),结果放到resultMat中
Result = -1 * alphamat * svecsmat;//float
cout << "the value of svdim is " << svdim << endl;
//得到最终的setSVMDetector(const vector& detector)参数中可用的检测子
vector vec;
//将resultMat中的数据复制到数组vec中
for (int i = 0; i < svdim; ++i)
{
vec.push_back(Result.at(0, i));
}
vec.push_back(rho);
cout << "going to write the HOGDetectorForOpenCV.txt file" << endl;
//保存HOG特征到HOGDetectorForOpenCV.txt
ofstream fout(HogDetectorListFile);
for (int i = 0; i < vec.size(); ++i)
{
fout << vec[i] << endl;
}
fout.close();//关闭文件
/*********************************Testing**************************************************/
HOGDescriptor hog_test;
hog_test.setSVMDetector(vec);
cout << "all is ok" << endl;
Mat src = imread("D:/Pedestrain_Detection-master/Pedestrain_Detection-master/data/Test3.jpg");
vector found, found_filtered;
hog_test.detectMultiScale(src, found, 0, Size(8, 8), Size(32, 32), 1.05, 2);
cout << "found.size : " << found.size() << endl;
//找出所有没有嵌套的矩形框r,并放入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);
}
//画矩形框,因为hog检测出的矩形框比实际人体框要稍微大些,所以这里需要做一些调整
for (int i = 0; i < found_filtered.size(); i++)
{
Rect r = found_filtered[i];
r.x += cvRound(r.width * 0.1);
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, 255, 0), 3);
}
imwrite("ImgProcessed.jpg", src);
namedWindow("src", 0);
imshow("src", src);
waitKey(0);
return 0;
}
hardexample.cpp :难例训练
#include
#include
#include
#include
//#include "dataset.h"
#include
#include
#include
#include
#include
#include
using namespace std;
using namespace cv;
using namespace cv::ml;
int HardExampleCount = 1;
#define cropHardNegNum 10896
#define NegFile "D:/Pedestrain_Detection-master/Pedestrain_Detection-master/img_dir/sample_neg.txt"
#define HardFile "D:/Pedestrain_Detection-master/Pedestrain_Detection-master/img_dir/hard_neg.txt"
#define SvmLoadFile "D:/Pedestrain_Detection-master/Pedestrain_Detection-master/data/SVM_HOG8.xml"
#define NegImageFile "D:/Pedestrain_Detection-master/Pedestrain_Detection-master/normalized_images/train/neg/"
class MySVM : public ml::SVM
{
public:
//获得SVM的决策函数中的alpha数组
double get_svm_rho()
{
return this->getDecisionFunction(0, svm_alpha, svm_svidx);
}
//获得SVM的决策函数中的rho参数,即偏移量
vector svm_alpha;
vector svm_svidx;
float svm_rho;
};
int hard(int argc, char** argv)
{
Mat src;
string ImgName;
char saveName[600];//找出来的HardExample图片文件名
//打开原始负样本图片文件列表
ifstream fin(NegFile);
ofstream fout(HardFile, ios::trunc);//加路径
// int num = 1;
//检测窗口(64,128),块尺寸(16,16),块步长(8,8),cell尺寸(8,8),直方图bin个数9
//HOGDescriptor hog(Size(64,128),Size(16,16),Size(8,8),Size(8,8),9);//HOG检测器,用来计算HOG描述子的
int DescriptorDim;//HOG描述子的维数,由图片大小、检测窗口大小、块大小、细胞单元中直方图bin个数决定
///MySVM svm;//SVM分类器
// Ptr svm = SVM::create();// 创建分类器
///svm = svm::load("SVM_HOG.xml");
//svm ->save("/Users/macbookpro/CLionProjects/pedestrian_detection/data/SVM_HOG7.xml");
Ptr svm = Algorithm::load(SvmLoadFile);
/*************************************************************************************************
线性SVM训练完成后得到的XML文件里面,有一个数组,叫做support vector,还有一个数组,叫做alpha,有一个浮点数,叫做rho;
将alpha矩阵同support vector相乘,注意,alpha*supportVector,将得到一个列向量。之后,再该列向量的最后添加一个元素rho。
如此,变得到了一个分类器,利用该分类器,直接替换opencv中行人检测默认的那个分类器(cv::HOGDescriptor::setSVMDetector()),
就可以利用你的训练样本训练出来的分类器进行行人检测了。
***************************************************************************************************/
DescriptorDim = svm->getVarCount();//特征向量的维数,即HOG描述子的维数
Mat supportVector = svm->getSupportVectors();//支持向量的个数
int supportVectorNum = supportVector.rows;
//cout<<"支持向量个数:"< svm_alpha;
vector svm_svidx;
float svm_rho;
svm_rho = svm->getDecisionFunction(0, svm_alpha, svm_svidx);
Mat alphaMat = Mat::zeros(1, supportVectorNum, CV_32FC1);//alpha向量,长度等于支持向量个数
Mat supportVectorMat = Mat::zeros(supportVectorNum, DescriptorDim, CV_32FC1);//支持向量矩阵
Mat resultMat = Mat::zeros(1, DescriptorDim, CV_32FC1);//alpha向量乘以支持向量矩阵的结果
//将支持向量的数据复制到supportVectorMat矩阵中
supportVectorMat = supportVector;
//将alpha向量的数据复制到alphaMat中
///double * pAlphaData = svm.get_alpha_vector();//返回SVM的决策函数中的alpha向量
for (int i = 0; i < supportVectorNum; i++)
{
alphaMat.at(0, i) = svm_alpha[i];
}
//计算-(alphaMat * supportVectorMat),结果放到resultMat中
//gemm(alphaMat, supportVectorMat, -1, 0, 1, resultMat);
resultMat = -1 * alphaMat * supportVectorMat;
//得到最终的setSVMDetector(const vector& detector)参数中可用的检测子
vector myDetector;
//将resultMat中的数据复制到数组myDetector中
for (int i = 0; i < DescriptorDim; i++)
{
myDetector.push_back(resultMat.at(0, i));
}
//最后添加偏移量rho,得到检测子
/// myDetector.push_back(svm.get_rho());
myDetector.push_back(svm_rho);
cout << "检测子维数:" << myDetector.size() << endl;
//设置HOGDescriptor的检测子
HOGDescriptor myHOG;
myHOG.setSVMDetector(myDetector);
//myHOG.setSVMDetector(HOGDescriptor::getDefaultPeopleDetector());
// //保存检测子参数到文件
// ofstream fout("HOGDetectorForOpenCV.txt");
// for(int i=0; i found, found_filtered;
//double t = (double)getTickCount();
// run the detector with default parameters. to get a higher hit-rate
// (and more false alarms, respectively), decrease the hitThreshold and
// groupThreshold (set groupThreshold to 0 to turn off the grouping completely).
//对负样本原图进行多尺度检测,检测出的都是误报
myHOG.detectMultiScale(src, found, 0, Size(8, 8), Size(32, 32), 1.05, 2);
//t = (double)getTickCount() - t;
//printf("tdetection time = %gms\n", t*1000./cv::getTickFrequency());
//遍历从图像中检测出来的矩形框,得到hard example
size_t i, j;
for (i = 0; i < found.size(); i++)
{
Rect r = found[i];
for (j = 0; j < found.size(); j++)
if (j != i && (r & found[j]) == r)
break;
if (j == found.size())
found_filtered.push_back(r);
}
for (i = 0; i < found_filtered.size(); i++)
{
Rect r = found_filtered[i];
// the HOG detector returns slightly larger rectangles than the real objects.
// so we slightly shrink the rectangles to get a nicer output.
//r.x += cvRound(r.width*0.1);
//r.width = cvRound(r.width*0.8);
//r.y += cvRound(r.height*0.07);
//r.height = cvRound(r.height*0.8);
//检测出来的很多矩形框都超出了图像边界,将这些矩形框都强制规范在图像边界内部
if (r.x < 0)
r.x = 0;
if (r.y < 0)
r.y = 0;
if (r.x + r.width > src.cols)
r.width = src.cols - r.x;
if (r.y + r.height > src.rows)
r.height = src.rows - r.y;
//从原图上截取矩形框大小的图片
Mat imgROI = src(Rect(r.x, r.y, r.width, r.height));
//将剪裁出来的图片缩放为64*128大小
resize(imgROI, imgROI, Size(64, 128));
//生成hard example图片的文件名
sprintf_s(saveName, "D:/Pedestrain_Detection-master/Pedestrain_Detection-master/normalized_images/train/hard_neg/hardexample%06d.jpg", HardExampleCount);
//保存文件
imwrite(saveName, imgROI);
// //保存裁剪得到的图片名称到txt文件,换行分隔
fout << "hardexample" << setw(6) << setfill('0') << HardExampleCount << ".jpg" << endl;
// //num++;
HardExampleCount++;
//rectangle(src, r.tl(), r.br(), cv::Scalar(0,255,0), 3);
}
//imshow("people detector", src);
//waitKey(0);
}
fout.close();
cout << "HardExampleCount: " << HardExampleCount - 1 << endl;
return 0;
}
六、参考资料
代码参考:https://github.com/fanxinglanyu/Pedestrain_Detection