一、背景
图像匹配算法主要分为两大类,第一类是近些年发展迅速的立体匹配算法,通过极线矫正与代价计算等方法实现图像特征点的匹配,包括NCC、SSD等全局、半全局代价计算方法等;第二类是较为传统的局部匹配算法,主要通过针对特征点构建特征描述子,通过描述子之间的相似性进行图像特征点的匹配,同时,为了减少图像旋转、物体运动对图像特征点匹配带来的影响,提高局部匹配算法的鲁棒性,确定一个特征点主方向作为不变量,保证特征描述子的旋转不变性变得尤为重要!
二、SIFT算法特征点主方向
可参考以下链接:SIFT算法原理(3)-确定关键点的主方位,构建关键点描述符 - 浮沉沉浮 - 博客园 (cnblogs.com)
三、本文特征点主方向确定原理
(1)以特征点为中心,构建n*n大小的邻域(一般选择在此邻域内构建描述子);
(2)在邻域范围内,对每个像素点的梯度幅值M(h, w),θ(h, w)进行计算,计算公式参考了SIFT算法:
(3)存储每个像素点的梯度幅值与方向,梯度幅值矩阵与方向矩阵均为(n-2)*(n-2)型;
(4)将邻域内像素位置与邻域中心的距离作为梯度方向权重衡量标准,距离越远,权值越小,通过Gaussian公式构建二维高斯核;
(5)梯度方向0-360度,10度一个区间,共划分为36个区间;
(6)同一角度区间内,统计所有像素梯度幅值与权值乘积之和,即为特征点邻域内该区间的方向幅值。最终,得到36个方向幅值,幅值最大的为主方向。此外,若存在 幅值>最大幅值*80% 的方向幅值,则选取其中最大的为副方向(在主方向区间内,可选取区间中点作为精确主方向)。
四、算法代码实现
第一部分:自定义函数包括 生成邻域、像素梯度幅值与方向计算、生成二维高斯核、梯度方向统计量计算 四个函数,如下,
import numpy as np
import cv2
from matplotlib import pyplot as plt
"""外置函数:特征点邻域图像"""
#image是灰度图像,featurepoint是特征点坐标(h, w),size是邻域边长,偶数
def generate_featurepoint_neighberhood_image(image, featurepoint, bian_size):
"""image是灰度图像,featurepoint是特征点坐标(h, w),size是邻域边长"""
cv2.normalize(image, image, 0, 255, cv2.NORM_MINMAX)
h_min = 0
h_max = len(image) - 1
w_min = 0
w_max = len(image[0]) - 1
h = featurepoint[0]
w = featurepoint[1]
h_l = h - round(bian_size / 2)
w_l = w - round(bian_size / 2)
h_r = h + round(bian_size / 2)
w_r = w + round(bian_size / 2)
if h_min <= h_l and h_r <= h_max and w_min <= w_l and w_r <= w_max:
featurepoint_neighberhood_image = image[h_l:h_r, w_l:w_r]
else:
featurepoint_neighberhood_image = np.zeros((bian_size, bian_size), dtype=np.uint8)
return featurepoint_neighberhood_image
"""外置函数:像素梯度幅值与方向计算"""
def pixelglad_cal(image, point):
h = point[0]
w = point[1]
glad_h = float(image[h-1][w]) - float(image[h+1][w])
glad_w = float(image[h][w+1]) - float(image[h][w-1])
#梯度幅值计算
fuzhi = np.sqrt(glad_h * glad_h + glad_w * glad_w)
#梯度方向计算
rad = np.arctan2(glad_h, glad_w)
theta = round(rad / np.pi * 180)
if theta < 0:
theta = 360 + theta
return fuzhi, theta
"""外置函数:二维高斯核"""
def getgaussiankernel2d(ksize, sigma):
return cv2.getGaussianKernel(ksize, sigma) * cv2.getGaussianKernel(ksize, sigma).T
"""外置函数:梯度统计量计算"""
def glad_statistics(image):
#梯度图像的边长
glad_image_length = int(len(image) - 2)
#生成图像梯度阵列
glad_fuzhi = [[0] * (glad_image_length) for _ in range(glad_image_length)]
glad_theta = [[0] * (glad_image_length) for _ in range(glad_image_length)]
for h in range(1, glad_image_length+1):
for w in range(1, glad_image_length+1):
point = (h, w)
glad_fuzhi[h-1][w-1], glad_theta[h-1][w-1] = pixelglad_cal(image, point)
#梯度方向统计
glad_fuzhi_weight = getgaussiankernel2d(glad_image_length, 1)
result = [0 for i in range(36)]
for h in range(glad_image_length):
for w in range(glad_image_length):
dirct = int(glad_theta[h][w] / 10)
for n in range(len(result)):
if n == dirct:
result[n] = result[n] + glad_fuzhi_weight[h][w]*glad_fuzhi[h][w]
return result
第二部分:调用函数实现功能,
#图像读取
image_L = cv2.imread(r"...", 0)
image_R = cv2.imread(r"...", 0)
#特征点坐标,左右图像特征点位置一一对应
featurepoint_L = [(16, 16), (28, 39), (19, 36)]
featurepoint_R = [(16, 40), (40, 28), (37, 36)]
#梯度图像采样区域
i = 0 #特征点坐标索引
bian_size = 30 #邻域边长
featurepoint_neighberhood_image_L = generate_featurepoint_neighberhood_image(image_L, featurepoint_L[i], bian_size)
featurepoint_neighberhood_image_R = generate_featurepoint_neighberhood_image(image_R, featurepoint_R[i], bian_size)
#梯度图像及统计量计算
dirction_statistics_L = glad_statistics(featurepoint_neighberhood_image_L)
dirction_statistics_R = glad_statistics(featurepoint_neighberhood_image_R)
#绘制梯度方向统计直方图
dirction = list(range(0, 360, 10))
plt.subplot(1, 2, 1)
plt.bar(dirction, dirction_statistics_L, 5, 0)
plt.subplot(1, 2, 2)
plt.bar(dirction, dirction_statistics_R, 5, 0)
plt.show()
五、改进
精确主方向的确定:利用主方向两侧的方向幅值,通过牛顿插值、二次函数插值、二次函数拟合等方法,得到更精确的主方向(本文改进代码部分)。