OpenCV 4.12.0
开源计算机视觉
加载中...
搜索中...
无匹配项
SURF(加速鲁棒特征)简介

目标

在本章中,

  • 我们将了解 SURF 的基础知识
  • 我们将了解 OpenCV 中的 SURF 功能

理论

在上一章中,我们了解了用于关键点检测和描述的 SIFT。但它相对较慢,人们需要更快的版本。2006 年,Bay, H.、Tuytelaars, T. 和 Van Gool, L 三人发表了另一篇论文“SURF:加速稳健特征”,其中介绍了一种名为 SURF 的新算法。顾名思义,它是 SIFT 的加速版本。

在 SIFT 中,Lowe 使用高斯差分来近似高斯拉普拉斯算子,以寻找尺度空间。SURF 更进一步,使用盒式滤波器近似 LoG。下图显示了这种近似的演示。这种近似的一个巨大优势是,借助积分图像可以轻松计算与盒式滤波器的卷积。并且可以并行针对不同的尺度进行计算。此外,SURF 依赖于 Hessian 矩阵的行列式来确定尺度和位置。

image

对于方向分配,SURF 在大小为 6s 的邻域中使用水平和垂直方向的小波响应。还应用了适当的高斯权重。然后将它们绘制在如下面的图像中给出的空间中。通过计算 60 度角的滑动方向窗口内的所有响应之和来估计主要方向。有趣的是,可以使用积分图像在任何尺度上非常容易地找出小波响应。对于许多应用来说,不需要旋转不变性,因此不需要找到这个方向,这加快了处理速度。SURF 提供了一种名为 Upright-SURF 或 U-SURF 的功能。它可以提高速度,并且对于 \(\pm 15^{\circ}\) 是稳健的。OpenCV 支持这两种模式,具体取决于标志 upright。如果它为 0,则计算方向。如果它为 1,则不计算方向,并且速度更快。

image

对于特征描述,SURF 使用水平和垂直方向的小波响应(同样,积分图像的使用使事情变得更容易)。在关键点周围取一个大小为 20sX20s 的邻域,其中 s 是大小。它被分成 4x4 个子区域。对于每个子区域,获取水平和垂直小波响应,并形成如下向量,\(v=( \sum{d_x}, \sum{d_y}, \sum{|d_x|}, \sum{|d_y|})\)。当表示为向量时,它给出了具有总共 64 维的 SURF 特征描述符。维度越低,计算和匹配的速度越快,但提供更好的特征区分度。

为了获得更强的区分度,SURF 特征描述符具有扩展的 128 维版本。分别针对 \(d_y < 0\) 和 \(d_y \geq 0\) 计算 \(d_x\) 和 \(|d_x|\) 的总和。类似地,\(d_y\) 和 \(|d_y|\) 的总和根据 \(d_x\) 的符号进行拆分,从而使特征数量加倍。它没有增加太多计算复杂度。OpenCV 通过将标志 extended 的值设置为 0 和 1 分别支持 64 维和 128 维(默认值为 128 维)

另一个重要的改进是使用拉普拉斯算子的符号(Hessian 矩阵的迹)作为底层兴趣点。它不会增加计算成本,因为它已经在检测期间计算过了。拉普拉斯算子的符号区分了深色背景上的明亮斑点与相反的情况。在匹配阶段,我们仅比较具有相同对比度类型的特征(如下面的图像所示)。这种最小的信息允许更快的匹配,而不会降低描述符的性能。

image

简而言之,SURF 添加了许多功能以提高每个步骤的速度。分析表明,它比 SIFT 快 3 倍,而性能与 SIFT 相当。SURF 擅长处理模糊和旋转的图像,但不擅长处理视点变化和光照变化。

OpenCV 中的 SURF

OpenCV 提供了与 SIFT 类似的功能。您可以初始化一个 SURF 对象,其中包含一些可选条件,例如 64/128 维描述符、Upright/Normal SURF 等。所有细节都在文档中得到了很好的解释。然后,正如我们在 SIFT 中所做的那样,我们可以使用 SURF.detect()、SURF.compute() 等来查找关键点和描述符。

首先,我们将看到一个简单的演示,演示如何查找 SURF 关键点和描述符并绘制它。所有示例都在 Python 终端中显示,因为它与 SIFT 相同。

>>> img = cv.imread('fly.png', cv.IMREAD_GRAYSCALE)
# 创建 SURF 对象。您可以在此处或稍后指定参数。
# 在这里,我将 Hessian 阈值设置为 400
>>> surf = cv.xfeatures2d.SURF_create(400)
# 直接查找关键点和描述符
>>> kp, des = surf.detectAndCompute(img,None)
>>> len(kp)
699
CV_EXPORTS_W Mat imread(const String &filename, int flags=IMREAD_COLOR_BGR)
从文件加载图像。

1199 个关键点太多,无法在图片中显示。我们将其减少到大约 50 个,以将其绘制在图像上。匹配时,我们可能需要所有这些特征,但现在不需要。所以我们增加了 Hessian 阈值。

# 检查当前的 Hessian 阈值
>>> print( surf.getHessianThreshold() )
400.0
# 我们将其设置为 50000。请记住,这只是为了在图片中表示。
# 在实际情况下,最好使用 300-500 的值
>>> surf.setHessianThreshold(50000)
# 再次计算关键点并检查其数量。
>>> kp, des = surf.detectAndCompute(img,None)
>>> print( len(kp) )
47

它小于 50。让我们将其绘制在图像上。

>>> img2 = cv.drawKeypoints(img,kp,None,(255,0,0),4)
>>> plt.imshow(img2),plt.show()
void drawKeypoints(InputArray image, const std::vector< KeyPoint > &keypoints, InputOutputArray outImage, const Scalar &color=Scalar::all(-1), DrawMatchesFlags flags=DrawMatchesFlags::DEFAULT)
绘制关键点。

请参阅下面的结果。您可以看到 SURF 更像是一个斑点检测器。它检测蝴蝶翅膀上的白色斑点。您可以使用其他图像对其进行测试。

image

现在我想应用 U-SURF,这样它就不会找到方向。

# 检查 upright 标志,如果为 False,则将其设置为 True
>>> print( surf.getUpright() )
False
>>> surf.setUpright(True)
# 重新计算特征点并绘制它
>>> kp = surf.detect(img,None)
>>> img2 = cv.drawKeypoints(img,kp,None,(255,0,0),4)
>>> plt.imshow(img2),plt.show()

请参阅下面的结果。所有方向都显示在同一方向上。它比以前更快。如果您正在处理方向不是问题的案例(如全景拼接)等,则此方法更好。

image

最后,我们检查描述符大小,如果它只有 64 维,则将其更改为 128。

# 查找描述符的大小
>>> print( surf.descriptorSize() )
64
# 这意味着标志“extended”为 False。
>>> surf.getExtended()
False
# 因此我们将其设置为 True 以获得 128 维描述符。
>>> surf.setExtended(True)
>>> kp, des = surf.detectAndCompute(img,None)
>>> print( surf.descriptorSize() )
128
>>> print( des.shape )
(47, 128)

剩余部分是匹配,我们将在另一章中进行。