HOG特征原理主要参考http://blog.csdn.net/abcjennifer/article/details/7365651
HOG即histogram of oriented gradient, 是用于目标检测的特征描述子,该技术将图像局部出现的方向梯度次数进行计数,该方法和边缘方向直方图、scale-invariant feature transform类似,不同的是hog的计算基于一致空间的密度矩阵来提高准确率。Navneet Dalal and Bill Triggs首先在05年的CVPR中提出HOG,用于静态图像or视频的行人检测。
HOG特征原理:
HOG的核心思想是所检测的局部物体外形能够被光强梯度或边缘方向的分布所描述。通过将整幅图像分割成小的连接区域(称为cells),每个cell生成一个方向梯度直方图或者cell中pixel的边缘方向,这些直方图的组合可表示出(所检测目标的目标)描述子。为改善准确率,局部直方图可以通过计算图像中一个较大区域(称为block)的光强作为measure被对比标准化,然后用这个值(measure)归一化这个block中的所有cells.这个归一化过程完成了更好的照射/阴影不变性。
与其他描述子相比,HOG得到的描述子保持了几何和光学转化不变性(除非物体方向改变)。因此HOG描述子尤其适合人的检测。
通俗的讲:
HOG特征提取方法就是将一个image:
1. 灰度化(将图像看做一个x,y,z(灰度)的三维图像)
2. 划分成小cells(2*2)
3. 计算每个cell中每个pixel的gradient(即orientation)
4. 统计每个cell的梯度直方图(不同梯度的个数),即可形成每个cell的descriptor
再小谈下Hog、SIFT与PCA-SIFT的应用与区别:
Hog没有旋转和尺度不变性,因此计算量小;而SIFT中每个feature需要用128维的向量来描述,因此计算量相对很大。
那么行人检测中怎么应用HOG呢?
对于解决Scale-invariant 的问题:将图片进行不同尺度的缩放,就相当于对模板进行不同尺度scale的缩放
对于解决Rotation-invariant 的问题:建立不同方向的模版(一般取15*7的)进行匹配
总的来说,就是在不同尺度上的图像进行不同方向的模板(15*7)匹配,每个点形成一个8方向的梯度描述。
SIFT由于其庞大计算量不用与行人检测,而PCA-SIFT的方法过滤掉很多维度的信息,只保留20个主分量,因此只适用于行为变化不大的物体检测。
method |
Time |
Scale |
Rotation |
Blur |
Illumination |
Affine |
Sift |
common |
best |
best |
common |
common |
good |
PCA-sift |
good |
good |
good |
best |
good |
best |
Surf |
best |
common |
common |
good |
best |
good |
关于sift的其他讲解:
http://blog.csdn.net/abcjennifer/article/details/7639681
http://blog.csdn.net/abcjennifer/article/details/7372880
http://blog.csdn.net/abcjennifer/article/details/7365882
svm性别识别参考自:http://m.blog.csdn.net/article/details?id=50480518
支持向量机在解决二分类问题方面有着强大的威力(当然也可以解决多分类问题),性别识别是典型的二分类模式识别问题,因此很适合用SVM进行处理,同时OpenCv又对SVM进行了很好的封装,调用非常方便,因此我们在这个性别识别程序中考虑加入SVM方法。
在这里我们采用了HOG+SVM的模式来进行,即先提取图像的HOG特征,然后将这些HOG特征输入SVM中进行训练。
一、SVM概述
SVM的数学原理十分复杂,我们不在这里过多讨论,有关OpenCv中SVM的用法,这里为大家提供两篇博客以供参考:OpenCV的SVM用法以及OpenCV 2.4+ C++ SVM介绍。
二、HOG特征概述
HOG特征是图像的梯度特征,具体参见:目标检测的图像特征提取之(一)HOG特征
三、建立训练集
这里继续沿用上一篇博文中提到的性别识别训练集,400张男性人脸样本400张女性人脸样本,下载地址:性别识别数据集。
四、算法的训练与测试
1、建立控制台工程,配置OpenCv环境
这里将工程命名为:GenderSVM。
2、编写批量读取函数read_csv()
只要涉及到训练,都需要批量读取训练样本的操作,SVM也不例外,因此需要先编写批量读取函数read_csv()。考虑到之前的批量读取函数必须一次性将所有训练样本读入内存中,内存消耗较大,在这里做一个小小的改进:
void read_csv(String& csvPath,Vector<String>& trainPath,Vector<int>& label,char separator = ';') { string line,path,classLabel; ifstream file(csvPath.c_str(),ifstream::in); while (getline(file,line)) { stringstream lines(line); getline(lines,path,separator); getline(lines,classLabel); if (!path.empty()&&!classLabel.empty()) { trainPath.push_back(path); label.push_back(atoi(classLabel.c_str())); } } }
可见这里我们将输入参数由vector<Mat>改为vector<String>,然后返回装有训练样本的所有路径的容器,需要时在根据其中的路径进行读取,降低了内存占用量。
3、读入训练样本路径
string trainCsvPath = "E:\\性别识别数据库—CAS-PEAL\\at.txt"; vector<String> vecTrainPath; vector<int> vecTrainLabel; read_csv(trainCsvPath,vecTrainPath,vecTrainLabel);
顺利批量读入路径:
4、训练初始化
在提取HOG特征之前,需要初始化训练数据矩阵:
/**********初始化训练数据矩阵**********/ int iNumTrain = 800; Mat trainDataHog; Mat trainLabel = Mat::zeros(iNumTrain,1,CV_32FC1);
需要强调的是SVM的训练数据必须都是CV_32FC1格式,因此这里显式的将标签矩阵trainLabel初始化为CV_32FC1格式,trainDataHog稍后进行初始化。
5、提取图像HOG特征
接下来循环读入所有的训练样本,提取HOG特征,放在训练数据矩阵中。考虑嵌套代码的复杂性,这里先给出整体代码,稍后解释:
/**********提取HOG特征,放入训练数据矩阵中**********/ Mat imageSrc; for (int i = 0; i < iNumTrain; i++) { imageSrc = imread(vecTrainPath[i].c_str(),1); resize(imageSrc,imageSrc,Size(64,64)); HOGDescriptor *hog = new HOGDescriptor(cvSize(64,64),cvSize(16,16), cvSize(8,8),cvSize(8,8),9); vector<float> descriptor; hog->compute(imageSrc,descriptor,Size(1,1),Size(0,0)); if (i == 0) { trainDataHog = Mat::zeros(iNumTrain,descriptor.size(),CV_32FC1); } int n = 0; for (vector<float>::iterator iter = descriptor.begin();iter != descriptor.end();iter++) { trainDataHog.at<float>(i,n) = *iter; n++; } trainLabel.at<float>(i,0) = vecTrainLabel[i]; }
接下来我们对这段代码进行详细解释。
(1)循环读入训练样本
从vecTrainPath容器中逐条取出训练样本路径,然后读取:
imageSrc = imread(vecTrainPath[i].c_str(),1);
(2)尺寸归一化
我们这里将图像尺寸归一化为64*64,这是因为当时在写程序时参考了一篇关于HOG特征的博客。这里的尺寸大家可以随意设定,当然也会影响最终的识别效率,64*64可能并不是一个最优的尺寸:
imageSrc = imread(vecTrainPath[i].c_str(),1); resize(imageSrc,imageSrc,Size(64,64));
(3)计算HOG特征
OpenCv给出的HOG特征计算接口非常简洁,三句话即完成:
HOGDescriptor *hog = new HOGDescriptor(cvSize(64,64),cvSize(16,16), cvSize(8,8),cvSize(8,8),9); vector<float> descriptor; hog->compute(imageSrc,descriptor,Size(1,1),Size(0,0));
提取的特征以容器的数据 结构形式给出。至于计算时的参数设定,参见我之前提供的那两篇博客即可。
(4)初始化数据矩阵trainDataHog
前面提到,SVM中用到的训练数据矩阵必须是CV_32FLOAT形式的,因此需要对数据矩阵显示的指定其尺寸和类型。然后由于trainDataHog行数为训练样本个数,而列数为图片HOG特征的维数,因此无法在进行HOG特征提取之前确定其尺寸,因此这里选择在进行完第一张样本的HOG特征、得到对应维数之后,在进行初始化:
if (i == 0) { trainDataHog = Mat::zeros(iNumTrain,descriptor.size(),CV_32FC1); }
(5)将得到的HOG特征存入数据矩阵
得到的HOG特征是浮点数容器的形式,我们需要将其转换成矩阵的形式以便于训练SVM,这就涉及到了vector和Mat两个数据结构的遍历。vector遍历这里推荐使用迭代器的方式,而Mat遍历这里则选择了相对耗时但是最简单的方式——直接使用at函数:
int n = 0; for (vector<float>::iterator iter = descriptor.begin();iter != descriptor.end();iter++) { trainDataHog.at<float>(i,n) = *iter; n++; } trainLabel.at<float>(i,0) = vecTrainLabel[i];
训练得到的HOG特征如图所示:
可见在当前的参数设定下,提取到的HOG特征为1764维,共800张训练样本,每一行代表一个图片的HOG特征向量。通过“ctrl+鼠标滚轮”放大观察特征向量的具体参数:
6、训练SVM分类器
有关OpenCv中SVM分类器的使用可以参见以下博客:OpenCV 2.4+ C++ SVM介绍。
首先,初始化相关参数:
/**********初始化SVM分类器**********/ CvSVM svm; CvSVMParams param; CvTermCriteria criteria; criteria = cvTermCriteria( CV_TERMCRIT_EPS, 1000, FLT_EPSILON ); param = CvSVMParams(CvSVM::C_SVC, CvSVM::RBF, 10.0, 0.09, 1.0, 10.0, 0.5, 1.0, NULL, criteria );
开始训练、训练完成后保存分类器:
/**********训练并保存SVM**********/ svm.train(trainDataHog,trainLabel,Mat(),Mat(),param); svm.save("E:\\性别识别数据库—CAS-PEAL\\SVM_SEX_Model.txt");
注意我们这里选择将分类器保存为txt形式:
当然,我们可以打开这个txt文件,查看里面的参数:
7、测试分类效果
测试过程和训练过程基本相同,读取图片、尺寸归一化、提取HOG特征、预测:
/**********测试SVM分类性能**********/ Mat testImage = imread("E:\\性别识别数据库—CAS-PEAL\\测试样本\\女性测试样本\\face_35.bmp"); resize(testImage,testImage,Size(64,64)); HOGDescriptor *hog = new HOGDescriptor(cvSize(64,64),cvSize(16,16), cvSize(8,8),cvSize(8,8),9); vector<float> descriptor; hog->compute(testImage,descriptor,Size(1,1),Size(0,0)); Mat testHog = Mat::zeros(1,descriptor.size(),CV_32FC1); int n = 0; for (vector<float>::iterator iter = descriptor.begin();iter != descriptor.end();iter++) { testHog.at<float>(0,n) = *iter; n++; } int predictResult = svm.predict(testHog);
8、完整代码
这里给出HOG+SVM进行性别识别的完整代码:
// GenderSVM.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <opencv2\opencv.hpp> #include <iostream> #include <sstream> #include <fstream> using namespace std; using namespace cv; void read_csv(String& csvPath,vector<String>& trainPath,vector<int>& label,char separator = ';') { string line,path,classLabel; ifstream file(csvPath.c_str(),ifstream::in); while (getline(file,line)) { stringstream lines(line); getline(lines,path,separator); getline(lines,classLabel); if (!path.empty()&&!classLabel.empty()) { trainPath.push_back(path); label.push_back(atoi(classLabel.c_str())); } } } int _tmain(int argc, _TCHAR* argv[]) { /**********批量读入训练样本路径**********/ string trainCsvPath = "E:\\性别识别数据库—CAS-PEAL\\at.txt"; vector<String> vecTrainPath; vector<int> vecTrainLabel; read_csv(trainCsvPath,vecTrainPath,vecTrainLabel); /**********初始化训练数据矩阵**********/ int iNumTrain = 800; Mat trainDataHog; Mat trainLabel = Mat::zeros(iNumTrain,1,CV_32FC1); /**********提取HOG特征,放入训练数据矩阵中**********/ Mat imageSrc; for (int i = 0; i < iNumTrain; i++) { imageSrc = imread(vecTrainPath[i].c_str(),1); resize(imageSrc,imageSrc,Size(64,64)); HOGDescriptor *hog = new HOGDescriptor(cvSize(64,64),cvSize(16,16), cvSize(8,8),cvSize(8,8),9); vector<float> descriptor; hog->compute(imageSrc,descriptor,Size(1,1),Size(0,0)); if (i == 0) { trainDataHog = Mat::zeros(iNumTrain,descriptor.size(),CV_32FC1); } int n = 0; for (vector<float>::iterator iter = descriptor.begin();iter != descriptor.end();iter++) { trainDataHog.at<float>(i,n) = *iter; n++; } trainLabel.at<float>(i,0) = vecTrainLabel[i]; } /**********初始化SVM分类器**********/ CvSVM svm; CvSVMParams param; CvTermCriteria criteria; criteria = cvTermCriteria( CV_TERMCRIT_EPS, 1000, FLT_EPSILON ); param = CvSVMParams(CvSVM::C_SVC, CvSVM::RBF, 10.0, 0.09, 1.0, 10.0, 0.5, 1.0, NULL, criteria ); /**********训练并保存SVM**********/ svm.train(trainDataHog,trainLabel,Mat(),Mat(),param); svm.save("E:\\性别识别数据库—CAS-PEAL\\SVM_SEX_Model.txt"); /**********测试SVM分类性能**********/ Mat testImage = imread("E:\\性别识别数据库—CAS-PEAL\\测试样本\\女性测试样本\\face_35.bmp"); resize(testImage,testImage,Size(64,64)); HOGDescriptor *hog = new HOGDescriptor(cvSize(64,64),cvSize(16,16), cvSize(8,8),cvSize(8,8),9); vector<float> descriptor; hog->compute(testImage,descriptor,Size(1,1),Size(0,0)); Mat testHog = Mat::zeros(1,descriptor.size(),CV_32FC1); int n = 0; for (vector<float>::iterator iter = descriptor.begin();iter != descriptor.end();iter++) { testHog.at<float>(0,n) = *iter; n++; } int predictResult = svm.predict(testHog); return 0; }
五、总结
以上就是通过HOG特征+SVM进行性别识别的完整代码,在编写代码的过程中遇到了一些有趣的问题,这里稍作总结。
1、变量命名格式
当代码量很大的时候,变量的命名格式就显得十分重要,相信大家早已不用那种a、b、m、n这种简单的无意义的命名方法了。在C++中推荐大家使用匈牙利命名法,即“类型缩写+变量名缩写”的命名格式。例如vecTrainPath这个变量名,前缀“vec”表明这个变量是一个vector格式的变量,而“TrainPath”则表明这个容器中存放的是训练样本的路径。这种命名方式在大型工程中非常重要,还有一点需要注意的是当变量名中出现多个缩略短语时,推荐第一个短语小写,其他短语的首字母大写。
2、为何选择HOG特征
通过实验发现,直接将图像向量化后输入SVM(不经过特征提取)的方式的正确率将不理想。虽然本质上像素本身最能代表图像的语义信息,但由于SVM并不具备特征提取能力,因此效果不佳。确切的说,特征提取是模式分类的必要过程,即便是深度学习也不例外,因为深度学习(DeepLearning)本质上也是一种特征提取的手段,只不过提取得到的特征更深层,更抽象,表现力更强。为此我之前曾专门写过一篇博客进行阐述:浅谈模式识别中的特征提取
当然这里大家可以尝试提取其他特征之后再进行分类,甚至可以考虑通过提起深度特征来进行分类,这里只是以HOG特征为例而已。
4、有关vector的一些使用(为什么不用int型数组)
在这段代码中我们大量用到了vector结构,这是C++11的新特性。仔细观察,其实vector结构的最明显的一个优势就是能够动态分配大小,实时添加/删除元素,这点是数组所不能实现的。虽然可以通过new操作符来实现数组的动态分配,但我们仍推荐大家在需要使用可动态变化的数组的场合,使用vector。
5、Vectot和vector
在编写代码是仔细留心编译器给出的拼写提示,会发现这样一现象:
那么vector和Vector有什么区别呢?一句话,Vector是OpenCv中的vector,类似的还有String和string等。Vector和String这类结构是隶属于OpenCv的: