图像特征工程-HOG特征描述符学习笔记

来源

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)/20
16.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)
  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值