转载地址:http://blog.csdn.net/xw20084898/article/details/21078801
1、HOG算子定义
HOG又叫梯度直方图特征,是一种对图像局部重叠区域的密集型描述符, 它通过计算局部区域的梯度方向直方图来构成特征。Hog特征结合SVM分类器已经被广泛应用于图像识别中,尤其在行人检测中获得了极大的成功。需要提醒的是,HOG+SVM进行行人检测的方法是法国研究人员Dalal在2005的CVPR上提出的,而如今虽然有很多行人检测算法不断提出,但基本都是以HOG+SVM的思路为主。
HOG特征是一种局部区域描述符,它通过计算局部区域上的梯度方向直方图来构成人体特征,能够很好地描述人体的边缘。它对光照变化和小量的偏移不敏感。
图像中像素点(x,y)的梯度为
Dalal提出的Hog特征提取的过程:把样本图像分割为若干个像素的单元(cell),把梯度方向平均划分为9个区间(bin),在每个单元里面对所有像素的梯度方向在各个方向区间进行直方图统计,得到一个9维的特征向量,每相邻的4个单元构成一个块(block),把一个块内的特征向量联起来得到36维的特征向量,用块对样本图像进行扫描,扫描步长为一个单元。最后将所有块的特征串联起来,就得到了人体的特征。例如,对于64*128的图像而言,每2*2的单元(16*16的像素)构成一个块,每个块内有4*9=36个特征,以8个像素为步长,那么,水平方向将有7个扫描窗口,垂直方向将有15个扫描窗口。也就是说,64*128的图片,总共有36*7*15=3780个特征。
在行人检测过程中,除了上面提到的HOG特征提取过程,还包括彩图转灰度,亮度校正等步骤。总结一下,在行人检测中,HOG特征计算的步骤:
(1)将输入的彩图转换为灰度图;
(2)采用Gamma校正法对输入图像进行颜色空间的标准化(归一化); 目的是调节图像的对比度,降低图像局部的阴影和光照变化所造成的影响,同时可以抑制噪音的干扰;
(3)计算梯度;主要是为了捕获轮廓信息,同时进一步弱化光照的干扰。
(4)将梯度投影到单元的梯度方向;目的是为局部图像区域提供一个编码,
(5)将所有单元格在块上进行归一化;归一化能够更进一步对光照、阴影和边缘进行压缩,通常,每个单元格由多个不同的块共享,但它的归一化是基于不同块的,所以计算结果也不一样。因此,一个单元格的特征会以不同的结果多次出现在最后的向量中。我们将归一化之后的块描述符就称之为HOG描述符。
(6)收集得到检测空间所有块的HOG特征;该步骤就是将检测窗口中所有重叠的块进行HOG特征的收集,并将它们结合成最终的特征向量供分类使用。
7) HOG特征算子的网络参考资料
http://www.cnblogs.com/tornadomeet/archive/2012/08/15/2640754.html //讲的很好,讲解了源码的算法和思路
http://blog.csdn.net/carson2005/article/details/7841443#
http://blog.csdn.net/abcjennifer/article/details/7365651
http://blog.csdn.net/zouxy09/article/details/7929348#
2、HOG特征描述算子——原理、思路、步骤
-----------------------------------------------------------------------------------
1. HOG特征描述子的定义
HOG Descriptor: locally normalised histogram of gradient orientation in dense overlapping grids,即局部归一化的梯度方向直方图。
2. HOG特征的基本思想
Histogram of Oriented Gradient descriptors provide a dense overlapping description of image regions,即统计图像局部区域的梯度方向信息来作为该局部图像区域的表征。
HOG有点类似于SIFT特征描述子,区别:
1)HOG没有选取主方向,也没有旋转梯度方向直方图,因而本身不具有旋转不变性(较大的方向变化),其rotate不变性是通过采用不同旋转方向的训练样本来实现的;
2)HOG本身不具有scale不变性,其scale不变性是通过改变检测图像的size来实现的;
3)HOG是在dense采样的图像块中求取的,在计算得到的HOG特征向量中隐含了该块与检测窗口之间的空间位子关系,而SIFT特征向量是在一些独立并离散分布的特征点上提取的(dense SIFT除外)。
3. HOG特征的假设和出发点
The hypothesis is that local object appearance and shape can often be characterised rather well by the distribution of local intensity gradient or edge directions, even without precise knowledge of the corresponding gradient or edge positions. 即,在图像中,物体的局部外观和形状能够通过其局部梯度或边缘信息很好地表征和描述。
3. HOG特征描述向量的提取与计算步骤
HOG特征提取的流程图如下图所示:
1)全局图像归一化
目的:减少光照的影响
方法:gamma compression
a) 对每个颜色通道分别,计算平方根,或者
b) 对每个颜色通道分别,求log
2)计算图像梯度
目的:通过梯度信息来描述图像中物体的边缘、轮廓、形状等纹理信息
方法:对每个颜色通道分别计算梯度。梯度算子:水平边缘算子: [-1, 0, 1] ;垂直边缘算子: [-1, 0, 1]T 。
最后选择三个通道中具有最大模值norm的梯度vector作为该像素的gradient vector。
3)统计局部图像梯度信息
目的:统计局部图像梯度信息并进行量化(或称为编码),得到局部图像区域的特征描述向量。该特征描述算向量既能够描述局部图像的内容,又能够对该图像区域内的pose或外观的小变化具有不变性。
方法:求取梯度方向直方图
a)将image window划分为多个区域“cell”;
b)为每个“cell”计算一个1-D的加权梯度方向直方图;
其中,直方图包含9个bin,划分区间:0°-180°或0°-360°。
其中,加权采用三线性插值方法,即将当前像素的梯度方向大小、像素在cell中的x坐标与y坐标这三个值来作为插值权重,而被用来插入的值为像素的梯度幅值。
采用三线性插值的好处在于:避免了梯度方向直方图在cell边界和梯度方向量化的bin边界处的突然变化。
4)归一化
目的:对每个block得到的histogram进行归一化后,能够够对光照、阴影、边缘对比度等具有更好的不变性、
方法:
1)将多个临近的cell组合成一个block块,然后求其梯度方向直方图向量;
2)采用L2-Norm with Hysteresis threshold方式进行归一化,即将直方图向量中bin值的最大值限制为0.2以下,然后再重新归一化一次;
注意:block之间的是“共享”的,也即是说,一个cell会被多个block“共享”。另外,每个“cell”在被归一化时都是“block”independent的,也就是说每个cell在其所属的block中都会被归一化一次,得到一个vector。
5)生成特征描述向量
即将所有“block”的HOG descriptors组合在一起,形成最终的feature vector,该feature vector就描述了detect window的图像内容。
4. HOG描述算子的优点:
1)orientation histogram
能够有效地描述图像区域的local shape的特征信息
2)采用“cell”方式进行梯度方向量化,使得特征描述算子具有一些(a small amount of)平移或旋转不变性
通过改变histogram的bin个数,以及“cell”的size,能够控制捕获图像局部区域特征信息的精度和保持特征具有不变性
3)具有光照不变性
Gamma normalisation and local contrast normalisation(局部对比度归一化) contribute another key component: illumination invariance.
4)overlapping blocks
The use of overlapping of blocks provides alternative normalisations so that the classifier can choose the most relevant one.
5. 影响HOG性能的几个因素:
finescale gradients, fine orientation binning, relatively coarse spatial binning, and high-quality local contrast normalisation in overlapping descriptor blocks are all important for good performance.
6. 解释
1)为什么使用orientation histogram?
capture local shape information
2)为什么使用“cell”?
achieve a small amount of spatial invariance
3)为什么使用“overlapping blocks”?
在众多的local contrast normalisation方法中,采用overlapping blocks得到的效果最好。
7. HOG代码
OpenCV中包含了HOG特征的提取和描述类cv::HOGDescriptor。通过该类可以提取指定图像区域的HOG特征。
8、Opencv中的HOGDescriptor类
其定义在 object.hpp中找到的:
- struct CV_EXPORTS_W HOGDescriptor
- {
- public:
- enum { L2Hys=0 };
- enum { DEFAULT_NLEVELS=64 };
- CV_WRAP HOGDescriptor() : 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)
- {}
- CV_WRAP HOGDescriptor(Size _winSize, Size _blockSize, Size _blockStride,
- Size _cellSize, int _nbins, int _derivAperture=1, double _winSigma=-1,
- int _histogramNormType=HOGDescriptor::L2Hys,
- double _L2HysThreshold=0.2, bool _gammaCorrection=false,
- int _nlevels=HOGDescriptor::DEFAULT_NLEVELS)
- : winSize(_winSize), blockSize(_blockSize), blockStride(_blockStride), cellSize(_cellSize),
- nbins(_nbins), derivAperture(_derivAperture), winSigma(_winSigma),
- histogramNormType(_histogramNormType), L2HysThreshold(_L2HysThreshold),
- gammaCorrection(_gammaCorrection), nlevels(_nlevels)
- {}
- CV_WRAP HOGDescriptor(const String& filename)
- {
- load(filename);
- }
- HOGDescriptor(const HOGDescriptor& d)
- {
- d.copyTo(*this);
- }
- virtual ~HOGDescriptor() {}
- CV_WRAP size_t getDescriptorSize() const;
- CV_WRAP bool checkDetectorSize() const;
- CV_WRAP double getWinSigma() const;
- CV_WRAP virtual void setSVMDetector(InputArray _svmdetector);
- virtual bool read(FileNode& fn);
- virtual void write(FileStorage& fs, const String& objname) const;
- CV_WRAP virtual bool load(const String& filename, const String& objname=String());
- CV_WRAP virtual void save(const String& filename, const String& objname=String()) const;
- virtual void copyTo(HOGDescriptor& c) const;
- CV_WRAP virtual void compute(const Mat& img,
- CV_OUT vector<float>& descriptors,
- Size winStride=Size(), Size padding=Size(),
- const vector<Point>& locations=vector<Point>()) const;
- //with found weights output
- CV_WRAP virtual void detect(const Mat& img, CV_OUT vector<Point>& foundLocations,
- CV_OUT vector<double>& weights,
- double hitThreshold=0, Size winStride=Size(),
- Size padding=Size(),
- const vector<Point>& searchLocations=vector<Point>()) const;
- //without found weights output
- virtual void detect(const Mat& img, CV_OUT vector<Point>& foundLocations,
- double hitThreshold=0, Size winStride=Size(),
- Size padding=Size(),
- const vector<Point>& searchLocations=vector<Point>()) const;
- //with result weights output
- CV_WRAP virtual void detectMultiScale(const Mat& img, CV_OUT vector<Rect>& foundLocations,
- CV_OUT vector<double>& foundWeights, double hitThreshold=0,
- Size winStride=Size(), Size padding=Size(), double scale=1.05,
- double finalThreshold=2.0,bool useMeanshiftGrouping = false) const;
- //without found weights output
- virtual void detectMultiScale(const Mat& img, CV_OUT vector<Rect>& foundLocations,
- double hitThreshold=0, Size winStride=Size(),
- Size padding=Size(), double scale=1.05,
- double finalThreshold=2.0, bool useMeanshiftGrouping = false) const;
- CV_WRAP virtual void computeGradient(const Mat& img, CV_OUT Mat& grad, CV_OUT Mat& angleOfs,
- Size paddingTL=Size(), Size paddingBR=Size()) const;
- CV_WRAP static vector<float> getDefaultPeopleDetector();
- CV_WRAP static vector<float> getDaimlerPeopleDetector();
- CV_PROP Size winSize;
- CV_PROP Size blockSize;
- CV_PROP Size blockStride;
- CV_PROP Size cellSize;
- CV_PROP int nbins;
- CV_PROP int derivAperture;
- CV_PROP double winSigma;
- CV_PROP int histogramNormType;
- CV_PROP double L2HysThreshold;
- CV_PROP bool gammaCorrection;
- CV_PROP vector<float> svmDetector;
- CV_PROP int nlevels;
- // evaluate specified ROI and return confidence value for each location
- void detectROI(const cv::Mat& img, const vector<cv::Point> &locations,
- CV_OUT std::vector<cv::Point>& foundLocations, CV_OUT std::vector<double>& confidences,
- double hitThreshold = 0, cv::Size winStride = Size(),
- cv::Size padding = Size()) const;
- // evaluate specified ROI and return confidence value for each location in multiple scales
- void detectMultiScaleROI(const cv::Mat& img,
- CV_OUT std::vector<cv::Rect>& foundLocations,
- std::vector<DetectionROI>& locations,
- double hitThreshold = 0,
- int groupThreshold = 0) const;
- // read/parse Dalal's alt model file
- void readALTModel(std::string modelfile);
- };
默认构造函数的几个参数:
- 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 :块的大小
cellSize: 胞元的大小
nbins: 方向bin的个数 nBins表示在一个胞元(cell)中统计梯度的方向数目,例如nBins=9时,在一个胞元内统计9个方向的梯度直方图,每个方向为360/9=40度。
更详细请移步: http://blog.csdn.net/raocong2010/article/details/6239431
=++++++++++++++++++++++提取 HOG 特征+++++++++++++++++++++++++=
//样本矩阵,nImgNum:横坐标是样本数量。 列数是该 样本对应的 特征维数。ex: 样本是学生,其样本特征可以由 身高,体重,年龄 组成,那么 第二个参数就是 3 啦。
CvMat *data_mat = cvCreateMat( nImgNum, 1764, CV_32FC1 );
//类型矩阵,存储每个样本的类型标志 , 一维,只需要存储该样本属于哪一类即可(只有两类)
CvMat * res_mat = cvCreateMat( nImgNum, 1, CV_32FC1 );
HOGDescriptor *hog=new HOGDescriptor(cvSize(64,64),cvSize(16,16),cvSize(8,8),cvSize(8,8),9);
// 计算hog特征
// trainImg是读入的需要计算特征的图像,IplImage* trainImg=cvCreateImage(cvSize(64,64),8,3);
//descriptors 是结果数组 vector<float> descriptors; HOG特征的维数就是 = descriptors.size 啦,上例中,就是 那个3 啦。
hog->compute(trainImg, descriptors,Size(1,1), Size(0,0));
//计算完成后,把hog特征存储到 上面声明的那个 样本 矩阵中
// i 是当前处理的第 i 张 图片, n 从 0 开始 ++ ,从第 0 列 开始存储。 *iter 是 (vector<float>::iterator iter=descriptors.begin();iter!=descriptors.end();iter++)
cvmSet(data_mat, i, n,*iter);
// 训练读入的图片是有 标签 的( 知道已知属于哪一类), 将标签存入 标签 矩阵 。i 是当前处理的 图片 的 编号。 img_catg[i] 是 读入 的已知的 数据。
cvmSet( res_mat, i, 0, img_catg[i] );
++++++++++++++++++++++++++++++++++开始训练+++++++++++++++++++++++++++
首先要/新建一个SVM
CvSVM svm = CvSVM();
// 开始训练~
svm.train( data_mat, res_mat, NULL, NULL, param ); //data_mat 是 上面提取 到的 HOG特征,存储 m 个样本的 n 个特征, res_mat 是标签矩阵,m个样本属于哪一类,已// 知的。 param 的定义如下:
CvSVMParams param = CvSVMParams( CvSVM::C_SVC, CvSVM::RBF, 10.0, 0.09, 1.0, 10.0, 0.5, 1.0, NULL, criteria );
CvTermCriteria criteria = cvTermCriteria( CV_TERMCRIT_EPS, 1000, FLT_EPSILON );
// 将训练结果保存在 xml文件中
svm.save( "SVM_DATA.xml" );
此阶段生成文件:
SVM_DATA.xml
训练完成之后,就开始 对 你所需要 的 数据 进行 预测。 这里预测 当前 图片 属于 那一类别。
++++++++++++++++++++++++++++++++++检测样本+++++++++++++++++++++++++++
读入当前要预测的图片 testImg
将testImg 缩放 至 与 训练图片 一样大小 ,直接存放到 trainImg中
计算读入的图片的Hog特征,
hog->compute(trainImg, descriptors,Size(1,1), Size(0,0)); //调用计算函数开始计算
仍用 vector<float> descriptors; 存放结果
创建一个 一行 n 列 的向量。 n 是 特征的个数 。 就是上面的 3 啊, descriptors.size() 啊。 用来存放 当前要预测的图片的 特征
CvMat* SVMtrainMat=cvCreateMat(1,descriptors.size(),CV_32FC1);
// 开始预测
int ret = svm.predict(SVMtrainMat);
ret 返回的是 当前 预测 的 图片 的 类别。 就是 一开始 读到 标签 矩阵 中的 数据。 一般 用 0 or 1 来标示 两大类别。
可将结果文件保存在:
SVM_PREDICT.txt
+++++++++++++++++=OPENCV HOG特征向量个数计算方法+++++++++++++++++++++++
下面参照 网上 方法 说下 怎么计算 的 每个向量 的 特征 维数, 就是一开始就声明 的
样本矩阵 CvMat *data_mat = cvCreateMat( nImgNum, 1764, CV_32FC1 );
中这个 1764 是如何计算出来的。
1. 先确定 你 要训练 以及 检测 的图片 的 大小 IplImage* trainImg=cvCreateImage(cvSize(64,64),8,3);
ok 这里是 64 x 64
2. 确定 HOGDescriptor *hog=new HOGDescriptor(cvSize(64,64),cvSize(16,16),cvSize(8,8),cvSize(8,8),9);
第一个 窗口 大小 设置 为 上面的图片大小 64 x 64 。
第二个 块大小 是 16 x 16 的话 [ 额 这个肿么确定?与前面的 窗口大小有关系 么? 这是opencv中默认的大小]
第三个 块block的步进 stride 8 x 8
第四个是 胞元cell大小 8 x 8
第五个是 cell的直方图的 bin = 9 [ 不懂 +_+]
每个 cell 有 9 个向量
每个block 有 (16 / 8 ) * (16 / 8) = 2 * 2 = 4 个 cell, 那么现在就有 4 * 9 = 36 个向量啦
每个 窗口 有多少个 block 呢?
利用公式 (window_size - block_size)/block_stride + 1 对两个方向进行计算:
( 64 - 16) / 8 + 1 = 7
两个方向 7 * 7 = 49
so 共有 49* 36 = 1764