Python与C++中梯度方向直方图的实现


Posted in Python onMarch 17, 2022

原文链接:Histogram of Oriented Gradients

(文中的图片均来自翻译原文)

什么是特征描述子

特征描述子一张图片或者一个图片块的一种表示,通过提取有用信息并扔掉多余的信息来简化图像。

通常,特征描述子将一张大小为width×height×3 (通道数)的图片化成一个长度为n的特征向量/数组。以HOG特征为例,输入图像的大小是64×128×3,输出是一个长度为3780的特征向量。

注意一点,HOG特征也可以是其它大小,但这里我使用原文献中使用的大小,这样你可以更容易地通过一个具体的例子来理解这个概念。

上面这些听起来不错,但是对于一张图片的信息,哪些是有用的哪些是冗余的呢?为了定义这个有用信息,我们需要知道它对什么有用。显然,特征向量对于我们看一张图像没什么用。但是,它对图像识别和目标检测这样的任务很有用。将由这些算法生成的特征向量作为支持向量机等分类算法的输入往往可以得到不错的结果。

但是,对于分类任务来说,哪类特征是有用的呢?让我们先用一个例子讨论一下。假设我们想设计一个目标检测器来检测衬衫或者外套上的纽扣。通常纽扣是圆的(可能在图片上会是椭圆)而且一般会有一些孔用于缝纫。你可以在一张纽扣的图片上执行边缘检测,仅仅通过观察边缘图像就可以判断它是不是一个纽扣。在这个例子中,边缘信息是有用的而颜色信息是无用的。此外,特征也需要有区分能力。例如,从某一张图片中提取的一个好的特征应具备区分纽扣和其他圆心物体(如硬币和车轮)的能力。

对于HOG特征描述子,选用梯度方向的分布作为特征。一张图像的梯度(x和y方向的导数)很有用因为在边缘和拐角(强度变化剧烈的区域)处的梯度幅值很大。而且我们知道边缘和拐角比其他平坦的区域包含更多关于物体形状的信息。

如何计算梯度方向直方图

在这一节,我们将详细介绍HOG描述子的计算。为了解释计算的每个步骤,我们使用一个图片块进行分析。

Step 1: 预处理

正像之前提到的那样,HOG特征通过在一张64×128的图片块上计算得到以用于行人检测。当然完整的图片可以是任意的尺寸。通常我们会在图片的不同位置分析多尺度图片块。唯一的要求就是图片块需要有固定的长宽比。在我们的例子中,图片块需要保持1:2 的纵横比。比如:100×200, 128×256或者1000×2000都可以,但101×205就不满足要求。

为了解释这一点,我在下面选用了一张720×475的图片。我们在图上选择一个图片块来计算HOG特征。这个小块是从原图像上裁剪下来的并且纵横比调整为64×128。这样我们就准备好计算这个图片块的HOG特征了。

Python与C++中梯度方向直方图的实现

 

原始文献中Dalal和Triggs也将 γ γ 矫正放在预处理步骤中,但是带来的增益很小因此这里我们忽略这一步。

Step 2: 计算梯度图

为了计算HOG特征,我们需要先计算图像水平和竖直方向的梯度。毕竟我们想要计算梯度直方图。这一步可以很容易通过的核对对原图像进行滤波实现。

Python与C++中梯度方向直方图的实现

 

我们也可以使用OpenCV中的Sobel算子(kernel size设为1)得到相同的结果。

// C++ gradient calculation. 
// Read image
Mat img = imread("bolt.png");
img.convertTo(img, CV_32F, 1/255.0);

// Calculate gradients gx, gy
Mat gx, gy; 
Sobel(img, gx, CV_32F, 1, 0, 1);
Sobel(img, gy, CV_32F, 0, 1, 1);
# Python gradient calculation 

# Read image
im = cv2.imread('bolt.png')
im = np.float32(im) / 255.0

# Calculate gradient 
gx = cv2.Sobel(img, cv2.CV_32F, 1, 0, ksize=1)
gy = cv2.Sobel(img, cv2.CV_32F, 0, 1, ksize=1)

下一步我们可以用下面的公式来计算梯度的幅值和方向。

Python与C++中梯度方向直方图的实现

 

如果你使用OpenCV,可以通过下面的cartToPolar函数实现。

// C++ Calculate gradient magnitude and direction (in degrees)
Mat mag, angle; 
cartToPolar(gx, gy, mag, angle, 1);

在Python中实现如下:

# Python Calculate gradient magnitude and direction ( in degrees ) 
mag, angle = cv2.cartToPolar(gx, gy, angleInDegrees=True)

下图显示了计算得到的梯度:

Python与C++中梯度方向直方图的实现

左:X方向梯度幅值图;中:Y方向梯度幅值图;右:梯度幅值图
 

X方向的梯度凸显竖直的线而Y方向的梯度凸显水平的线。梯度的幅值出现在强度变化剧烈的地方。在强度平坦的区域几乎没有梯度。我故意忽略了梯度方向图,因为在图像上显示梯度方向没有传递太多的信息。

梯度图像移除了很多不必要的信息(比如不变的背景),突出了轮廓信息。也就是说,你仅仅通过看梯度图像还是可以辨认出图像有有一个人。

对于每一个像素,梯度都会有幅值和方向。对于彩色图像,需要分别计算3个通道的梯度(如上图所示)。而该像素点的幅值是这3个通道梯度幅值的最大值,方向是最大梯度幅值对应的角度。

Step 3: 在8×8的cell中计算梯度直方图

在这一步,图像被分成8×8的很多cell,而梯度直方图是在这些cell中计算出来的。

Python与C++中梯度方向直方图的实现

 

我们一会儿将学习直方图,但在那之前,我们先理解一下为什么将图像切分成很多8×8的cell。使用特征描述子来描述图像中的一小块的一个重要原因是它用更为紧凑的表示方法刻画了原图像。一个8×8的图片块包含了8×8×3=192个像素值。而这个图像块的每个像素点梯度信息包含梯度幅值和方向两个值,一共是8×8×2=128个值,这128个值可以通过用包含9个bin的直方图表示成一个一维数组(包含9个值)。这样做不仅可以使图像表示更紧凑,而且在一个图片块中计算直方图可以让这种表示方法对噪声有更强的鲁棒性。单个像素的梯度信息可能包含噪声,而一个8×8的图片块中的直方图让这种表示方法对噪声更不敏感。

但是为什么要用8×8的图片块呢?为什么不是32×32?这是由我们需要寻找的特征比例决定的。HOG特征起初是被用来检测行人的。8×8的cell在一张64×128的行人图片块中的足以捕捉感兴趣的特征(如人脸、头顶等)。

上述梯度直方图本质上是一个包含9个数字的向量(或数组),这9个数字分别对应0°、20°、40°、… 160°。

我们来看一下图片块中的一个8×8 cell的梯度是什么样子。

Python与C++中梯度方向直方图的实现

中:一个RGB cell及其梯度(用箭头表示);右:cell中的梯度大小和方向(数字表示)
 

如果你是一个计算机视觉领域新手,中间的这幅图为你提供了很多信息。它展示了用箭头表示的梯度信息的梯度图——箭头的指向表示了梯度的方向而箭头的长度表示了梯度的大小。需要注意到的一点是箭头的方向指向了图像强度变化的方向,而梯度大小表示了强度的变化有多大。

在右边的图上,我们发现8×8的cell中表示梯度的数字有一点细微的差别——角度实在0°到180°之间的而不是0°到360°之间。这些叫做“无符号梯度”,因为因为一个正负方向的两个梯度由同一个数字表示。换句话说,某一个梯度箭头和它对应的另一个值(加上180°对应的那个值)被当作是同一个梯度。根据经验,无符号梯度被证明比有符号梯度效果更好。一些HOG特征的实现代码会允许你选择是否使用有符号梯度。

下一步就是在这些8×8的cells上创建梯度直方图。直方图包含9个bins分别对应着0°、20°、40°、… 160°。

