Shi-Tomasi角点检测 & SIFT
一、Shi-Tomasi角点检测
在本小节中,我们将会了解到Shi-Tomasi角点检测的基本概念,用到以下函数:cv.goodFeaturesToTrack()
- 用法如下:
- cv.goodFeaturesToTrack( image, maxCorners, qualityLevel, minDistance[, corners[, ask[, blockSize[, useHarrisDetector[, k]]]]] ) -> corners
- cv.goodFeaturesToTrack( image, maxCorners, qualityLevel, minDistance, mask, blockSize, gradientSize[, corners[, useHarrisDetector[, k]]] ) -> corners
- cv.goodFeaturesToTrackWithQuality( image, maxCorners, qualityLevel, minDistance, mask[, corners[, cornersQuality[, blockSize[, gradientSize[, useHarrisDetector[, k]]]]]] ) -> corners,
1. 概念
在上一章中,我们看到了 Harris Corner Detector。 1994 年晚些时候,J. Shi 和 C. Tomasi 在他们的论文 Good Features to Track 中对其进行了小修改,与 Harris Corner Detector 相比,它显示出更好的结果。 Harris Corner Detector 中的评分函数由下式给出:
但是shi却给出了如下函数进行评分:
如果大于阈值,则将其视为角点。 如果我们像在 Harris Corner Detector 中那样将其绘制在 空间中,我们会得到如下图像:
从图中可以看出,只有当 和 大于最小值 时,才被认为是角点(绿色区域)。
2. 角点检测
OpenCV 有一个函数,cv.goodFeaturesToTrack()。 它通过 Shi-Tomasi 方法(也可以指定为 Harris 角点检测)在图像中找到 N 个最强角点。 像往常一样,图像应该是灰度图像。 然后指定要查找的角点数。 然后你指定质量级别,这是一个介于 0-1 之间的值,若角点的值小于此级别则会被抑制。 然后我们提供检测到的角点之间的最小欧几里德距离。
有了所有这些信息,该函数就可以找到图像中的角点。 所有低于质量水平的角都被抑制。 然后它根据质量按降序对剩余的角进行排序。 然后函数取第一个最强角,扔掉最小距离范围内的所有附近角,并返回 N 个最强角。
import cv2 as cv
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
%matplotlib inline
def cv_show(name, img):
cv.imshow(name, img)
cv.waitKey(0)
cv.destroyAllWindows()
def compare(imgs):
# for i in range(len(imgs)):
# imgs[i][:,-3:-1,:] = [255,255,255]
res = np.hstack(imgs)
cv_show('Compare', res)
img = cv.imread('chessboard.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.xticks([]),plt.yticks([])
plt.imshow(img)
plt.show()
三、尺度不变特征转换(SIFT)
在本小节中,我们会了解到SIFT算法概念以及找出SIFT关键点和描述符的方法
1. 概念
在前几章中,我们看到了一些角点检测器,如 Harris 等。它们是旋转不变的,这意味着即使图像旋转,我们也可以找到相同的角点。 很明显,因为角在旋转图像中仍然是角。 但是缩放呢? 如果图像被缩放,一个角可能不是一个角。 例如,检查下面的简单图像。 当在同一窗口中放大时,小窗口内的小图像中的角是平坦的。 所以哈里斯角不是尺度不变的。
也即是当图像整体被放大时,原位置的图像像素应该还保留着角点的特征
2004 年,不列颠哥伦比亚大学的 D.Lowe 在他的论文 Distinctive Image Features from Scale-Invariant Keypoints 中提出了一种新算法,即 Scale Invariant Feature Transform (SIFT),该算法提取关键点并计算其描述符。 (本文易于理解,被认为是 SIFT 上可用的最佳材料。此解释只是本文的简短摘要)。
SIFT算法主要涉及四个步骤。 下面进行逐一介绍:
A. 尺度空间极值检测
从上图可以看出,我们不能使用同一个窗口来检测不同尺度的关键点。 小角可以检测, 但是要检测更大的角落,我们需要更大的窗口。 为此,使用了尺度空间过滤。 在其中,对于具有各种 σ 值的图像,找到了高斯的拉普拉斯算子。 LoG 充当斑点检测器,可检测由于 σ 的变化而产生的各种大小的斑点。 简而言之,σ 充当缩放参数。 例如,在上图中,具有低 σ 的高斯核为小角提供了高值,而具有高 σ 的高斯核非常适合较大的角。 因此,我们可以找到跨尺度和空间的局部最大值,这为我们提供了 (x,y,σ) 值的列表,这意味着在 σ 尺度的 (x,y) 处存在潜在的关键点。
但是这个 LoG 有点耗时耗力,所以 SIFT 算法使用了高斯差值,它是 LoG 的近似值。 高斯差值作为具有两个不同 σ 的图像的高斯模糊差值得到,分别为 σ 和 kσ。 这个过程是针对高斯金字塔中图像的不同倍频程完成的。 如下图所示:
一旦找到这个DoG,就会在图像上搜索尺度和空间上的局部极值。 例如,将图像中的一个像素与其 8 个相邻像素以及下一个比例的 9 个像素和前一个比例的 9 个像素进行比较。 如果是局部极值,就是潜在的关键点。 这基本上意味着关键点在该比例中得到最好的表示。 如下图所示:
关于不同的参数,本文给出了一些经验数据,可以总结为,八度音阶数= 4,音阶数= 5,初始σ=1.6,k=等作为最佳值
B. 关键点定位
一旦找到潜在的关键点位置,就必须对其进行细化以获得更准确的结果。 他们使用尺度空间的泰勒级数扩展来获得更准确的极值位置,如果该极值处的强度小于阈值(根据论文为 0.03),则将其抑制。
这个阈值在 OpenCV 中称为 contrastThreshold,DoG 对边缘的响应较高,因此也需要去除边缘。 为此,使用了类似于 Harris 角点检测器的概念。 他们使用 2x2 Hessian 矩阵 (H) 来计算主曲率。 我们从 Harris 角点检测器中得知,对于边缘,一个特征值大于另一个特征值。 所以在这里他们使用了一个简单的函数,如果这个比率大于一个阈值,在 OpenCV 中称为 edgeThreshold,那么这个关键点就会被丢弃。 它在论文上以 10 的形式给出。 所以它消除了任何低对比度的关键点和边缘关键点,剩下的是强烈的兴趣点。
C. 方向分配
现在为每个关键点分配一个方向,以实现图像旋转的不变性。 根据尺度在关键点位置周围取一个邻域,并在该区域计算梯度大小和方向。 创建一个具有 36 个 bin 覆盖 360 度的方向直方图(它由梯度幅度和高斯加权圆形窗口加权,σ 等于关键点尺度的 1.5 倍)。 任何高于直方图中的最高峰 80% 的峰也被认为是计算方向。 它创建具有相同位置和比例但方向不同的关键点。 它有助于匹配的稳定性。
D. 关键点描述符
现在创建了关键点描述符。 取关键点周围的 16x16 邻域。 它分为 16 个 4x4 大小的子块。 对于每个子块,创建 8 个 bin 方向直方图。 因此共有 128 个 bin 值可用。 它被表示为一个向量以形成关键点描述符。 除此之外,还采取了一些措施来实现对光照变化、旋转等的鲁棒性。
E. 关键点匹配
通过识别它们最近的邻居来匹配两个图像之间的关键点。 但在某些情况下,第二个最接近的匹配可能非常接近第一个。 这可能是由于噪音或其他一些原因而发生的。 在这种情况下,采用最近距离与次近距离的比率。 如果大于 0.8,则抑制。 根据论文,它消除了大约 90% 的错误匹配,而仅丢弃了 5% 的正确匹配。
这是SIFT算法的总结。 有关更多详细信息和理解,强烈建议阅读原始论文。
2. OpenCV中的特征匹配
现在让我们看看 OpenCV 中可用的 SIFT 功能。 请注意,这些以前仅在 opencv contrib repo 中可用,但专利于 2020 年到期。因此它们现在包含在主 repo 中。 让我们从关键点检测开始并绘制它们。 首先,我们必须构造一个 SIFT 对象。 我们可以将不同的参数传递给它,这些参数是可选的,它们在文档中有很好的解释。
- 将关键点绘制在灰度图像上: cv.drawKeypoints( image, keypoints, outImage[, color[, flags]] ) -> outImage
img = cv.imread('lena.png')
gray= cv.cvtColor(img,cv.COLOR_BGR2GRAY)
sift = cv.SIFT_create()
kp = sift.detect(gray,None)
cv.drawKeypoints(gray,kp,img)
# gray = cv.merge([gray,gray,gray])
# compare([gray,img])
array([[[162, 162, 162],
[162, 162, 162],
[162, 162, 162],
...,
[170, 170, 170],
[155, 155, 155],
[128, 128, 128]],
[[162, 162, 162],
[162, 162, 162],
[162, 162, 162],
...,
[170, 170, 170],
[155, 155, 155],
[128, 128, 128]],
[[162, 162, 162],
[162, 162, 162],
[162, 162, 162],
...,
[170, 170, 170],
[155, 155, 155],
[128, 128, 128]],
...,
[[ 43, 43, 43],
[ 43, 43, 43],
[ 50, 50, 50],
...,
[104, 104, 104],
[100, 100, 100],
[ 98, 98, 98]],
[[ 44, 44, 44],
[ 44, 44, 44],
[ 55, 55, 55],
...,
[104, 104, 104],
[105, 105, 105],
[108, 108, 108]],
[[ 44, 44, 44],
[ 44, 44, 44],
[ 55, 55, 55],
...,
[104, 104, 104],
[105, 105, 105],
[108, 108, 108]]], dtype=uint8)
sift.detect() 函数找到图像中的关键点。 如果您只想搜索图像的一部分,则可以传入掩码。 每个关键点都是一个特殊的结构,它具有许多属性,例如它的 (x,y) 坐标、有意义的邻域的大小、指定其方向的角度、指定关键点强度的响应等。
OpenCV 还提供了 cv.drawKeyPoints() 函数,它在关键点的位置上绘制小圆圈。 如果您将标志 cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS 传递给它,它将绘制一个关键点大小的圆,甚至会显示其方向。 请参见下面的示例:
cv.drawKeypoints(gray,kp,img,flags=cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
gray = cv.merge([gray,gray,gray])
compare([gray, img])
现在要计算描述符,OpenCV 提供了两种方法。 由于您已经找到了关键点,您可以调用 sift.compute() 从我们找到的关键点计算描述符。 例如:kp,des = sift.compute(gray,kp) 如果没有找到关键点,直接使用函数 sift.detectAndCompute() 一步找到关键点和描述符。
img = cv.imread('lena.png')
gray= cv.cvtColor(img,cv.COLOR_BGR2GRAY)
sift = cv.SIFT_create()
kp, des = sift.detectAndCompute(gray,None)
res = img.copy()
cv.drawKeypoints(img,kp,res)
compare([img,res])
这里 kp 将是一个关键点列表,而 des 是一个形状为(关键点数)×128 的 numpy 数组。 所以我们得到了关键点、描述符等。现在我们想看看如何匹配不同图像中的关键点。 我们将在接下来的章节中学习。