HOG原理与OpenCV实现

本文详细介绍了方向梯度直方图(HOG)特征提取算法的原理及其在OpenCV中的实现方法,包括色彩和伽马归一化、梯度计算、特征向量构建等内容。

方向梯度直方图(Histogram of Oriented Gradient, HOG)于2005年提出,是一种常用的特征提取方法,HOG+SVM在行人检测中有着优异的效果。


HOG特征提取算法原理

在一幅图像中,梯度或边缘的方向密度分布能够很好地描述局部目标区域的特征,HOG正是利用这种思想,对梯度信息做出统计,并生成最后的特征描述。在HOG中,对一幅图像进行了如下划分:
图像(image)->检测窗口(win)->图像块(block)->细胞单元(cell)

流程图如下:
这里写图片描述
对于上述流程图,有几点需要注意的地方:
1.色彩和伽马归一化为了减少光照因素的影响,首先需要将整个图像进行规范化(归一化)。在图像的纹理强度中,局部的表层曝光贡献的比重较大,所以,这种压缩处理能够有效地降低图像局部的阴影和光照变化。
2.图像的梯度针对的是每一个像素计算得到,然后再cell中进行方向梯度直方图的构建,在block中进行对比度归一化操作。
3.由于窗口的滑动性与块的滑动行,窗口与块都会出现不同程度的重叠(由步长决定),此时在块内划分出的cell就会多次出现,这就意味着:每一个细胞单元的输出都多次作用于最终的描述器。


数字图像梯度的计算:

在二元连续函数的情形下,设函数z=f(x,y)在平面区域D内具有一阶连续偏导数,则对于每一点p(x,y)⊆Dp\left ( x,y \right )\subseteq Dp(x,y)D,都可以定出一个向量
∂f∂xi+∂f∂yj \frac{\partial f}{\partial x}i+\frac{\partial f}{\partial y}j xfi+yfj
这向量称为函数z=f(x,y)z=f\left(x,y\right)z=f(x,y)在点p(x,y)p\left ( x,y \right )p(x,y)的梯度,记作gradf(x,y)grad f\left ( x,y \right )gradf(x,y)
而对于数字图像图像而言,相当于对二维离散函数求梯度,如下:
G(x,y)=dx(x,y)+dy(x,y)dx(x,y)=I(x+1,y)−I(x,y)dy(x,y)=I(x,y+1)−I(x,y) G\left(x,y\right)=d_x\left(x,y\right)+d_y\left(x,y\right)\\d_x\left(x,y\right)=I\left(x+1,y\right)-I\left(x,y\right)\\d_y\left(x,y\right)=I\left(x,y+1\right)-I\left(x,y\right) G(x,y)=dx(x,y)+dy(x,y)dx(x,y)=I(x+1,y)I(x,y)dy(x,y)=I(x,y+1)I(x,y)
其中,I(x,y)I\left(x,y\right)I(x,y)是图像在点(x,y)\left(x,y\right)(x,y)处的像素值。


HOG中的win ,block ,cell

HOG最先是用来做行人检测的,显然这是一个目标检测的任务,当我们使用滑动窗遍历方法实现目标检测任务时,首先我们需要构建一个滑动窗,这个滑动窗就是HOG中win的概念。可以理解为,在HOG特征提取时,一个窗口是最小的特征提取单元,在目标检测任务中,滑动窗将以一个设定的步长在整个图像中顺序的滑动,每一次滑动后,都会提取窗口内的HOG特征,提取到的特征将送入到预先训练好的分类器中,如果分类器模型判定其为目标,则完成目标检测任务。
比如,在一个图像中选择检测窗口,依靠检测窗口尺寸,窗口滑动步长与图像尺寸共同决定将选择几个检测窗口,比如图像的尺寸为166×80166\times 80166×80,检测窗口的尺寸为64×6464\times 6464×64,窗口步长为(8,8)\left (8,8 \right )(8,8)。那么在图像的列中将滑动次数如下:
numcols=(166−64)8+1=13.75 num_{cols}=\frac{\left(166-64\right)}{8}+1=13.75 numcols=8(16664)+1=13.75
显然这并不合乎逻辑,此时将自动填充,所以列中可以滑动出14个win;
同理,行中滑动次数如下:
numrows=(80−64)8+1=3 num_{rows}=\frac{\left(80-64\right)}{8}+1=3 numrows=8(8064)+1=3
所以图像中共滑动14×3=4214\times 3=4214×3=42个窗口。
那么对于一个窗口内选择块是一样的原理,假设给出块的尺寸为16×1616\times 1616×16,块步长为(8,8)\left (8,8 \right )(8,8),经过计算:检测窗口中共滑动7×7=497\times 7=497×7=49个block。
在一个块中选择细胞单元不再滑动,给出细胞单元的尺寸为(8,8)\left (8,8 \right )(8,8),所以一个块中一共有2×2=42\times 2=42×2=4个cell。
在这里有一个需要注意的地方时,窗口在滑动时可以根据像素填充方法补齐的,但是对于块与cell来说,是不可以的。因为这是算法本身的一个硬性要求,所在这要求我们在做窗口尺寸,块尺寸,块步长,单元尺寸选择时,必须满足一下条件:
1.一个窗口内根据块步长与块尺寸滑动块时,必须可以滑动出整数个块。
2.在块内确定单元个数时,必须要整数个单元。


HOG构建方向梯度直方图:

HOG构建方向梯度直方图在cell中完成,bins的个数决定了方向的范围。
这里写图片描述
细胞单元中的每一个像素点都为某个基于方向的直方图通道投票。
投票是采取加权投票的方式,即每一票都是带有权值的,这个权值是根据该像素点的梯度幅度计算出来。可以采用幅值本身或者它的函数来表示这个权值,实际测试表明: 使用幅值来表示权值能获得最佳的效果,当然,也可以选择幅值的函数来表示,比如幅值的平方根、幅值的平方、幅值的截断形式等。细胞单元可以是矩形的,也可以是星形的。直方图通道是平均分布在0-180(无向)或0-360(有向)范围内。经研究发现,采用无向的梯度和9个直方图通道,能在行人检测试验中取得最佳的效果。而在这种情况下方向的范围划分为1809=20\frac{180}{9}=209180=20度。


特征向量维数

之前提到过,cell的中方向范围的个数由bins来决定,还是以9为例:所以,一个cell中的向量为9个。以上面的例子,在一个尺寸为64×6464\times 6464×64的检测窗中,描述子的维数就应该为:9×4×49=17649\times 4\times49=17649×4×49=1764 。其中4为一个block中cell的个数,49为一个win中block的个数。

那么HOG作为一种特征提取算法,对于图像分类问题该如何提取特征呢?此时的窗口将是整幅图像,也就是说,窗口将不再在图像中滑动。


HOG的OpenCV实现

注意事项

在HOG的原理部分,其实我们已经提到了一些注意的事项,那就是块尺寸,块步长,单元尺寸,窗口步长的选择问题。这些参数在自行设计时应该满足滑动出整数的条件,否则代码会出现异常。在Opencv中,在构建类HOGDescriptor的对象时,它是带有初始值的:

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)

计算一下的话会发现这些值满足之前说的条件,所以当我们在设计这些参数时,也要注意这点。
此外,上面这些参数是没有窗口步长的,这是因为窗口步长定义在hog.compute()函数中,该函数对滑动窗是有自动补齐功能的。


代码实现

OpenCV中,HOG被封装在了HOGDescriptor 类中,而且OpenCV提供了直接利用HOG+SVM进行多尺度行人检测的函数detectMultiScale(),在这里我们不介绍它,只说明如何利用HOG提取出可以输入到SVM中的特征矩阵。需要说明的是,这是一个图像分类任务的特征提取过程,所以,这要求我们将整个图像作为一个窗口在构建hog特征。hog.compute()函数在计算特征时,不在滑动窗口。

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/objdetect/objdetect.hpp>

using namespace std;
using namespace cv;

#define Posnum   2  //正样本个数
#define Negnum 2    //负样本个数

int main()
{
  char adpos[128],adneg[128];
  HOGDescriptor hog(Size(64,64),Size(16,16),Size(8,8),Size(8,8),3);//利用构造函数,给对象赋值。
  int DescriptorDim;//HOG描述子的维数
  Mat samFeatureMat, samLabelMat;
//依次读取正样本图片,生成HOG描述子
  for (int i = 1;i <= Posnum ;i++)  
 {  
	sprintf_s(adpos, "F:\\pos\\%d.jpg", i);
	Mat src = imread(adpos);//读取图片
	resize(src,src,Size(64,64));  
	vector<float> descriptors;//HOG描述子向量
	hog.compute(src,descriptors);
	if ( i == 1)
	{
		DescriptorDim = descriptors.size();
		samFeatureMat = Mat::zeros(Posnum +Negnum , DescriptorDim, CV_32FC1);
		samLabelMat = Mat::zeros(Posnum +Negnum , 1, CV_32FC1);
	}
		for(int j=0; j<DescriptorDim; j++)
		{
			samFeatureMat.at<float>(i-1,j) = descriptors[j];
			samLabelMat.at<float>(i-1,0) = 1;
		}
 }
//依次读取负样本图片,生成HOG描述子
  for (int k = 1;k <= Negnum ;k++)  
 {  
	sprintf_s(adneg, "F:\\neg\\%d.jpg", k);
	Mat src = imread(adneg);//读取图片
	resize(src,src,Size(64,64)); 
	vector<float> descriptors;//HOG描述子向量
	hog.compute(src,descriptors);
		for(int l=0; l<DescriptorDim; l++)
		{
			samFeatureMat.at<float>(k+Posnum-1,l) = descriptors[l];
			samLabelMat.at<float>(k+Posnum-1,0) = -1;
		}
 }
  cout<<"特征个数:"<<samFeatureMat.rows<<endl;
  cout<<"特征维度:"<<samFeatureMat.cols<<endl;
  return 0;
}

