Python OpenCV SIFT特征提取的原理与代码实现

在这里对SIFT提取关键点与描述符的方法进行介绍。

Harris Detectors具有旋转不变性,可以找到图像中的角点。但如果对图像扩大规模,如缩放,如下图所示,那么原本的角点在变换后的某些窗口中可能就不是角点,因此,Harris Detectors不具有尺度不变性。

为了解决这一问题,SIFT从尺度不变的关键点中提取关键点并计算其描述子。

尺度空间极值检测

为了解决尺度不变性问题,采用尺度空间滤波器。将σ视作缩放参数,使用高斯拉普拉斯函数(LoG)作为点检测器。

例如,在上图中,低σ的高斯核可以为小角点提供高值,而高σ的高斯核则适合于大角点。因此,我们可以在尺度空间中找到局部极大值,这给了我们一个(x,y,σ)值列表,这意味着在σ尺度的(x,y)处有一个潜在的关键点。

在SIFT中,并没有使用耗时的LoG,而是使用了与之类似的高斯差分(Difference of Gaussian,DoG)。高斯差分是指具有两个不同σ(设为σ和kσ)的图像的高斯模糊差,如下图所示。

在DoG的基础上,图像就会在尺度空间上搜索局部极值。例如,将图像中的一个像素与其8个相邻像素、下一个尺度中的9个像素和前一个尺度中的9个像素进行比较,如下图所示。如果它是一个局部极值,它就是一个潜在的关键点。

一些基于经验主义的超参数设置: number of octaves = 4, number of scale levels = 5, initial σ=1.6, k=√2

关键点定位

一旦找到潜在的关键点位置,就必须对其进行优化,以获得更准确的结果。

具体地,可以使用尺度空间的泰勒级数展开来获得更精确的极值位置,如果该极值处的强度小于阈值,则会被拒绝。该阈值在OpenCV中称为对比度阈值。

另一方面,DoG对于边缘的相应值较大,所以需要去除这一类候选的关键点。 为此,使用类似于哈里斯角检测器的概念,用一个2x2的 Hessian 矩阵(H)来计算主曲率,并求解候选关键点曲率之间的比率。如果该比率大于一个阈值(在 OpenCV 中称为边阈值) ,则该关键点将被丢弃。

经过这样的两步操作之后,去除了低对比度的关键点与边缘关键点。

主方向指定

既然要实现旋转不变性,那么每一个关键点都应该指定一个主方向。

根据尺度的不同,在关键点位置附近取一邻域,计算该区域的梯度大小和方向,根据该结果建立一个方向直方图。取直方图中的最高峰与超过其80%的其他峰,用它们创建出位置和尺度相同但主方向不同的关键点。相较于仅取最高峰,可以有效提升匹配的稳定性。

关键点描述符

取关键点附近的16*16邻域,将它们分成16个4*4的子块。对于每个子块,建立一个方向直方图,每个子块8个bin,那么对于一个关键点,共计有128个bin,从而可以将它们表示成一个向量,这就是关键点的描述符。

关键点匹配

两张图像之间的关键点通过确定其最近邻(根据向量之间的距离)来匹配。但在某些情况下,第二个最接近的匹配可能非常接近第一个。在这种情况下,取最近距离与第二最近距离的比值,如果大于阈值,则拒绝。

SIFT的OpenCV实现

# SIFT Algorithm

def sift_algorithm(img_path):

    # 读取图像

    img = cv2.imread(img_path)

    # 颜色空间转换

    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)


    # 创建SIFT对象实例

    sift = cv2.SIFT_create()


    # sift.detect()作用:在图像中找到关键点。

    # 如果只想搜索图像的一部分,可以传递mask。

    # 每个关键点都是一个特殊的结构,它有许多属性,比如(x,y)坐标、有意义邻域的大小、指定其方向的角度、指定关键点强度的响应等。

    kp = sift.detect(gray, None)


    # 现在已经找到关键点,就可以使用sift.compute()根据关键点计算描述符。

    # 最终得到的kp是一个关键点元组,des是一个形状(关键点数量)×128的numpy数组。

    kp, des = sift.compute(gray, kp)


    # 刚刚我们分两步,先找到关键点、再计算描述符。

    # 可以直接一步实现,即使用sift.detectAndCompute()方法。

    # kp, des = sift.detectAndCompute(gray, None)


    # cv2.drawKeypoints可视化关键点。

    # 可以传入flags参数,选择绘制带有大小与方向的更为丰富的关键点信息。

    # img = cv2.drawKeypoints(gray, kp, img)

    img = cv2.drawKeypoints(gray, kp, img, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)

我们以这张图片作为输入:

  • kp:关键点元组,每一个元素都是一个Keypoint类型

(<KeyPoint 0000021E20446660>, <KeyPoint 0000021E20446630>, <KeyPoint 0000021E20446600>, ...,<KeyPoint 0000021E20DFE450>)

  • des:(关键点数量×128)的numpy数组,对于这张图片,shape为(3071, 128)
  • drawKeyPoints返回的img:(1440, 1080, 3)
    • flags为None:

  • flags为cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS:

猜你喜欢

转载自blog.csdn.net/qq_41112170/article/details/125849051