特征提取-----HOG

一、HOG特征简介

来源于《Histograms of Oriented Gradients for Human Detection》

《Finding People in Images and Videos》(PhD Thesis) (较为详细)

二、分割图像

HOG是一个局部特征,如果对一大幅图片直接提取特征,是得不到好的效果的。从信息论角度讲,例如一幅640*480的图像,大概有30万个像素点,也就是说原始数据有30万维特征,如果直接做HOG的话,就算按照360度,分成360个bin,也没有表示这么大一幅图像的能力。从特征工程的角度看,只有图像区域比较小的情况,基于统计原理的直方图对于该区域才有表达能力,如果图像区域比较大,那么两个完全不同的图像的HOG特征,也可能很相似。但是如果区域较小,这种可能性就很小。最后,把图像分割成很多区块,然后对每个区块计算HOG特征,这也包含了几何(位置)特性。例如,正面的人脸,左上部分的图像区块提取的HOG特征一般是和眼睛的HOG特征符合的。接下来说HOG的图像分割策略,一般来说有overlap和non-overlap两种,如下图所示。overlap指的是分割出的区块(patch)互相交叠,有重合的区域。non-overlap指的是区块不交叠,没有重合的区域。

non-overlapoverlap

先说overlap,这种分割方式可以防止对一些物体的切割,还是以眼睛为例,如果分割的时候正好把眼睛从中间切割并且分到了两个patch中,提取完HOG特征之后,这会影响接下来的分类效果,但是如果两个patch之间overlap,那么至少在一个patch会有完整的眼睛。overlap的缺点是计算量大,因为重叠区域的像素需要重复计算。

再说non-overlap,缺点就是上面提到的,有时会将一个连续的物体切割开,得到不太“好”的HOG特征,优点是计算量小,尤其是与Pyramid(金字塔)结合时,这个优点更为明显。

三、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除外)。

四、HOG特征提取的流程图如下图所示:


(1)灰度化
    由于颜色信息作用不大,通常转化为灰度图。
(2)标准化gamma空间
    为了减少光照因素的影响,首先需要将整个图像进行规范化(归一化),这种处理能够有效地降低图像局部的阴影和光照变化。
            Gamma压缩公式:
            
             比如可以取Gamma=1/2;
 
(3)计算图像每个像素的梯度(包括大小和方向)
    计算图像横坐标和纵坐标方向的梯度,并据此计算每个像素位置的梯度方向值;求导操作不仅能够捕获轮廓,人影和一些纹理信息,还能进一步弱化光照的影响。
    梯度算子:水平边缘算子: [-1, 0, 1] ;垂直边缘算子: [-1, 0, 1]T   
    图像中像素点(x,y)的梯度为:

    作者也尝试了其他一些更复杂的模板,如3×3 Sobel 模板,或对角线模板(diagonal masks),但是在这个行人检测的实验中,这些复杂模板的表现都较差,所以作者的结论是:模板越简单,效果反而越好。

    作者也尝试了在使用微分模板前加入 一个高斯平滑滤波,但是这个高斯平滑滤波的加入使得检测效果更差,原因是:许多有用的图像信息是来自变化剧烈的边缘,而在计算梯度之前加入高斯滤波会把这些边缘滤除掉。
 
(4)将图像分割为小的Cell单元格
    由于Cell单元格是HOG特征最小的结构单位,而且其块Block和检测窗口Win的滑动步长就是一个Cell的宽度或高度,所以,先把整个图像分割为一个个的Cell单元格(8*8像素)。
 
(5)为每个单元格构建梯度方向直方图【重点】

    这步的目的是:统计局部图像梯度信息并进行量化(或称为编码),得到局部图像区域的特征描述向量。同时能够保持对图像中人体对象的姿势和外观的弱敏感性。

    我们将图像分成若干个“单元格cell”,例如每个cell为8*8个像素(可以是矩形的(rectangular),也可以是星形的(radial))。假设我们采用9个bin的直方图来统计这8*8个像素的梯度信息。也就是将cell的梯度方向360度分成9个方向块,如图所示:例如:如果这个像素的梯度方向是20-40度,直方图第2个bin的计数就加一,这样,对cell内每个像素用梯度方向在直方图中进行加权投影(映射到固定的角度范围),就可以得到这个cell的梯度方向直方图了,就是该cell对应的9维特征向量(因为有9个bin)。

    像素梯度方向用到了,那么梯度大小呢?梯度大小就是作为投影的权值的。例如说:这个像素的梯度方向是20-40度,然后它的梯度大小是2(假设啊),那么直方图第2个bin的计数就不是加一了,而是加二(假设啊)。

    单元格Cell中的每一个像素点都为某个基于方向的直方图通道(orientation-based histogram channel)投票。投票是采取加权投票(weighted voting)的方式,即每一票都是带权值的,这个权值是根据该像素点的梯度幅度计算出来。可以采用幅值本身或者它的函数来表示这个权值,实际测试表明: 使用幅值来表示权值能获得最佳的效果,当然,也可以选择幅值的函数来表示,比如幅值的平方根(square root)、幅值的平方(square of the gradient magnitude)、幅值的截断形式(clipped version of the magnitude)等。根据Dalal等人论文的测试结果,采用梯度幅值量级本身得到的检测效果最佳,使用量级的平方根会轻微降低检测结果,而使用二值的边缘权值表示会严重降低效果。

    其中,加权采用三线性插值(链接为详细说明的博文)方法,即将当前像素的梯度方向大小、像素在cell中的x坐标与y坐标这三个值来作为插值权重,而被用来插入的值为像素的梯度幅值。采用三线性插值的好处在于:避免了梯度方向直方图在cell边界和梯度方向量化的bin边界处的突然变化。

 

(6)把单元格组合成大的块(block),块 内归一化 梯度直方图【重点】
    由于局部光照的变化以及前景-背景对比度的变化,使得梯度强度的变化范围非常大。这就需要对梯度强度做归一化。归一化能够进一步地对光照、阴影和边缘进行压缩。

    方法:

(6-1)将多个临近的cell组合成一个block块,然后求其梯度方向直方图向量;

(6-2)采用L2-Norm with Hysteresis threshold方式进行归一化,即将直方图向量中bin值的最大值限制为0.2以下,然后再重新归一化一次;

    注意:block之间的是“共享”的,也即是说,一个cell会被多个block“共享”。另外,每个“cell”在被归一化时都是“block”independent的,也就是说每个cell在其所属的block中都会被归一化一次,得到一个vector。这就意味着:每一个单元格的特征会以不同的结果多次出现在最后的特征向量中。

(6-3)四种归一化的方法:

    采用了四中不同的方法对区间进行归一化,并对结果进行了比较。引入v表示一个还没有被归一 化的向量,它包含了给定区间(block)的所有直方图信息。||Vk||表示v的k阶范数,这里的k去1、2。用e表示一个很小的常数。这时,归一化因子可以表示如下:

        L2-norm:

        L1-norm:

        L1-sqrt:

        L2-Hys:它可以通过先进行L2-norm,对结果进行截短(clipping)(即值被限制为v - 0.2v之间),然后再重新归一化得到。

    采用L2- Hys,L2-norm 和 L1-sqrt方式所取得的效果是一样的,L1-norm稍微表现出一点点不可靠性。但是对于没有被归一化的数据来说,这四种方法都表现出来显着的改进。

    

(6-4)区间(块)有两个主要的几何形状——矩形区间(R-HOG)和环形区间(C-HOG)。

    A、R-HOG区间(blocks):大体上是一些方形的格子,它可以有三个参数来表征:每个区间中细胞单元的数目、每个细胞单元中像素点的数目、每个细胞的直方图通道数目。例如:行人检测的最佳参数设置是:3×3细胞/区间、6×6像素/细胞、9个直方图通道。则一块的特征数为:3*3*9;作者还发现,对于R-HOG,在对直方图做处理之前,给每个区间(block)加一个高斯空域窗口(Gaussian spatial window)是非常必要的,因为这样可以降低边缘的周围像素点(pixels around the edge)的权重。R-HOG是各区间被组合起来用于对空域信息进行编码(are used in conjunction to encode spatial form information)。

    B、C-HOG区间(blocks):有两种不同的形式,它们的区别在于:一个的中心细胞是完整的,一个的中心细胞是被分割的。如右图所示:

C-HOG的这两种形式都能取得相同的效果。C-HOG区间(blocks)可以用四个参数来表征:角度盒子的个数(number of angular bins)、半径盒子个数(number of radial bins)、中心盒子的半径(radius of the center bin)、半径的伸展因子(expansion factor for the radius)。通过实验,对于行人检测,最佳的参数设置为:4个角度盒子、2个半径盒子、中心盒子半径为4个像素、伸展因子为2。前面提到过,对于R-HOG,中间加一个高斯空域窗口是非常有必要的,但对于C-HOG,这显得没有必要。C-HOG看起来很像基于形状上下文(Shape Contexts)的方法,但不同之处是:C-HOG的区间中包含的细胞单元有多个方向通道(orientation channels),而基于形状上下文的方法仅仅只用到了一个单一的边缘存在数(edge presence count)。

(6-5)HOG描述符(不同于OpenCV定义):我们将归一化之后的块描述符(向量)就称之为HOG描述符。

(6-6)块划分带来的问题:块与块之间是相互独立的吗?

答:通常的将某个变量范围固定划分为几个区域,由于边界变量与相邻区域也有相关性,所以变量只对一个区域进行投影而对相邻区域完全无关时会对其他区域产生混叠效应

    分块之间的相关性问题的解决:

方案一:块重叠,重复统计计算

    在重叠方式中,块与块之间的边缘点被重复根据权重投影到各自相邻块(block)中,从而一定模糊了块与块之间的边界,处于块边缘部分的像素点也能够给相邻块中的方向梯度直方图提供一定贡献,从而达到关联块与块之间的关系的作用。Datal对于块和块之间相互重叠程度对人体目标检测识别率影响也做了实验分析。

方案二:线性插值权重分配

    有些文献采用的不是块与块重叠的方法,而是采用线性插值的方法来削弱混叠效应。这种方法的主要思想是每个Block都对临近的Block都有影响,这种影响,我们可以以一种加权方式附加上去。

    基于线性插值的基本思想,对于上图四个方向(横纵两个45度斜角方向)个进行一次线性插值就可以达到权重分配目的。下面介绍一维线性插值。假设x1和x2是x块相邻两块的中心,且x1

    

    其中b在横纵方向取块间隔,而在斜45度方向则可采用sqrt(2)倍的块间隔。

(7)生成HOG特征描述向量

    将所有“block”的HOG描述符组合在一起,形成最终的feature vector,该feature vector就描述了detect window的图像内容。

五、直方图表示的解释:

假设检测窗为64(列)*128(行)大小,block为16*16大小,每个block划分为4个cell,block每次滑动8个像素(也就是一个cell的宽),以及梯度方向划分为9个区间,在0~180度范围内统计,以下的说明都以上述假设为例.

btly与btlx分别表示block所在位置左上角点处的坐标。对于前述假设,一个检测窗内会有105个block存在,因此第一个block左上角的坐标是(1,1),第二个是(9,1)…,此行最后一个是block的左上角坐标是(49,1),然后下一个block就需要向下滑动8个像素,并回到最左边,此时的block左上角坐标为(1,9),接着block重新开始新的横向滑动…如此这般,在检测窗内最后一个block的坐标就是(49,113).

block每滑动到一个新的位置,就需要停下来计算它内部的那四个cell中的梯度方向直方图.(bj,bi)就是来存储cell左上角的坐标的(cell的坐标以block左上角为原点).

 (j,i)就表示cell中的像素在整个检测窗(64*128的图像)中的坐标.另外,我在程序里有个jorbj与iorbi,这在Localinterpolate的情况下(也就是标准的原始HOG情况),就是bj与bi.

关于hist3dbig:

