HOG特征使用OPENCV提取梯度幅度和方向的实现,主要是可视化理解

HOG特征提取全Numpy实现

HOG步骤详解

本项目使用的代码原地址,我改编的地址

代码详解:

创建一个HOG的类:

  • __init__是图片,cell,划分角度等基础信息(基础)
  • extract 是提取cell中的HOG特征,然后可视化,在提取block中的HOG特征(主要流程)
  • global_gradient 用于得到图片梯度幅度和梯度方向(在统计HOG特征前面需要得到的信息)
  • get_closest_bins 用于在对角度进行划分的时候,找到这个角度夹杂那两个角度之间。(在统计HOG的时候,用于对梯度幅度的分配)
  • render_gradient 用于可视化统计的变量。
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
        #gamma校准,0.5的n次方
        self.img = np.sqrt(img / float(np.max(img)))
        #从0-1还原图片为0-255
        self.img = self.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统计一次,所以整个图缩小了cell倍。
        cell_gradient_vector = np.zeros((int(height / self.cell_size), int(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统计量上二维的点,对应会原图的位置,然后取出原图的梯度幅度和方向
                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 = []
        #2*2个cell是一个block,滑动形式和卷积一样,所以遍历需要减一
        for i in range(cell_gradient_vector.shape[0] - 1):
            for j in range(cell_gradient_vector.shape[1] - 1):
                #得到一个block
                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)
        #这个是用0.5横梯度+0.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
    #cell统计量
    def cell_gradient(self, cell_magnitude, cell_angle):
        #创建统计量计数
        orientation_centers = [0] * self.bin_size
        #遍历cell里面的参数
        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):
        #这部分最难理解的是维度的划分,因为360和0是接在一起的,所以会有一个统计量维度是多出来的,
        #可以把下列print取消注释,就可以清晰的了解这个过程
        #print('\n\n')B
        #print('寻找最近的索引值,当前梯度角度是{},划分的角度范围是{}'.format(gradient_angle,
        #                                                                   self.angle_unit))
        #当前角度除以角度范围,得到对应索引,是如果角度加载两个范围之间,idx是前一个数值
        idx = int(gradient_angle / self.angle_unit)
        #print('理他最左圆整的索引值是{},一共拥有的索引值是{}'.format(idx,self.bin_size))
        #mod是对应的余数,就是按角度划分,多出来的部分
        mod = gradient_angle % self.angle_unit
        #如果得到前一个数值就已经是是最大的索引了的情况,只有在角度等于360度的时候才会遇上,因为0°和360°重叠
        if idx == self.bin_size:
            #print('遇到特殊情况360°,最近的索引值是{},对应的角度是{},第二进的索引值是{},对应角度是{}'.format(idx-1,(idx-1)*self.angle_unit,
            #                                                            (idx)%self.bin_size,(idx)%self.bin_size*self.angle_unit))
            return idx - 1, (idx) % self.bin_size, mod
        #(idx + 1) % self.bin_size是防止超界。
        #print('最近的索引值是{},对应的角度是{},第二进的索引值是{},对应角度是{}'.format(idx,idx*self.angle_unit,
        #                                                               (idx+1)%self.bin_size,(idx+1)%self.bin_size*self.angle_unit))
        return idx, (idx + 1) % self.bin_size, mod
 
    def render_gradient(self, image, cell_gradient):
        #这里的image是图片大小
        #这里的cell_gradient只有所有cell中的统计量
        #得到cell的宽度
        cell_width = self.cell_size / 2
        #得到cell中最大的梯度幅度
        max_mag = np.array(cell_gradient).max()
        #遍历所有cell
        for x in range(cell_gradient.shape[0]):
            for y in range(cell_gradient.shape[1]):
                #取出统计量,统计量维度是16维
                cell_grad = cell_gradient[x][y]
                #归一化,最大为1 
                cell_grad /= max_mag
                angle = 0
                angle_gap = self.angle_unit
                #遍历一个cell中的16维统计量
                #改成8维度
                cell_grad = [(cell_grad[idx]+cell_grad[idx+int(len(cell_grad)/2)])/2
                             for idx in range(int(len(cell_grad)/2))]
                
                for magnitude in cell_grad:
                    #print(angle)
                    angle_radian = math.radians(angle)
                    xcent = int((x)* self.cell_size + cell_width)
                    ycent = int ((y)* self.cell_size + cell_width)
                    x1 = int(xcent + (cell_width-1)*math.cos(angle_radian))
                    y1 = int(ycent + (cell_width-1) * math.sin(angle_radian))
                    x2 = int(xcent - (cell_width-1) * math.cos(angle_radian))
                    y2 = int(ycent - (cell_width-1) * math.sin(angle_radian))
                    cv2.line(image, (y1, x1), (y2, x2), int(255 * (magnitude)))
                    angle += angle_gap
        return image

调用用HOG类:

from skimage import  exposure
img_ori = cv2.imread('HOG1.jpg')
img_ori = img_ori[:,:,::-1]
img = cv2.imread('HOG1.jpg', cv2.IMREAD_GRAYSCALE)
hog = Hog_descriptor(img, cell_size=16, bin_size=16)
vector, image = hog.extract()
plt.figure(figsize=(40,40))
plt.subplot(2,1,1)
plt.imshow(img_ori)
plt.subplot(2,1,2)
hog_image_rescaled = exposure.rescale_intensity(image, in_range=(0, 50))
plt.imshow(hog_image_rescaled, cmap='gray')
plt.show()

结果如下:

这里我对原代码的划线部分进行改动,效果如下,我将同一直线的相反方向的幅度相加用颜色表示幅度强度:

HOG特征使用OPENCV提取梯度幅度和方向的实现,主要是可视化理解_第1张图片

HOG特征使用OPENCV提取梯度幅度和方向的实现,主要是可视化理解_第2张图片

 

skimage的第三方软件包的可视化结果如下:效果非常好,意义一眼就了然于心,比我做的好多了,可能是方向选取的问题才造成这样的结果:

import matplotlib.pyplot as plt
from skimage.feature import hog
from skimage import data, exposure
image = cv2.imread('HOG1.jpg')
image = image[:,:,::-1]
fd, hog_image = hog(image, orientations=8, pixels_per_cell=(16, 16),
                    cells_per_block=(1, 1), visualize=True, multichannel=True)
print(image.shape,hog_image.shape)
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(50, 50), sharex=True, sharey=True)
ax1.axis('on')
ax1.imshow(image, cmap=plt.cm.gray)
ax1.set_title('Input image')
# Rescale histogram for better display
hog_image_rescaled = exposure.rescale_intensity(hog_image, in_range=(0, 10))
ax2.axis('off')
ax2.imshow(hog_image_rescaled, cmap=plt.cm.gray)
ax2.set_title('Histogram of Oriented Gradients')
plt.show()

结果如下: 

HOG特征使用OPENCV提取梯度幅度和方向的实现,主要是可视化理解_第3张图片

你可能感兴趣的:(HOG特征使用OPENCV提取梯度幅度和方向的实现,主要是可视化理解)