一.HOG算法原理
方向梯度直方图(Histogram of Oriented Gradient, HOG)特征是一种在计算机视觉和图像处理中用来进行物体检测的特征描述子。HOG特征通过计算和统计图像局部区域的梯度方向直方图来构成特征。
1、主要思想:
在一副图像中,局部目标的表象和形状能够被梯度或边缘的方向密度分布很好地描述。其本质为:梯度的统计信息,而梯度主要存在于边缘的地方。
Hog特征结合SVM分类器已经被广泛应用于图像识别中,尤其在行人检测中获得了极大的成功。
2、实现方法:
首先将图像分成小的连通区域,这些连通区域被叫做细胞单元。然后采集细胞单元中各像素点的梯度的或边缘的方向直方图。最后把这些直方图组合起来,就可以构成特征描述符。
3、性能提高:
将这些局部直方图在图像的更大的范围内(叫做区间)进行对比度归一化,可以提高该算法的性能,所采用的方法是:先计算各直方图在这个区间中的密度,然后根据这个密度对区间中的各个细胞单元做归一化。通过这个归一化后,能对光照变化和阴影获得更好的效果。
二.HOG算法代码
"""
下面的程序中,cell大小为8*8。
因此128/8 = 16, 64/8 = 8
一个128*64的图片,最终划分成了 16*8个cell, 特征维度为 16*8*9
再计算归一化后的block的特征
"""
import cv2
import numpy as np
import math
import matplotlib.pyplot as plt
class Hog_descriptor():
"""
HOG描述符的实现
"""
def __init__(self, img, cell_size=8, bin_size=9):
"""
构造函数
默认参数,一个block由2x2个cell组成,步长为1个cell大小
args:
img:输入图像(更准确的说是检测窗口),这里要求为灰度图像 对于行人检测图像大小一般为128x64 即是输入图像上的一小块裁切区域
cell_size:细胞单元的大小 如8,表示8x8个像素
bin_size:直方图的bin个数
"""
self.img = img
'''
采用Gamma校正法对输入图像进行颜色空间的标准化(归一化),目的是调节图像的对比度,降低图像局部
的阴影和光照变化所造成的影响,同时可以抑制噪音。采用的gamma值为0.5。 f(I)=I^γ
'''
self.img = np.sqrt(img * 1.0 / float(np.max(img)))
self.img = self.img * 255
# print('img',self.img.dtype) #float64
# 参数初始化
self.cell_size = cell_size
self.bin_size = bin_size
self.angle_unit = 180 / self.bin_size # 这里采用180°
assert type(self.bin_size) == int, "bin_size should be integer,"
assert type(self.cell_size) == int, "cell_size should be integer,"
assert 180 % self.bin_size == 0, "bin_size should be divisible by 180"
def extract(self):
"""
计算图像的HOG描述符,以及HOG-image特征图
"""
height, width = self.img.shape
'''
1、计算图像每一个像素点的梯度幅值和角度
'''
# gradient_magnitude:shape为(128,64)
# 每个像素点的梯度值、梯度方向
gradient_magnitude, gradient_angle = self.global_gradient()
gradient_magnitude = abs(gradient_magnitude)
'''
2、计算输入图像的每个cell单元的梯度直方图,形成每个cell的descriptor 比如输入图像为128x64 可以得到16x8个cell,每个cell由9个bin组成
'''
# 梯度方向的特征向量初始化的shape为:(16,8,9)
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]):
# 计算第[i][j]个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)
# 将得到的每个cell的梯度方向直方图绘出,得到特征图
hog_image = self.render_gradient(np.zeros([height, width]), cell_gradient_vector)
'''
3、将2x2个cell组成一个block,一个block内所有cell的特征串联起来得到该block的HOG特征descriptor
将图像image内所有block的HOG特征descriptor串联起来得到该image(检测目标)的HOG特征descriptor,
这就是最终分类的特征向量
'''
hog_vector = []
# 默认步长为一个cell大小,一个block由2x2个cell组成,遍历每一个block
for i in range(cell_gradient_vector.shape[0] - 1): # 16-1 = 15
for j in range(cell_gradient_vector.shape[1] - 1): # 8-1 = 15
# 每一个block由四个cell构成,因此,将相邻的四个cell的特征,存到一个列表中即可
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])
'''块内归一化梯度直方图,去除光照、阴影等变化,增加鲁棒性'''
# 计算l2范数
# 计算的目标是一个矩阵,4*9的矩阵
# 将矩阵中的每一个元素按照L2范数进行归一化 。因此先求矩阵的平方和,再把元素除以平方和
# 下面lambda函数的作用就是求和, 得到一个数字
mag = lambda vector: math.sqrt(sum(i ** 2 for i in vector))
magnitude = mag(block_vector) + 1e-5
# 归一化
if magnitude != 0:
# 第二行别看错了,normalize就是一个匿名函数而已, 跟 p没什么区别
normalize = lambda block_vector, magnitude: [element / magnitude for element in block_vector]
block_vector = normalize(block_vector, magnitude)
hog_vector.append(block_vector)
return np.asarray(hog_vector), hog_image
def global_gradient(self):
"""
分别计算图像沿x轴和y轴的梯度
"""
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*gradient_values_x + 0.5*gradient_values_y
# 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)
gradient_magnitude, gradient_angle = cv2.cartToPolar(gradient_values_x, gradient_values_y, angleInDegrees=True)
# 角度大于180°的,减去180度
gradient_angle[gradient_angle > 180.0] -= 180
# print('gradient',gradient_magnitude.shape,gradient_angle.shape,np.min(gradient_angle),np.max(gradient_angle))
return gradient_magnitude, gradient_angle
def cell_gradient(self, cell_magnitude, cell_angle):
"""
为每个细胞单元构建梯度方向直方图
args:
cell_magnitude:cell中每个像素点的梯度幅值,
cell_angle:cell中每个像素点的梯度方向
return:
返回该cell对应的梯度直方图,长度为bin_size
"""
# 构建每一个cell的梯度直方图,因为分箱分成了9个,初始化时,orientation_centers=[0,0,0,0,0,0,0,0,0]
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: 第i个索引, max_angle : 第i+1个索引, 这里的命名真是有问题
# 下标的范围是0到8
min_angle, max_angle, weight = self.get_closest_bins(gradient_angle)
orientation_centers[min_angle] += (gradient_strength * (1 - weight))
orientation_centers[max_angle] += (gradient_strength * weight)
return orientation_centers
def get_closest_bins(self, gradient_angle):
"""
计算梯度方向gradient_angle位于哪一个bin中,这里采用的计算方式为双线性插值
具体参考:https://www.leiphone.com/news/201708/ZKsGd2JRKr766wEd.html
例如:当我们把180°划分为9个bin的时候,分别对应对应0,20,40,...160这些角度。
角度是10,副值是4,因为角度10介于0-20度的中间(正好一半),所以把幅值
一分为二地放到0和20两个bin里面去。
args:
gradient_angle:角度
return:
start,end,weight:起始bin索引,终止bin的索引,end索引对应bin所占权重
"""
idx = int(gradient_angle / self.angle_unit) # 如int(165/20) = 8
mod = gradient_angle % self.angle_unit # 165%20 = 15。 u= 15/20 = 0.75, 则分到idx的梯度的值的权重为1-u = 0.25
return idx % self.bin_size, (idx + 1) % self.bin_size, mod / self.angle_unit # 分到idx+1的权重为u = 0.75
# 之所以要对9取余,防止越界。因为9*20=180。180度在分箱的时候实际上属于0度
def render_gradient(self, image, cell_gradient):
"""
将得到的每个cell的梯度方向直方图绘出,得到特征图
args:
image:画布,和输入图像一样大 [h,w]
cell_gradient:输入图像的每个cell单元的梯度直方图,形状为[h/cell_size,w/cell_size,bin_size]
return:
image:特征图
"""
cell_width = self.cell_size / 2
max_mag = np.array(cell_gradient).max() # 获取(累计)梯度最大值,用于归一化;在可视化画图时,采取的是整个图片的最大值归一化
# 遍历每一个cell
for x in range(cell_gradient.shape[0]): # cell_gradient:(16,8,9),当然,只是一个例子
for y in range(cell_gradient.shape[1]):
# 获取第[i][j]个cell的梯度直方图
cell_grad = cell_gradient[x][y] # cell_grad是一个9维的向量
# 归一化
cell_grad /= max_mag
angle = 0
angle_gap = self.angle_unit # 范围为20度
# 遍历每一个bin区间
for magnitude in cell_grad:
# 转换为弧度
angle_radian = math.radians(angle)
# 计算起始坐标和终点坐标,长度为幅值(归一化),幅值越大、绘制的线条越长、越亮
# 只是使用如下的方式,来可视化每个点的幅值大小
x1 = int(x * self.cell_size + cell_width + magnitude * cell_width * math.cos(angle_radian))
y1 = int(y * self.cell_size + cell_width + magnitude * cell_width * math.sin(angle_radian))
x2 = int(x * self.cell_size + cell_width - magnitude * cell_width * math.cos(angle_radian))
y2 = int(y * self.cell_size + cell_width - magnitude * cell_width * math.sin(angle_radian))
cv2.line(image, (y1, x1), (y2, x2), int(255 * math.sqrt(magnitude)))
angle += angle_gap
return image
if __name__ == '__main__':
# 加载图像
img_copy = cv2.imread('D://1000.webp')
img_rgb = cv2.cvtColor(img_copy, cv2.COLOR_BGR2RGB)
width = 64
height = 128
# img_copy = img[320:320 + height, 570:570 + width][:, :, ::-1]
# gray_copy = cv2.cvtColor(img_copy, cv2.COLOR_BGR2GRAY)
gray_copy = cv2.cvtColor(img_copy, cv2.COLOR_BGR2GRAY)
# 显示原图像
plt.figure(figsize=(6.4, 2.0 * 3.2))
plt.subplot(1, 2, 1)
plt.imshow(img_rgb)
# HOG特征提取
hog = Hog_descriptor(gray_copy, cell_size=8, bin_size=9)
hog_vector, hog_image = hog.extract()
print('hog_vector', hog_vector.shape)
print('hog_image', hog_image.shape)
# 绘制特征图
plt.subplot(1, 2, 2)
plt.imshow(hog_image, cmap=plt.cm.gray)
plt.show()
三.高斯模糊原理
高斯模糊(英语:Gaussian Blur),也叫高斯平滑,是在Adobe Photoshop、GIMP以及Paint.NET等图像处理软件中广泛使用的处理效果,通常用它来减少图像噪声以及降低细节层次。这种模糊技术生成的图像,其视觉效果就像是经过一个毛玻璃在观察图像,这与镜头焦外成像效果散景以及普通照明阴影中的效果都明显不同。高斯平滑也用于计算机视觉算法中的预先处理阶段,以增强图像在不同比例大小下的图像效果(参见尺度空间表示以及尺度空间实现)。 从数学的角度来看,图像的高斯模糊过程就是图像与正态分布做卷积。由于正态分布又叫作高斯分布,所以这项技术就叫作高斯模糊。图像与圆形方框模糊做卷积将会生成更加精确的焦外成像效果。由于高斯函数的傅立叶变换是另外一个高斯函数,所以高斯模糊对于图像来说就是一个低通滤波器。
四.高斯模糊代码
# -*- coding: utf-8 -*-
import math
import numpy as np
import Image
class MyGaussianBlur():
#初始化
def __init__(self, radius=1, sigema=1.5):
self.radius=radius
self.sigema=sigema
#高斯的计算公式
def calc(self,x,y):
res1=1/(2*math.pi*self.sigema*self.sigema)
res2=math.exp(-(x*x+y*y)/(2*self.sigema*self.sigema))
return res1*res2
#得到滤波模版
def template(self):
sideLength=self.radius*2+1
result = np.zeros((sideLength, sideLength))
for i in range(sideLength):
for j in range(sideLength):
result[i,j]=self.calc(i-self.radius, j-self.radius)
all=result.sum()
return result/all
#滤波函数
def filter(self, image, template):
arr=np.array(image)
height=arr.shape[0]
width=arr.shape[1]
newData=np.zeros((height, width))
for i in range(self.radius, height-self.radius):
for j in range(self.radius, width-self.radius):
t=arr[i-self.radius:i+self.radius+1, j-self.radius:j+self.radius+1]
a= np.multiply(t, template)
newData[i, j] = a.sum()
newImage = Image.fromarray(newData)
return newImage
r=1 #模版半径,自己自由调整
s=2 #sigema数值,自己自由调整
GBlur=MyGaussianBlur(radius=r, sigema=s)#声明高斯模糊类
temp=GBlur.template()#得到滤波模版
im=Image.open('D://1000.webp')#打开图片
image=GBlur.filter(im, temp)#高斯模糊滤波,得到新的图片
image.show()#图片显示