OpenCV 4.11.0
开源计算机视觉库
加载中…
搜索中…
未找到匹配项
SIFT (尺度不变特征变换) 简介

目标

在本章中,

  • 我们将学习SIFT算法的概念。
  • 我们将学习如何找到SIFT关键点和描述符。

理论

在前面几章中,我们看到了一些角点检测器,例如Harris等。它们是旋转不变的,这意味着即使图像旋转,我们也能找到相同的角点。这是显而易见的,因为角点在旋转后的图像中仍然是角点。但是尺度呢?如果图像缩放,角点可能就不再是角点了。例如,检查下面的简单图像。小图像中一个小窗口内的角点,在相同窗口放大后就变平了。因此,Harris角点不是尺度不变的。

图像

2004年,不列颠哥伦比亚大学的D.Lowe在他的论文《从尺度不变关键点提取显著图像特征》中提出了一种新的算法——尺度不变特征变换(SIFT),该算法提取关键点并计算其描述符。(这篇论文很容易理解,被认为是关于SIFT最好的资料。本解释只是这篇论文的一个简短总结)

SIFT算法主要包含四个步骤。我们将逐一了解它们。

1. 尺度空间极值点检测

从上图可以看出,我们不能使用相同的窗口来检测不同尺度的关键点。对于小角点来说是可以的。但是要检测较大的角点,我们需要更大的窗口。为此,使用尺度空间滤波。其中,使用不同的σ值对图像进行高斯-拉普拉斯变换(LoG)。LoG充当斑点检测器,由于σ的变化,它可以检测不同大小的斑点。简而言之,σ充当比例参数。例如,在上图中,σ较低的高斯核对小角点给出较高的值,而σ较高的核则更适合较大的角点。因此,我们可以找到跨尺度和空间的局部最大值,这将给我们一个(x,y,σ)值的列表,这意味着在σ尺度下(x,y)处存在一个潜在的关键点。

但是这个LoG计算成本略高,因此SIFT算法使用高斯差分(DoG),它是LoG的近似值。高斯差分是使用两个不同的σ(设为σ和kσ)对图像进行高斯模糊后的差值。这个过程是在高斯金字塔的不同八度上进行的。它在下图中表示

图像

一旦找到DoG,就会在尺度和空间上搜索局部极值点。例如,图像中的一个像素与其8个邻居以及下一尺度的9个像素和上一尺度的9个像素进行比较。如果它是一个局部极值,它就是一个潜在的关键点。这基本上意味着关键点在该尺度上表示得最好。如下图所示

图像

关于不同的参数,论文给出了一些经验数据,可以总结为:八度数=4,尺度级别数=5,初始σ=1.6,k=√2等作为最佳值。

2. 关键点定位

一旦找到潜在的关键点位置,就必须对其进行细化以获得更准确的结果。他们使用尺度空间的泰勒级数展开来获得极值点的更精确位置,如果该极值点处的强度小于阈值(论文中为0.03),则将其拒绝。这个阈值在OpenCV中称为contrastThreshold

DoG对边缘的响应较高,因此也需要去除边缘。为此,使用了类似于Harris角点检测器的概念。他们使用2x2 Hessian矩阵(H)来计算主曲率。我们从Harris角点检测器中知道,对于边缘,一个特征值大于另一个特征值。因此,在这里他们使用了一个简单的函数:

如果该比率大于一个阈值(在OpenCV中称为edgeThreshold),则丢弃该关键点。论文中给定为10。

因此,它消除了任何低对比度的关键点和边缘关键点,剩下的就是强兴趣点。

3. 方向分配

现在为每个关键点分配一个方向以实现对图像旋转的不变性。根据尺度,在关键点位置周围取一个邻域,并计算该区域的梯度幅度和方向。创建一个包含36个bin的360度方向直方图(它由梯度幅度和具有σ等于关键点尺度的1.5倍的高斯加权圆形窗口加权)。直方图中的最高峰值被采用,并且任何高于其80%的峰值也被考虑用于计算方向。它创建具有相同位置和尺度但方向不同的关键点。这有助于匹配的稳定性。

