前面的章节中,我们提到了角点检测,例如Harris角点,他们是旋转不变的,因为,图像无论如何旋转,其角点特性不会发生改变,所以这类特征也称为旋转不变特征。但是如果图像缩放,原本在小图像中一定的窗口下是角点,放大后,却是平坦区域,即不是角点。如下图:
D.Lowe, Distinctive Image Features from Scale-Invariant Keypoints
步骤
从上图可以看出,我们不能用同样的窗口来检测不同尺度的关键点,对于小角点是可以的。,但为了探测更大的拐角,我们需要更大的窗口。为此,使用尺度空间过滤。文中给出了不同 σ σ σ值的高斯拉普拉斯(LoG
)算子应用于图像。LoG
作为一个blob检测器,它检测由于 σ σ σ变化而产生的各种大小的blob
。简而言之, σ σ σ作为一个尺度参数。例如上图中, σ σ σ值低的高斯核对于小角的值较高,而 σ σ σ值高的高斯核对于大拐角的值较好。因此,我们可以在尺度和空间上找到局部极大值,这给了我们一个 ( x , y , σ ) (x,y, σ) (x,y,σ)值的列表,这意味着在 σ σ σ尺度上 ( x , y ) (x,y) (x,y)有一个潜在的关键点。
但是这个LoG
还是比较耗时的,所以SIFT算法使用了近似于LoG
的高斯差分。高斯差分图像是通过使用不同的 σ \sigma σ对图像进行模糊得到的高斯模糊图像再进行差分获得。分别设它们为 σ σ σ和 k σ kσ kσ。这一过程是对不同阶数的高斯金字塔图像进行的。如下图所示:
一旦完成了DoG
计算,就会在图像的不同尺度和空间上搜索局部极值,例如,一个像素会与当前尺度的8邻域、上一尺度的9个像素和下一个尺度的9个像素进行比较,如果是局部极值,这就有可能是关键点,如下图所示
针对不同的参数,本文给出了一些经验数据,可归纳为:高斯金字塔层级数: 4 4 4, 每一层的尺度数 5 5 5, 初始 σ = 1.6 \sigma=1.6 σ=1.6, k = 2 k=\sqrt{2} k=2等等。
一旦找到了潜在的关键点位置,就必须对其进行细化以获得更准确的结果。他们使用尺度空间的泰勒级数展开来获得更准确的极值位置,如果该极值处的强度小于阈值(根据论文的说法为0.03),则会被拒绝。这个阈值在OpenCV中称为contrastThreshold
DoG对边缘有较高的响应,因此也需要去除边缘。为此,使用了类似于Harris
角点检测器的概念。他们使用2x2 Hessian
矩阵(H)来计算主曲率。我们从 Harris
角点检测器得知,对于边,一个特征值比另一个大。这里他们用了一个简单的函数,
如果这个比值大于一个阈值 ( OpenCV 中称为 edgeThreshold ),则该关键点将被丢弃。论文中是10
。
因此,它消除了任何低对比度的关键点和边缘关键点,剩下的是强烈的兴趣点。
现在为每个关键点分配方向,以实现图像旋转的不变性。根据尺度在关键点位置周围取邻域,并在该区域内计算梯度大小和方向。创建36个 bins 覆盖 360度 的梯度方向直方图(采用梯度幅值和高斯加权圆窗口加权, σ σ σ 等于关键点尺度的1.5倍)。取直方图中最高的峰值,任何超过其 80%
的峰值也被视为计算方向。它创建的关键点具有相同的位置和尺度,但方向不同。它有助于匹配的稳定性。
现在关键点描述子已经创建。取关键点邻域的 16*16
区域,它被分为16
个 4x4
大小的子块。对于每个子块,创建8
个bin 方向直方图。所以总共有128
个 bin 值可用。将其表示为一个向量,形成关键点描述子。除此之外,还采取了一些措施来实现对光照变化、旋转等的鲁棒性。
两个图像之间的关键点通过识别它们最近的邻居来匹配。但在某些情况下,第二个最接近的匹配可能非常接近第一个。这可能是由于噪音或其他原因造成的。在这种情况下,取最近距离与次近距离的比值。如果大于0.8,则拒绝它们。根据这篇论文,它消除了大约90%的错误匹配,而只丢弃了5%的正确匹配。
这是对SIFT算法的总结。要了解更多细节和理解,强烈建议阅读原文。
import numpy
import cv2
# 读图像
img = cv2.imread("assets/home.jpg")
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 创建 SIFT 检测器
sift = cv2.SIFT_create()
# 检测 SIFT 关键点
kp = sift.detect(img_gray, None)
# 另一种:检测 SIFT 关键点并 计算特征描述
# kp, descriptors = sift.detectAndCompute(img_gray, None)
# 画出所有关键点
# img = cv2.drawKeypoints(img_gray, kp, img)
# 画出尺度方向等丰富的信息
img = cv2.drawKeypoints(img_gray, kp, img, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
cv2.imshow("result", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.SIFT_create( [, nfeatures[, nOctaveLayers[, contrastThreshold[, edgeThreshold[, sigma]]]]] ) -> retval
cv2.SIFT_create( nfeatures, nOctaveLayers, contrastThreshold, edgeThreshold, sigma, descriptorType ) -> retval
创建 SIFT 检测器
- nfeatures: 最好关键点的数量,SIFT里用局部对比度作为排序的分数。
- nOctaveLayers: 每一个金字塔层级里 层的数量,论文中为 3; 金字塔层级根据图像分辨率自动推导。
- contrastThreshold: 对比度阈值,过滤那些对比度比较小的关键点,阈值越大,剩下的关键点越少。
- edgeThreshold: 用于过滤那些类似边缘的特征。不同于 contrastThreshold,阈值越大,过滤的越少。
- sigma: 在原始图像中 高斯核的 sigma, 如果图像质量较差,减小该数值。
- descriptorType: 描述子类型。
detect( image[, mask] ) -> keypoints
detect( images[, masks] ) -> keypoints
在应用的时候,根据
SIFT_Create
创建的对象,再调用detect
检测关键点
- image: 待检测的图像;
- keypoints: 检测到的关键点,
- mask: 掩码图像,ROI
compute( image, keypoints[, descriptors] ) -> keypoints, descriptors
compute( images, keypoints[, descriptors] ) -> keypoints, descriptors
在应用的时候,根据
SIFT_Create
创建的对象,再调用compute
检测关键点
- image: 待计算的图像
- keypoints: 输入的关键点,关键点因为无法计算特征的时候被删除,也可以添加;
- descriptors: 计算的特征描述
detectAndCompute( image, mask[, descriptors[, useProvidedKeypoints]] ) -> keypoints, descriptors
检测关键点,并计算特征描述,参数描述见上述两个函数。
cv2.drawKeypoints( image, keypoints, outImage[, color[, flags]] ) -> outImage
绘制关键点
- image: 源图像
- keypoints: 源图像对应的关键点
- outImage: 输出的图像,
- color: 颜色
- flags: Python API, flags are modified as cv2.DRAW_MATCHES_FLAGS_DEFAULT, cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS, cv2.DRAW_MATCHES_FLAGS_DRAW_OVER_OUTIMG, cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS
- angle: float
- class_id: int, 类别
- octave: int, 金字塔层级
- pt: Point2f,坐标点
- response: float 响应值
- size: float 有效邻域尺寸