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实现去除代码前行号的方法
Mar 10 Python
Python与shell的3种交互方式介绍
Apr 11 Python
Python2.X/Python3.X中urllib库区别讲解
Dec 19 Python
Python+matplotlib+numpy实现在不同平面的二维条形图
Jan 02 Python
python调用opencv实现猫脸检测功能
Jan 15 Python
python程序变成软件的实操方法
Jun 24 Python
python子线程退出及线程退出控制的代码
Oct 16 Python
python opencv圆、椭圆与任意多边形的绘制实例详解
Feb 06 Python
使用python实现下载我们想听的歌曲,速度超快
Jul 09 Python
15款Python编辑器的优缺点,别再问我“选什么编辑器”啦
Oct 19 Python
python读取excel数据绘制简单曲线图的完整步骤记录
Oct 30 Python
Python os库常用操作代码汇总
Nov 03 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
如何提高MYSQL数据库的查询统计速度 select 索引应用
2007/04/11 PHP
PHP数组和explode函数示例总结
2015/05/08 PHP
laravel model模型处理之修改查询或修改字段时的类型格式案例
2019/10/17 PHP
重定向实现代码
2006/11/20 Javascript
JavaScript简单实现网页回到顶部功能
2013/11/12 Javascript
JavaScript原型链示例分享
2014/01/26 Javascript
js判断一个字符串是否包含一个子串的方法
2015/01/26 Javascript
js获取内联样式的方法
2015/01/27 Javascript
JS继承之借用构造函数继承和组合继承
2016/09/07 Javascript
基于Vue2的移动端开发环境搭建详解
2016/11/03 Javascript
详解vue 实例方法和数据
2017/10/23 Javascript
JavaScript函数apply()和call()用法与异同分析
2018/08/10 Javascript
Vue.js上传图片到阿里云OSS存储的方法示例
2018/12/13 Javascript
vue组件间的参数传递实例详解
2019/04/26 Javascript
layer ui插件显示tips时,修改字体颜色的实现方法
2019/09/11 Javascript
vue封装可复用组件confirm,并绑定在vue原型上的示例
2019/10/31 Javascript
JavaScript如何处理移动端拍摄图片旋转问题
2019/11/16 Javascript
python模块restful使用方法实例
2013/12/10 Python
python解决网站的反爬虫策略总结
2016/10/26 Python
python 计算一个字符串中所有数字的和实例
2019/06/11 Python
python挖矿算力测试程序详解
2019/07/03 Python
django做form表单的数据验证过程详解
2019/07/26 Python
Python银行系统实战源码
2019/10/25 Python
django创建css文件夹的具体方法
2020/07/31 Python
HTML5 Web Database 数据库的SQL语句的使用方法
2012/12/09 HTML / CSS
美国杂志订阅折扣与优惠网站:Magazines.com
2016/08/31 全球购物
英国精品买手店:Browns Fashion
2016/09/29 全球购物
俄罗斯电子产品在线商店:UltraTrade
2020/01/30 全球购物
制冷与电控专业应届生求职信
2013/11/11 职场文书
学校食堂采购员岗位职责
2013/12/05 职场文书
高考升学宴答谢词
2015/01/20 职场文书
2015仓库保管员年终工作总结
2015/05/13 职场文书
幼儿园六一儿童节主持词
2015/06/30 职场文书
2020年基层司法所建设情况调研报告
2019/11/30 职场文书
健身房被搭讪?用python写了个小米计时器助人为乐
2021/06/08 Python
Python实现批量将文件复制到新的目录中再修改名称
2022/04/12 Python