这是一个三维的矩阵,用来存储三维直方图。最常见的一维的直方图是这个样子,

二维直方图呢?是这个样子,一个一个的柱子是一个统计bin,柱子的高低代表统计值的大小


三维直方图呢?是这个样子,立体的一个一个的小格子,每个小格子是一个统计bin, 小格子用来装统计值。以上面的例子,那么对一个block来说,它的直方图是下面这样的:

再来说线性插值,线性插值时,一个统计值需被“按一定比例分配”到这个统计点最邻近的区间中去,下面的图显示了一维直方图时,落在虚线标记范围内的统计点,它最近邻的区间就是标有红色圆点的两个区间

若是二维直方图,那落在如下虚线矩形中的统计点,周围的这四个统计区间就是它最近邻的区间。这个虚线矩形由四个统计区间各自的1/4组成。

三维直方图,对一个统计点来说,它的最近邻的区间有八个,如下图,可以想象一下,只有当这个统计点落在由如下八个统计区间各自的1/8组成的一个立方体内内时,这八个区间才是对统计点最近邻的。

统计时如何分配权重呢?以一维直方图简单说一下线性插值的意思,对于下面绿色小方点(x)的统计值来说,假设标红点的两个bin的中心位置分别为x1,x2,那么对于x,它的分配权重为左边bin: 1-(x-x1)/s, 即 1-a/s = b/s, 右边bin: 1-(x2-x)/s, 即1-b/s = a/s.

类似,那么对三维直方图来说,统计时的累积式(从Dalal的论文里截来的)就是:

上面,w 就是准备被分配的统计值。(x1,y1,z1)…共八个点表示八个统计区间的中心位置坐标,上式用h(x1,y1,z1)这样的标记来表示所要累积的统计区间。我在编程时就使用的这个式子,只不过我用bin的下标号来表示bin块,就像前面三维直方图示意中(binx=1,biny=2,binθ=9),不过在程序中θ轴是用z轴表示了。

binx1 = floor((jorbj-1+cellpw/2)/cellpw) + 1;

biny1 = floor((iorbi-1+cellph/2)/cellph) + 1;

binz1 = floor((go+(or*pi/nthet)/2)/(or*pi/nthet)) + 1;

binx2 = binx1 + 1;

biny2 = biny1 + 1;

binz2 = binz1 + 1;

这几句,就是用来计算八个统计区间中心点的坐标的。

在计算前面所讲的统计区间的中心坐标,分配权值之前,我为了处理边缘时程序简洁点,就给那个2*2*9的立体直方图外边又包了一层,形成了一个4*4*11的三维直方图(示意图如下),原来的2*2*9直方图就是被包在中间的部分。这样,在原来直方图里坐标为(binx=1,biny=2,binz=9)的bin,在新的直方图里坐标为(binx=2,biny=3,binz=10)。


对上面的4*4*11的直方图来个与xoy平面平行的剖面图:

粗实线框就是原三维直方图的剖面,也就是一个block,对于像落在粗实线框与粗虚线框之间的点,其最近邻区间是不够8个的,我为了写程序时省点脑力。。。,就用外扩了的这一圈bin,这样落在粗实线框与粗虚线框之间的统计点有了8个区间,用matlab编程时,那个四层for循环中的部分就只用把那八个累积公式写上,也不用判断是不是在落在像上面粗实线框与粗虚线框之间的那种区域。在程序中2*2*9的直方图为hist3d,4*4*11的直方图为hist3dbig.当在这个hist3dbig中计算都结束后,我把外层这一圈剥去,就是hist3d了。
有了这些准备,我就可以计算出当前像素点的梯度方向幅值应该往hist3dbig中的哪八个bin块累积了。binx1,biny1,binz1 在这里就是那个八个bin块之中离  当前要统计的像素点在直方图中对应的位置  最接近的bin块的下标。binx2,biny2,binz2对应就是最远的bin块的下标了。x1,y1,z1就是bin块(binx1,biny1,binz1)中心点对应的实际像素所在的位置(x1,y1)与梯度方向的角度(z1). 我仍然以原block(即没扩前的block)左上角处作为x1,y1的原点,因为matlab以1作为图像像素索引的开始,我把原点就认为是(1,1),那(1,1)左边外扩出来的部分,就给以0,-1,-2,-3…这样的坐标,向上也类似,如下图所示,(1,1)位置为红点所示,蓝点处坐标就是(-3,1).

扩展出来的绿块的下标是(binx=1,biny=1,binz1=1),由于像素坐标在红点处为(1,1),而黄块才是block的第一个cell,对应bin块的下标(2,2).因为下标设计的原因,我在求x1,y1,z1时减了1.5而非0.5.

x1 = (binx1-1.5)*cellpw + 0.5;

y1 = (biny1-1.5)*cellph + 0.5;

z1 = (binz1-1.5)*(or*pi/nthet);

上面的式子中x1,y1还加了0.5,因为像素坐标是离散的,而第一个坐标总是从1开始,这样对如图中第一个cell的中心(黑点)处应该是4.5. z1没加0.5,是因为角度值是从0开始的,并且是连续的。

在signed(即梯度方向从0度到360度)情况下,因为实际上角度的投票区间是首尾相接环形的,若统计间隔是40度,那么0-40度和320-360度就是相邻区间,那么在4*4*11的直方图中,投给binz==11区间(相当于360-380度)的值应该返给binz==2(0-40度),投给binz==1区间的值应该返给binz==10区间,如4*4*11直方图中所示,对应在程序中就是

if or == 2

hist3dbig(:,:,2) = hist3dbig(:,:,2) + hist3dbig(:,:,nthet+2);

hist3dbig(:,:,(nthet+1)) = hist3dbig(:,:,(nthet+1)) + hist3dbig(:,:,1);

end


六、Matlab详细注释:

梯度方向数

nbins代表在一个胞元中统计梯度的方向数目。如:nbins=9表示一个胞元内统计9个方向的梯度直方图。

源码:

