OpenCV 4.12.0
开源计算机视觉
加载中...
搜索中...
无匹配项
SIFT(尺度不变特征变换)简介

目标

在本章中,

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

理论

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

image

2004 年,不列颠哥伦比亚大学的 D.Lowe 在他的论文《来自尺度不变关键点的独特图像特征》中提出了一种新的算法,即尺度不变特征变换(SIFT),该算法提取关键点并计算其描述符。(本文易于理解,被认为是关于 SIFT 的最佳材料。此解释只是本文的简短摘要)

SIFT 算法主要涉及四个步骤。我们将逐一介绍它们。

1. 尺度空间极值检测

从上图中可以明显看出,我们不能使用相同的窗口来检测不同尺度的关键点。对于小角点来说还可以。但是要检测更大的角点,我们需要更大的窗口。为此,使用了尺度空间滤波。在尺度空间滤波中,找到具有各种 \(\sigma\) 值的图像的高斯拉普拉斯算子 (LoG)。LoG 充当斑点检测器,由于 \(\sigma\) 的变化,它会检测各种大小的斑点。简而言之,\(\sigma\) 充当缩放参数。例如,在上图中,低 \(\sigma\) 的高斯核对小角点给出高值,而高 \(\sigma\) 的高斯核非常适合更大的角点。因此,我们可以找到跨尺度和空间的局部最大值,从而给我们一个 \((x,y,\sigma)\) 值列表,这意味着在 \(\sigma\) 尺度上 (x,y) 处有一个潜在的关键点。

但是这个 LoG 有点昂贵,所以 SIFT 算法使用高斯差分 (DoG),它是 LoG 的近似值。高斯差分是通过对具有两个不同 \(\sigma\) 的图像进行高斯模糊的差获得的,假设它是 \(\sigma\) 和 \(k\sigma\)。此过程针对高斯金字塔中图像的不同八度音阶执行。它在下图表示

image

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

image

关于不同的参数,该论文给出了一些经验数据,可以概括为:八度音阶数 = 4,尺度级别数 = 5,初始 \(\sigma=1.6\),\(k=\sqrt{2}\) 等作为最佳值。

2. 关键点定位

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

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

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

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

3. 方向分配

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

4. 关键点描述符

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

5. 关键点匹配

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

这是 SIFT 算法的摘要。有关更多详细信息和理解,强烈建议阅读原始论文。

OpenCV 中的 SIFT

现在让我们看看 OpenCV 中可用的 SIFT 功能。请注意,这些以前仅在 opencv contrib repo 中可用,但该专利已于 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)

请参见以下两个结果

image

现在要计算描述符,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 数组。

所以我们得到了关键点、描述符等。现在我们想看看如何在不同的图像中匹配关键点。我们将在后面的章节中学习。