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()
结果如下:
这里我对原代码的划线部分进行改动,效果如下,我将同一直线的相反方向的幅度相加用颜色表示幅度强度:
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()
结果如下: