HOG(Histogram of Oriented Gradient)是2005年CVPR会议上,法国国家计算机科学及自动控制研究所的Dalal等人提出的一种解决人体目标检测的图像描述子,该方法使用梯度方向直方图(Histogram of Oriented Gradients,简称HOG)特征来表达人体,提取人体的外形信息和运动信息,形成丰富的特征集。
HOG描述子高维图像特征向量生成步骤:1.图像归一化; 2.利用一阶微分计算图像梯度; 3.基于梯度幅值的方向权重投影; 4.HOG特征向量归一化; 5.得出HOG最终的特征向量
归一化图像的主要目的是提高检测器对光照的鲁棒性,因为实际的人体目标可能出现的各种不同的场合,检测器,必须对光照不太敏感才会有好的效果。
Step 2 利用一阶微分计算图像梯度
对于灰度图像,一般为了去除噪点,所以会先利用离散高斯平滑模板进行平滑:高斯函数在不同平滑的尺度下进行对灰度图像进行平滑操作,Dalal等实验表明在下,人体检测效果最佳(即不做高斯平滑),使得错误率缩小了约一倍。不做平滑操作,可能原因:图像时基于边缘的,平滑会降低边缘信息的对比度,从而减少图像中的信号信息。
Step 3 基于梯度幅值的方向权重投影
HOG结构: 通常使用的HOG结构大致有三种:矩形HOG(简称为R-HOG),圆形HOG和中心环绕HOG。它们的单位都是Block(即块)。Dalal的试验证明矩形HOG和圆形HOG的检测效果基本一致,而环绕形HOG效果相对差一些。并且,圆形与环绕形的HOG文献比较少,应用研究没有矩形HOG普遍。所以,着重讲解矩形HOG的情况。矩形HOG块的划分:一般一个块(Block)都由若干单元(Cell)组成,一个单元都有如干个像素点组成。
在每个Cell中有独立做梯度方向统计,从而以梯度方向为横轴的的直方图,梯度方向可取0度到180度或0度~360度,但dalal实验表明,对于人体目标检测0度~180度这种忽略度数正负级的方向范围能够取得更好的结果。然后又将这个梯度分布平均分成个方向角度(orientation bins),每个方向角度范围都会对应一个直方柱。 根据Dalal等人实验,在人体目标检测中,在无符号方向角度范围并将其平均分成9份(bins)能取得最好的效果,当bin的数目继续增大效果改变不明显,故一般在人体目标检测中使用bin数目为9范围0~180度的度量方式。
对梯度方向的投影权重方式的选取: 对于梯度方向的加权投影,一般都采用一个权重投影函数,它可以是像素点的梯度幅值,梯度幅值的平方根或梯度幅值的平方,甚至可以使梯度幅值的省略形式,它们都能够一定程度上反应出像素上一定的边缘信息。根据Dalal等人论文的测试结果,采用梯度幅值量级本身得到的检测效果最佳,使用量级的平方根会轻微降低检测结果,而使用二值的边缘权值表示会严重降低效果(约为5%个单位10-4FPPW(False Positives Per Window))。
问:块与块之间是相互独立的么?答:通常的将某个变量范围固定划分为几个区域,由于边界变量与相邻区域也有相关性,所以变量只对一个区域进行投影而对相邻区域完全无关时会对其他区域产生混叠效应。
分块之间的相关性问题的解决:方案一:块重叠,重复统计计算。
方案二:线性插值权重分配
重叠块:Datal等人在他们那篇关于HOG最为经典的论文《Histogram of Oriented Gradient for Human Detection》提出了利用块与块的重叠(Overlap)来解决混叠,并且取得了不错的效果。 在重叠方式中,块与块之间的边缘点被重复根据权重投影到各自相邻块(block)中,从而一定模糊了块与块之间的边界,处于块边缘部分的像素点也能够给相邻块中的方向梯度直方图提供一定贡献,从而达到关联块与块之间的关系的作用。Datal对于块和块之间相互重叠程度对人体目标检测识别率影响也做了实验分析。
利用线性插值的方法解决分块之间联系问题:有些文献采用的不是块与块重叠的方法,而是采用线性插值的方法来削弱混叠效应。这种方法的主要思想是每个Block都对临近的Block都有影响,这种影响,我们可以以一种加权方式附加上去。
Step 4:HOG特征向量归一化
我们要对block块内的HOG特征向量进行归一化。对block块内特征向量的归一化主要是为了使特征向量空间对光照,阴影和边缘变化具有鲁棒性。还有归一化是针对每一个block进行的,一般采用的归一化函数有以下四种:
在人体检测系统中进行HOG计算时一般使用L2-norm,Dalal的文章也验证了对于人体检测系统使用L2-norm的时候效果最好。
Step 5 HOG最终的特征向量生成
HOG与SIFT的区别
HOG和SIFT都属于描述子,以及由于在具体操作上有很多相似的步骤,所以致使很多人误认为HOG是SIFT的一种,其实两者在使用目的和具体处理细节上是有很大的区别的。HOG与SIFT的主要区别如下:
① SIFT是基于关键点特征向量的描述。
② HOG是将图像均匀的分成相邻的小块,然后在所有的小块内统计梯度直方图。
③ SIFT需要对图像尺度空间下对像素求极值点,而HOG中不需要。
④ SIFT一般有两大步骤,第一个步骤是对图像提取特征点,而HOG不会对图像提取特征点。
HOG的优点:HOG表示的是边缘(梯度)的结构特征,因此可以描述局部的形状信息;位置和方向空间的量化一定程度上可以抑制平移和旋转带来的影响;采取在局部区域归一化直方图,可以部分抵消光照变化带来的影响。
HOG的缺点:描述子生成过程冗长,导致速度慢,实时性差;很难处理遮挡问题。由于梯度的性质,该描述子对噪点相当敏感。
在初步了解HOG理论的基础上,我在网上找到了HOG的matlab代码,并做了相应的注释,虽然现在还不是很清楚它的原理,不过还是贴出来,供大家学习和参考:
function F = hogcalculator1(img, cellpw, cellph, nblockw, nblockh, nthet, overlap, isglobalinterpolate, issigned, normmethod)
% IMG: 是输入图像, CELLPW和CELLPH分别是单元格像素宽度和高度。
% NBLOCKW、NBLCOKH: 分别是块中单元格在x和y方向上数量。
% NTHET: 是梯度方向直方图容器的数目
% ISSIGNED: 是梯度方向直方图范围从无符号的0到pi和有符号是0到2*pi条件。可以通过设置变量的值ISSIGNED来指定。
% OVERLAP: 两个相邻块重叠的比例。
% ISGLOBALINTERPOLATE:严格按照Dalal论文中程序要求来的。
% NORMMETHOD:是块直方图归一化方法,设置为下列字符串:‘none',这意味着非规范化;
% ’l1’,这意味着L1规范标准化;
% 'l2’,这意味着L2规范标准化;
% 'l1sqrt’,这意味着L1sqrt规范标准化;
% 'l2hys',这意味着L2-hys-norm标准化。
% F:是一个行向量,存储所有块的最后的直方图,从左上角到右下角一个一个扫描图像的方式,单元格的直方图以相同的方式存储在每个块中。
% 注意:CELLPW*NBLOCKW 和 CELLPH*NBLOCKH 应该分别等于IMG的宽度和高度。
% 调用方式1: 这里是一个示范,在Dalal的论文中提到所有参数的设置最佳值是:当窗口是128*64的大小(128行、64列)
% F = hogcalculator(window, 8, 8, 2, 2, 9, 0.5, 'localinterpolate', 'unsigned', 'l2hys');
% 调用方式2: 函数也可以像下面这样调用:F = hogcalculator(window);其他参数的设置都通过使用上述dalal的提到的最佳值来默认。
%—————— 设置默认参数值。
if nargin < 2
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
% ——————检查参数的有效性。
[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
% 设置高斯空间权量窗口的标准偏差。
delta = cellpw*nblockw * 0.5;
% 计算梯度比例矩阵。
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));
% 计算梯度方向矩阵。 加上较小数值避免划分为零。
gradscalxplus = gradscalx+ones(size(gradscalx))*0.0001;
gradorient = zeros(M,N);
% 无符号的情况:方向范围是0到pi。
if strcmp(issigned, 'unsigned') == 1
gradorient =...
atan(gradscaly./gradscalxplus) + pi/2;
or = 1;
else
%有符号的情况:方向范围是0到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
% 计算块滑步。
xbstride = cellpw*nblockw*(1-overlap);
ybstride = cellph*nblockh*(1-overlap);
xbstridend = N - cellpw*nblockw + 1;
ybstridend = M - cellph*nblockh + 1;
% 计算在窗口检测到所有块的数量,这是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
% 生成矩阵来存储一个块的直方图;
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;
% 块标准化
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
通过调用hogcalculator1(x3),其中x3是图像为,运行后产生临时变量ans,用ans的值进行plot(ans)操作,最终图像。程序的完整性和正确性还有待进一步研究。