%Matlab版HOG代码
function F = hogcalculator(img, cellpw, cellph, nblockw, nblockh,...
    nthet, overlap, isglobalinterpolate, issigned, normmethod)
% HOGCALCULATOR calculate R-HOG feature vector of an input image using the
% procedure presented in Dalal and Triggs's paper in CVPR 2005.
%
% Author:   timeHandle
% Time:     March 24, 2010
%           May 12,2010 update.
%
%       this copy of code is written for my personal interest, which is an 
%       original and inornate realization of [Dalal CVPR2005]'s algorithm
%       without any optimization. I just want to check whether I understand
%       the algorithm really or not, and also do some practices for knowing
%       matlab programming more well because I could be called as 'novice'. 
%       OpenCV 2.0 has realized Dalal's HOG algorithm which runs faster
%       than mine without any doubt, ╮(╯▽╰)╭ . Ronan pointed a error in 
%       the code,thanks for his correction. Note that at the end of this
%       code, there are some demonstration code,please remove in your work.
% 
% F = hogcalculator(img, cellpw, cellph, nblockw, nblockh,
%    nthet, overlap, isglobalinterpolate, issigned, normmethod)
%
% IMG:
%       IMG is the input image.
%
% CELLPW, CELLPH:
%       CELLPW and CELLPH are cell's pixel width and height respectively.
%
% NBLOCKW, NBLCOKH:
%       NBLOCKW and NBLCOKH are block size counted by cells number in x and
%       y directions respectively.
%
% NTHET, ISSIGNED:
%       NTHET is the number of the bins of the histogram of oriented
%       gradient. The histogram of oriented gradient ranges from 0 to pi in
%       'unsigned' condition while to 2*pi in 'signed' condition, which can
%       be specified through setting the value of the variable ISSIGNED by
%       the string 'unsigned' or 'signed'.
%
% OVERLAP:
%       OVERLAP is the overlap proportion of two neighboring block.
%
% ISGLOBALINTERPOLATE:
%       ISGLOBALINTERPOLATE specifies whether the trilinear interpolation
%       is done in a single global 3d histogram of the whole detecting
%       window by the string 'globalinterpolate' or in each local 3d
%       histogram corresponding to respective blocks by the string
%       'localinterpolate' which is in strict accordance with the procedure
%       proposed in Dalal's paper. Interpolating in the whole detecting
%       window requires the block's sliding step to be an integral multiple
%       of cell's width and height because the histogram is fixing before
%       interpolate. In fact here the so called 'global interpolation' is
%       a notation given by myself. at first the spatial interpolation is 
%       done without any relevant to block's slide position, but when I was
%       doing calculation while OVERLAP is 0.75, something occurred and
%       confused me o__O"… . This let me find that the operation I firstly
%       did is different from which mentioned in Dalal's paper. But this
%       does not mean it is incorrect ^◎^, so I reserve this. As for name,
%       besides 'global interpolate', any others would be all ok, like 'Lady GaGa' 
%       or what else, :-).
%
% NORMMETHOD:
%       NORMMETHOD is the block histogram normalized method which can be
%       set as one of the following strings:
%               'none', which means non-normalization;
%               'l1', which means L1-norm normalization;
%               'l2', which means L2-norm normalization;
%               'l1sqrt', which means L1-sqrt-norm normalization;
%               'l2hys', which means L2-hys-norm normalization.
% F:
%       F is a row vector storing the final histogram of all of the blocks 
%       one by one in a top-left to bottom-right image scan manner, the
%       cells histogram are stored in the same manner in each block's
%       section of F.
%
% Note that CELLPW*NBLOCKW and CELLPH*NBLOCKH should be equal to IMG's
% width and height respectively.
%
% Here is a demonstration, which all of parameters are set as the
% best value mentioned in Dalal's paper when the window detected is 128*64
% size(128 rows, 64 columns):
%       F = hogcalculator(window, 8, 8, 2, 2, 9, 0.5,
%                               'localinterpolate', 'unsigned', 'l2hys');
% Also the function can be called like:
%       F = hogcalculator(window);
% the other parameters are all set by using the above-mentioned "dalal's
% best value" as default.
%
if nargin < 2
    % set default parameters value.
    cellpw = 8;
    cellph = 8;
    nblockw = 2;
    nblockh = 2;
    nthet = 9;
    overlap = 0.5;
    isglobalinterpolate = 'localinterpolate';
    issigned = 'unsigned';
    normmethod = 'l2hys';
else
    if nargin < 10
        error('Input parameters are not enough.');
    end
end
% check parameters's validity.
[M, N, K] = size(img);
if mod(M,cellph*nblockh) ~= 0
    error('IMG''s height should be an integral multiple of CELLPH*NBLOCKH.');
end
if mod(N,cellpw*nblockw) ~= 0
    error('IMG''s width should be an integral multiple of CELLPW*NBLOCKW.');
end
if mod((1-overlap)*cellpw*nblockw, cellpw) ~= 0 ||...
        mod((1-overlap)*cellph*nblockh, cellph) ~= 0
    str1 = 'Incorrect OVERLAP or ISGLOBALINTERPOLATE parameter';
    str2 = ', slide step should be an intergral multiple of cell size';
    error([str1, str2]);
end
% set the standard deviation of gaussian spatial weight window.
delta = cellpw*nblockw * 0.5;
% calculate gradient scale matrix.
hx = [-1,0,1];
hy = -hx';
gradscalx = imfilter(double(img),hx);
gradscaly = imfilter(double(img),hy);
if K > 1
    gradscalx = max(max(gradscalx(:,:,1),gradscalx(:,:,2)), gradscalx(:,:,3));
    gradscaly = max(max(gradscaly(:,:,1),gradscaly(:,:,2)), gradscaly(:,:,3));
end
gradscal = sqrt(double(gradscalx.*gradscalx + gradscaly.*gradscaly));
% calculate gradient orientation matrix.
% plus small number for avoiding dividing zero.
gradscalxplus = gradscalx+ones(size(gradscalx))*0.0001;
gradorient = zeros(M,N);
% unsigned situation: orientation region is 0 to pi.
if strcmp(issigned, 'unsigned') == 1
    gradorient =...
        atan(gradscaly./gradscalxplus) + pi/2;
    or = 1;
