HOG特征
一、什么是HOG特征
- 是关于目标区域梯度方向的特征
- 是一个向量
二、如何提取HOG特征
- 图片归一化处理,减弱光线、阴影等影响
- 图像梯度计算,一般用卷积方法,水平模板为[-1,0,1],竖直模板为[-1,0,1]T,看到这个,很容易联想到边缘检测,实际上,这个梯度很大程度上就代表了图像的边缘轮廓信息
- 统计梯度方向,将目标窗口(win:64*128)继续细分为块(block:16*16),而块又继续细分为细胞窗口(cell:8*8)。
- 块之间仅相差8个像素,那么目标窗口内一共划分【(64/16)*2-1 】*【(128/16)*2-1】=7*15=105个块block,
- 而每个块里面又可划分为2*2=4个细胞cell。
- 计算每个细胞的梯度方向直方图,假定该直方图为9个bin,那么,对于整个目标窗口(我们手动框定的部分),就可以提取到9*4*105=3780个bin,将每个bin的值按顺序排列形成一个3780维的向量,这个向量,即为目标区域的HOG特征
- 一个网上流传的流程图如下:
三、 窗口(win)、块(block)、细胞(cell)与像素的关系
- 图中蓝色框是我们的目标区域,也就是我们总共要提取hog特征的区域,假设他的size为(64*128)
- 图中黄色框为一个块(block),size为(16*16)【一般来说这个block的宽、高都为win窗口可以整除的数】
- 图中红色框为一个细胞(cell),size为(8.8)【那么可以看到,一个block里面可以放4个cell】
- cell里面的一个方格为1个像素
四、特征向量维度数目的计算
- 一般设定黄色框(block)每次只沿x或y移动一半size,即8个像素
- 那么,对于蓝色框(win),一共可分为[(64/8)-1]*[(128/8)-1]=7*15=105个block
- 那么,对于蓝色框(win),一共可分为105*4=420个cell
- 我们对每个cell都提取一个梯度直方图,这个梯度直方图有9个bin
- 那么, 对于蓝色框(win),一共就有420*9=3780个bin
- 如果不知道直方图是什么,看一下这个就明白了
五、关于梯度直方图的计算(重点)
(1)图像卷积
图像梯度一般利用图像与梯度算子卷积实现,关于这部分内容,可以参考Opencv2.4学习::图像卷积
(2)梯度算子
HOG特征提取所用的梯度算子为:水平方向 【-1,0,1】 , 垂直方向 【-1,0,1】T
(3)对于像素I(x,y)的梯度信息为:
水平梯度:
垂直梯度:
梯度幅值:
梯度方向 : ,
(4)当我们获得了一个cell的所有像素的梯度信息之后,如何生成cell的梯度直方图?
1)划分区域,对180度划分9个区域,其中0,20】,20,40】。。。。。类推即可
2)制定落入规则,如果我们知道I(x,y)像素的梯度方向为15度时,可将他划分到【0,20】这个区域(或者叫bin),当梯度方向为25度时,可直接将他划分到【20,40】区域,或者按比例划分到【0,20】和【20,40】这两个区域之中。一般这个规则可 根据实际实际情况制定。
3)直方图bin数值计算: 假如区域为【0,20】为bin[0],当某个像素的梯度方向恰好落在这个区域时,那么bin[0]加上该像素梯度幅值即可,即,即
4)对所有像素遍历,并且将该像素梯度幅值加到对应的bin上,即生成关于cell的梯度直方图
六、 生成HOG特征向量
(1)对每个block的直方图进行归一化
一个block有4个cell,这个操作就是将这4个cell的直方图信息4*9=36个bins串接起来,形成一个36维向量,然后进行归一化。这个归一化方法一般采用L2归一化;
(2)直方图串接形成HOG特征向量
将每个block归一化完成之后的直方图串接起来,一共有105*36=3780个bins,那么,这个HOG特征向量是一个3780维的向量。
六、代码实现
- 代码中利用积分图对上述计算过程实现了一定的简化
#include <iostream>
#include<stdlib.h>
#include<stdio.h>
#include<string>
using namespace std;
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/opencv.hpp>
using namespace cv;
#define NBINS 9
#define THETA 20
#define R 4
#define BLOCKSIZE 16
#define CELLSIZE 8
#define BLOCKSTEP 8
//计算积分图
std::vector<Mat> CalculateIntegralHOG(Mat &srcMat)
{
//cvtColor(srcMat,srcMat,CV_BGR2GRAY);
//sobel边缘检测
Mat sobelMatX,sobelMatY;
Sobel(srcMat,sobelMatX,CV_32F,1,0);
Sobel(srcMat,sobelMatY,CV_32F,0,1);
std::vector<Mat> bins(NBINS);
for(int i=0;i<NBINS;i++){
bins[i]=Mat::zeros(srcMat.size(),CV_32F);//每个bins储存一副与原图尺寸一样的32FMat
}
Mat magnMat,angleMat;
//貌似是直接利用X方向的梯度,和 Y方向的梯度,直接得到了幅值图和角度图
//幅值图每个像素点的计算公式为:magnMat(x,y)=((sobelMatX(x,y)^2+sobelMatY(x,y)^2)^0.5
//角度图每个像素点的计算公式为:angleMat(x,y)=atan2(sobelMatX(x,y),sobelMatY(x,y))*180/PI
//atan2(y,x)求的是y/x的反正切,其返回值为[-pi,+pi]之间的一个数
//所以角度图的每个像素点的值为角度,而不是弧度制
cartToPolar(sobelMatX,sobelMatY,magnMat,angleMat,true);
//下面操作的意义是,将角度图的每个像素值限制在0-180度范围内,因为30度和210度是同一个方向
//if angleMat(x,y)<0 ,then angleMat(x,y)+=180;
cv::add(angleMat,Scalar(180),angleMat,angleMat<0);
//if angleMat(x,y)>180 ,then angleMat(x,y)-=180;
cv::add(angleMat,Scalar(-180),angleMat,angleMat>=180);
//将180度分为9份,每份20度
angleMat/=THETA;
//计算每个bin的幅值
for(int y=0;y<srcMat.rows;y++){
for(int x=0;x<srcMat.cols;x++){
int ind=angleMat.at<float>(y,x);//取整数
//当(x,y)这个像素的角度属于哪个0-20,20-40....160-180 哪个范围时,
//就将像素的梯度幅值加到对应的bin
bins[ind].at<float>(y,x)+=magnMat.at<float>(y,x);
}
}
//上面得到了0-20,20-40....160-180 9个区间下的图,
//即srcMat图像上梯度角度为0-20的像素点的梯度幅值被赋值到bins[0]的对应像素点上,以此类推
std::vector<Mat> integrals(NBINS);
for(int i=0;i<NBINS;i++){
//计算积分图
//积分图意义:integrals[i](X,Y)=bins[i](x<X,y<Y);
//就是说,在积分图integrals[i]的某个像素(X,Y)的值=
//图bins[i]中矩形范围Rect(0,0,X,Y)内所有像素点的值之和
//那么对于整个图bins[i]的所有像素点之和=integrals[i](width,height),即等于右下角的像素点值
integral(bins[i],integrals[i]);
//到这里,如果想要获取srcMat图指定区域Rect(Xs,Ys,Width,Height)的HOG特征,只需
//i遍历9次:
//HOG_Bin[i]=integrals[i](Xs+Width,Ys+Height)-integrals[i](Xs+Width,Ys)-
//integrals[i](Xs,Ys+Height)+integrals[i](Xs,Ys);
}
return integrals;
}
//计算单个cell的HOG特征
void cacHOGinCell(Mat &HOGCellMat,Rect roi,std::vector<Mat> &integrals,int cell_id)
{
int x0=roi.x;
int y0=roi.y;
int x1=x0+roi.width;
int y1=y0+roi.height;
//Mat hist(Size(NBINS,1),CV_32F);
for(int i=0;i<NBINS;i++){
//获取指定区域的HOG特征 有多少个BIN就遍历多少次
Mat integral=integrals[i];
float a=integral.at<double>(y1,x1);
float b=integral.at<double>(y0,x1);
float c=integral.at<double>(y1,x0);
float d=integral.at<double>(y0,x0);
//HOGCellMat 这个Mat只有一行,列数为一个块block里面bin的数量=blocksize*blocksize*NBINS
HOGCellMat.at<float>(0,i+cell_id*NBINS)=a-b-c+d;
}
//return hist;
}
//计算单个BLOCK的HOG特征
cv::Mat cacHOGinBlock(Point block_pt_start,std::vector<Mat> &integrals)
{
Mat hist(Size(NBINS*(BLOCKSIZE/CELLSIZE)*(BLOCKSIZE/CELLSIZE),1),CV_32F);
int cell_bins_num=NBINS;
for(int i=0;i<BLOCKSIZE/CELLSIZE;i++){
for(int j=0;j<BLOCKSIZE/CELLSIZE;j++){
Rect roi(block_pt_start.x+j*CELLSIZE,block_pt_start.y+i*CELLSIZE,CELLSIZE,CELLSIZE);
cacHOGinCell(hist,roi,integrals,i*BLOCKSIZE/CELLSIZE+j);
// for(int k=0;k<cell_bins_num;k++){
// hist.at<float>(0,k+(i*BLOCKSIZE/CELLSIZE+j)*cell_bins_num)=
// histinCell.at<float>(0,k);
// }
}
}
//归一化到L2
normalize(hist,hist,1,0,NORM_L2);
return hist;
}
//计算目标区域的HOG特征
cv::Mat cacHOGinWin(Rect roi_win,std::vector<Mat>&integrals)
{
int block_bins_num=NBINS*(BLOCKSIZE/CELLSIZE)*(BLOCKSIZE/CELLSIZE);
Rect roi_resize=Rect(roi_win.x,roi_win.y,int(roi_win.width/8)*8,int(roi_win.height/8)*8);
Mat hist(Size(block_bins_num*
(roi_resize.width/CELLSIZE-1)*(roi_resize.height/CELLSIZE-1),1),CV_32F);
int size=0;
for(int i=0;i<roi_resize.height/CELLSIZE-1;i++){
for(int j=0;j<roi_resize.width/CELLSIZE-1;j++){
Point block_pt_start=Point(roi_win.x+j*CELLSIZE,roi_win.y+i*CELLSIZE);
Mat histinBlock=cacHOGinBlock(block_pt_start,integrals);
for(int k=0;k<block_bins_num;k++){
hist.at<float>(0,k+((i*(roi_resize.width/CELLSIZE-1)+j)*block_bins_num))=
histinBlock.at<float>(0,k);
// if(hist.at<float>(0,k+(i*(roi_resize.width/CELLSIZE-1)+j)*block_bins_num)>1)
// cout<<histinBlock.at<float>(0,k)<<endl;
}
}
}
return hist;
}
int main()
{
Mat srcImage=imread("//home//msi//obama.jpg");
if(!srcImage.data){
cout<<"failed to read"<<endl;
system("pause");
return -1;
}
Mat srcGray;
cvtColor(srcImage,srcGray,CV_BGR2GRAY);
//要提取HOG特征向量的区域
Rect testR(10,10,64,128);
//生成积分图
std::vector<Mat> integrals=CalculateIntegralHOG(srcGray);
//计算HOG特征
Mat hist=cacHOGinWin(testR,integrals);
//输出
for(int i=0;i<hist.cols;i++){
printf("第%d维:%f\n",i,hist.at<float>(0,i));
}
return 0;
}
运行测试:
参考文章:
https://blog.csdn.net/liyuqian199695/article/details/53835989
https://blog.csdn.net/yuan1125/article/details/70878104
https://www.jianshu.com/p/ed21c357ec12