Matlab 提取Hog特征方法详细解析
前期时间里,撰写了一篇关于MATLAB中HOG特征提取方法浅述的文章,最初仅完成对其基本原理的初步理解。近来,对HOG特征的提取方法进行了深入探讨,并利用积分直方图加快了计算速度。为以后的应用方便起见,特意将其中涉及的核心思路、具体实现方式以及遇到的问题详细记录下来。
相关资料:
此外,在博客上还有其他相关文章值得参考:例如[()这一篇文章深入探讨了相关的技术细节;同时[()则从另一个角度提供了独特的见解;此外[()这一篇文章则详细介绍了相关的应用场景
该算法的基本实现流程如下:首先将输入图像划分为多个细胞(cell),随后每个细胞又被划分为多个块(block)。对于每个块内所有的像素点方向梯度进行计算并统计得到直方图。最后将所有块对应的直方图进行融合汇总,从而得到该图像的Hog特征描述。下面详细阐述我的编程实现思路。
主程序(Main.m):
<span style="font-family:SimSun;font-size:18px;">close all;clear all;clc;
filePath = 'E:\HOG\HogSamples\';
fileExt = '*.bmp';
files = dir(fullfile(filePath,fileExt));
len = size(files,1);
% Generate a test Image.
% testImg = reshape((1:16:256),4,4);
% hogFeature = ExtrHogFeature(testImg,3,3,1,180,9);
for i = 1:len
tic;
disp(['----------',num2str(i),'----------'])
srcImg = imread(strcat(filePath,files(i,1).name));
hogFeature = ExtrHogFeature(srcImg,5,4,2,360,15);
toc;
end</span>
在主程序中进行如下操作:首先遍历指定文件夹中的指定后缀名文件(如*.bmp),随后将获取的图像作为窗口图像传递给ExtrHogFeature函数。该函数是我自行开发用于提取Hog特征的功能模块。
<span style="font-family:SimSun;font-size:18px;">function feature = ExtrHogFeature(imgFile,cellStep,blockStep,overlap,angle,binNum)
if nargin < 2
cellStep = 3;
blockStep = 4;
overlap = 1;
angle = 180;
binNum = 9;
elseif nargin < 6
error('Input parameters are not enough.');
elseif overlap < 0 || overlap >= blockStep
error('The input parameter "overlap" is wrong!');
elseif angle ~= 180 && angle ~= 360
error('The input parameter "angle" is only 180 or 360!');
end
[h,w,d] = size(imgFile);
if(d ~= 1)
grayImg = rgb2gray(imgFile);
else
grayImg = imgFile;
end
% Correct the width and height of imgFile.
if w < cellStep
cellNum_W = 1;
else
cellNum_W = ceil(w / cellStep);
if cellNum_W < blockStep
blockNum_W = 1;
else
blockNum_W = floor((cellNum_W - blockStep) / (blockStep - overlap)) + 1;
end
end
corrW = cellStep * ((blockStep - overlap) * (blockNum_W - 1) + blockStep);
if h < cellStep
cellNum_H = 1;
else
cellNum_H = ceil(h / cellStep);
if cellNum_H < blockStep
blockNum_H = 1;
else
blockNum_H = floor((cellNum_H - blockStep) / (blockStep - overlap)) + 1;
end
end
corrH = cellStep * ((blockStep - overlap) * (blockNum_H - 1) + blockStep);
% Bilinear interpolation
corrImg = BiliInter(grayImg,corrH,corrW);
% The following contents need to be modified.
corrImg = double(corrImg);
flipImg = FlipImg(corrImg,1,1);
Gx = zeros(corrH,corrW);
Gy = zeros(corrH,corrW);
range = angle / binNum;
for i = 1:corrH
for j = 1:corrW
Gx(i,j) = flipImg(i,j + 1 + 1) - flipImg(i,j);
Gy(i,j) = flipImg(i + 1 + 1,j) - flipImg(i,j);
Gxy(i,j) = sqrt((Gx(i,j))^2 + (Gy(i,j))^2);
end
end
if angle == 180
Axy = (atan(Gy ./ Gx) / pi) * 180;
Axy(find(Axy < 0)) = Axy(find(Axy < 0)) + 180;
elseif angle == 360
Axy = (atan2(Gx,Gy) / pi) * 180;
Axy(find(Axy < 0)) = Axy(find(Axy < 0)) + 360;
end
Axy(isnan(Axy)) = 0;
Axy = floor((Axy / range) + 1);
coluHist = zeros(1,corrW,binNum);
inteHist = zeros(corrH,corrW,binNum);
for i = 1:corrW
coluHist(1,i,Axy(1,i)) = Gxy(1,i);
inteHist(1,i,Axy(1,i)) = Gxy(1,i);
if i > 1
inteHist(1,i,:) = inteHist(1,i,:) + inteHist(1,i - 1,:);
end
end
for i = 2:corrH
for j = 1:corrW
coluHist(1,j,Axy(i,j)) = coluHist(1,j,Axy(i,j)) + Gxy(i,j);
if j == 1
inteHist(i,j,:) = coluHist(1,j,:);
else
inteHist(i,j,:) = inteHist(i,j - 1,:) + coluHist(1,j,:);
end
end
end
tmpFeature = zeros(1,binNum);
for i = 1:blockNum_H
for j = 1:blockNum_W
i1 = (i * (blockStep - overlap) + overlap - blockStep + 1) * cellStep;
j1 = (j * (blockStep - overlap) + overlap - blockStep + 1) * cellStep;
i2 = (i * (blockStep - overlap) + overlap - blockStep + 1) * cellStep;
j2 = (j * (blockStep - overlap) + overlap) * cellStep;
i3 = (i * (blockStep - overlap) + overlap) * cellStep;
j3 = (j * (blockStep - overlap) + overlap - blockStep + 1) * cellStep;
i4 = (i * (blockStep - overlap) + overlap) * cellStep;
j4 = (j * (blockStep - overlap) + overlap) * cellStep;
if i == 1 && j == 1
tmpFeature(:) = inteHist(i4,j4,:);
feature = tmpFeature;
else
tmpFeature(:) = inteHist(i4,j4,:) - inteHist(i2,j2,:) - inteHist(i3,j3,:) + inteHist(i1,j1,:);
feature = [feature,tmpFeature];
end
end
end
maxValue = max(feature(:));
minValue = min(feature(:));
feature = (feature - minValue) / (maxValue - minValue) * 1000;
% save ExtrHogFeature_Data</span>
在ExtrHogFeature函数中:
1.参数意义:
(1).imgFile:输入的窗口图像文件。
(2). cellStep表示每个单元格包含多少个像素。理论上单元格宽度与高度可以不一致。然而,在编程实现中并无实际价值。因此我们默认每个单元格为正方形。同样的逻辑适用于blockStep和overlap
(3).blockStep:block的步长,即一个block有多少个cell。注意:不是有多少个像素,是有多少个cell。
(4).overlap:重叠数目。即后(下)一个block与前(上)一个block有几列(行)cell重叠。
(5).angle:只能是两个值,180或者360。
(6).binNum:积分直方图的区域数量。
2.判断输入参数是否正确。
(1).判断参数的数量。参数的数量只能是两种,1个或者6个。如果是1个,对除imgFile之外的其他参数进行默认赋值。
(2).评估overlap是否合理。其中overlap变量代表两个block之间的重叠数量,因此, overlap值必须严格小于blockStep.
(3).判断angle是否正确。angle只能是180或360。
识别图像的空间维度后并将其转换为灰度图。该过程涉及将图像标准化至Gamma和Color空间。从数学理论层面而言,则仅需对方阵根数进行计算。为了提高计算效率而采用灰度处理
在提取图像特征时, 我始终坚持"避免减少图像特征"的原则. 即使面对原始图像难以直接生成整数倍cell和block的情况, 我们仍然会选择通过双线性插值方法对原始图像进行缩放处理, 从而能够均匀地划分成整数数量的block.
该函数对testImg进行了处理操作。其中testImg为大小均为20像素的测试窗口图像。其中cellStep被设定为3;而每行cell的数量计算为20除以3得到6.6;因此取其整数部分并向上取整得到7个单元格数量即为cellNum_W的值。
设blockNum_W是每一行block的数量,满足下列公式:
(blockStep - overlap) * (blockNum_W - 1) + blockStep >= cellNum_W ①
取满足公式的最小整数。即:
blockNum_W = floor((cellNum_W - blockStep) / (blockStep - overlap)) + 1 ②
基于公式①的计算结果表明, Block number W达到至少2.5, 因此(同样地,也可以通过公式②得出)Block number W被确定为3. 计算Cell数量W时, 按照如下公式进行: (4−2)×(3−1)+4 = 8. 经过校正后得到的图像宽度corrw被确定为8乘以3等于24个像素单位. 高度corrh的计算方式与此类似
基于corrW和corrH的方法用于对原始图像实施双线性插值缩放。双线性插值函数BiliInter位于本文末尾部分。由于此内容属于文章次要部分且较为简单明了,故无需进一步阐述。
5.计算每个像素的方向梯度。水平模板fx=[-1,0,1],垂直模板fy=fx’。
为了实现corrImg四个边界的方向梯度计算过程,请将这四个边界进行反转操作。与双线性插值方法类似,在本文的末尾处提供了反转图像函数FlipImg的实现细节。
注意:atan和atan2的区别。atan(y/x)根据正切值y/x求出对应的角度,是2象限的反正切。atan2(y,x)的取值不仅取决于y/x的正切值,还取决于(x,y)落于哪个象限,是4象限的反正切。所以,当angle=180的时候,经过atan计算小于0的值是在-pi/20之间,需要+pi,把反正切值校正到pi/2pi。当angle=360的时候,经过atan2计算小于0的值是在-pi0之间,需要+2pi,把反正切值校正到pi2pi。
Axy中可能会出现0/0=Nan的情况,造成程序中止。所以,要有Axy(isnan(Axy)) = 0。
6.为了获取原始窗口图像对应的积分直方图inteHist值,则需要调用算法实现该计算功能。在具体实现过程中,我是参考文献2的基础上进行代码开发,并在此基础上对二维积分图计算方法进行了适当的拓展与优化处理,在此基础上实现了三维积分直方图的具体计算过程
基于积分直方图的方法,在处理窗口图像时,首先通过计算每个块在窗口中的积分直方图,并将这些块的积分直方图进行综合处理,从而得到图像的整体HOG特征描述。接着对这些特征值进行归一化处理至0到1000的比例范围内。
BiliInter函数:
<span style="font-family:SimSun;font-size:18px;">function img = BiliInter(imgFile,dstH,dstW)
if dstH <= 0 || dstW <= 0
error('Input parameters are wrong!');
end
[srcH,srcW] = size(imgFile);
img = zeros(dstH,dstW);
for i = 0:dstH - 1
for j = 0:dstW - 1
actualW = (j / (dstW - 1)) * (srcW - 1);
actualH = (i / (dstH - 1)) * (srcH - 1);
w_Scale = actualW - floor(actualW);
h_Scale = actualH - floor(actualH);
leftUp = [floor(actualH) + 1,floor(actualW) + 1];
rightUp = [floor(actualH) + 1,ceil(actualW) + 1];
leftLow = [ceil(actualH) + 1,floor(actualW) + 1];
rightLow = [ceil(actualH) + 1,ceil(actualW) + 1];
tmpUp = (imgFile(rightUp(1),rightUp(2)) - imgFile(leftUp(1),leftUp(2))) * w_Scale + imgFile(leftUp(1),leftUp(2));
tmpLow = (imgFile(rightLow(1),rightLow(2)) - imgFile(leftLow(1),leftLow(2))) * w_Scale + imgFile(leftLow(1),leftLow(2));
img(i + 1,j + 1) = (tmpLow - tmpUp) * h_Scale + tmpUp;
end
end
img = uint8(img);</span>
FlipImg函数:
<span style="font-family:SimSun;font-size:18px;">function img = FlipImg(imgFile,flipRowNum,flipColNum)
% The flipRowNum is added on imgFile one way.
% The flipColNum is similar to flipRowNum.
[srcH,srcW] = size(imgFile);
if flipRowNum > srcH || flipColNum > srcW
error('Input parameters are too big!');
end
dstH = srcH + 2 * flipRowNum;
dstW = srcW + 2 * flipColNum;
tmpImgRow = zeros(dstH,srcW);
img = zeros(dstH,dstW);
% Fliped with Row.
tmpImgRow(1:flipRowNum,:) = imgFile(flipRowNum:-1:1,:);
tmpImgRow(flipRowNum + 1:flipRowNum + srcH,:) = imgFile(:,:);
tmpImgRow(flipRowNum + srcH + 1:end,:) = imgFile(srcH:-1:srcH - flipRowNum + 1,:);
% Fliped with Column.
img(:,1:flipColNum) = tmpImgRow(:,flipColNum:-1:1);
img(:,flipColNum + 1:flipColNum + srcW) = tmpImgRow(:,:);
img(:,flipColNum + srcW + 1:end) = tmpImgRow(:,srcW:-1:srcW - flipColNum + 1);</span>