else
    % signed situation: orientation region is 0 to 2*pi.
    if strcmp(issigned, 'signed') == 1
        idx = find(gradscalx >= 0 & gradscaly >= 0);
        gradorient(idx) = atan(gradscaly(idx)./gradscalxplus(idx));
        idx = find(gradscalx < 0);
        gradorient(idx) = atan(gradscaly(idx)./gradscalxplus(idx)) + pi;
        idx = find(gradscalx >= 0 & gradscaly < 0);
        gradorient(idx) = atan(gradscaly(idx)./gradscalxplus(idx)) + 2*pi;
        or = 2;
    else
        error('Incorrect ISSIGNED parameter.');
    end
end
% calculate block slide step.
xbstride = cellpw*nblockw*(1-overlap);
ybstride = cellph*nblockh*(1-overlap);
xbstridend = N - cellpw*nblockw + 1;
ybstridend = M - cellph*nblockh + 1;
% calculate the total blocks number in the window detected, which is
% ntotalbh*ntotalbw.
ntotalbh = ((M-cellph*nblockh)/ybstride)+1;
ntotalbw = ((N-cellpw*nblockw)/xbstride)+1;
% generate the matrix hist3dbig for storing the 3-dimensions histogram. the
% matrix covers the whole image in the 'globalinterpolate' condition or
% covers the local block in the 'localinterpolate' condition. The matrix is
% bigger than the area where it covers by adding additional elements
% (corresponding to the cells) to the surround for calculation convenience.
if strcmp(isglobalinterpolate, 'globalinterpolate') == 1
    ncellx = N / cellpw;
    ncelly = M / cellph;
    hist3dbig = zeros(ncelly+2, ncellx+2, nthet+2);
    F = zeros(1, (M/cellph-1)*(N/cellpw-1)*nblockw*nblockh*nthet);
    glbalinter = 1;
else
    if strcmp(isglobalinterpolate, 'localinterpolate') == 1
        hist3dbig = zeros(nblockh+2, nblockw+2, nthet+2);
        F = zeros(1, ntotalbh*ntotalbw*nblockw*nblockh*nthet);
        glbalinter = 0;
    else
        error('Incorrect ISGLOBALINTERPOLATE parameter.')
    end
end
% generate the matrix for storing histogram of one block;
sF = zeros(1, nblockw*nblockh*nthet);
% generate gaussian spatial weight.
[gaussx, gaussy] = meshgrid(0:(cellpw*nblockw-1), 0:(cellph*nblockh-1));
weight = exp(-((gaussx-(cellpw*nblockw-1)/2)...
    .*(gaussx-(cellpw*nblockw-1)/2)+(gaussy-(cellph*nblockh-1)/2)...
    .*(gaussy-(cellph*nblockh-1)/2))/(delta*delta));
% vote for histogram. there are two situations according to the interpolate
% condition('global' interpolate or local interpolate). The hist3d which is
% generated from the 'bigger' matrix hist3dbig is the final histogram.
if glbalinter == 1
    xbstep = nblockw*cellpw;
    ybstep = nblockh*cellph;
else
    xbstep = xbstride;
    ybstep = ybstride;
end
% block slide loop
for btly = 1:ybstep:ybstridend
    for btlx = 1:xbstep:xbstridend
        for bi = 1:(cellph*nblockh)
            for bj = 1:(cellpw*nblockw)
                
                i = btly + bi - 1;
                j = btlx + bj - 1;
                gaussweight = weight(bi,bj);
                
                gs = gradscal(i,j);
                go = gradorient(i,j);
                
                if glbalinter == 1
                    jorbj = j;
                    iorbi = i;
                else
                    jorbj = bj;
                    iorbi = bi;
                end
                
                % calculate bin index of hist3dbig
                binx1 = floor((jorbj-1+cellpw/2)/cellpw) + 1;
                biny1 = floor((iorbi-1+cellph/2)/cellph) + 1;
                binz1 = floor((go+(or*pi/nthet)/2)/(or*pi/nthet)) + 1;
                
                if gs == 0
                    continue;
                end
                
                binx2 = binx1 + 1;
                biny2 = biny1 + 1;
                binz2 = binz1 + 1;
                
                x1 = (binx1-1.5)*cellpw + 0.5;
                y1 = (biny1-1.5)*cellph + 0.5;
                z1 = (binz1-1.5)*(or*pi/nthet);
                
                % trilinear interpolation.
                hist3dbig(biny1,binx1,binz1) =...
                    hist3dbig(biny1,binx1,binz1) + gs*gaussweight...
                    * (1-(jorbj-x1)/cellpw)*(1-(iorbi-y1)/cellph)...
                    *(1-(go-z1)/(or*pi/nthet));
                hist3dbig(biny1,binx1,binz2) =...
                    hist3dbig(biny1,binx1,binz2) + gs*gaussweight...
                    * (1-(jorbj-x1)/cellpw)*(1-(iorbi-y1)/cellph)...
                    *((go-z1)/(or*pi/nthet));
                hist3dbig(biny2,binx1,binz1) =...
                    hist3dbig(biny2,binx1,binz1) + gs*gaussweight...
                    * (1-(jorbj-x1)/cellpw)*((iorbi-y1)/cellph)...
                    *(1-(go-z1)/(or*pi/nthet));
                hist3dbig(biny2,binx1,binz2) =...
                    hist3dbig(biny2,binx1,binz2) + gs*gaussweight...
                    * (1-(jorbj-x1)/cellpw)*((iorbi-y1)/cellph)...
                    *((go-z1)/(or*pi/nthet));
                hist3dbig(biny1,binx2,binz1) =...
                    hist3dbig(biny1,binx2,binz1) + gs*gaussweight...
                    * ((jorbj-x1)/cellpw)*(1-(iorbi-y1)/cellph)...
                    *(1-(go-z1)/(or*pi/nthet));
                hist3dbig(biny1,binx2,binz2) =...
                    hist3dbig(biny1,binx2,binz2) + gs*gaussweight...
                    * ((jorbj-x1)/cellpw)*(1-(iorbi-y1)/cellph)...
                    *((go-z1)/(or*pi/nthet));
                hist3dbig(biny2,binx2,binz1) =...
                    hist3dbig(biny2,binx2,binz1) + gs*gaussweight...
                    * ((jorbj-x1)/cellpw)*((iorbi-y1)/cellph)...
                    *(1-(go-z1)/(or*pi/nthet));
                hist3dbig(biny2,binx2,binz2) =...
                    hist3dbig(biny2,binx2,binz2) + gs*gaussweight...
                    * ((jorbj-x1)/cellpw)*((iorbi-y1)/cellph)...
                    *((go-z1)/(or*pi/nthet));
            end
        end
        
        % In the local interpolate condition. F is generated in this block 
        % slide loop. hist3dbig should be cleared in each loop.
        if glbalinter == 0
            if or == 2
                hist3dbig(:,:,2) = hist3dbig(:,:,2)...
                    + hist3dbig(:,:,nthet+2);
                hist3dbig(:,:,(nthet+1)) =...
                    hist3dbig(:,:,(nthet+1)) + hist3dbig(:,:,1);
            end
            hist3d = hist3dbig(2:(nblockh+1), 2:(nblockw+1), 2:(nthet+1));
            for ibin = 1:nblockh
                for jbin = 1:nblockw
                    idsF = nthet*((ibin-1)*nblockw+jbin-1)+1;
                    idsF = idsF:(idsF+nthet-1);
                    sF(idsF) = hist3d(ibin,jbin,:);
                end
            end
            iblock = ((btly-1)/ybstride)*ntotalbw +...
                ((btlx-1)/xbstride) + 1;
            idF = (iblock-1)*nblockw*nblockh*nthet+1;
            idF = idF:(idF+nblockw*nblockh*nthet-1);
            F(idF) = sF;
            hist3dbig(:,:,:) = 0;
        end
    end
