1.特征描述符

特征描述符是图像或图像一部分的表示,它通过提取有用信息并丢弃不相关信息来简化图像。

通常,特征描述符将w*h*3的图像转化为一个长度为n的特征向量,如HOG特征描述符中,输入64*28*3的图像,输出特征向量长度为3780

注意:HOG描述符能够计算其他size图片的特征向量。此文中只是给出一个size固定具体例子...

什么是有用的信息呢?

显然,特征向量对于查看图片是无用的,但对于图像识别和目标检测等任务是相当有用的。可将一些算法生成的特征向量输入到图像分类算法中,如SVM支持向量机中,产生分类结果。

假设想创建一个目标检测器,检测衬衫和外套上的纽扣,一个纽扣呈圆形,在图像上看起来也可能像椭圆形,且通常会有一些小孔。可以对纽扣图像执行一个边缘检测器,简单地查看边缘图像辨别它是否是个按钮。此时,边缘信息是有用的,颜色信息是没用的。此外,这些特征还需要具有辨别力,如好的特征应该能够区分按钮和其他圆形物体之间的区别,比如硬币和汽车轮胎。

在HOG特征描述符中,方向梯度的分布直方图被当作特征。图像的梯度是有用的(对x和y求导),因为在边缘处和角点处(像素强度突变)的梯度大小是非常大的,并且我们都知道边角比平面区域包含更多关于物体形状的信息。

Hog特征提取的是纹理特征,颜色信息不起作用,所以首先将彩色图转为灰度图

2.如何计算HOG(Histogram of Oriented Gradients)

(1)预处理

     一张图像可以是任意尺寸,通常,在多个图像位置对多个尺度的图像patch进行分析,唯一的限制只是分析的patches要有一个固定的宽高比例,此案例中--patches维持一个1:2的ratio,如patches可以为100*200、128*256、1000*2000等,但不能是101*205

python 怎么判断一张图片在另外一张图片中_直方图

(2)计算梯度图像

     要计算HOG描述符,首先需要计算水平方向和垂直方向的梯度;毕竟想要得到梯度的直方图。梯度计算通过内核与图像的卷积实现,可使用OpenCV中的Sobel操作实现梯度计算,kernel size = 1 or3 or 5

python 怎么判断一张图片在另外一张图片中_特征向量_02

# 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)

接下来,使用如下公式找到梯度的大小和方向:

使用OpenCV的话,可由从cv2.cartToPolar()函数计算

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

梯度图如下图中所示

python 怎么判断一张图片在另外一张图片中_描述符_03

   Left : Absolute value of x-gradient. Center : Absolute value of y-gradient. Right : Magnitude of gradient.

注意:X方向的梯度触发的是垂直方向的线,同理Y方向。梯度大小代表此处强度发生剧烈变化,当区域是平滑时,不会触发。

梯度图消除了很多不必要的信息(如不变化的彩色背景),但突出了轮廓,换句话说,查看梯度图像(gradien image),仍可以容易地看到有一个人在图片中。

在每个像素处,梯度都有大小和方向。对于彩色图像,计算三个通道的梯度。每个像素处的梯度大小分别是相应三个通道中梯度大小的最大值,方向是与梯度值大小对应的那个角度。

import cv2
import numpy as np
from matplotlib import pyplot as plt

"""
HOG 方向梯度直方图进行边缘检测
"""

# 以灰度格式读取图像,检测边缘时靠强度(亮度)变化,与颜色不太相关
# img_yuan_gray = cv2.imread('image/142429199607253863.jpg', cv2.IMREAD_GRAYSCALE)
img_yuan_gray = cv2.imread('image/7E_BIR9IL{}5%0RKIK_$F62.png', cv2.IMREAD_GRAYSCALE)
print(img_yuan_gray.dtype)

'''
输出数据类型为cv2.CV_8U或np.uint8。以黑白线过渡为正斜率(为正值),
以白黑线过渡为负斜率(它有负值),当把数据转换成np.uint8,所有负斜率为零。
如果想检测由明到暗及由暗到明,更好的选择是将输出数据类型保持为一些更高的形式,
比如cv2.CV_16S、 cv2.CV_64F等,取其绝对值,然后转换回cv2.CV_8U。
'''
# 使用sobel算子分别计算x和y方向的梯度,平滑图像(Sobel算子是高斯平滑加微分的联合运算)
gx_sobel = cv2.Sobel(img_yuan_gray, cv2.CV_64F, 1, 0, ksize=3)
gy_sobel = cv2.Sobel(img_yuan_gray, cv2.CV_64F, 0, 1, ksize=3)
abs_gx_sobel = np.absolute(gx_sobel)
gx_8u_sobel = np.uint8(abs_gx_sobel)
abs_gy_sobel = np.absolute(gy_sobel)
gy_8u_sobel = np.uint8(abs_gy_sobel)

'''
找到梯度
'''
magnitude, angle = cv2.cartToPolar(gx_sobel, gy_sobel, 1)


# g_sobel = np.sqrt(np.sqrt(gx_8u_sobel)+np.square(gy_8u_sobel))
# g_angle = np.arctan(gy_8u_sobel/gx_8u_sobel)

