![]() |
OpenCV 4.12.0
开源计算机视觉
|
在本章中,
在之前的几个章节中,我们了解了一些角点检测器,如 Harris 等。它们是旋转不变的,这意味着即使图像旋转,我们也可以找到相同的角点。这是显而易见的,因为在旋转的图像中,角点仍然是角点。但是缩放呢?如果图像被缩放,角点可能不再是角点。例如,查看下面的一个简单图像。当一个小图像中的一个小窗口中的一个角点在同一个窗口中放大时,它是平坦的。因此,Harris 角点不是尺度不变的。
2004 年,不列颠哥伦比亚大学的 D.Lowe 在他的论文《来自尺度不变关键点的独特图像特征》中提出了一种新的算法,即尺度不变特征变换(SIFT),该算法提取关键点并计算其描述符。(本文易于理解,被认为是关于 SIFT 的最佳材料。此解释只是本文的简短摘要)。
SIFT 算法主要涉及四个步骤。我们将逐一介绍它们。
从上图中可以明显看出,我们不能使用相同的窗口来检测不同尺度的关键点。对于小角点来说还可以。但是要检测更大的角点,我们需要更大的窗口。为此,使用了尺度空间滤波。在尺度空间滤波中,找到具有各种 \(\sigma\) 值的图像的高斯拉普拉斯算子 (LoG)。LoG 充当斑点检测器,由于 \(\sigma\) 的变化,它会检测各种大小的斑点。简而言之,\(\sigma\) 充当缩放参数。例如,在上图中,低 \(\sigma\) 的高斯核对小角点给出高值,而高 \(\sigma\) 的高斯核非常适合更大的角点。因此,我们可以找到跨尺度和空间的局部最大值,从而给我们一个 \((x,y,\sigma)\) 值列表,这意味着在 \(\sigma\) 尺度上 (x,y) 处有一个潜在的关键点。
但是这个 LoG 有点昂贵,所以 SIFT 算法使用高斯差分 (DoG),它是 LoG 的近似值。高斯差分是通过对具有两个不同 \(\sigma\) 的图像进行高斯模糊的差获得的,假设它是 \(\sigma\) 和 \(k\sigma\)。此过程针对高斯金字塔中图像的不同八度音阶执行。它在下图表示
一旦找到这个 DoG,就会在图像中搜索尺度和空间的局部极值。例如,图像中的一个像素与其 8 个邻居以及下一尺度的 9 个像素和先前尺度的 9 个像素进行比较。如果它是局部极值,则它是潜在的关键点。它基本上意味着关键点在该尺度中得到最好的表示。它在下图显示
关于不同的参数,该论文给出了一些经验数据,可以概括为:八度音阶数 = 4,尺度级别数 = 5,初始 \(\sigma=1.6\),\(k=\sqrt{2}\) 等作为最佳值。
一旦找到潜在的关键点位置,就必须对其进行细化以获得更准确的结果。他们使用尺度空间的泰勒级数展开来获得更精确的极值位置,如果此极值处的强度小于阈值(根据论文为 0.03),则将其拒绝。此阈值在 OpenCV 中称为 contrastThreshold
DoG 对边缘具有更高的响应,因此也需要删除边缘。为此,使用了类似于 Harris 角点检测器的概念。他们使用 2x2 Hessian 矩阵 (H) 来计算主曲率。我们从 Harris 角点检测器知道,对于边缘,一个特征值大于另一个。所以这里他们使用了一个简单的函数,
如果此比率大于阈值(在 OpenCV 中称为 edgeThreshold),则丢弃该关键点。在论文中给出为 10。
因此,它可以消除任何低对比度关键点和边缘关键点,剩下的就是强大的兴趣点。
现在,为每个关键点分配一个方向,以实现图像旋转不变性。在关键点位置周围,根据尺度取一个邻域,并计算该区域中的梯度幅度和方向。创建一个包含 36 个 bin 的方向直方图,覆盖 360 度(它由梯度幅度加权,高斯加权圆形窗口的 \(\sigma\) 等于关键点尺度的 1.5 倍)。取直方图中的最高峰,并且还考虑高于其 80% 的任何峰来计算方向。它创建具有相同位置和尺度但方向不同的关键点。它有助于匹配的稳定性。
现在创建关键点描述符。取关键点周围的 16x16 邻域。它分为 16 个 4x4 大小的子块。对于每个子块,创建 8 个 bin 方向直方图。因此,总共有 128 个 bin 值可用。它表示为向量以形成关键点描述符。除此之外,还采取了一些措施来实现针对光照变化、旋转等的鲁棒性。
通过识别两个图像之间的最近邻居来匹配关键点。但在某些情况下,第二个最接近的匹配可能非常接近第一个。这可能是由于噪声或其他原因造成的。在这种情况下,取最近距离与第二最接近距离的比率。如果它大于 0.8,则拒绝它们。根据论文,它可以消除大约 90% 的错误匹配,同时仅丢弃 5% 的正确匹配。
这是 SIFT 算法的摘要。有关更多详细信息和理解,强烈建议阅读原始论文。
现在让我们看看 OpenCV 中可用的 SIFT 功能。请注意,这些以前仅在 opencv contrib repo 中可用,但该专利已于 2020 年到期。因此,它们现在已包含在主存储库中。让我们从关键点检测开始并绘制它们。首先,我们必须构造一个 SIFT 对象。我们可以将不同的参数传递给它,这些参数是可选的,并且在文档中有很好的解释。
sift.detect() 函数查找图像中的关键点。如果要仅搜索图像的一部分,可以传递一个掩码。每个关键点都是一个特殊的结构,它具有许多属性,例如其 (x,y) 坐标、有意义的邻域大小、指定其方向的角度、指定关键点强度的响应等。
OpenCV 还提供了 cv.drawKeyPoints() 函数,该函数在关键点的位置上绘制小圆圈。如果传递标志 cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS 给它,它将绘制一个带有关键点大小的圆圈,甚至会显示其方向。请参见以下示例。
请参见以下两个结果
现在要计算描述符,OpenCV 提供了两种方法。
我们将看到第二种方法
这里 kp 将是关键点列表,des 是形状为 \(\text{(关键点数量)} \times 128\) 的 numpy 数组。
所以我们得到了关键点、描述符等。现在我们想看看如何在不同的图像中匹配关键点。我们将在后面的章节中学习。