图像特征之HOG特征(附python代码)

  • 2020 年 6 月 26 日
  • AI

嘻!long time no see…最近主要再忙课程论文(期末了 害)论文有看但也没有更新,一下就都月底了。接下来打算把图像课程论文的一些东西搬运过来这边(又懒了我)


图像特征介绍

为了更好地对图像进行形式化的描述和表达,我们引入了图像特征,它是可以用来描述图像或图像区域所对应的景物的性质特点。图像特征可以是人类视觉能够识别的自然特征,也可以是人为所定义的某些特征。良好的图像特征对计算机视觉以及模式识别等领域起到了至关重要的作用。图像特征的分类多种多样,从直观视觉上,可以大致分为颜色特征、纹理特征、形状特征和空间关系特征
1)颜色特征:它是一种全局特征,描述了图像或图像区域所对应的景物的颜色性质特点。一般颜色特征是基于像素点的特征,此时所有属于图像或图像区域的像素都有各自的贡献。常用的颜色特征提取方法有颜色直方图、颜色矩、颜色集、颜色一致性向量等。其中,颜色直方图是最常用的表达颜色特征的方法,它可以不受图像旋转和平移变化的影响,但在表达出颜色空间分布的信息上有所欠缺。
2)纹理特征:它同颜色特征一样为全局特征,用来描述的是表面性质。与颜色特征不同,纹理特征不是基于像素点的特征,而是在拥有多个像素点的图像区域中进行统计计算。在检索具有粗细、疏密等方面具有较大差别的纹理图像时,纹理特征可以发挥较大作用。常用的纹理特征描述方法包括几何法、统计法、模型法以及信号处理法等。
3)形状特征:此特征是基于内容图像检索中最底层特征,却是人类视觉直接相关和显著的特征。形状的表示以及过程中描述子的使用分析对于基于形状特征的图像检索至关重要。按照传统的分类方法,形状特征的表示方法可以分为基于轮廓方法和基于区域的方法,前者主要是针对物体的外边界,而后者关系到整个形状的区域。
4)空间关系特征:空间关系,是指图像中分割出来的多个目标之间的相互的空间位置或相对方向关系。按照空间位置信息可以分为两类:相对空间位置信息和绝对空间位置信息。空间关系特征的使用可加强对图像内容的描述区分能力。图像空间关系特征的提取可分为两种:一种是首先对图像进行自动分割,划分出图像中所包含的对象或颜色区域,然后根据这些区域提取图像特征,并建立索引;另一种方法则简单地将图像均匀地划分为若干规则子块,然后对每个图像子块提取特征,并建立索引。
当然除了上述从直观视觉上对图像特征进行分类,还可以从特征的提取方式、是否为全局特征等角度对图像特征进行划分。

HOG特征

2005 年,Dalal等人在研究行人检测的时候,首次提出HOG特征来处理行人检测问题,并且取得了相当好的检测效果。它是目前计算机视觉、模式识别领域常用的一种描述图像局部纹理的特征。HOG特征核心思想即图像中局部对象的外观或形状,可以通过梯度方向和强度分布来描述。此特征可以较好地描述物体的形状,成为刻画形状的常用特征之一。
HOG特征的本质是梯度的统计信息,而这些梯度主要存在于边缘的地方。HOG特征提取算法的过程主要分为四大步骤:
第一,输入图像,对图像进行颜色空间的归一化;
第二,计算图像的梯度;
第三,为每个cell单元构建梯度方向直方图;
第四,把cell单元组合成block,块内归一化梯度直方图。
(如图所示即为完整过程)。

每一步骤的具体实现如下:
1)颜色空间的归一化:为了减少光照因素的影响,首先需要将整个图像进行归一化。由于在图像纹理强度中,局部的表层曝光贡献的比重较大,所以进行Gamma归一化处理,可以有效地降低图像局部阴影和光照的变化,还可以抑制噪音的干扰。这边的Gamma归一化处理可以是对每个颜色通道分别计算平方根或者对每个颜色通道分别求log。
2)计算图像的梯度:计算图像的梯度主要是为了通过梯度信息来描述图像中物体的边缘、轮廓、形状等纹理信息。在这个步骤中,首先需要计算图像的横纵坐标方向的梯度,然后根据计算出的图像横坐标和纵坐标方向梯度算出每个像素位置的梯度方向值。假设一张图像中像素点用(x,y)表示,则该像素点的水平梯度和垂直梯度分别为Gx(x,y)和Gy(x,y),计算如公式如下:

其中H(x,y)表示输入的图像在像素点(x,y)的像素值。根据得到的像素点水平梯度和垂直梯度就可以得到图像中素点(x,y)处的梯度幅值G(x,y)和梯度方向α(x,y)如下所示:

在这边最常用的方法是:首先用[-1,0,1]梯度算子对原图像做卷积运算,得到x方向(水平方向)的梯度分量,然后用[1,0,-1]T梯度算子对原图像做卷积运算,得到y方向(竖直方向)的梯度分。然后再用以上公式计算该像素点的梯度大小和方向。
3)为每个cell单元构建梯度方向直方图:此步骤主要是为局部图像区域提供一个编码,同时能够保持对图像中人体对象的姿势和外观的弱敏感性。首先把图像分割成许多个cell单元格,然后把所有cell单元的全部像素通过梯度方向在直方图中执行加权投影的操作,则可获得每个cell单元格的梯度方向直方图。
4)把cell单元组合成block,块内归一化梯度直方图。 由于局部光照的变化以及前景-背景对比度的变化,使得梯度强度的变化范围非常大。所以必须归一化梯度强度,归一化梯度强度可以实现对边缘、光照和阴影的压缩。最后再合并每个块的特征描述子就能够获得这张图像的 HOG 特征描述子,也就是 HOG 特征向量。