end
% In the global interpolate condition. F is generated here outside the
% block slide loop 
if glbalinter == 1
    ncellx = N / cellpw;
    ncelly = M / cellph;
    if or == 2
        hist3dbig(:,:,2) = hist3dbig(:,:,2) + hist3dbig(:,:,nthet+2);
        hist3dbig(:,:,(nthet+1)) = hist3dbig(:,:,(nthet+1)) + hist3dbig(:,:,1);
    end
    hist3d = hist3dbig(2:(ncelly+1), 2:(ncellx+1), 2:(nthet+1));
    
    iblock = 1;
    for btly = 1:ybstride:ybstridend
        for btlx = 1:xbstride:xbstridend
            binidx = floor((btlx-1)/cellpw)+1;
            binidy = floor((btly-1)/cellph)+1;
            idF = (iblock-1)*nblockw*nblockh*nthet+1;
            idF = idF:(idF+nblockw*nblockh*nthet-1);
            for ibin = 1:nblockh
                for jbin = 1:nblockw
                    idsF = nthet*((ibin-1)*nblockw+jbin-1)+1;
                    idsF = idsF:(idsF+nthet-1);
                    sF(idsF) = hist3d(binidy+ibin-1, binidx+jbin-1, :);
                end
            end
            F(idF) = sF;
            iblock = iblock + 1;
        end
    end
end
% adjust the negative value caused by accuracy of floating-point
% operations.these value's scale is very small, usually at E-03 magnitude
% while others will be E+02 or E+03 before normalization.
F(F<0) = 0;
% block normalization.
e = 0.001;
l2hysthreshold = 0.2;
fslidestep = nblockw*nblockh*nthet;
switch normmethod
    case 'none'
    case 'l1'
        for fi = 1:fslidestep:size(F,2)
            div = sum(F(fi:(fi+fslidestep-1)));
            F(fi:(fi+fslidestep-1)) = F(fi:(fi+fslidestep-1))/(div+e);
        end
    case 'l1sqrt'
        for fi = 1:fslidestep:size(F,2)
            div = sum(F(fi:(fi+fslidestep-1)));
            F(fi:(fi+fslidestep-1)) = sqrt(F(fi:(fi+fslidestep-1))/(div+e));
        end
    case 'l2'
        for fi = 1:fslidestep:size(F,2)
            sF = F(fi:(fi+fslidestep-1)).*F(fi:(fi+fslidestep-1));
            div = sqrt(sum(sF)+e*e);
            F(fi:(fi+fslidestep-1)) = F(fi:(fi+fslidestep-1))/div;
        end
    case 'l2hys'
        for fi = 1:fslidestep:size(F,2)
            sF = F(fi:(fi+fslidestep-1)).*F(fi:(fi+fslidestep-1));
            div = sqrt(sum(sF)+e*e);
            sF = F(fi:(fi+fslidestep-1))/div;
            sF(sF>l2hysthreshold) = l2hysthreshold;
            div = sqrt(sum(sF.*sF)+e*e);
            F(fi:(fi+fslidestep-1)) = sF/div;
        end
    otherwise
        error('Incorrect NORMMETHOD parameter.');
end
% the following code, which can be removed because of having no
% contributions to HOG feature calculation, are just for result
% demonstration when the trilinear interpolation is 'global' for this
% condition is easier to give a simple and intuitive illustration. so in
% 'local' condition it will produce error.
figure;
hold on;
axis equal;
xlim([0, N]);
ylim([0, M]);
for u = 1:(M/cellph)
    for v = 1:(N/cellpw)
        cx = (v-1)*cellpw + cellpw/2 + 0.5;
        cy = (u-1)*cellph + cellph/2 + 0.5;
        hist3d(u,v,:)=0.9*min(cellpw,cellph)*hist3d(u,v,:)/max(hist3d(u,v,:));
        for t = 1:nthet
            s = hist3d(u,v,t);
            thet = (t-1)*pi/nthet + pi*0.5/nthet;
            x1 = cx - s*0.5*cos(thet);
            x2 = cx + s*0.5*cos(thet);
            y1 = cy - s*0.5*sin(thet);
            y2 = cy + s*0.5*sin(thet);
            plot([x1,x2],[M-y1+1,M-y2+1]);
        end
    end
