SIFT尺度不变特征变换
理论
像Harris这样的拐角检测器,它们是旋转不变的,即使图像旋转了,也可以找到相同的角,因为转角在旋转的图像中也仍然是转角。但是缩放呢?如果缩放图像,则拐角可能不是角;例如,检查下面的简单图像,在同一窗口中放大了小窗口中小图像中的拐角时,该角是平坦的;因此,Harris拐角不是尺度不变的。
D.Lowe提出了尺度不变特征变换(SIFT),该算法提取关键点并计算其描述算符, SIFT算法主要包括四个步骤。
1. 尺度空间极值检测
不能使用相同的窗口来检测具有不同比例的关键点,即便小拐角可以,但是要检测更大的拐角,将需要更大的窗口;为此,使用了比例空间滤波,在其中,找到具有各种σ值的图像的高斯拉普拉斯算子,LoG用作斑点检测器,可检测由于σ的变化而导致的各种大小的斑点。简而言之,σ用作缩放参数;例如在上图中,低σ的高斯核对于较小的拐角给出较高的值,而高σ的高斯核对于较大的拐角而言非常合适;因此可以找到整个尺度和空间上的局部最大值,这给了(x, y, σ)值的列表,这意味着在(x, y)在σ尺度上有一个潜在的关键点。
但是这种LoG代价昂贵,因此SIFT算法使用的是高斯差值,它是LoG的近似值;高斯差是作为具有两个不同σ的图像的高斯模糊差而获得的,设为σ和kσ。此过程是针对高斯金字塔中图像的不同八度(在特定尺寸(长宽)下,经不同高斯核模糊的图像的集合)完成的。如下图所示:
一旦找到该DoG,便会在图像上搜索比例和空间上的局部极值;例如,将图像中的一个像素与其8个相邻像素以及下一个比例的9个像素和前一个比例的9个像素进行比较,如果是局部极值,则可能是关键点;从根本上说,关键点是最好的代表,如下图所示:
对于不同的参数,文档给出了一些经验数据,可以概括为:octaves=4,缩放尺度=5,初始σ=1.6,
k
=
2
k=\sqrt{2}
k=2等作为最佳值。
2. 关键点定位
一旦找到潜在的关键点位置,就必须对其进行优化以获取更准确的结果;使用标度空间的泰勒级数展开来获得更精确的极值位置,如果该极值处的强度小于阈值(论文中为0.03),则将其拒绝;在OpenCV DoG中,此阈值称为ContrastThreshold,它对边缘的响应较高,因此也需要删除边缘。
为此,使用类似于哈里斯拐角检测器的概念,使用2x2的Hessian矩阵(H)计算主曲率;从哈里斯拐角检测器知道,对于边缘,一个特征值大于另一个特征值。因此,这里使用了一个简单的函数;如果该比率大于一个阈值(在OpenCV中称为edgeThreshold),则该关键点将被丢弃,论文上写的值为10;因此,它消除了任何低对比度的关键点和边缘关键点,剩下的就是很可能的目标点。
3. 方向分配
现在,将方向分配给每个关键点,以实现图像旋转的不变性;根据比例在关键点位置附近采取邻域,并在该区域中计算梯度大小和方向,创建一个具有36个覆盖360度的bin的方向直方图,即每10度一个方向,总共36个方向,直方图的峰值则代表了该关键点处邻域梯度的主方向,即作为该关键点的方向;考虑离极值点近的方向的像素点对极值点影响较大,做一个高斯滤波(1.5σ),这样就有一个权重,计算方向直方图时,需要用一个参数等于关键点所在尺度1.5倍的高斯权重窗对方向直方图进行加权。提取直方图中的最高峰,并且将其超过80%的任何峰也视为计算方向;它创建位置和比例相同但方向不同的关键点,有助于匹配的稳定性。
4. 关键点描述
在关键点周围采用了16x16的邻域,它分为16个4x4大小的子块,对于每个子块,创建8 bin方向直方图,因此共有128个bin值可用;它被表示为形成关键点描述符的向量,除此之外,还采取了几种措施来实现针对照明变化,旋转等的鲁棒性。
5. 关键点匹配
通过识别两个图像的最近邻,可以匹配两个图像之间的关键点;但在某些情况下,第二个最接近的匹配可能非常接近第一个,它可能是由于噪音或其他原因而发生的;在那种情况下,采用最接近距离与第二最接近距离之比,如果大于0.8,将被拒绝;根据论文,它可以消除大约90%的错误匹配,而仅丢弃5%的正确匹配。
OpenCV中的SIFT
从关键点检测开始并进行绘制,首先必须构造一个sift对象,将不同的参数传递给它,这些参数是可选的;函数sift.detect()
在图像中找到关键点,如果只想搜索图像的一部分,则可以通过掩码,每个关键点是一个特殊的结构,具有许多属性,例如其(x,y)坐标,有意义的邻域的大小,指定其方向的角度,指定关键点强度的响应;函数cv.drawKeyPoints()
在关键点的位置绘制小圆圈,如果将标志cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS
传递给它,它将绘制一个具有关键点大小的圆,甚至会显示其方向。
现在要计算描述符,OpenCV提供了两种方法。 1. 由于已经找到关键点,因此可以调用sift.compute()
,该函数根据找到的关键点来计算描述符,例如:kp,des = sift.compute(gray,kp)。 2. 如果找不到关键点,则可以使用ke, des = sift.detectAndCompute(gray, None)
在单步骤中直接找到关键点和描述符,kp将是一个关键点列表,而des是一个形状为NumberOfKeyPoints * 128的数字数组。
sift.py
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('./OpenCV/blox.png')
img1 = img.copy()
img2 = img.copy()
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
sift = cv.SIFT_create()
kp1, des1 = sift.detectAndCompute(gray,None)
print(kp1, des1)
kp = sift.detect(gray, None)
img1 = cv.drawKeypoints(gray, kp, img1)
img2 = cv.drawKeypoints(gray, kp, img2, flags = cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
kp2, des2 = sift.compute(gray, kp)
print(kp2, des2)
images = [img, img1, img2]
titles = ['Original', 'KeyPoints', 'RichKeyPoints']
for i in range(3):
plt.subplot(1, 3, i + 1)
plt.imshow(cv.cvtColor(images[i], cv.COLOR_BGR2RGB))
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()
出现错误:
sift = cv.xfeatures2d.SIFT_create()
AttributeError: module 'cv2.cv2' has no attribute 'xfeatures2d'
OpenCV高版本可将 sift = cv2.xfeatures2d.SIFT_create()
改为 sift = cv2.SIFT_create()
,或将OpenCV版本降至3.4.2.17
SURF简介(加速的强大功能)
理论
SIFT用于关键点检测和描述符,但相对缓慢,H .Tuytelaars,T. and Van Gool,L,发表了另一篇论文,“SURF:加速健壮的特征”,引入了一种名为“SURF”的新算法,它是一个加速版本的SIFT。
SURF使用Box Filter近似LoG,下图显示了这种近似值的演示,这种近似的一大优点是,借助积分图像可以轻松地计算出盒滤波器的卷积,并且可以针对不同规模并行执行;SURF还依赖于Hessian矩阵的行列式来确定尺度和位置。
对于方向分配,SURF在水平和垂直方向上对大小为6s的邻域使用小波响应,适当的高斯权重也适用于它;然后将它们绘制在下图所示的空间中通过计算角度为60度的滑动方向窗口内所有响应的总和,可以估算主导方向;小波响应可以很容易地使用积分图像在任何规模下发现,对于许多应用,不需要旋转不变性,因此无需查找此方向,从而加快了过程;SURF提供了称为Upright-SURF或U-SURF的功能,它提高了速度,并具有高达
±
1
5
∘
\pm 15^{\circ}
±15∘的鲁棒性;OpenCV根据标志支持两种方式,如果为0,则计算方向,如果为1,则不计算方向并且速度更快。
对于功能描述,SURF在水平和垂直方向上使用小波响应(同样,使用积分图像使事情变得更容易);在s是大小的关键点周围采用大小为20sX20s的邻域,它分为4x4子区域,对于每个子区域,获取水平和垂直小波响应,并像这样形成向量:
v
=
(
∑
d
x
,
∑
d
y
,
∑
∣
d
x
∣
,
∑
∣
d
y
∣
)
v =(\sum dx,\sum dy,\sum |dx|,\sum |dy|)
v=(∑dx,∑dy,∑∣dx∣,∑∣dy∣);当表示为向量时,这将为SURF特征描述符提供总共64个维度,尺寸越小,计算和匹配速度越快,特征的区分性越好。
为了更加独特,SURF特征描述符具有扩展的128维版本;dx和|dx|的和分别针对dy < 0和dy ≥ 0进行计算,同样dy和|dy|的总和分别针对dy < 0和dy ≥ 0进行计算,从而使特征数量加倍,它不会增加太多的计算复杂性;OpenCV通过将分别为64-dim和128-dim(默认值为128-dim)的标志的值设置为0和1来支持这两者(默认为128-dim)。
另一个重要的改进是对基础兴趣点使用了Laplacian算符(海森矩阵的迹),它不增加计算成本,因为它已经在检测期间进行了计算,拉普拉斯算子的标志将深色背景上的明亮斑点与相反的情况区分开;在匹配阶段,仅比较具有相同对比类型的特征(如下图所示),这些最少的信息可加快匹配速度,而不会降低描述符的性能。
SURF添加了许多功能来提高每一步的速度,分析表明,它的速度是SIFT的3倍,而性能却与SIFT相当;SURF擅长处理具有模糊和旋转的图像,但不擅长处理视点变化和照明变化。
OpenCV中的SURF
可以使用一些可选条件(例如64 / 128-dim描述符,Upright / Normal SURF等)来启动SURF对象,然后,就像在SIFT中所做的那样,可以使用SURF.detect()
,SURF.compute()
等来查找关键点和描述符。
首先示例简单演示如何找到SURF关键点和描述符并进行绘制,可能找到的SURF关键点过多,可以将其减少到50左右以绘制在图像上,匹配时,可能需要所有这些特征,但现在不需要,因此增加了海森阈值;再计算关键点并检查其数量,小于50,在图像上绘制它;SURF更像是斑点检测器,可检测到图像上的斑点;可以应用U-SURF,以便它不会找到方向,所有的方向都显示在同一个方向上,它比以前更快了;一些情况下,方向不是一个问题(如全景拼接)等,这是更好的。最后,检查描述符的大小,如果只有64维,则将其更改为128。
surf.py
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('./OpenCV/blox.png')
# 创建SURF对象,可以在此处或以后指定参数,这里设置海森矩阵的阈值为400
surf = cv.SURF_create(400)
kp, des = surf.detectAndCompute(img, None)
print(len(kp))
# 检查海森矩阵阈值
print(surf.getHessianThreshold())
# 将其设置为50000,它仅用于表示图片
# 实际情况下,最好将值设为300-500
surf.setHessianThreshold(50000)
# 再次计算关键点并检查其数量
kp, des = surf.detectAndCompute(img, None)
print(len(kp))
img2 = cv.drawKeypoints(img, kp, None, (255, 0, 0), 3)
# 检查flag标志,如果为False,则将其设置为True
if not surf.getUpright():
surf.setUpright(True)
kp = surf.detect(img, None)
img3 = cv.drawKeypoints(img, kp, None, (0, 0, 255), 3)
images = [img, img2, img3]
titles = ['Original', 'Surf', 'Surf Upright']
for i in range(3):
plt.subplot(1, 3, i + 1)
plt.imshow(cv.cvtColor(images[i], cv.COLOR_BGR2RGB))
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()
print(surf.descriptorSize())
# 输出为64表示flag"extened"为False
if not surf.getExtended():
surf.setExtended(True)
kp, des = surf.detectAndCompute(img, None)
print(surf.descriptorSize())
出现错误:
surf = cv.SURF_create(400)
AttributeError: module 'cv2.cv2' has no attribute 'SURF_create'
将OpenCV版本降至3.4.2.17,建议除了下降opencv-python的版本之外,同时安装与opencv-python同样版本的opencv-contrib-python。
pip install opencv-contrib-python==3.4.2.17
pip install opencv-python==3.4.2.17
sift出现错误时,我使用 sift = cv2.SIFT_create()
可以了;但surf的错误,我安装3.4.2.17版本的opencv-python和opencv-contrib-python后,surf和sift都不可用了。
学习来源:OpenCV-Python中文文档、
SIFT算法原理(3)-确定关键点的主方位,构建关键点描述符、
关于python-opencv的SIFT,SURF算法调用错误(AttributeError: module ‘cv2.cv2’ has no attribute ‘xfeatures2d’)