小结
与其他的特征描述方法相比,HOG特征是在图像的局部方格单元上操作的,因此它对图像几何的和光学的形变都能保持很好的不变性。故,HOG特征凭借其优势得到了广泛应用,同时它还与其他先进算法相结合应用到了其他的领域,例如HOG特征结合SVM分类器的方法<已经被广泛应用于图像识别,尤其在行人检测中获得了巨大的成功。

代码实现

import cv2
import numpy as np
import math
import matplotlib.pyplot as plt


class Hog_descriptor():
    def __init__(self, img, cell_size=16, bin_size=8):
        self.img = img
        self.img = np.sqrt(img / np.max(img))
        self.img = img * 255
        self.cell_size = cell_size
        self.bin_size = bin_size
        self.angle_unit = 360 / self.bin_size
        assert type(self.bin_size) == int, "bin_size should be integer,"
        assert type(self.cell_size) == int, "cell_size should be integer,"
        assert type(self.angle_unit) == int, "bin_size should be divisible by 360"

    def extract(self):
        height, width = self.img.shape
        gradient_magnitude, gradient_angle = self.global_gradient()
        gradient_magnitude = abs(gradient_magnitude)
        cell_gradient_vector = np.zeros((height / self.cell_size, width / self.cell_size, self.bin_size))
        for i in range(cell_gradient_vector.shape[0]):
            for j in range(cell_gradient_vector.shape[1]):
                cell_magnitude = gradient_magnitude[i * self.cell_size:(i + 1) * self.cell_size,
                                 j * self.cell_size:(j + 1) * self.cell_size]
                cell_angle = gradient_angle[i * self.cell_size:(i + 1) * self.cell_size,
                             j * self.cell_size:(j + 1) * self.cell_size]
                cell_gradient_vector[i][j] = self.cell_gradient(cell_magnitude, cell_angle)

        hog_image = self.render_gradient(np.zeros([height, width]), cell_gradient_vector)
        hog_vector = []
        for i in range(cell_gradient_vector.shape[0] - 1):
            for j in range(cell_gradient_vector.shape[1] - 1):
                block_vector = []
                block_vector.extend(cell_gradient_vector[i][j])
                block_vector.extend(cell_gradient_vector[i][j + 1])
                block_vector.extend(cell_gradient_vector[i + 1][j])
                block_vector.extend(cell_gradient_vector[i + 1][j + 1])
                mag = lambda vector: math.sqrt(sum(i ** 2 for i in vector))
                magnitude = mag(block_vector)
                if magnitude != 0:
                    normalize = lambda block_vector, magnitude: [element / magnitude for element in block_vector]
                    block_vector = normalize(block_vector, magnitude)
                hog_vector.append(block_vector)
        return hog_vector, hog_image

    def global_gradient(self):
        gradient_values_x = cv2.Sobel(self.img, cv2.CV_64F, 1, 0, ksize=5)
        gradient_values_y = cv2.Sobel(self.img, cv2.CV_64F, 0, 1, ksize=5)
        gradient_magnitude = cv2.addWeighted(gradient_values_x, 0.5, gradient_values_y, 0.5, 0)
        gradient_angle = cv2.phase(gradient_values_x, gradient_values_y, angleInDegrees=True)
        return gradient_magnitude, gradient_angle

    def cell_gradient(self, cell_magnitude, cell_angle):
        orientation_centers = [0] * self.bin_size
        for i in range(cell_magnitude.shape[0]):
            for j in range(cell_magnitude.shape[1]):
                gradient_strength = cell_magnitude[i][j]
                gradient_angle = cell_angle[i][j]
                min_angle, max_angle, mod = self.get_closest_bins(gradient_angle)
                orientation_centers[min_angle] += (gradient_strength * (1 - (mod / self.angle_unit)))
                orientation_centers[max_angle] += (gradient_strength * (mod / self.angle_unit))
        return orientation_centers

    def get_closest_bins(self, gradient_angle):
        idx = int(gradient_angle / self.angle_unit)
        mod = gradient_angle % self.angle_unit
        return idx, (idx + 1) % self.bin_size, mod

    def render_gradient(self, image, cell_gradient):
        cell_width = self.cell_size / 2
        max_mag = np.array(cell_gradient).max()
        for x in range(cell_gradient.shape[0]):
            for y in range(cell_gradient.shape[1]):
                cell_grad = cell_gradient[x][y]
                cell_grad /= max_mag
                angle = 0
                angle_gap = self.angle_unit
                for magnitude in cell_grad:
                    angle_radian = math.radians(angle)
                    x1 = int(x * self.cell_size + magnitude * cell_width * math.cos(angle_radian))
                    y1 = int(y * self.cell_size + magnitude * cell_width * math.sin(angle_radian))
                    x2 = int(x * self.cell_size - magnitude * cell_width * math.cos(angle_radian))
                    y2 = int(y * self.cell_size - magnitude * cell_width * math.sin(angle_radian))
                    cv2.line(image, (y1, x1), (y2, x2), int(255 * math.sqrt(magnitude)))
                    angle += angle_gap
        return image

img = cv2.imread('person_037.png', cv2.IMREAD_GRAYSCALE)
hog = Hog_descriptor(img, cell_size=8, bin_size=8)
vector, image = hog.extract()
print np.array(vector).shape
plt.imshow(image, cmap=plt.cm.gray)
plt.show()

参考:80行Python实现-HOG梯度特征提取


Ending~端午安康