plt.subplot(2,2,1)
plt.imshow(img_yuan_gray)
plt.subplot(2,2,2)
plt.imshow(gx_8u_sobel)
plt.subplot(2,2,3)
plt.imshow(gy_8u_sobel)
plt.subplot(2,2,4)
plt.imshow(magnitude)
plt.show()

结果图如下所示:

python 怎么判断一张图片在另外一张图片中_特征向量_04

(3)计算8*8cell的梯度直方图

此步骤中,图像被分割成8*8的单元格,为每个8*8单元格计算梯度直方图(a image patch is divide into 8*8 cells),为什么要将图片分割为8*8单元格呢?

    1.使用特征描述符描述一块图片patch的重要原因之一是提供一个压缩的表示,一个8*8图像patch包含8*8*3 = 192个像素值,而这个patch的每个像素的梯度只包含2个值(梯度方向和大小),加起来一共8*8*2 = 128. 这部分结尾将看如何使用9-bin的直方图表示这128个数字的,该直方图可以存储为9个数字的数组。不光表示更紧凑,计算一个patch上的直方图也使得这种表示对噪声更加鲁棒。单个梯度可能含有噪声,但8*8patch的直方图使得表示对噪声不那么敏感。

     为什么要选择8*8patch? 而不是32*32呢?这个选择是由期望的特征规模决定的。

     直方图本质上是一个由 9个bin 组成的向量(或数组),分别对应角度0、20、40、60……160。

     先看一个图像中一块8*8的patch,其梯度看起来如何!

python 怎么判断一张图片在另外一张图片中_特征向量_05

        Center : The RGB patch and gradients represented using arrows.

        Right : The gradients in the same patch represented as numbers

        中间图像块用箭头覆盖的部分显示其梯度信息,箭头表示梯度的方向,箭头的长度表示梯度的大小。注意:箭头方向是指向强度变化的方向的,而大小显示了这种差异有多大。

       在右侧,看到的原始数字代表了8×8细胞的梯度,只有一个微小的差异——角度在0°到180°之间,而不是0°360°。这些称为“无符号”梯度,梯度和它反方向是由相同的数字表示的。换句话说,梯度箭头和它的180度对边是相同的。但是,为什么不用0 - 360度呢?实验表明,无符号梯度比有符号梯度更适合于行人检测。

接下来就要在这些8*8patch中创建梯度直方图了,下图阐释了直方图的形成过程,

      根据方向选择一个bin,并根据大小选择投票(值进入bin的)。首先关注下图中蓝色圈的像素,它的梯度向量为80°的角度且大小为2,所以将2加入到第5个bin中。红色圈的像素梯度有一个10°的角度,大小为4,10°介于1与20的中间线,所以投票时将该像素平均分成两部分分别投到两个Bin中。

     

python 怎么判断一张图片在另外一张图片中_直方图_06

有个细节要注意:如果角度是大于160°的,即在160°与180°之间,我们知道0°和180°相等,所以梯度165°的像素按比例贡献给第0Bin和第160Bin. (问题:比例是如何形成的,啥样的比例?)

python 怎么判断一张图片在另外一张图片中_描述符_07

 

将8×8单元格中所有像素的贡献值相加,得到9-bin直方图,对于上述提到的Patch,其梯度直方图如下所示:

python 怎么判断一张图片在另外一张图片中_特征向量_08

(4)16*16Block归一化

      在前面的步骤中,我们创建了一个基于图像梯度的直方图。图像的梯度对整体光照很敏感。如果将所有像素值除以2使图像变暗,梯度大小将变化一半,因此直方图值将变化一半。理想情况下,希望我们的描述符不受光照变化影响。换句话说,我们希望将直方图“归一化”,这样它们就不会受到光照变化的影响。

那么如何对直方图进行归一化呢?先看一下一个长度为3的向量是如何被归一化的吧!

      假设有一个RGB颜色向量[128,64,32],这个向量的长度是\sqrt{128^2 + 64^2 + 32^2} = 146.64,这也叫做向量的L2范数。该向量的每个元素除以146.64得到一个标准化的向量[0.87,0.43,0.22]。现在考虑另一个向量,其中元素是第一个向量的两倍[128,64,32]=[256,128,64],标准化[256,128,64]将得到[0.87,0.43,0.22],这与原始RGB向量的标准化版本相同。可以看到,对一个向量进行标准化可以消除比例影响。

      既然知道了如何标准化一个向量,可能会想,在计算HOG时,可以简单地标准化9×1直方图,就像标准化上面的3×1向量一样。但更好的idea是在一个更大的16×16块上标准化。一个16×16的块有4个直方图,它们可以连接起来形成一个36×1的元素向量,它可以像一个3×1的向量一样被标准化。然后将窗口移动8个像素(参见动画),并在此窗口上计算一个标准化的36×1向量,然后重复该过程。

(5)计算HOG特征向量

为了计算整个图像patch的最终特征向量,36*1的向量被连接成一个巨大的向量。这个向量的大小大概是多少~

     1. 我们有多少个16*16的position呢?水平有7个和垂直15个,总共7 x 15 = 105个位置

     2.每个16×16patch由一个36×1的向量表示,当把它们连接成一个gaint向量时我们得到一个36×105 = 3780维的向量。

可视化方向梯度直方图