下图解释了具体的过程。我们在和上面那个图一样的8×8的cells上查看梯度的大小和方向。每个bin是基于梯度方向选出来的,对应的票数(加在当前bin上的值)对应着梯度的大小。我们先来看看用蓝色圆圈出来的像素,它的梯度的角度是80°,大小为2。因此它在第5个bin上加2。下图中用红色圈出来的梯度的角度是10°,大小是4。由于10°是在0°和20°的中间位置, 因此改位置梯度对应票数被一分为二加到相邻的两个bin上。

Python与C++中梯度方向直方图的实现

 

还有一个细节需要注意。如果梯度方向大于160°。此时梯度的角度位于160°和180°之间。我们知道0°和180°是一样的(无符号梯度),因此在下面的例子中,梯度方向为165°的像素按比例将梯度大小分配到0°和160°的bin中。

Python与C++中梯度方向直方图的实现

 

8×8的cell中所有像素处的梯度按照方向将梯度大小累加到9个bin以创建最后的梯度直方图。上图中的cell对应的梯度直方图如下:

Python与C++中梯度方向直方图的实现

 

在我们的表示结果中,y轴对应0°。你可以发现上面的直方图中有大量的权重(梯度大小的投票结果)在0°和180°附近,这从另外一个角度说明了在这个cell中大部分梯度方向要么朝上要么朝下。

Step 4: 16×16 Block标准化

Python与C++中梯度方向直方图的实现

 

在上述步骤中,我们基于图像的梯度创建直方图。一张图片的梯度对整体的光线的光线比较敏感。如果你把图像所有的像素值除以2使整张图像变暗,梯度的大小也会变为原来的一半,从而梯度直方图的值也会将为原来的一半。理想情况下,我们想让特征描述子独立于光线变化。换句话说,我们想要“标准化”这个直方图使它不受光线变化的影响。

在我讲梯度直方图如何标准化之前,我们先来看看一个长度为3的向量如何标准化。

假设我们有一个RGB颜色向量[128, 64, 32]。这个向量的长度是Python与C++中梯度方向直方图的实现

。这也叫做向量的L2范数。将这个向量的所有元素除以向量长度146.64就可以得到一个标准化的向量[0.87, 0.43, 0.22],现在考虑另外一个向量,这个向量的元素是第一个向量的两倍即2×[128, 64, 32]=[256, 128, 64]。你可以自己计算一下它对应的标准化结果,发现结果仍然是[0.87, 0.43, 0.22],这个值和第一个RGB向量的标准化向量相同。你可以发现标准化一个向量移除了这个向量的尺度信息。

现在我们知道如何标准化一个向量,你可能想到当计算HOG特征的时候你可以像之前标准化一个3×1向量一样去标准化9×1的直方图。这的确是个不错的想法,但是更好的做法是标准化一个更大的16×16的block。一个16×16的block包含4个直方图,这4个直方图可以连接成一个36×1的向量,而且这个向量仍然可以像那个3×1的向量一样进行标准化。每次标准化后,整个窗口移动8个像素并再次计算得到一个36×1的标准化向量,就这样一直重复这个过程。

Step 5: 计算HOG特征向量

为了计算整个图片块最终的特征向量所有的36×1的向量被连接成一个大向量。这个大向量的维度是多少大呢?

我们来计算: - 1. 我们有多少个不同位置的16×16的block?一共有 (64-8)/8=7 个水平的位置和 (128-8)/8=15 个竖直的位置,所以总计7×15=105个。 - 2. 每一个16×16的block被表示成一个36×1的向量。因此,当我们把他们连接成一个大向量的时候会得到一个36×105=3780维度的向量。

梯度直方图可视化

一个图像块梯度特征的可视化通常通过在所有8×8的cell里画出对应的标准化的9×1向量(直方图)。如下图所示。你会注意到直方图的主要方向捕捉了人的形状,尤其是在躯干和腿附近。

不幸的是,目前在OpenCV中并没有一个简单的方法方法来可视化HOG特征。

