Python-Opencv 实现一种确定特征点描述子主方向的方法(参考SIFT算法)

一、背景

       图像匹配算法主要分为两大类,第一类是近些年发展迅速的立体匹配算法,通过极线矫正与代价计算等方法实现图像特征点的匹配,包括NCC、SSD等全局、半全局代价计算方法等;第二类是较为传统的局部匹配算法,主要通过针对特征点构建特征描述子,通过描述子之间的相似性进行图像特征点的匹配,同时,为了减少图像旋转、物体运动对图像特征点匹配带来的影响,提高局部匹配算法的鲁棒性,确定一个特征点主方向作为不变量,保证特征描述子的旋转不变性变得尤为重要!

二、SIFT算法特征点主方向

可参考以下链接:SIFT算法原理(3)-确定关键点的主方位,构建关键点描述符 - 浮沉沉浮 - 博客园 (cnblogs.com)

三、本文特征点主方向确定原理

(1)以特征点为中心,构建n*n大小的邻域(一般选择在此邻域内构建描述子);

(2)在邻域范围内,对每个像素点的梯度幅值M(h, w),θ(h, w)进行计算,计算公式参考了SIFT算法:
        M(h, w) = \sqrt{(I(h+1, w)-I(h-1, w))^{2} + (I(h, w+1)-I(h, w-1))^{2}}
        \theta = arctan((I(h+1, w)-I(h-1, w))/(I(h, w+1)-I(h, w-1)))

(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()

五、改进

精确主方向的确定:利用主方向两侧的方向幅值,通过牛顿插值、二次函数插值、二次函数拟合等方法,得到更精确的主方向(本文改进代码部分)。

猜你喜欢

转载自blog.csdn.net/csqqscCSQQSC/article/details/129022440