特征提取方法(一):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)∈D,都可以定出一个向量

这向量称为函数z=f(x,y)在点P(x,y)的梯度,记作gradf(x,y)

而对于数字图像图像而言,相当于对二维离散函数求梯度,如下:

其中,I是图像像素的值(如:RGB值),(i,j)为像素的坐标。

HOG中的win ,block ,cell : 在一个图像中选择检测窗口,依靠检测窗口尺寸,窗口滑动步长与图像尺寸共同决定将选择几个检测窗口,比如图像的尺寸为166*80,检测窗口的尺寸为64*64,窗口步长为8*8。那么在图像的列中将滑动 (166-64)/8+1 = 13.75 显然这并不合乎逻辑,OpenCV封装的HOG提供了自动补偿功能,所以列中可以滑动出14个win;同理,行中: (80-64)/8+1 = 3 所以图像中共滑动3*14 = 42个win。

在一个检测窗口中选择块是一样的原理,给出块的尺寸为16*16,块步长为8*8,经过计算:检测窗口中共滑动7*7 = 49个block。

在一个块中选择细胞单元不再滑动,给出细胞单元的尺寸为8*8,块中一共有2*2个cell。

HOG构建方向梯度直方图: HOG构建方向梯度直方图在cell中完成,bins的个数决定了方向的范围。

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

HOG如何确定特征向量维数: 之前提到过,cell的中方向范围的个数由bins来决定,还是以9为例:所以,一个cell中的向量为9个,以上面的例子166*80图像中,描述子的威数就应该为:9*4*49*42=74088.

HOG的OpenCV实现: OpenCV中,HOG被封装在了HOGDescriptor 类中,而且OpenCV提供了直接利用HOG+SVM进行多尺度行人检测的函数detectMultiScale(),在这里我们不介绍它,只说明如何利用HOG提取出可以输入到SVM中的特征矩阵。

#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);//读取图片
    vector<float> descriptors;//HOG描述子向量
    hog.compute(src,descriptors,Size(8,8));
    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);//读取图片
    vector<float> descriptors;//HOG描述子向量
    hog.compute(src,descriptors,Size(8,8));

        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的则为:所有样本的个数*描述子维数。这也就是为啥初始化要放在循环里面了,因为没有提取特征呢,谁知道描述子维数是多少呢?(这样就不用手算了)

最后,我往文件夹里随便放了两张图片,测试了一下代码,图片太大了,导致维度有些高:

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Coding迪斯尼

使用视觉化方法揭露卷积网络是如何从图片中抽取规律信息的

我们在前几节介绍过卷积网络的运算原理,以及通过代码实践,体验到了卷积网络对图片信息抽取的有效性。现在一个问题在于,我们知道卷积网络的运算过程,但不知道为什么卷积...

803
来自专栏企鹅号快讯

常用的像素操作算法:Resize、Flip、Rotate

Resize 图像缩放是把原图像按照目标尺寸放大或者缩小,是图像处理的一种。 图像缩放有多种算法。最为简单的是最临近插值算法,它是根据原图像和目标图像的尺寸,计...

40110
来自专栏数据派THU

Python实例介绍正则化贪心森林算法(附代码)

作者:Ankit Chaoudhary 翻译:笪洁琼 校对:梁傅淇 通过本文与大家讨论一个被称为正则化的贪心森林算法。 引言 作为一名参与多个机器学习竞赛数据...

3706
来自专栏深度学习自然语言处理

【机器学习】今天想跟大家聊聊SVM

之前我在自己的简书上写过SVM,可是当时写的只是皮毛(主要是现在忘了O.O),那么现在想再次拾起的原因是什么呢?

913
来自专栏AI研习社

只要130 行代码即可生成二维样本,心动了吗?

AI 研习社按:此前我们曾编译了一篇英文教程,详细介绍了如何基于 PyTorch 平台用 50 行代码实现 GAN(生成对抗网络),详情参见:在 PyTorch...

2774
来自专栏技术专栏

Python3入门机器学习(五)-线性回归算法

线性回归算法以一个坐标系里一个维度为结果,其他维度为特征(如二维平面坐标系中横轴为特征,纵轴为结果),无数的训练集放在坐标系中,发现他们是围绕着一条执行分布。线...

1953
来自专栏Petrichor的专栏

图像处理: 超像素(superpixels)分割 SLIC算法

超像素概念是2003年Xiaofeng Ren提出和发展起来的图像分割技术,是指具有相似纹理、颜色、亮度等特征的相邻像素构成的有一定视觉意义的不规则像素块。它利...

3583
来自专栏marsggbo

DeepLearning.ai学习笔记(四)卷积神经网络 -- week3 目标检测

一、目标定位 这一小节视频主要介绍了我们在实现目标定位时标签该如何定义。 ? 上图左下角给出了损失函数的计算公式(这里使用的是平方差) 如图示,加入我们需...

2016
来自专栏自学笔记

Radial Basis Function Network

使用高斯核函数方式把数据维度扩展到无限维度进而得到一条粗壮的分界线。仔细看一下这个分割函数,其实就是一些Gaussian函数的线性组合,y就是增长的方向。 ...

1472
来自专栏有趣的Python和你

sklearn调包侠之线性回归

如图所示,这是一组二维的数据,我们先想想如何通过一条直线较好的拟合这些散点了?直白的说:尽量让拟合的直线穿过这些散点(这些点离拟合直线很近)。

741

扫码关注云+社区