OpenCV基础教程——特征提取与描述(SIFT)
Shi-Tomasi角点探测器和适合于跟踪的图像特征
目标:
本章节你需要学习以下内容:
*我们将了解另一个角点探测器:Shi-Tomasi角点探测器
*我们将看到函数:cv.goodFeaturesToTrack()
代码解读
1、理论
在之前的章节中, 我们对 Harris 角点检测进行了探讨. 1994 年之后不久, J.Shi 与 C.Tomasi 在其论文《Good Features to Track》中提出了一个改进方案. 相比而言, 在性能指标上有所提升. 检测器的评分函数由下式给出: R(x,y) = \sum_{w} w(x,y) \cdot [I(x+\delta_x, y+\delta_y) - I(x,y)]^2
R = \lambda_1 \lambda_2 - k(\lambda_1+\lambda_2)^2
除此之外,Shi-Tomasi提出:
R = min(\lambda_1, \lambda_2)
当λ₁ - λ₂的差值超过阈值时,我们将其归类为拐角点。当我们像在Harris角点检测器中那样,在λ₁ - λ₂空间中描绘其特征时,可观察到清晰的分布图。

在图形中观察到的情况是:仅在变量\lambda_1和\lambda_2均大于最小值\lambda_{\text{min}}的情况下被识别为一个角(绿色区域标注)。
2、代码实现
OpenCV提供了一个名为cv.goodFeaturesToTrack()的功能模块。该模块采用Shi-Tomasi算法(可选Harris角点检测方法)从图像中提取N个最显著的关键特征点。与常规操作一致,在此过程中,默认输入应为灰度图象。随后,请确定希望提取的关键点数量。接着设置一个阈值参数——范围在0到1之间——用于筛选高质量的关键点。最后,请定义相邻关键点之间的最小欧氏距离作为筛选标准
基于所有这些信息提供的数据集,在图像中检测出多个候选角点。将不满足质量标准的所有候选点排除在外。随后按照质量指标从高到低对剩下的候选点进行排序。接着该系统选择具有最高强度值的第一个候选点,并移除与其距离最近的小范围内的所有候选点后返回前N个强度最高的特征描述符。
在下面的示例中,我们将尝试找到25个最佳角点:
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('blox.jpg')
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
corners = cv.goodFeaturesToTrack(gray,25,0.01,10)
corners = np.int0(corners)
for i in corners:
x,y = i.ravel()
cv.circle(img,(x,y),3,255,-1)
plt.imshow(img),plt.show()
代码解读
结果如下图所示:

我们以后会发现这个函数很适合在目标跟踪中使用。
介绍SIFT(Scale-Invariant Feature Trans-form)
目标:
本章节你需要学习以下内容:
*我们将了解SIFT算法的概念
*我们将学习如何找到SIFT关键点和描述符。
代码解读
1、理论
在上一节中 我们考察了几种关键特征检测器 包括Harris等经典的角落检测器 它们均具备旋转不变性 这表明即使将图像发生旋转变换后同样能够定位这些特征 由此可见 在这种情况下这些特征仍然保持有效性 然而需要注意的是 当对图像进行缩放处理时其几何特性会发生变化 举个例子 考虑如下简单的场景: 当在同一观察窗口内放大显示时 其内部区域可能会显得较为平缓 因此这种方法不具备尺度不变性这一关键属性

因而,在2004年,
不列颠哥本哈根大学D.Lowe在其论文中提出了一种新的算法,
这一算法以其名为SIFT,
从其独特的关键点的图像特性出发,
该方法通过提取关键点实现了对关键点的描述符生成。(本文易于理解并被认为是SIFT上最好的材料。因此这个解释只是本文的简要概述)
SIFT算法主要涉及四个步骤。我们将逐一看到它们。
(1)尺度空间极值检测
从图中可以看出,在不同尺度的空间中使用相同的窗口无法有效检测极值点。对于较小的角点应采用较小尺寸的窗口进行处理;而较大的角点则需要较大的窗口来捕捉特征。为了实现这一目标,我们采用了基于尺度空间的滤波器设计方法。这些滤波器由一系列具有不同标准差\sigma的高斯卷积核组成。通过应用具有不同标准差\sigma的高斯拉普拉斯算子(LoG)对图像进行卷积处理,在 LoG 参数化过程中由于其标准差\sigma的不同取值范围可以有效识别大小不一的目标斑块(当 LoG 的标准差\sigma与目标斑块直径相当时能够使斑块完全平滑)。在这里面\sigma代表一个尺度变换因子,在图像的不同区域可以通过调节其值来控制特征提取的程度。具体而言,在上图中选择较小的标准差\sigma能够较好地检测出图像中的小范围角点;而当标准差\sigma较大时,则更适合于检测图像中的大范围角点特性。因此在多尺度的空间域和二维平面域上都可以找到局部最大值的位置(x,y,\sigma)表示在特定的标准差\sigma下(x,y)位置可能是一个关键特征点。(需要注意的是高斯核的标准差与所选窗口尺寸之间存在固定比例关系:通常选取的标准差乘以6再加1即得到对应的窗宽大小)
然而LoG(Laplacian of Gaussian)的计算量较大,在实际应用中存在一定的局限性。因此SIFT算法采用高斯差分算子(即DoG)对LoG进行近似计算。为了构建有效的尺度空间描述器,在处理图像金字塔时通常会降低采样密度(例如仅选取奇数行或奇数列),从而生成不同尺寸(如1.0, 0.5, 0.25等)的一系列图像金字塔框架。在每一幅图中应用不同尺度(由σ值决定)的高斯滤波器生成多分辨率图像金字塔结构,并基于此构建多尺度空间特征描述器体系框架。(Difference of Gaussians, DoG即为相邻两层图像金字塔之间求差的结果)。

