OpenCV基础教程——特征提取与描述(SURF)
介绍SURF(Speeded-Up Robust Features)
目标:
本章节你需要学习以下内容:
*我们将看到SURF的基础知识
*我们将在OpenCV中看到SURF功能
代码解读
1、理论
在上一节中我们讲述了我们在上一节中运用SIFT算法来进行关键点检测以及描述过程。然而这种算法运行速度较慢,在实际应用中仍需寻求更快捷的方法。于2006年,H.Bay,Tuytelaars,T.与L.Van Gool共同提出了一种称为SURF(加速稳健特征)的新算法。与名称相似的是一个称为加速版SIFT的新算法。
Within the SIFT framework, Lowe adopted Difference of Gaussians (DoG) to approximate Laplacian of Gaussian (LoG) during scale space construction. The SURF algorithm further refines this approach by using box filters to approximate LoG. As demonstrated in the figure, such approximation is visually represented. The application of integral images streamlines convolution computations, which is a significant advantage of box filters. Notably, this computation method allows for parallel processing across different scale spaces. Similarly, the SURF algorithm calculates keypoint scales and positions based on the determinant of the Hessian matrix.

为了保持特征矢量的不变形特性,在每个特征点上都需要为其分配一个主导方向。基于特征点为中心,并以 6s(s 表示特征点尺度)为半径绘制圆形区域,在此区域内对图像执行 Harr 小波变换运算。这种方法本质上等同于对图像进行梯度运算,在此基础上利用积分图像技术可显著提升梯度计算效率;进而计算主方向值时,则需设计以方向为中心并具有 60 度张角的扇形滑动窗口,并以此步长约为 0.2 弧度依次旋转该滑动窗口;在此过程中对窗口内图像的 Harr 小波响应值进行累加求和操作;最终确定的最大 Haar 响应累加值所对应的方向即为主方向;在实际应用中由于很多场景并不需要考虑旋转不变性特性,则省却了计算主方向这一过程时,则算法运行速度更快;此外 OpenCV 提供了 U-SURF 功能模块,在实现上其速度相较于传统 SURF 明显提升的同时仍能维持±15 度范围内旋转变换稳定性

在提取特征点时会生成对应的特征矢量,在图像中通过计算其Haar小波响应来确定这些特征点的位置信息。具体来说,在一个矩形区域中以每个特征点为中心沿着主要方向划分为20×20的小块(单位为像素),并在每个小块内应用尺寸为2×2的小型Haar小波模板来进行响应检测工作;接着统计每个子块内的响应值并将其汇总到向量v中(v由四个部分构成:水平方向上的总变化\sum{d_x}、垂直方向上的总变化\sum{d_y}以及它们的绝对值\sum{|d_x|}和\sum{|d_y|})。该描述符具有64个维度;通过降低空间维度来加快后续处理速度的同时也能提高区分度
为了增强特征点的独特性,在 SURF 算法中还提供了一个增强版的 128 维特征描述符。对于不同的 d_y 值(正或负),分别对相应的 d_x 和其绝对值求和;同时,在计算 d_y 和 |d_x| 的时候也需要进行区分处理。这样一来虽然能够使特征数量翻倍但并未导致计算量上升。同样支持这一特性 OpenCV 在默认情况下使用 128 维表示法但可以通过将参数 extended 设置为 1` 来启用这一功能;如果希望减少维度则可以选择设置该参数为 0(此时会切换至使用 $64 维表示)。
另一项显著优化基于Hessian矩阵迹值作为潜在兴趣点引入。该方法无需额外计算成本。其特征能够清晰地区分暗场中的亮斑与反向情况。在配准过程中仅考虑同一对比程度下的特征进行匹配,并通过视觉辅助图示进一步验证了该方法的有效性。这种简洁信息不仅加速了匹配速度,并且维持了描述符的有效性。

简单来说,SURF算法运用了多种方法对每一个步骤进行改进以显著提升运行速度.研究表明,在处理相同效果的情况下,SURF的运行速度是SIFT的三分之三倍.SURF尤其擅长处理具有模糊和旋转特性的图像,但在面对视角变化和光照变化时则显得力有未逮.
2、OpenCV中的SURF
与SIFT类似,OpenCV也提供了SURF功能。为了初始化一个SURF对象,请先使用诸如64/128-dim描述符和Upright/Normal SURF等可选参数进行配置。这些详细信息已在文档中进行了详尽阐述。类似于我们在SIFT中进行的操作,在处理 SURF 时,则可以调用 SURF.detect() 和 SURF.compute() 方法来获取关键点和描述符。
首先,在这里我们将体验一个关于 SURF 关键点和描述符的学习过程,并展示一个简单的操作流程图。所有案例均展示在 Python 终端界面中,并且其本质与 SIFT 类似。
>>> img = cv.imread('fly.png',0)
# Create SURF object. You can specify params here or later.
# Here I set Hessian Threshold to 400
>>> surf = cv.xfeatures2d.SURF_create(400)
# Find keypoints and descriptors directly
>>> kp, des = surf.detectAndCompute(img,None)
>>> len(kp)
699
代码解读
大量关键点过于繁多以至于无法在一个图像中呈现。我们将关键点数量降低至约50以便将其绘制于图像上。在匹配过程中我们预期会使用所有的这些功能但目前并不需要。因此我们提高了Hessian临界值以防止模型过拟合。
# Check present Hessian threshold
>>> print( surf.getHessianThreshold() )
400.0
# We set it to some 50000. Remember, it is just for representing in picture.
# In actual cases, it is better to have a value 300-500
>>> surf.setHessianThreshold(50000)
# Again compute keypoints and check its number.
>>> 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()
代码解读
如需了解更多信息,请参考下方的内容。从功能角度来看,SURF类似于一种专门用于识别斑点的设备,在这种情况下它能够准确捕捉到昆虫翅膀上细微的白色斑块特征。该系统能够精确识别并标记出昆虫翅膀上细微的白色斑块特征,并且可以通过不同类型的图片进行分析以验证系统的适用性

现在我们尝试一下U-SURF,它不会检测关键点的方向。
# Check upright flag, if it False, set it to True
>>> print( surf.getUpright() )
False
>>> surf.setUpright(True)
# Recompute the feature points and draw it
>>> kp = surf.detect(img,None)
>>> img2 = cv.drawKeypoints(img,kp,None,(255,0,0),4)
>>> plt.imshow(img2),plt.show()
代码解读
实验结果表明,在所有测试案例中,各个关键区域的方向保持一致。相较于之前的方法而言,这种方案具有显著优势。如果您的工作对于关键点的方向并无特殊要求(例如,在全景图拼接这样的场景下),则该方法将展现出更大的效率提升。

最后,我们检查描述符大小,如果它只有64-dim,则将其更改为128。
# Find size of descriptor
>>> print( surf.descriptorSize() )
64
# That means flag, "extended" is False.
>>> surf.getExtended()
False
# So we make it to True to get 128-dim descriptors.
>>> surf.setExtended(True)
>>> kp, des = surf.detectAndCompute(img,None)
>>> print( surf.descriptorSize() )
128
>>> print( des.shape )
(47, 128)
代码解读
