【行人检测】利用HOG+SVM训练模型,检测行人
1.准备样本
2.提取hog特征
3. 训练SVM分类器
4.利用SVM训练的分类器,进行检测
理论基础
使用OpenCv进行行人检测的主要思想: HOG + SVM
HOG: 方向梯度直方图(Histogram of Oriented Gradient, HOG)特征是一种在计算机视觉和图像处理中用来进行物体检测的特征描述子。HOG特征通过计算和统计图像局部区域的梯度方向直方图来构成特征.
SVM: (Support Vector Machine)指的是支持向量机,是常见的一种判别方法。在机器学习领域,是一个有监督的学习模型,通常用来进行模式识别、分类以及回归分析, 在行人检测中可以用作区分行人和非行人的分类器。
在使用HOG + SVM进行行人检测时, 采集HOG特征的主要思想是通过对一幅图像进行分析, 局部目标的表象和形状可以被剃度或者边缘密度方向分布很好的好的描述. 我们对图像的各个像素点采集土堆或者边缘的方向直方图, 根据直方图的信息就可以描述图片的特征. 好在OpenCv 中已经提供了计算HOG特征的方法, 根据采集到的HOG特征向量, 供SVM分类使用.
SVM简单来说就是一个分类器, 在行人检测中就可以转化为行人与非行人的两类分类问题, 在OpenCv中运用到的是基于网格法的SVM.使用采集到的正样本(行人)和负样本(非行人, 可以是汽车, 树木, 路灯等等)的HOG特征, 然后使用SVM分类器进行训练, 得到行人检测模型, 进行行人检测.
整个流程
1.准备样本:准备正负样本,注意:大小一致,64*128
2.利用样本提取hog描述子:维度为3780+1=3781
--imgSize: 64*128
--block: 16*16(每个img中的block数目与block大小和步长都有关)
--cell: 8*8(每个block有2*2个cell,每个cell里有9个特征值)
--stride: 8*8(所以,一个img中的block计算,x向有(64-16)/8+1=7个block, y方向有(128-16)/8+1=15个block)
--bin: 9(每个cell里的梯度直方图分为9个bin,180°分9份,每份180÷9=20°。为什么不是360而是180°?因为tan的周期为π )
所以hog特征维度为:
9*(2*2)*7 *15 =3780
再加上一个偏置共 :
3780+1=3781
具体hog特征计算原理,可戳:https://blog.csdn.net/u012679707/article/details/80657020
3.利用提取到的hog特征来训练SVM
m个样本,带m个标签,可提取到的hog特征数目为(m*3781),用特征和标签来训练SVM。
具体SVM原理,可戳:https://blog.csdn.net/u012679707/article/details/80501358
4.利用SVM训练的模型,来进行检测。
1.准备样本
正样本:INRIA数据集中的pos样本614张(..\INRIAPerson\Train\pos),因图片大小不一,后续利用时需统一resize成(64*128)
负样本:INRIA数据集中的neg样本1218张(..\INRIAPerson\Train\neg),需将负样本裁剪成和正样本相同大小(64*128)。在裁剪时,将每张负样本图片经过两次随机裁剪,所以总数变为1218*2=2436。
数据集下载地址:点击打开链接
负样本裁剪程序:
/********************************* 随机剪裁负样本 *******************************************/ void crop_negsample_random() { string imgName; char saveName[200]; //string newNegFile = "F:\\opencv_project\\data\\INRIAPerson\\Train\\new_neg\\" ; ifstream fileNeg("F:\\opencv_project\\data\\INRIAPerson\\Train\\neg\\ANegData.txt"); int num=0; //读取负样本 for (int i = 0;i < cropNegNum && getline(fileNeg, imgName); i++) { imgName = "F:\\opencv_project\\data\\INRIAPerson\\Train\\neg\\" + imgName; //加路径 Mat img = imread(imgName, IMREAD_UNCHANGED); //Mat img; if (img.empty()) { cout << "can not load the image:" << imgName << endl; continue; } if (img.cols >= 64 && img.rows >= 128) { num = 0; //从每张图片中随机剪裁2张64*128的负样本 for (int j = 0;j < 2;j++) { srand(time(NULL));//设置随机数种子 int x = rand() % (img.cols - 64); //左上角x int y = rand() % (img.rows - 128); //左上角y Mat src = img(Rect(x, y, 64, 128)); sprintf_s(saveName, "F:\\opencv_project\\data\\INRIAPerson\\Train\\new_neg\\neg%dCropped%d.png",i, num++); imwrite(saveName,src); } } } cout << "crop ok!" << endl; }
然后,利用cmd命令行操作,生成列表文件,得到APosData.txt和ANegData.txt。
2.提取hog特征
计算hog特征,主要是compute()函数:
vector<float> descriptors;//hog描述符 向量 hog.compute(img, descriptors, Size(8, 8));//计算hog描述子,检测窗口移动步长(8,8) //cout << "负样本描述子维数:" << descriptors.size() << endl; /** CV_WRAP virtual void compute(InputArray img,CV_OUT std::vector<float>& descriptors,Size winStride = Size(), Size padding = Size(), const std::vector<Point>& locations = std::vector<Point>()) const; @brief Computes HOG descriptors of given image. @param img Matrix of the type CV_8U containing an image where HOG features will be calculated.要计算hog的img @param descriptors Matrix of the type CV_32F 计算得到的hog特征描述符,vector<float>型 @param winStride Window stride. It must be a multiple of block stride.窗口移动的步长 @param padding Padding @param locations Vector of Point */
2.1读取正负样本,计算hog描述子 ----相当于特征提取
//读取正样本,生成hog描述符 for (int i = 0;i < PosNum && getline(filePos, imgName); i++) { //cout << "处理正样本:" << imgName << endl; imgName = "F:\\opencv_project\\data\\INRIAPerson\\Train\\pos\\" + imgName; //加路径 Mat img1 = imread(imgName, IMREAD_UNCHANGED); Mat img; if (img1.empty()) { cout << "can not load the image:" << imgName << endl; continue; } //img = img(Rect(16, 16, 64, 128));//将96*160的正样本裁剪未64*128 resize(img1,img,Size(64,128)); vector<float> descriptors;//hog描述符 向量 hog.compute(img, descriptors, Size(8, 8));//计算hog描述子,检测窗口移动步长(8,8) //cout << "描述子维数:" << descriptors.size() << endl; //根据第一个样本,计算出维数,创建特征矩阵和标签矩阵 if (0 == i) { hogDescriptorDim = descriptors.size();//hogDescriptorDim=3780 sampleFeatureMat = Mat::zeros(PosNum + NegNum, hogDescriptorDim, CV_32FC1); sampleLabelMat= Mat::zeros(1,PosNum + NegNum, CV_32FC1); } //将计算得到的描述子复制到样本特征矩阵 for (int j = 0;j < hogDescriptorDim;j++) { sampleFeatureMat.at<float>(i, j) = descriptors[j];//第i个样本的特征向量中的第j个元素 } sampleLabelMat.at<int>(0, i) = 1;//正样本标签 } cout << "pos ok!" << endl; //读取负样本,生成hog描述符 for (int i = 0;i < NegNum && getline(fileNeg, imgName); i++) { //cout << "处理正样本:" << imgName << endl; imgName = "F:\\opencv_project\\data\\INRIAPerson\\Train\\new_neg\\" + imgName; //加路径 Mat img = imread(imgName); vector<float> descriptors;//hog描述符 向量 hog.compute(img, descriptors, Size(8, 8));//计算hog描述子,检测窗口移动步长(8,8) //cout << "负样本描述子维数:" << descriptors.size() << endl; /** CV_WRAP virtual void compute(InputArray img,CV_OUT std::vector<float>& descriptors, Size winStride = Size(), Size padding = Size(), const std::vector<Point>& locations = std::vector<Point>()) const; @brief Computes HOG descriptors of given image. @param img Matrix of the type CV_8U containing an image where HOG features will be calculated. @param descriptors Matrix of the type CV_32F @param winStride Window stride. It must be a multiple of block stride. @param padding Padding @param locations Vector of Point */ //将计算得到的描述子复制到样本特征矩阵 for (int j = 0;j < hogDescriptorDim;j++) { sampleFeatureMat.at<float>(i+PosNum , j) = descriptors[j];//第i个样本的特征向量中的第j个元素 } sampleLabelMat.at<int>(0, i) = -1;//负样本标签 } cout << "neg ok!" << endl;
3. 训练SVM分类器
训练SVM:
1.设置参数(线性SVM、核、迭代次数1000、误差)
2.使用样本和标签进行学习(训练),将学得的分类器保存.xml(包括sv、α、rho)。
3.为了后续得到检测器,可利用函数svm->getSupportVectors()获得sv(支持向量),利用svm->getDecisionFunction(0, alpha, svIndex)获得α(拉格朗日乘子)、rho(偏置)
//设置参数,注意Ptr的使用 Ptr<ml::SVM> svm = ml::SVM::create(); svm->setType(ml::SVM::C_SVC); svm->setKernel(ml::SVM::LINEAR);//注意必须使用线性SVM进行训练,因为HogDescriptor检测函数只支持线性检测!!! svm->setTermCriteria(TermCriteria(CV_TERMCRIT_ITER, 1000, FLT_EPSILON)); //使用SVM学习 svm->train(sampleFeatureMat, ml::ROW_SAMPLE, sampleLabelVec); //保存分类器(里面包括了SVM的参数,支持向量support vector , α和rho) svm->save("data\\HOG_SVM_Classifier.xml"); /* SVM训练完成后得到的XML文件里面,有一个数组,叫做support vector,还有一个数组,叫做alpha,有一个浮点数,叫做rho; 将alpha矩阵同support vector相乘,注意,alpha*supportVector,将得到一个行向量,将该向量前面乘以-1。 之后,在该行向量的最后添加一个元素rho。如此,便得到了一个分类器,利用该分类器,直接替换opencv中行人检测默认的 那个分类器(cv::HOGDescriptor::setSVMDetector()), */ //获取支持向量机:矩阵默认是CV_32F浮点型 Mat supportVector = svm->getSupportVectors(); //获取alpha和rho Mat alpha;//每个支持向量对应的参数α(拉格朗日乘子),默认alpha是float64的 Mat svIndex;//支持向量所在的索引 float rho = svm->getDecisionFunction(0, alpha, svIndex); //转换类型:这里一定要注意,需要转换为32的 Mat alpha2; alpha.convertTo(alpha2, CV_32FC1); //结果矩阵,两个矩阵相乘 Mat resultMat(1, hogDescriptorDim, CV_32FC1); resultMat = alpha2 * supportVector; //乘以-1,这里为什么会乘以-1? //注意因为svm.predict使用的是alpha*sv*another-rho,如果为负的话则认为是正样本,在HOG的检测函数中,使用rho+alpha*sv*another(another为-1) for (int i = 0; i < hogDescriptorDim; ++i) resultMat.at<float>(0, i) *= -1;
训练得到的分类器xml如下:
4.利用SVM训练的分类器,进行检测
运行结果:
检测结果:
检测结果不太理想,毕竟样本有限,此次目的主要是为了了解整个过程,熟悉HOG特征以及利用Hog特征进行SVM训练。为日后自己设计检测方法,提供思路。
完整程序:
// Hog_SVM_Pedestrian_detection.cpp: 定义控制台应用程序的入口点。 // 自己训练HOG+SVM实现图片中的行人检测 #include"stdafx.h" #include<fstream>//fstream #include<opencv2/core/core.hpp> #include<opencv2/highgui/highgui.hpp> #include<opencv2/imgproc/imgproc.hpp> #include<opencv2/objdetect/objdetect.hpp> // hog #include<opencv2/ml/ml.hpp>//SVM #include<iostream> #include<time.h> #include<stdlib.h> using namespace std; using namespace cv; //using namespace ml; #define PosNum 614 #define NegNum 2436 #define cropNegNum 1218 //#define totalDim 3780 /********************************* 随机剪裁负样本 *******************************************/ void crop_negsample_random() { string imgName; char saveName[200]; //string newNegFile = "F:\\opencv_project\\data\\INRIAPerson\\Train\\new_neg\\" ; ifstream fileNeg("F:\\opencv_project\\data\\INRIAPerson\\Train\\neg\\ANegData.txt"); int num=0; //读取负样本 for (int i = 0;i < cropNegNum && getline(fileNeg, imgName); i++) { imgName = "F:\\opencv_project\\data\\INRIAPerson\\Train\\neg\\" + imgName; //加路径 Mat img = imread(imgName, IMREAD_UNCHANGED); //Mat img; if (img.empty()) { cout << "can not load the image:" << imgName << endl; continue; } if (img.cols >= 64 && img.rows >= 128) { num = 0; //从每张图片中随机剪裁2张64*128的负样本 for (int j = 0;j < 2;j++) { srand(time(NULL));//设置随机数种子 int x = rand() % (img.cols - 64); //左上角x int y = rand() % (img.rows - 128); //左上角y Mat src = img(Rect(x, y, 64, 128)); sprintf_s(saveName, "F:\\opencv_project\\data\\INRIAPerson\\Train\\new_neg\\neg%dCropped%d.png",i, num++); imwrite(saveName,src); } } } cout << "crop ok!" << endl; } /********************************* 训练hog特征 *******************************************/ vector<float> trainHogSvm(HOGDescriptor &hog) { size_t hogDescriptorDim; // hogDesDim ,与imgSize、blockSize、cellSize、paddingBlock、bin 有关 string imgName; ifstream filePos("F:\\opencv_project\\data\\INRIAPerson\\Train\\pos\\APosData.txt"); ifstream fileNeg("F:\\opencv_project\\data\\INRIAPerson\\Train\\new_neg\\ANegData.txt"); Mat sampleFeatureMat; //所有训练样本的特征组成的矩阵,每个样本占一行,列数等于hogDescriptorDim //Mat sampleLabelMat; //样本标签,正样本=1,负样本=-1 vector<int> sampleLabelVec; //读取正样本,生成hog描述符 for (int i = 0;i < PosNum && getline(filePos, imgName); i++) { //cout << "处理正样本:" << imgName << endl; imgName = "F:\\opencv_project\\data\\INRIAPerson\\Train\\pos\\" + imgName; //加路径 Mat img1 = imread(imgName, IMREAD_UNCHANGED); Mat img; if (img1.empty()) { cout << "can not load the image:" << imgName << endl; continue; } //img = img(Rect(16, 16, 64, 128));//将96*160的正样本裁剪未64*128 resize(img1,img,Size(64,128)); vector<float> descriptors;//hog描述符 向量 hog.compute(img, descriptors, Size(8, 8));//计算hog描述子,检测窗口移动步长(8,8) //cout << "描述子维数:" << descriptors.size() << endl; //根据第一个样本,计算出维数,创建特征矩阵和标签矩阵 if (0 == i) { hogDescriptorDim = descriptors.size();//hogDescriptorDim=3780 sampleFeatureMat = Mat::zeros(PosNum + NegNum, hogDescriptorDim, CV_32FC1); //sampleLabelMat= Mat::zeros(1,PosNum + NegNum, ); } //将计算得到的描述子复制到样本特征矩阵 for (int j = 0;j < hogDescriptorDim;j++) { sampleFeatureMat.at<float>(i, j) = descriptors[j];//第i个样本的特征向量中的第j个元素 } //sampleLabelMat.at<int>(0, i) = 1;//正样本标签 sampleLabelVec.push_back(1); } cout << "pos ok!" << endl; //读取负样本,生成hog描述符 for (int i = 0;i < NegNum && getline(fileNeg, imgName); i++) { //cout << "处理正样本:" << imgName << endl; imgName = "F:\\opencv_project\\data\\INRIAPerson\\Train\\new_neg\\" + imgName; //加路径 Mat img = imread(imgName); vector<float> descriptors;//hog描述符 向量 hog.compute(img, descriptors, Size(8, 8));//计算hog描述子,检测窗口移动步长(8,8) //cout << "负样本描述子维数:" << descriptors.size() << endl; /** CV_WRAP virtual void compute(InputArray img,CV_OUT std::vector<float>& descriptors, Size winStride = Size(), Size padding = Size(), const std::vector<Point>& locations = std::vector<Point>()) const; @brief Computes HOG descriptors of given image. @param img Matrix of the type CV_8U containing an image where HOG features will be calculated. @param descriptors Matrix of the type CV_32F @param winStride Window stride. It must be a multiple of block stride. @param padding Padding @param locations Vector of Point */ //将计算得到的描述子复制到样本特征矩阵 for (int j = 0;j < hogDescriptorDim;j++) { sampleFeatureMat.at<float>(i+PosNum , j) = descriptors[j];//第i个样本的特征向量中的第j个元素 } //sampleLabelMat.at<int>(0, i) = -1;//负样本标签 sampleLabelVec.push_back(-1); } cout << "neg ok!" << endl; //输出样本的HOG特征向量矩阵到文件 ofstream fout("data\\hogDescriptor.txt"); for (int i = 0;i < PosNum + NegNum;i++) { fout << i << endl; for (int j = 0;j < hogDescriptorDim;j++) fout << sampleFeatureMat.at<float>(i, j) << " "; fout << endl; } fout.close(); ///////////////////////////////////使用SVM分类器训练/////////////////////////////////////////////////// //设置参数,注意Ptr的使用 Ptr<ml::SVM> svm = ml::SVM::create(); svm->setType(ml::SVM::C_SVC); svm->setKernel(ml::SVM::LINEAR);//注意必须使用线性SVM进行训练,因为HogDescriptor检测函数只支持线性检测!!! svm->setTermCriteria(TermCriteria(CV_TERMCRIT_ITER, 1000, FLT_EPSILON)); //使用SVM学习 svm->train(sampleFeatureMat, ml::ROW_SAMPLE, sampleLabelVec); /** train(InputArray samples, int layout, InputArray responses); @brief Trains the statistical model @param samples training samples @param layout See ml::SampleTypes. @param responses vector of responses associated with the training samples. */ //保存分类器(里面包括了SVM的参数,支持向量support vector , α和rho) svm->save("data\\HOG_SVM_Classifier.xml"); /* SVM训练完成后得到的XML文件里面,有一个数组,叫做support vector,还有一个数组,叫做alpha,有一个浮点数,叫做rho; 将alpha矩阵同support vector相乘,注意,alpha*supportVector,将得到一个行向量,将该向量前面乘以-1。 之后,在该行向量的最后添加一个元素rho。如此,便得到了一个分类器,利用该分类器,直接替换opencv中行人检测默认的 那个分类器(cv::HOGDescriptor::setSVMDetector()), */ //获取支持向量机:矩阵默认是CV_32F浮点型 Mat supportVector = svm->getSupportVectors(); /** getSupportVectors(); @brief Retrieves all the support vectors the method returns all the support vectors as a floating-point matrix, where support vectors are stored as matrix rows. */ //获取alpha和rho Mat alpha;//每个支持向量对应的参数α(拉格朗日乘子),默认alpha是float64的 Mat svIndex;//支持向量所在的索引 float rho = svm->getDecisionFunction(0, alpha, svIndex); /** CV_WRAP virtual double getDecisionFunction(int i, OutputArray alpha, OutputArray svidx) const = 0; @brief Retrieves the decision function决策函数 @param i the index of the decision function. If the problem solved is regression, 1-class or 2-class classification, then there will be just one decision function and the index should always be 0. Otherwise, in the case of N-class classification, there will be N(N-1)/2 (ovr一对多) decision functions. @param alpha the optional output vector for weights, corresponding to different support vectors. In the case of linear %SVM all the alpha's will be 1's. @param svidx the optional output vector of indices of support vectors within the matrix of support vectors (which can be retrieved by SVM::getSupportVectors). In the case of linear %SVM each decision function consists of a single "compressed" support vector. The method returns rho parameter of the decision function, a scalar subtracted from the weighted sum of kernel responses. */ //转换类型:这里一定要注意,需要转换为32的 Mat alpha2; alpha.convertTo(alpha2, CV_32FC1); //结果矩阵,两个矩阵相乘 Mat resultMat(1, hogDescriptorDim, CV_32FC1); resultMat = alpha2 * supportVector; //乘以-1,这里为什么会乘以-1? //注意因为svm.predict使用的是alpha*sv*another-rho,如果为负的话则认为是正样本,在HOG的检测函数中,使用rho+alpha*sv*another(another为-1) for (int i = 0; i < hogDescriptorDim; ++i) resultMat.at<float>(0, i) *= -1; //得到最终的setSVMDetector(const vector<float>& detector)参数中可用的检测子 vector<float> myDetector; //将resultMat中的数据复制到数组myDetector中 for (int i = 0; i<hogDescriptorDim; i++) { myDetector.push_back(resultMat.at<float>(0, i)); } //最后添加偏移量rho,得到检测子 myDetector.push_back(rho); cout << "检测子维数:" << myDetector.size() << endl; //将分类器保存到文件,便于HOG识别 //这个才是真正的判别函数的参数(ω),HOG可以直接使用该参数进行识别 ofstream fopen1("data\\HOG_SVM.txt"); for (int i = 0; i< myDetector.size(); i++) { fopen1<<myDetector[i]<<" "; } fopen1.close(); //设置HOGDescriptor的检测子 /*HOGDescriptor myHOG; myHOG.setSVMDetector(myDetector);*/ //myHOG.setSVMDetector(HOGDescriptor::getDefaultPeopleDetector()); return myDetector; } /** getSupportVectors() @brief Retrieves all the support vectors The method returns all the support vectors as a floating-point matrix, where support vectors are stored as matrix rows. */ void detectAndDraw(HOGDescriptor &hog, Mat &img) { vector<Rect> found, found_filtered; double t = (double)getTickCount(); hog.detectMultiScale(img, found, 0, Size(8, 8), Size(32, 32), 1.05, 2);//多尺度检测目标,返回的矩形从大到小排列 t = (double)getTickCount() - t; cout << "detection time = " << (t*1000. / cv::getTickFrequency()) << " ms" << endl; cout << "detection result = " << found.size() << " Rects" << endl; for (size_t i = 0; i < found.size(); i++) { Rect r = found[i]; size_t j; // Do not add small detections inside a bigger detection. 如果有嵌套的话,则取外面最大的那个矩形框放入found_filtered中 for (j = 0; j < found.size(); j++) if (j != i && (r & found[j]) == r) break; if (j == found.size()) found_filtered.push_back(r); } cout << "Real detection result = " << found_filtered.size() << " Rects" << endl; for (size_t i = 0; i < found_filtered.size(); i++) { Rect r = found_filtered[i]; // The HOG detector returns slightly larger rectangles than the real objects, // hog检测结果返回的矩形比实际的要大一些 // 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); rectangle(img, r.tl(), r.br(), cv::Scalar(0, 255, 0), 3); } } /********************************* main() *******************************************/ int main() { //crop_negsample_random(); //裁剪负样本
//Hog描述符:图片尺寸64*128,block尺寸16*16,块步长(8,8),cell尺寸8*8,方向梯度直方图bin=9
HOGDescriptor hog(Size(64, 128), Size(16, 16), Size(8, 8), Size(8, 8), 9);
vector<float> myDetector;
myDetector=trainHogSvm(hog);//HOG特征 + SVM训练
hog.setSVMDetector(myDetector);//设置SVM检测器
Mat img = imread("pedestrian.jpg");
if(img.empty()) cout<<"read fail\n"<<endl;
detectAndDraw(hog, img);//检测
namedWindow("frame");
imshow("frame", img);
while(waitKey(10)!=27) ;
destroyWindow("frame");
return 0;
}
------------------------------------------- END -------------------------------------
参考:https://blog.csdn.net/masibuaa/article/details/16113373
https://blog.csdn.net/masibuaa/article/details/16104981
https://blog.csdn.net/qq_37753409/article/details/79047063
https://blog.csdn.net/qianqing13579/article/details/46509037