end




注释代码:

function [hog_Feature] = HOG(detectedImg, options)
% -------------------------------------------------------------------------
% 实现HOG(Histogram of gradient)特征的提取过程
%
% detectedImg-- 检测窗口包含的图像(灰度图)
% options-- 参数结构体,包含许多参数设置:
%            cellH, cellW:单元大小  
%            blockH, blockW:块大小
%            winH, winW:检测窗口大小    
%            stride:块移动步长
%            bins:直方图长度    
%            flag:梯度方向隐射区间(0:[0,pi],1:[0,2*pi])
%            epsilon: 用于做归一化的常量因子
% @hog_Feature-- 检测窗口图像对应的HOG特征向量,大小为1*M,其中
%    M = ((winH-blockH)/stride+1)*((winW-blockW)/stride+1)...
%        *(blockW/cellW)*(blockH/cellH) * bins
%
% HOG特征提取步骤:
% ----------------
% step 1.由于Dalal在论文中提到色彩和伽马归一化步骤对最终的结果没有影响,故省略该步骤;
% 利用[-1,0,1]和[1,0,-1]'分别计算图像的x方向和y方向的梯度(这里不采用Sobel或是其他
% 边缘算子来计算梯度,是因为它们对图像做了平滑处理后再求梯度,这样会丢失很多梯度信息)
% 然后计算每个像素点对应的梯度幅值和方向:
%    ||grad|| = |grad_x| + |grad_y|(或||grad|| = sqrt(grad_x^2+grad_y^2))
%    gradOri = arctan(grad_y/grad_x) (gradOri属于(-pi/2,pi/2))
% 在根据参数flag将每个像素点的梯度方向映射到对应区间中,如果flag为0则选择区间[0,pi]
% 位于(i,j)位置像素点的方向为: 
%       gradOri(i,j)=gradOri(i,j)<0?gradOri(i,j)+pi, gradOri(i,j);
% 如果flag为1选择区间为[0,2*pi],这时需要根据grad_x和grad_y的正负来判断:
%  (1)grad_x>=0&&grad_y>=0(第一象限) gradOri(i,j)=arctan(grad_y/grad_x);
%  (2)grad_x<0&&grad_y>=0(第二象限)  gradOri(i,j)=arctan(grad_y/grad_x)+pi;
%  (3)grad_x<0&&grad_y<0(第三象限)   gradOri(i,j)=arctan(grad_y/grad_x)+pi;
%  (4)grad_x>=0&&grad_y<0(第四象限)  gradOri(i,j)=arctan(grad_y/grad_x)+2*pi;
% ------------------
% step 2.为了便于理解,直接写上四层循环,外面两层循环定位block,里面两层定位cell;
% 一个block对应(blockH*blockW/(cellH*cellW)*bins的特征向量,每个cell对应1*bins
% 的直方图,计算block的直方图在函数calHist中完成,这里计算直方图需要注意两点:
% (1)计算cell直方图时,根据像素点梯度幅值进行权值投影,投影时采用软分配方式,即
% 采用插值的方式进行投影,根据梯度方向距离相邻两个区间的中心点的距离进行插值;
% (2)Dalal在论文中提到,对于R-HOG而言,处理直方图前,在整个block加上一个高斯窗口
% 这样可以降低block边界像素点的权重,直方图投票值由原先的幅值变为幅值和高斯乘积;
% (3)完成block直方图的计算后,需要在整个block范围内进行直方图归一化操作,归一化
% 方式有多种,这里默认采用L2-norm(hist=hist/sqrt(hist^2+epsilon^2)).
% ------------------
% step 3.合并检测窗口中的所有block的向量(HOG特征向量)
%
% 注:整个过程还涉及到一些细节,比如导入图像尺度和设置的检测窗口大小不同时,需要
% 完成尺度缩放;在求图像梯度时,边界问题如何处理,是直接填充0还是复制边界,这里
% 直接填充0;最后一点就是在计算block直方图时,没有进行三维插值,即每个单元中的像
% 素点只对该单元有投票的权力,对当前block的其他单元没有影响。
%
% Author: L.L.He
% Time: 6/8/2014
% -------------------------------------------------------------------------
tic;
assert(nargin>=1);
if ~exist('options', 'var')
    % 如果参数没有指定,则设置为如下默认值
    options = struct;
    options.cellH = 8;   options.cellW = 8;
    options.blockH = 16; options.blockW = 16;
    options.winH = 64;   options.winW = 128;
    options.stride = 8;  options.bins = 9;
    options.flag = 1;    options.epsilon = 1e-4;
end
% 处理输入的待检测图像
[r, c, d] = size(detectedImg);
if d ~= 1
    % 需要转换为灰度图
    detectedImg = rgb2gray(detectedImg);
end
detectedImg = double(detectedImg);
if r~=options.winH && c~=options.winW
    % 根据检测窗口的大小对输入图像进行尺度缩放(采用双线性插值)
    detectedImg = imresize(detectedImg, [options.winH options.winW],...
                           'bilinear');
end

% step 1--采用1-D差分卷积核计算x方向和y方向的梯度(幅值和方向)
mask = [-1, 0, 1];
[grad, gradOri] = calGrad(detectedImg, mask, options.flag);

% 根据block的大小计算高斯核
sigma = min(options.blockH, options.blockW)*0.5;
sigma_2 = sigma.^2;
[X, Y] = meshgrid(0:options.blockW-1,0:options.blockH-1);
X = X - (options.blockW-1)/2;
Y = Y - (options.blockH-1)/2;
gaussWeight = exp(-(X.^2+Y.^2)/(2*sigma_2));

% 创建一个三维矩阵存放所有block的直方图
r_tmp = (options.winH-options.blockH)/options.stride+1;
c_tmp = (options.winW-options.blockW)/options.stride+1;
b_tmp = options.bins *(options.blockH*options.blockW)/...
        (options.cellH*options.cellW);
blockHist = zeros(r_tmp, c_tmp, b_tmp);

% step 2--计算检测窗口中每个block的直方图(HOG特征向量)
for i=1:options.stride:(options.winH-options.blockH+1)
    for j=1:options.stride:(options.winW-options.blockW+1)
        block_grad = grad(i:i+options.blockH-1,j:j+options.blockW-1);
        block_gradOri = gradOri(i:i+options.blockH-1,j:j+options.blockW-1);
        % 计算单个block的直方图(投票值为梯度幅值和高斯权重的乘积),并进
        % 进行归一化处理
        block_r = floor(i/options.stride)+1;
        block_c = floor(j/options.stride)+1;
        blockHist(block_r,block_c,:) = calHist(block_grad.*gaussWeight, ...
                           block_gradOri, options);
    end
end

% step 3--将所有block的直方图拼接成一维向量作为检测窗口的HOG特征向量
hog_Feature = reshape(blockHist, [1 numel(blockHist)]);
toc;
end

% =========================================================================
function [grad, gradOri] = calGrad(img, mask, flag)
% -------------------------------------------------------------------------
% 利用指定的差分卷积核计算x方向和y方向的梯度(包括幅值和方向)
% img-- 源图像
% mask-- 计算x方向梯度的差分卷积核(y方向的卷积核是转置后取反)
% flag-- 梯度方向隐射区间标识
% @grad-- 梯度幅值
% @gradOri-- 梯度方向
% -------------------------------------------------------------------------
assert(nargin==3);
xMask = mask;
yMask = -mask';
grad = zeros(size(img));
gradOri = zeros(size(img));
grad_x = imfilter(img, xMask);
grad_y = imfilter(img, yMask);
% 计算梯度幅值和方向角
grad = sqrt(double(grad_x.^2 + grad_y.^2));
if flag == 0
    % 将梯度方向映射到区间[0,pi]
    gradOri = atan(grad_y./(grad_x+eps));
    idx = find(gradOri<0);
    gradOri(idx) = gradOri(idx) + pi;
else
    % 将梯度方向映射到区间[0,2*pi]
    % 第一象限
    idx_1 = find(grad_x>=0 & grad_y>=0);
    gradOri(idx_1) = atan(grad_y(idx_1)./(grad_x(idx_1)+eps));
    % 第二(三)象限
    idx_2_3 = find(grad_x<0);
    gradOri(idx_2_3) = atan(grad_y(idx_2_3)./(grad_x(idx_2_3)+eps)) + pi;
    % 第四象限
    idx_4 = find(grad_x>=0 & grad_y<0);
    gradOri(idx_4) = atan(grad_y(idx_4)./(grad_x(idx_4)+eps)) + 2*pi;
end
end
% =========================================================================

% =========================================================================
function hist = calHist(block_grad, block_gradOri, options)
% -------------------------------------------------------------------------
% 计算单个block的直方图(它由多个cell直方图拼接而成),并归一化处理
% block_grad-- block区域对应的梯度幅值矩阵
% block_gradOri-- block区域对应的梯度方向矩阵
% options-- 参数结构体,可以得到block中有多少个cell
% -------------------------------------------------------------------------
bins = options.bins;
cellH = options.cellH; cellW = options.cellW;
blockH = options.blockH; blockW = options.blockW;
assert(mod(blockH,cellH)==0&&mod(blockW,cellW)==0);
hist = zeros(blockH/cellH, blockW/cellW, bins);
% 每个bin对应的角度大小(如果bins为9,每个bin为20度)
if options.flag == 0
    anglePerBin = pi/bins; 
    correctVar = pi; % 用来修正currOri为负的情况
else
    anglePerBin = 2*pi/bins;
    correctVar = 2*pi;
end
halfAngle = anglePerBin/2; % 后面要用到先计算出来
for i = 1:blockH
    for j=1:blockW
        % 计算当前位置(i,j)属于的单元
        cell_r = floor((i-1)/cellH)+1;
        cell_c = floor((j-1)/cellW)+1;
        
        % 计算当前像素点相连的两个bin并投票
        currOri = block_gradOri(i,j) - halfAngle;
        % 为了将第一个bin和最后一个bin连接起来,视0度和180度等价
        if currOri <= 0
            currOri = currOri + correctVar;
        end
        % 计算该像素点梯度方向所属的两个相连bin的下标
        pre_idxOfbin = floor(currOri/anglePerBin) + 1;
        pro_idxOfbin = mod(pre_idxOfbin,bins) + 1;
        % 向相邻的两个bins进行投票(到中心点的距离作为权重)
        center = (2*pre_idxOfbin-1)*halfAngle;
        dist_w = (currOri + halfAngle-center)/anglePerBin;
        hist(cell_r,cell_c,pre_idxOfbin) = hist(cell_r,cell_c,pre_idxOfbin)...
                                           + (1-dist_w)*block_grad(i,j);
        hist(cell_r,cell_c,pro_idxOfbin) = hist(cell_r,cell_c,pro_idxOfbin)...
                                           + dist_w*block_grad(i,j);
    end
end
% 将每个cell的直方图合并(拼接一维向量)
hist = reshape(hist, [1 numel(hist)]);
% 归一化处理(默认选择L2-norm,可以用其他规则替代)
hist = hist./sqrt(hist*hist'+ options.epsilon.^2);
end
% =========================================================================

七、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.
八、影响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.

九、解释
1)为什么使用orientation histogram?
capture local shape information
2)为什么使用“cell”?
achieve a small amount of spatial invariance
3)为什么使用“overlapping blocks”?
在众多的local contrast normalisation方法中,采用overlapping blocks得到的效果最好。

十、HOG代码

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#

http://blog.163.com/jiaqiang_wang/blog/static/1188961532013111164832236/


你可能感兴趣的:(Computer,Vision,Matlab点滴,Machine,Learning)