OpenCV 4.10.0
开源计算机视觉
|
在本篇教程中,
在过去的几章中,我们了解到一些角点检测器,比如 Harris 等。它们是旋转不变的,这意味着即使图像发生了旋转,我们也可以找到相同的角点。这是显而易见的,因为在旋转后的图像中,角点仍然是角点。但是缩放呢?如果图像进行了缩放,则角点可能不再是角点。例如,请查看下面的一个简单图像。当一个小图像中的一个角点在小窗口内被放大到相同的窗口时,它就变成了平的。因此,Harris 角点不是尺度不变的。
在 2004 年,英属哥伦比亚大学的 D.Lowe 在其论文“源自尺度不变关键点的独特图像特征”中提出了一种新的算法,即尺度不变特征变换 (SIFT),该算法提取关键点并计算其描述符。 *(这篇论文易于理解,被认为是关于 SIFT 的现有最佳资料。本说明只是对这篇论文的一个简要总结)*。
SIFT 算法主要包括四个步骤。我们将逐一了解这些步骤。
根据上面提供的图,显而易见我们无法使用相同的窗口来检测具有不同尺度的关键点。对于小的角点,这样做是可以的。但是,为了检测更大的角点,我们需要更大的窗口。对于此目的,使用了尺度空间滤波。在尺度空间滤波中,针对具有各种 \(\sigma\) 值的图像找到了高斯拉普拉斯算子。LoG 充当斑点检测器,它因 \(\sigma\) 的变化而检测到了各种尺寸的斑点。简而言之,\(\sigma\) 作为缩放参数。例如,在上面的图像中,具有低 \(\sigma\) 的高斯核为小的角点提供了较高的值,而具有高 \(\sigma\) 的高斯核非常适合较大的角点。因此,我们可以找到跨尺度和空间的局部极大值,这些极值提供了 \((x,y,\sigma)\) 值列表,这意味着在 \(\sigma\) 尺度下,(x,y) 处有一个潜在关键点。
但是,这个 LoG 有点昂贵,所以 SIFT 算法使用了高斯差分,这是 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 个箱的、覆盖 360 度的方向直方图(它是通过梯度幅度和高斯加权圆形窗口加权的,\(\sigma\) 等于关键点比例的 1.5 倍)。选取直方图中的最高峰值,并考虑高于其 80% 的任何峰值来计算方向。它创建了位置和比例相同但方向不同的关键点。它有助于匹配的稳定性。
现在创建了关键点描述符。取关键点周围的 16x16 邻域。将其分成 16 个 4x4 大小的子块。为每个子块创建 8 个箱的方向直方图。因此,总共有 128 个箱值可用。它表示为一个向量,以形成关键点描述符。除此之外,还采取了几项措施来提高对光照变化、旋转等因素的鲁棒性。
通过识别两幅图像之间的最近邻居来匹配关键点。但在某些情况下,第二近匹配可能非常接近于第一近匹配。这可能是由于噪声或其他原因造成的。在这种情况下,取最近距离与第二近距离的比率。如果大于 0.8,则拒绝它们。根据该论文,此操作消除了大约 90% 的错误匹配,同时仅舍弃了 5% 的正确匹配。
这是 SIFT 算法的摘要。有关更多详细信息和理解,强烈建议阅读原始论文。
现在,让我们来看看 OpenCV 中的 SIFT 功能。请注意,这些以前仅在 opencv contrib 仓库中可用,但该专利已于 2020 年到期。因此,它们现在包含在主仓库中。让我们从关键点检测开始,并绘制它们。我们首先必须构造一个 SIFT 对象。我们可以向其传递不同的参数,这些参数是可选的,并且它们在文档中得到了很好的解释。
sift.detect() 函数查找图像中的关键点。如果你只想搜索图像的一部分,你可以传递一个蒙版。每个关键点都是一个特殊结构,它具有许多属性,例如:它的 (x,y) 坐标、有意义邻域的大小、指定其方向的角度、指定关键点强度的响应等。
OpenCV 还提供了 cv.drawKeyPoints() 函数,该函数在关键点的位置绘制小圆圈。如果你向其传递一个标志 cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS,它将绘制一个大小与关键点大小相同且甚至会显示其方向的圆圈。请参见下面的示例。
请参见下面的两个结果
现在,OpenCV 提供两种方法来计算描述符。
我们看看第二种方法
这里,kp 将是一个关键点列表,而 des 是形状为 \(\text{(关键点数)} \times 128\) 的 numpy 数组。
因此,我们获得了关键点、描述符等。现在,我们想了解如何在不同的图像中匹配关键点。我们将在接下来的章节中学到这一点。