4. 关键点描述符

现在创建关键点描述符。取关键点周围16x16的邻域。将其划分为16个4x4大小的子块。对于每个子块,创建一个8 bin方向直方图。因此,共有128个bin值可用。它表示为一个向量来形成关键点描述符。除此之外,还采取了一些措施来提高对光照变化、旋转等的鲁棒性。

5. 关键点匹配

通过识别最近邻来匹配两幅图像之间的关键点。但在某些情况下,第二近邻可能非常接近第一个。这可能是由于噪声或其他原因造成的。在这种情况下,取最近距离与第二近距离的比率。如果它大于0.8,则将其拒绝。根据论文,它消除了大约90%的错误匹配,而只丢弃了5%的正确匹配。

这是SIFT算法的总结。为了更详细地了解,强烈建议阅读原始论文。

OpenCV中的SIFT

现在让我们看看OpenCV中可用的SIFT功能。请注意,这些以前仅在opencv contrib库中可用,但专利于2020年过期。因此,它们现在包含在主库中。让我们从关键点检测开始,并绘制它们。首先,我们必须构造一个SIFT对象。我们可以向其中传递不同的可选参数,这些参数在文档中有很好的解释。

import numpy as np
import cv2 as cv
img = cv.imread('home.jpg')
gray= cv.cvtColor(img,cv.COLOR_BGR2GRAY)
sift = cv.SIFT_create()
kp = sift.detect(gray,None)
img=cv.drawKeypoints(gray,kp,img)
cv.imwrite('sift_keypoints.jpg',img)
void drawKeypoints(InputArray image, const std::vector< KeyPoint > &keypoints, InputOutputArray outImage, const Scalar &color=Scalar::all(-1), DrawMatchesFlags flags=DrawMatchesFlags::DEFAULT)
绘制关键点。
CV_EXPORTS_W bool imwrite(const String &filename, InputArray img, const std::vector< int > &params=std::vector< int >())
将图像保存到指定文件。
CV_EXPORTS_W Mat imread(const String &filename, int flags=IMREAD_COLOR_BGR)
从文件中加载图像。
void cvtColor(InputArray src, OutputArray dst, int code, int dstCn=0, AlgorithmHint hint=cv::ALGO_HINT_DEFAULT)
将图像从一种颜色空间转换为另一种颜色空间。

sift.detect() 函数查找图像中的关键点。如果只想搜索图像的一部分,可以传入掩码。每个关键点都是一个特殊的结构,具有许多属性,例如其 (x,y) 坐标、有意义的邻域的大小、指定其方向的角度、指定关键点强度的响应等。

OpenCV 还提供cv.drawKeyPoints() 函数,该函数在关键点的位置绘制小圆圈。如果传入标志cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS,它将绘制一个与关键点大小相同的圆圈,甚至会显示其方向。请参见下面的示例。

img=cv.drawKeypoints(gray,kp,img,flags=cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
cv.imwrite('sift_keypoints.jpg',img)

请参见下面的两个结果

图像

现在,要计算描述符,OpenCV 提供了两种方法。

  1. 既然您已经找到了关键点,您可以调用sift.compute(),它将根据我们找到的关键点计算描述符。例如:kp,des = sift.compute(gray,kp)
  2. 如果未找到关键点,请使用函数sift.detectAndCompute()一步直接查找关键点和描述符。

我们将看到第二种方法

sift = cv.SIFT_create()
kp, des = sift.detectAndCompute(gray,None)

这里 kp 将是关键点列表,des 是形状为\(\text{(关键点数)} \times 128\) 的 numpy 数组。

因此,我们得到了关键点、描述符等。现在我们想看看如何在不同的图像中匹配关键点。这将在接下来的章节中学习。