完成DoG算法后

该SIFT算法的设计者在其论文中建议采用octaves=4这一参数设置。该方法通过降采样技术实现图像尺寸的逐步缩减,并构建了一个基于尺度递减的图像金字塔结构。这种设计使得尺度空间由五个不同高斯核卷积生成(其中初始方差设为1.6),即σ=1.6,k=√2等取值被认为是最佳参数设置。
(2)关键点(极值点)定位
一旦找到关键点后,在OpenCV中我们会对这些关键点进行修正进而获得更精确的位置信息。在处理图像时,在OpenCV中我们可以利用尺度空间中的泰勒级数展开式来确定极值的位置坐标。若检测到的关键点其灰度强度低于设定阈值(通常取为0.03),则会被跳过不予以考虑。其中,在OpenCV中这一特定的阈值参数被称为contrastThreshold,并用于平衡噪声抑制与边缘检测能力之间的关系。
DoG算法对于边界的高度敏感性导致其应用受到限制。因此我们必须将这些边界排除在外。在此之前介绍的Harris算法不仅适用于角点检测还被用来识别边界。正如上文所述的方法一样在计算主曲率的过程中采用了大小为2x2的Hessian矩阵。通过Harris角点检测算法可知在两个相邻特征值差异较大的情况下识别出的是边界
因此他们采用了简单的函数实现。如果某个区域的比例比设定的边界检测阈值更高,在OpenCV中被定义为边界检测阈值的情况下,该关键点会被忽略掉。具体设定的边界检测阈值为10。
通过使用图像处理技术的自动检测方法,在低对比度图像中将具有较低对比度的关键点以及边缘关键点被去除掉后,在剩余区域中即是我们关注的重点区域。
(3)为关键点(极值点)指定方向参数
为了使该方法具备旋转不变性属性, 我们将对每个关键点赋些建立反向映射机制. 通过计算该区域的梯度级和方向, 并基于这些信息生成包含36个梯度方向(每10度一个)的方向直方图. 使用当前尺度空间σ值为1.5倍的标准差选取圆形高斯窗口, 并以梯度级作为权重对直方图进行加权处理. 直方图中的峰值对应主方向参数, 而若其他任意柱子的高度超过峰值80%, 则将被视为辅助方向. 这种方法在相同尺度空间内构建了不同指向的关键点分布, 对于提升匹配稳定性具有重要意义
(4)关键点描述符
新关键点描述符通过生成的方式被创建。从关键点周围的16×16区域中提取并划分为16个4×4的小块区域,并对每个小块区域进行统计得到方向直方图。总计生成了128个直方图特征向量来构成关键点描述符。此外还需要对这些向量进行若干测量以确保其对光照变化、旋转等环境因素的稳定响应。
(5)关键点匹配
下一步可以采用关键点特征向量之间的欧式距离作为衡量两张图像中关键点相似程度的标准。对于第一个图像中的每一个关键点,在第二个图像中寻找与其最近的关键点。然而,在某些情况下,第二个最近的关键点与第一个最近的关键点过于接近。这可能由噪声或其他干扰因素导致。此时需要计算这两个距离的比例值。如果这个比例超过0.8,则认为这两个匹配是错误的,并予以剔除。这种方法能够有效地去除90%以上的错误匹配项的同时仅排除5%的真实正确匹配项。正如文章所述,请务必仔细阅读原始文献以加深对SIFT算法的理解。需要注意的是该算法受到专利保护因此它包含在OpenCV软件库中的付费功能模块中。
2、OpenCV中的SIFT
我们现在来探讨一下OpenCV框架中的SIFT函数相关内容。接下来我们将从关键点检测与绘制入手进行分析。在开始之前,请确保我们已经初始化了一个适当的对象。具体实现时可以选择不同的参数设置……这些设置并非必要项,在后续章节中我们会详细讲解如何根据实际需求选择合适的参数配置。有关如何设置这些参数的具体说明……可以在官方文档中找到详细信息。
import numpy as np
import cv2 as cv
img = cv.imread('home.jpg')
gray= cv.cvtColor(img,cv.COLOR_BGR2GRAY)
sift = cv.xfeatures2d.SIFT_create()
kp = sift.detect(gray,None)
img=cv.drawKeypoints(gray,kp,img)
cv.imwrite('sift_keypoints.jpg',img)
代码解读
函数 sift.detect() 具有在图像中发现关键点的能力。若仅需在一个特定区域进行搜索,则可提供一个掩模图作为参数参与运算。返回值是一个具有丰富特性的特殊数据结构体,在此结构体中包含了多个重要信息:如目标位置坐标(x, y)、有意义的空间范围以及目标方向角度等多个方面,并且每个特征都有相应的强度响应指标等信息。
OpenCV也提供了实现关键点可视化的功能:cv2.drawKeypoints()。该函数能够在图像的关键点位置绘制小圆圈以标记其位置。当设置参数为cv2.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提供了两种方法。
- 因为已经找到了关键点的原因是这样,在这种情况下就可以调用sift.compute()来计算我们找到的关键点的描述符。例如:kp, des = sift.compute(灰色, kp)
- 如果未找到关键点的情况,则建议使用函数sift.detectAndCompute()来进行一次完整的操作以获取所需的关键点和描述符。
我们将看到第二种方法:
sift = cv.xfeatures2d.SIFT_create()
kp, des = sift.detectAndCompute(gray,None)
代码解读
在这一段中,kp表示关键点列表,在这里des是一个形状为Number\_of\_Keypoints \times 128的numpy数组。
所以我们在研究中成功获得了关键点以及相关描述符。目前我们关注的重点是如何在不同图像中匹配关键点。
