来源
https://www.analyticsvidhya.com/blog/2019/09/feature-engineering-images-introduction-hog-feature-descriptor/
参考
方向梯度直方图(HOG,Histogram of Gradient)学习笔记二 HOG正篇
http://blog.sina.com.cn/s/blog_60e6e3d50101bkpn.html
方向梯度直方图(HOG)
https://www.jianshu.com/p/6f69c751e9e7
这篇文章包含针对cell(单元) block(块)window(窗口) image(图像) 步长(stride)的解析,很清楚!
https://cloud.tencent.com/developer/article/1010111
基于python-opencv的HOG特征提取和使用cv2.HOGDescriptor()
https://blog.csdn.net/qq_36852276/article/details/94293375
总览
1.了解HOG功能描述符背后的内部原理和数学
2.HOG特征描述符在计算机视觉中广泛用于对象检测
3.适用于所有计算机视觉爱好者的有价值的功能工程指南
目录
1.什么是特征描述符?
2.HOG特征描述符简介
3.HOG的计算过程
3.1预处理数据
3.2计算梯度
3.3计算梯度幅值和方向
4.使用梯度和方向创建直方图的方法
5.HOG的计算过程
5.1计算梯度直方图
5.2归一化梯度
5.3完整图像的特征
6.在Python中实现HOG特征描述符
1.什么是特征描述符
对于下面这两幅图像,您可以区分图像中的对象吗?
我们可以清楚地看到,右图有一条狗,而左图有一辆车。现在,让我使此任务稍微复杂一些,确定下图所示的对象:
相比之下。第一幅图像具有很多信息,例如对象的形状,颜色,边缘,背景等;第二幅图像信息则只有形状和边缘,但是我们仍然可以识别两个物体。这是为什么,因为它任然具有识别对象所需的必要信息,而这正是特征描述符的作用:
特征描述符是图像的简化表示,仅包含有关图像的最重要信息,足够我们识别图像。
当前比较流行的特征描述符:
HOG:定向梯度直方图
SIFT:尺度不变特征变换
SURF:加速的强大特征
本文重点介绍HOG特征描述符及其工作方式
2.HOG特征描述符简介
HOG或定向梯度直方图是一种特征描述符,通常用于从图像数据中提取特征。它广泛用于计算机视觉任务中的对象检测。
让我们看一下HOG的一些重要方面,使其与其他特征描述符有所不同:
- HOG描述符着重于对象的结构或形状。现在您可能会问,这与我们从图像提取的边缘特征有何不同?对于边缘特征,我们仅识别像素是否为边缘。HOG能够提供边缘方向,这是通过提取边缘的梯度和方向来完成的。
- 另外,这些取向是在“局部”中计算的。这意味着将完整图像分解为较小的区域,并针对每个区域计算梯度和方向。我们将在接下来的部分中对此进行更详细的讨论。
- 最后,HOG将分别为这些区域中的每个区域生成直方图。使用像素值的梯度赋值和方向创建直方图,因此名称为“定向梯度直方图”
HOG特征描述符针对图像的各个部分中梯度取向的出现进行计数。
3.定向梯度直方图(HOG)的计算过程
考虑下面的图像尺寸(180 x 280)。让我们详细了解如何为该图像创建HOG特征:
3.1预处理数据
我们需要预处理图像并将宽高比降低到1:2。图像大小最好为64 x128。这是因为我们将图像分为8 * 8和16 * 16的方阵以提取特征。指定大小(64 x 128)将使我们所有的计算变得非常简单。实际上,这是原始纸张中使用的确切值。
让我们现在将64 x 128的尺寸作为标准图像尺寸。这是调整大小后的图像:
3.2计算梯度
下一步是计算图像中每个像素的梯度。梯度是x和y方向的微小变化。在这里,我将从图像中提取一个小补丁,并据此计算梯度:
我们将获得此补丁的像素值。假设我们为给定补丁生成了以下像素矩阵(此处显示的矩阵仅用作示例,而并非给定补丁的原始像素值):
我已经突出显示了像素值85。现在,要确定x方向上的梯度(或大小变化),我们需要从右侧的像素值中减去左侧的值。同样,要计算y方向上的梯度,我们将从所选像素上方的像素值中减去下方的像素值。
因此,此像素在x和y方向上的合成梯度为:
- X方向变化 Grad_x = 89 – 78 = 11
- Y方向变化 Grad_y = 68 – 56 = 12
此过程将为我们提供两个新的矩阵-一个在x方向上,另一个在y方向上。这类似于使用大小为1的Sobel内核。当强度发生急剧变化(例如在边缘附近)时,幅度将更高。
我们已经分别计算了x和y方向上的梯度。对图像中的所有像素重复相同的过程。下一步将是使用这些值找到梯度和方向。
3.3计算梯度幅值和方向
使用我们在上一步中计算出的梯度,现在我们将确定每个像素值的大小和方向。对于这一步,我们将使用pythagoras定理。
如下图:
此处的渐变基本上是基础且垂直。因此,对于前面的示例,我们将G_x和G_y分别设置为11和8。让我们应用pythagoras定理来计算总梯度大小:
总体梯 = √[(G_x)^2
+(G_y)^2 ]
总梯度=√[(11)^2 + (12)^2] = 16.27
接下来,计算同一像素的方向(或方向)。我们知道我们可以将角度用反三角函数表示:
tan(φ) = G_y / G_x
因此,角度值将为:
φ = arctan(G_y/G_x)
对于角度值小于0的这样计算
我们可以得到该角度值为45,现在,对于每个像素值,我们都有总梯度幅值和方向。我们需要使用这些梯度幅值和方向来生成直方图。
4.使用梯度和方向创建直方图的方法
直方图是显示一组连续数据的频率分布的曲线图。我们在x轴上表示角度值,在y轴上表示取得对应角度的个数(频数)。
4.1方法1
让我们从生成直方图的最简单方法开始。我们将获取每个像素值,找到像素的方向并更新频率表。
这是高亮像素(85)的处理。由于此像素的角度值是45,我们将针对角度值45添加一个1。(图片数字错误,45处对应为1)
对所有像素值重复相同的过程,最后得到一个频率表,该频率表表示角度以及图像中这些角度的出现。
4.2方法2
此方法与先前的方法相似,不同之处在于此处的箱大小为20。因此,此处要获得的存储桶数为9。
同样,对于每个像素,我们将检查方向,并以9 x 1矩阵的形式存储方向值的频率。绘制此图可得出直方图:(图片数字错误,40处对应为1)
4.3方法3
以上两种方法仅使用角度值来生成直方图,而没有考虑梯度值。这是我们可以生成直方图的另一种方法,而不是使用频率,而是可以使用梯度来填充矩阵中的值。下面是一个示例:(图片数字错误,40处对应为16.27)
行人检测结果
4.4 方法4
让我们对上述方法进行一些小的修改。在这里,我们将像素梯度的贡献添加到像素梯度任一侧的bin上。请记住,对bin值的贡献越大,该bin值就越靠近方向。(图片数字错误,40处对应为16.27)
Magnitude = 16.27
orientation = 45
(45-20)/2016.7 对应到20
(60-45)/2016.7 对应到0
5.HOG的计算过程
5.1计算梯度直方图
在HOG特征描述符中创建的直方图不会针对整个图像生成。取而代之的是,将图像划分为8×8个像元,并为每个像元计算定向梯度的直方图。
这样,我们获得了较小色块的特征(或直方图),这些色块又代表了整个图像。我们当然可以在此处将此值从8 x 8更改为16 x 16或32 x 32。
如果将图像划分为8×8个单元l并生成直方图,则每个单元将获得9 x 1的矩阵。该矩阵是使用我们在上一节中讨论的方法4生成的。
一旦我们为图像中的8×8色块生成了HOG,下一步就是将直方图归一化。
5.2归一化梯度
尽管我们已经为图像的8×8单元创建了HOG特征,但是图像的梯度对整体亮度很敏感。这意味着对于特定的图片,与其他部分相比,图像的某些部分会非常明亮。
我们无法从图像中完全消除这一点。但是我们可以通过采用16×16块对梯度进行归一化来减少这种照明变化。这是一个示例,可以解释如何创建16×16块:
在这里,我们将组合四个8×8单元以创建16×16块。我们已经知道,每个8×8像元都有一个9×1的直方图矩阵。因此,我们将有四个9×1矩阵或一个36×1矩阵。为了规范化此矩阵,我们将这些值中的每一个除以值的平方和的平方根。数学上,对于给定的向量V:
V = [a1,a2,a3,… .a36]
计算平方和的根:
k =√(a1)^2+(a2) ^2+(a3) ^2 + … (a36) ^2
并将向量V中的所有值除以该值k:
结果将是大小为36×1的归一化向量。
5.3完整图像的特征
我们已经为图像的16×16块创建了特征。
现在,我们将所有这些结合起来以获得最终图像的特征。我们首先需要找出单个64×128图像可以得到多少16×16块,
这里步长为8,为一个cell的长度,即我们使用1616的块以步长8在64128的图像中滑动做计算,需要多少个块。
我们将有715个16×16的块。这715个块中的每个块都有一个36×1的向量作为特征。因此,图像的总特征将为71536*1 个特征。
6.在Python中实现HOG特征描述符
6.1借助opencv提取HOG特征
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author:lucky
# albert time:2020/9/29
import cv2 as cv
import numpy as np
#读取图像
img = cv.imread('puppy_image.jpeg')
# cv.imshow('img',img)
#print(img.shape) (300, 175, 3)
#图像灰度转化
imgGray = cv.cvtColor(img,cv.COLOR_RGB2GRAY)
#调整图像大小
resized_img = cv.resize(imgGray,(128,64))
# cv.imshow('resized_image',resized_img)
# print(resized_img.shape)
#1.采用opencv内置函数提取HOG特征
# hog = cv2.HOGDescriptor(winSize,blockSize,blockStride,cellSize,nbins,derivAperture,winSigma,
# histogramNormType,L2HysThreshold,gammaCorrection,nlevels)
# 常用的是winSize, blockSize, blockStride, cellSize, nbins这四个,分别是窗口大小(单位:像素)、block大小(单位:像素)、block步长(单位:像素)、cell大小(单位:像素)、bin的取值
#设置参数
#窗口
winSize = (128,64) #与图片大小一样
#块
blockSize = (16,16)
#块滑动步长
blockStride = (8,8)
#单元
cellSize =(8,8)
#直方图中箱数
nbins = 9
#定义对象hog,同时输入定义的参数,剩下的默认即可
hog = cv.HOGDescriptor(winSize,blockSize,blockStride,cellSize,nbins)
winStride = (0,0)
padding = (0,0)
test_hog = hog.compute(resized_img, winStride, padding).reshape((-1,))
print(test_hog)
print(np.shape(test_hog)) #7*15*4*9
#添加延迟
cv.waitKey(0)
6.2 python手写实现图片HOG特征提取
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author:lucky
# albert time:2020/9/30
import numpy as np
import cv2 as cv
import math
#灰度图提取HOG
#灰度图为单通道图像
def getHOG_1dims(pic_name):
img = cv.imread(pic_name,cv.IMREAD_GRAYSCALE)#直接以灰度图像模式读入图片
img = img/255 #归一化图像,使其像素值由[0 255] -> [0 1]
img = cv.resize(img,(64,128))
#创建梯度方向矩阵
g_img = np.zeros((img.shape[0],img.shape[1],2))
#计算每一个像素点的梯度
for i in range(1,img.shape[0]-1):
for j in range(1,img.shape[1]-1):
gx = img[i+1,j] - img[i-1,j] # 在x方向上梯度幅值
gy = img[i,j+1] - img[i,j-1] #在y方向上梯度幅值
g = (gx**2 + gy**2)**0.5 #总体梯度幅值
#计算梯度方向 角度值
#注意两种特殊情况
#考虑 gx=0,不能直接使用dy/dx = dg
if gx == 0 and gy == 0:
dg = 0
elif gx == 0 and g!= 0:
dg = math.pi/2
else:
dg = math.atan(gy / gx)
#考虑dg为负数的情况,对其转化
if dg < 0:
dg = dg + math.pi
#注意9个bins 梯度方向角度均为[),如0 ->[0,20),20 ->[20,40) 160 ->[160,180)
#所以我们这里把180°放到第一个bins中
if dg == math.pi:
dg = 0
g_img[i,j,0] = g
g_img[i,j,1] = dg
#bins为9
bins = np.zeros((9))
#创建单元cell 8*8
#cell size h
h_size = 8
#cell size w
w_size = 8
#计算图像中cell个数
#x方向上
cell_x = img.shape[0] // 8
# " / " 表示浮点数除法,返回浮点结果;
# " // "表示整数除法, 返回不大于结果的一个最大的整数
#y方向上
cell_y = img.shape[1] // 8
#每个cell生成9*1特征矩阵
#总体生成矩阵个数
cell_feature = np.zeros((cell_x,cell_y,9))
#创建梯度方向直方图
#提取每一个cell大小的特征
for m in range(cell_x):
for n in range(cell_y):
for i in range(h_size * m, h_size * (m + 1)):
for j in range(w_size * n,w_size*(n + 1)):
#bins[g] = sum(dg)
# " // "表示整数除法, 返回不大于结果的一个最大的整数
bins[int(g_img[i,j,1]//(math.pi/9))] += g_img[i,j,0]
cell_feature[m,n] = bins
#创建block
#每个block含有n个cell,每个cell可生成一个9*1特征矩阵
#block size h
block_h = 16
#block size w
block_w = 16
block_stride = 8
# 计算中block个数
# x方向上
block_x = ((img.shape[0] - block_h) // block_stride) + 1
# " / " 表示浮点数除法,返回浮点结果;
# " // "表示整数除法, 返回不大于结果的一个最大的整数
# y方向上
block_y = ((img.shape[1] - block_w) // block_stride) + 1
#每个block含有4个cell,每个cell可生成一个9*1特征矩阵
#这里只是将每个block中的cell的特征矩阵叠加
block_feature = np.zeros((block_x,block_y,9))
for p in range(block_x):
for q in range(block_y):
for i in range(p,p+1):
for j in range(q,q+1):
block_feature[p,q] += cell_feature[i,j]
#归一化梯度
for i in range(block_x):
for j in range(block_y):
k = (np.linalg.norm(block_feature[i,j])**2 + 0.000001)**0.5
block_feature[i][j] = block_feature[i][j]/k
#print(block_feature.shape)
feature = np.resize(block_feature,(block_x*block_y,9))
return feature
# print(feature.shape)
pic_name = 'puppy_image.jpeg'
feature = getHOG_1dims(pic_name)