首先来说说图像处理领域的HOG特征:
HOG(histogram of oriented gradient) 是用于目标检测的特征描述子,由 Navneet Dalal 和 Bill Triggs 于05年在CVPR首次提出,是用于静态图像或视频行人检测的实用方法。HOG算法步骤:
扫描一副图像,
①将图像灰度化
②采用Gamma校正法进行颜色空间的标准化(归一化)——调节图像的对比度,降低图像局部的阴影、光照变化所造成的影响,抑制噪音的干扰
③计算每个像素梯度的大小和方向
④将图像划分成小cells
⑤统计每个cell的梯度直方图
⑥将2x2cell或者3x3cell或者更多...组成一个block,一个block内所有cell的特征descriptor串联起来便得到该block的HOG特征descriptor。将图像内的所有block的HOG特征descriptor串联起来就可以得到该图像的HOG特征descriptor。
说了这么多,其实我这种一般的玩家并没有懂得HOG算法的深层数学性原理,以后有空要好好研究一下。下面来说说HOG与SVM的结合。
我利用opencv+VS2010 for win7 64bits,很方便地实现了HOG特征的提取和SVM训练。OpenCV中的HOG特征提取功能使用了HOGDescriptor这个类来进行封装,其中也有现成的行人检测的接口。
①HOGDescriptor类,默认构造函数如下(更详细资料请看http://blog.csdn.net/raodotcong/article/details/6239431)
winSize(64,128), blockSize(16,16), blockStride(8,8),
cellSize(8,8), nbins(9), derivAperture(1), winSigma(-1),
histogramNormType(HOGDescriptor::L2Hys), L2HysThreshold(0.2), gammaCorrection(true),
nlevels(HOGDescriptor::DEFAULT_NLEVELS)
winSize : 窗口的大小
blockSize :块的大小
blockStride:块步长
cellSize: 胞元的大小
nbins: 方向bin的个数 nBins表示在一个胞元(cell)中统计梯度的方向数目,例如nBins=9时,在一个胞元内统计9个方向的梯度直方图,每个方向为360/9=40度
②HOGDescriptor.compute(src,descriptors,Size(8,8))方法:计算源图像src的描述子,步长为(8,8)
然后是SVM训练方法。首先需要了解CvSVM类,更详细资料请看http://blog.csdn.net/xidianzhimeng/article/details/41979785。
用opencv训练SVM的一般方法是:
①设置训练样本集
需要两组数据,一组是数据的类别,一组是数据的向量信息。
②设置SVM参数
利用CvSVMParams类实现类内的成员变量svm_type表示SVM类型:
CvSVM::C_SVC C-SVC
CvSVM::NU_SVCv-SVC
CvSVM::ONE_CLASS一类SVM
CvSVM::EPS_SVRe-SVR
CvSVM::NU_SVRv-SVR
成员变量kernel_type表示核函数的类型:
CvSVM::LINEAR线性:u‘v
CvSVM::POLY多项式:(r*u'v + coef0)^degree
CvSVM::RBFRBF函数:exp(-r|u-v|^2)
CvSVM::SIGMOIDsigmoid函数:tanh(r*u'v + coef0)
成员变量degree针对多项式核函数degree的设置,gamma针对多项式/rbf/sigmoid核函数的设置,coef0针对多项式/sigmoid核函数的设置,Cvalue为损失函数,在C-SVC、e-SVR、v-SVR中有效,nu设置v-SVC、一类SVM和v-SVR参数,p为设置e-SVR中损失函数的值,class_weightsC_SVC的权重,term_crit为SVM训练过程的终止条件。其中默认值degree = 0,gamma = 1,coef0 = 0,Cvalue = 1,nu = 0,p = 0,class_weights = 0
③训练SVM
调用CvSVM::train函数建立SVM模型,第一个参数为训练数据,第二个参数为分类结果,最后一个参数即CvSVMParams
用这个SVM进行分类
调用函数CvSVM::predict实现分类
④获得支持向量
除了分类,也可以得到SVM的支持向量,调用函数CvSVM::get_support_vector_count获得支持向量的个数,CvSVM::get_support_vector获得对应的索引编号的支持向量。
// step 1:
float labels[4] = {1.0, -1.0, -1.0, -1.0};
Mat labelsMat(3, 1, CV_32FC1, labels);
float trainingData[4][2] = { {501, 10}, {255, 10}, {501, 255}, {10, 501} };
Mat trainingDataMat(3, 2, CV_32FC1, trainingData);
// step 2:
CvSVMParams params;
params.svm_type = CvSVM::C_SVC;
params.kernel_type = CvSVM::LINEAR;
params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 100, 1e-6);
// step 3:
CvSVM SVM;
SVM.train(trainingDataMat, labelsMat, Mat(), Mat(), params);
// step 4:
Vec3b green(0, 255, 0), blue(255, 0, 0);
for (int i=0; i(1,2) << i,j);
float response = SVM.predict(sampleMat);
if (fabs(response-1.0) < 0.0001)
{
image.at(j, i) = green;
}
else if (fabs(response+1.0) < 0.001)
{
image.at(j, i) = blue;
}
}
}
// step 5:
int c = SVM.get_support_vector_count();
for (int i=0; i
好了接下来进入正题(咳咳,其实前面也是正题,不要在意我的措辞细节):
首先是找样本,正负样本当然是越多越好,我找的样本不多,正负样本各1200张左右,网上能找到很多有关人体检测的样本的,这里贴一下我的样本来源:http://pascal.inrialpes.fr/data/human/(很有名的INRIA行人数据库~)。样本搜集好之后,还要对正负样本的格式进行处理,我的处理步骤比较粗略:
①格式转换为统一的jpg格式(下载好的图片既有jpg也有png,所以为了处理方便,进行了统一转换);②正样本的大小进行归一化处理,我将正样本统一处理成64×128像素尺寸,负样本倒是不需要过多处理;③命名规则化——正负样本放入不同的文件夹,分别命名为1.jpg,2.jpg,3.jpg...这样做能方便图片的输入。
In particular...我刚开始自己编程写了个文件批量改名程序和格式转换程序,花了不少时间- -(声明:洛基是学渣)然后发现可以直接用专业的软件进行快速处理,当时心情简直了- -By the way,在这里洛基推荐一款图像处理方便挺不错的软件——ACDSee 18。除了会一直占用内存外,这款软件还是很好用的。
OK,now let's begin our game.
#include
#include
#include
#include
#include
#include
#include
using namespace cv;
using namespace std;
#define PosSamNO 1126 //正样本个数
#define NegSamNO 1210 //负样本个数
//生成setSVMDetector()中用到的检测子参数时要用到的SVM的decision_func参数时protected类型,只能继承之后通过函数访问
class MySVM : public CvSVM
{
public:
//获得SVM的决策函数中的alpha数组
double * get_alpha_vector()
{
return this->decision_func->alpha;
}
//获得SVM的决策函数中的rho参数,即偏移量
float get_rho()
{
return this->decision_func->rho;
}
};
int main()
{
HOGDescriptor hog(Size(64,128),Size(16,16),Size(8,8),Size(8,8),9);//窗口大小(64,128),块尺寸(16,16),块步长(8,8),cell尺寸(8,8),直方图bin个数9
int DescriptorDim;//HOG描述子的维数,由图片大小、检测窗口大小、块大小、细胞单元中直方图bin个数决定
MySVM svm;
string ImgName;//图片名
ifstream finPos("pos.txt");//正样本图片的文件名列表
ifstream finNeg("neg.txt");//负样本图片的文件名列表
Mat sampleFeatureMat;//所有训练样本的特征向量组成的矩阵,行数等于所有样本的个数,列数等于HOG描述子维数
Mat sampleLabelMat;//训练样本的类别向量,行数等于所有样本的个数,列数等于1;1表示有人,-1表示无人
//依次读取正样本图片,生成HOG描述子
for(int num=0; num descriptors;//HOG描述子向量
hog.compute(src,descriptors,Size(8,8));//计算HOG描述子,检测窗口移动步长(8,8)
//处理第一个样本时初始化特征向量矩阵和类别矩阵,因为只有知道了特征向量的维数才能初始化特征向量矩阵
if( 0 == num )
{
DescriptorDim = descriptors.size();//HOG描述子的维数
//初始化所有训练样本的特征向量组成的矩阵sampleFeatureMat,行数等于所有样本的个数,列数等于HOG描述子维数
sampleFeatureMat = Mat::zeros(PosSamNO+NegSamNO, DescriptorDim, CV_32FC1);
//初始化训练样本的类别向量,行数等于所有样本的个数,列数等于1;1表示有人,-1表示无人
sampleLabelMat = Mat::zeros(PosSamNO+NegSamNO+HardExampleNO, 1, CV_32FC1);
}
//将计算好的HOG描述子复制到样本特征矩阵sampleFeatureMat
for(int i=0; i(num,i) = descriptors[i];//第num个样本的特征向量中的第i个元素
sampleLabelMat.at(num,0) = 1;//正样本类别为1,有人
}
//处理负样本的流程和正样本大同小异
for(int num=0; num descriptors;//HOG描述子向量
hog.compute(src,descriptors,Size(8,8));//计算HOG描述子,检测窗口移动步长(8,8)
//将计算好的HOG描述子复制到样本特征矩阵sampleFeatureMat
for(int i=0; i(num+PosSamNO,i) = descriptors[i];//第PosSamNO+num个样本的特征向量中的第i个元素
sampleLabelMat.at(num+PosSamNO,0) = -1;//负样本类别为-1,无人
}
//输出样本的HOG特征向量矩阵到文件
ofstream fout("SampleFeatureMat.txt");
for(int i=0; i(i,j)<<" ";
fout<(i,j) = pSVData[j];//第i个向量的第j维数据
}
//将alpha向量的数据复制到alphaMat中
//double * pAlphaData = svm.get_alpha_vector();//返回SVM的决策函数中的alpha向量
double * pAlphaData = svm.get_alpha_vector();
for(int i=0; i(0,i) = pAlphaData[i];//alpha向量,长度等于支持向量个数
}
resultMat = -1 * alphaMat * supportVectorMat;//计算-(alphaMat * supportVectorMat),结果放到resultMat中,
//注意因为svm.predict使用的是alpha*sv*another-rho,如果为负的话则认为是正样本,在HOG的检测函数中,
//使用rho-alpha*sv*another如果为正的话是正样本,所以需要将后者变为负数之后保存起来
//得到最终的setSVMDetector(const vector& detector)参数中可用的检测子
vector myDetector;
//将resultMat中的数据复制到数组myDetector中
for(int i=0; i(0,i));
}
myDetector.push_back(svm.get_rho());//最后添加偏移量rho,得到检测子
cout<<"检测子维数:"< found, found_filtered;//矩形框数组
cout<<"进行多尺度HOG人体检测"<