代码的逻辑还是很简单的,要注意的地方在于读取正样本的for循环中加入了一个if判断是为了初始化samFeatureMat矩阵的行列,显然,最后SVM要用来训练的矩阵为samFeatureMat和samLabelMat。samLabelMat的列为1,因为他只存放了一个正或负的标签,而samFeatureMat的则为:所有样本的个数*描述子维数。这也就是为啥初始化要放在循环里面了,因为没有提取特征呢,谁知道描述子维数是多少呢?(这样就不用手算了)

### HOG特征描述符在计算机视觉中的应用及其实现 #### 计算机视觉中的HOG特征描述符概述 HOG(Histogram of Oriented Gradients)是一种用于物体检测的特征描述符,其核心思想是通过捕捉图像中局部区域的梯度方向分布来描述目标的形状特征[^1]。这种技术特别适合于需要识别特定对象的任务,例如行人检测。 #### HOG特征描述符的工作流程 以下是HOG特征描述符的主要计算步骤: ##### 数据预处理 为了提高算法性能,在实际操作前通常会对输入图像进行标准化处理,比如调整大小、灰度转换以及噪声去除等操作。 ##### 梯度计算 利用Sobel算子或其他边缘检测方法分别沿水平和垂直两个方向上求取像素变化率即偏导数,从而得到每个像素点处对应的梯度强度及其指向角度[^2]。 ##### 创建直方图 对于划分好的单元格(Cell),统计该区域内所有像素点关于它们各自所属区间范围内的频率计数值形成一个固定长度向量表示当前区块的信息摘要;接着再把这些相邻的小块组合起来构成更大的结构单位——称为Block,并对其进行L2范式规范化以增强鲁棒性对抗光照条件改变等因素影响。 #### Python实现HOG特征提取 下面展示如何借助`opencv-python`库完成基本版的人体部位探测器构建工作流实例代码如下所示: ```python import cv2 from skimage import io, transform # 加载测试图片并转化为灰阶模式 image_path = 'test_image.jpg' img_gray = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) # 初始化默认参数设置下的hog descriptor对象 win_size = (64, 128) block_size = (16, 16) block_stride = (8, 8) cell_size = (8, 8) nbins = 9 deriv_aperture = 1 win_sigma = -1. histogram_norm_type = 0 l2_hys_threshold = 0.2 gamma_correction = True nlevels = 64 signed_gradient = False hog_descriptor = cv2.HOGDescriptor(win_size, block_size, block_stride, cell_size, nbins, deriv_aperture, win_sigma, histogram_norm_type, l2_hys_threshold, gamma_correction, nlevels, signed_gradient) # 设置支持向量机(SVM)作为分类模型权重加载路径(如果有的话) svm_model_path = './default_svm.xml' if hasattr(hog_descriptor,'setSVMDetector'): hog_descriptor.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector()) # 执行多尺度滑动窗口扫描寻找可能的目标位置列表形式返回结果[(rect,(weight))] found_locations,_weights= hog_descriptor.detectMultiScale(img_gray,scaleFactor=1.05,hitThreshold=.5,finalThreshold=2,pad=(8,8)) for x,y,w,h in found_locations: cv2.rectangle(io.imread(image_path),(x,y),(x+w,y+h),color=[255,0,0],thickness=2) io.imshow(io.imread(image_path)) io.show() ``` 上述脚本片段展示了怎样定义自定义配置版本或者采用内置推荐值初始化霍夫变换类实例的过程,同时还演示了调用detectMultiscale函数接口执行整个框架逻辑链路直至最终绘制成矩形框标注出疑似人体所在具体坐标位置集合的操作方式。 ---
评论 9
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值