到此这篇关于Python与C++中梯度方向直方图的实现的文章就介绍到这了,更多相关Python 梯度方向直方图内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
python读取二进制mnist实例详解
May 31 Python
python中利用队列asyncio.Queue进行通讯详解
Sep 10 Python
Python 逐行分割大txt文件的方法
Oct 10 Python
分享6个隐藏的python功能
Dec 07 Python
python如何爬取个性签名
Jun 19 Python
编写多线程Python服务器 最适合基础
Sep 14 Python
python实现大转盘抽奖效果
Jan 22 Python
centos7之Python3.74安装教程
Aug 15 Python
将python安装信息加入注册表的示例
Nov 20 Python
python numpy数组中的复制知识解析
Feb 03 Python
python 判断一组数据是否符合正态分布
Sep 23 Python
如何利用python创作字符画
Jun 25 Python
JAVA SpringMVC实现自定义拦截器
Mar 16 #Python
Python Pandas 删除列操作
Mar 16 #Python
Python实现批量自动整理文件
Mar 16 #Python
Pandas-DataFrame知识点汇总
Mar 16 #Python
python 安全地删除列表元素的方法
Mar 16 #Python
python turtle绘制多边形和跳跃和改变速度特效
Python中的turtle画箭头,矩形,五角星
Mar 16 #Python
You might like
PHP 数组遍历顺序理解
2009/09/09 PHP
php Hex RGB颜色值互换的使用
2013/05/10 PHP
php实现常见图片格式的水印和缩略图制作(面向对象)
2016/06/15 PHP
PHP 网站修改默认访问文件的nginx配置
2017/05/27 PHP
PHP不使用内置函数实现字符串转整型的方法示例
2017/07/03 PHP
PHP常见字符串操作函数与用法总结
2019/03/04 PHP
在修改准备发的批量美化select+可修改select时,在非IE下发现了几个问题
2007/01/09 Javascript
setTimeout和setInterval的区别你真的了解吗?
2011/03/31 Javascript
Ext JS 4实现带week(星期)的日期选择控件(实战一)
2013/08/21 Javascript
jQuery中[attribute*=value]选择器用法实例
2014/12/31 Javascript
JQuery实现的图文自动轮播效果插件
2015/06/19 Javascript
jQuery实现的背景动态变化导航菜单效果
2015/08/24 Javascript
JavaScript实现输入框(密码框)出现提示语
2016/01/12 Javascript
动态加载JavaScript文件的两种方法
2016/04/22 Javascript
深入理解ECMAScript的几个关键语句
2016/06/01 Javascript
js实现键盘自动打字效果
2016/12/23 Javascript
SVG动画vivus.js库使用小结(实例代码)
2017/09/14 Javascript
动态创建Angular组件实现popup弹窗功能
2017/09/15 Javascript
vue项目中v-model父子组件通信的实现详解
2017/12/10 Javascript
微信小程序Flex布局用法深入浅出分析
2019/04/25 Javascript
解决Vue动态加载本地图片问题
2019/10/09 Javascript
VUE实现图片验证码功能
2020/11/18 Javascript
原生JS无缝滑动轮播图
2019/10/22 Javascript
Vue简单实现原理详解
2020/05/07 Javascript
[02:35]DOTA2英雄基础教程 狙击手
2014/01/14 DOTA
pytyon 带有重复的全排列
2013/08/13 Python
在IIS服务器上以CGI方式运行Python脚本的教程
2015/04/25 Python
python实现换位加密算法的示例
2018/10/14 Python
解决Django加载静态资源失败的问题
2019/07/28 Python
Python如何爬取qq音乐歌词到本地
2020/06/01 Python
使用npy转image图像并保存的实例
2020/07/01 Python
CSS3实现DIV圆角效果完整代码
2012/10/10 HTML / CSS
CSS3实现文字描边的2种方法(小结)
2020/02/14 HTML / CSS
南非领先的在线旅行社:Travelstart南非
2016/09/04 全球购物
西班牙美妆电商:Perfume’s Club(有中文站)
2018/08/08 全球购物
主题班会开场白
2015/06/01 职场文书