英文原文:
https://www.analyticsvidhya.com/blog/2019/10/detailed-guide-powerful-sift-technique-image-matching-python/
边翻译边学习
前言
查看如下的图片集,思考它们之间的共同点:
是的,金碧辉煌的埃菲尔铁塔,每张图像都有不同的背景,从不同的角度拍摄,前景中也有不同的物体。我相信你可以轻而易举的弄清除每张图之间的差异。无论图像是以任何角度旋转、放大到仅显示一半的铁塔都没有关系。这主要是因为您见过埃菲尔铁塔的图像(甚至亲身见过)并且对它的特征有所了解。我们自然明白图像的比例或角度可能会改变,但图像所描述的对象是保持不变的。
但是机器却很难做到这一点。如果我们改变拍摄角度或比例,机器是很难识别图像中对象的。但我们可以教它们以近乎人类的水平识别图像。
本文中,我们将讨论一种图像匹配算法,该算法可识别图像中的关键特征,并将这些特征与同一对象的新图像进行匹配。
SIFT简介
SIFT(Scale Invariant Feature Transform),是计算机视觉中的一种特征检测的算法。SIFT 有助于定位图像中的局部特征,通常称为图像的 Keypoints
。这些 关键点(Keypoints)是缩放和旋转不变的,可用于各种计算机视觉应用,如图像匹配、对象检测、场景检测等。我们还可以在模型训练期间使用 SIFT 生成的关键点作为图像的特征。 SIFT 特征相对于边缘特征或 hog 特征,它的主要优势在于它们不受图像大小或方向的影响。
例如,这是埃菲尔铁塔及其较小版本的另一张图片。第一张图像中对象的关键点与第二张图像中发现的关键点相匹配。当另一个图像中的对象稍微旋转时,两个图像仍然拥有匹配的关键点:
让我们开始了解这些关键点是如何识别的,以及用于确保尺度和旋转不变性的技术是什么。广义上讲,整个过程可以分为4个部分:
- Constructing a Scale Space:确保特征与尺度无关
- Keypoint Localization:确定合适的特征或关键点
- Orientation Assignment:确保关键点是旋转不变的
- Keypoint Descriptor:为每个关键点分配一个唯一的指纹
最后,我们可以使用这些关键点进行特征匹配!
Constructing the Scale Space
我们需要在忽略任何噪声的同时识别给定图像中最明显的特征。此外,我们还需要确保特征不依赖于尺度。
我们使用高斯模糊技术(Gaussian Blurring technique)来减少图像中的噪声。
对于图像中的每个像素,Gaussian Blurring 都会根据其相邻像素计算一个值。下面是应用 Gaussian Blurring 前后的图像示例。如您所见,纹理和次要细节已从图像中删除,仅保留形状和边缘等相关信息:
Gaussian Blurring 成功去除了图像中的大多数噪声,从而突出了图像的重要特征。现在,我们需要确保这些特征不能依赖于尺度。这意味着我们将通过创建 scale space 在多个尺度上搜索这些特征。
尺度空间是从单个图像生成的具有不同尺度的图像的集合。
因此,这些模糊图像是为多个尺度创建的。要创建一组不同比例的新图像,我们将采用原始图像并将比例缩小一半。对于每个新图像,我们将创建如上所示的模糊版本。
这是一个可以更好地理解它的示例。我们有尺寸为 (275, 183) 的原始图像和尺寸为 (138, 92) 的缩放图像。对于这两个图像,创建了两个模糊图像:
您可能会好奇,我们需要缩放图像多少次,以及需要为每个缩放图像创建多少个模糊图像呢?理想的阶数应该是四个,对于每阶,模糊图像的数量应该是五个。
Difference of Gaussian
到目前为止,我们已经创建了多个尺度(通常用 σ 表示)的图像,并对每个尺度使用高斯模糊来减少图像中的噪声。接下来,我们将尝试使用一种称为Difference of Gaussians (高斯差分或 DoG) 的技术来增强特征。
高斯差分是一种特征增强算法,涉及从原始图像的一个模糊版本减去另一个原始图像的模糊版本。DoG 为每个尺度创建另一组图像,方法是从相同比例的前一个图像中减去每个图像。以下是 DoG 实现方式的直观解释:
图片取自原论文。
让我们为尺度空间中的图像创建 DoG。看看下面的图表。在左侧,我们有 5 个图像,全部来自第一个 尺度(因此具有相同的比例)。每个后续图像都是通过在前一个图像上应用高斯模糊来创建的。
我们为这些图像中的每一个都提供了增强的功能
这里仅针对第一个尺度实施它,但所有尺度都会发生相同的过程。
现在我们有了一组新的图像,我们将使用它来找到重要的关键点。
Keypoint Localization
创建图像后,下一步是从图像中找到可用于特征匹配的重要关键点。这个想法是找到图像的局部最大值和最小值。这部分分为两步:
- 找到局部最大值和最小值
*(关键点选择)删除低对比度关键点
局部最大值和最小值
为了定位其局部最大值和最小值,我们遍历图像中的每个像素并将其与其相邻像素进行比较。此处的“相邻”不仅包括该图像的周围像素(像素所在),还包括同尺度中上一张和下一张图像的九个像素。
这意味着将每个像素值与其他 26(9+9+8) 个像素值进行比较,以确定它是否是局部最大值/最小值。例如下图所示,我们有来自第一个尺度的三个图像。标记为 x 的像素与相邻像素(绿色)进行比较,如果它是相邻像素中最高或最低的,则将其选为关键点:
我们现在有潜在的关键点来表示图像并且是尺度不变的。我们将对选定的关键点做一次检查,以确保这些关键点是表示图像的最准确的关键点。
Keypoint Selection
到目前为止,我们已经成功地生成了尺度不变的关键点。但其中一些关键点可能对噪声鲁棒性不好。这就是为什么我们需要执行最终检查以确保我们拥有最准确的关键点来表示图像特征。
因此,我们将消除对比度低或非常靠近边缘的关键点。
为了处理低对比度关键点,对每个关键点计算二阶泰勒展开。如果结果值小于 0.03(幅度),则去除此关键点。
接下来,我们执行检查以识别位置不佳的关键点。这些是靠近边缘并具有高边缘响应但可能对少量噪声鲁棒性不好的关键点。二阶 Hessian 矩阵用于识别此类关键点。
现在我们已经执行了对比度测试和边缘测试来去除不稳定的关键点,我们现在将为每个关键点分配一个方向值以使旋转不变。
Orientation Assignment
在这个阶段,我们已经有一组稳定的图像关键点。我们现在将为这些关键点中的每一个分配一个方向,以便它们对旋转保持不变。我们可以再次将这一步分为两个较小的步骤:
- 计算大小和方向
- 创建幅度和方向的直方图
计算幅度和方向
考虑下面显示的示例图像:
假设我们想要找到红色像素值的大小和方向。为此,我们将通过取 55 & 46 和 56 & 42 之间的差来计算 x 和 y 方向的梯度。结果分别为 和 。
一旦我们有了梯度,我们就可以使用以下公式找到大小和方向:
我们现在可以创建一个直方图,因为我们有这些像素的大小和方向值。
为幅度和方向创建直方图
x 轴上代表角度值的桶,例如 0-9、10 – 19、20-29,直到 360。由于我们的角度值为 57,它将落在第 6 个 桶。第 6 个 桶 值将与像素的大小成正比,即 16.64。我们将对关键点周围的所有像素执行此操作。
这就是我们如何得到下面的直方图:
该直方图会在某个点达到峰值。我们看到峰值的 bin 是关键点的方向。此外,如果有另一个在显著的峰值(在 80-100% 之间看到),则会生成另一个关键点。
Keypoint Descriptor
这是 SIFT 的最后一步。到目前为止,我们已经有了尺度不变和旋转不变的稳定关键点。在本节中,我们将使用相邻像素、它们的方向以及大小,为该关键点生成“描述符”。
此外,由于我们使用周围的像素,描述符将基本不受图像的照明或亮度影响。
我们将首先在关键点周围取一个 16×16 的邻域。这个 16×16 块被进一步划分为 4×4 子块,对于这些子块中的每一个,我们根据幅度和方向生成直方图。
此处我们设定桶的大小为8(不是 36 )。这些箭头中的每一个都代表 一个bin,箭头的长度定义了幅度。因此,对于每个关键点,我们总共将有 128 个 bin 值。
代码示例如下:
import cv2
import matplotlib.pyplot as plt
%matplotlib inline
#reading image
img1 = cv2.imread('eiffel_2.jpeg')
gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
#keypoints
sift = cv2.xfeatures2d.SIFT_create()
keypoints_1, descriptors_1 = sift.detectAndCompute(img1,None)
img_1 = cv2.drawKeypoints(gray1,keypoints_1,img1)
plt.imshow(img_1)
特征匹配
我们现在将使用 SIFT 特征进行特征匹配。为此,我下载了两张从不同位置拍摄的埃菲尔铁塔图像。您可以尝试使用任何您想要的两个图像。
这是我使用的两个图像:
import cv2
import matplotlib.pyplot as plt
%matplotlib inline
# read images
img1 = cv2.imread('eiffel_2.png')
img2 = cv2.imread('eiffel_1.png')
img1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
img2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
figure, ax = plt.subplots(1, 2, figsize=(16, 8))
ax[0].imshow(img1, cmap='gray')
ax[1].imshow(img2, cmap='gray')
现在,对于这两个图像,我们将生成 SIFT 特征。首先,我们必须构造一个 SIFT 对象,然后使用函数 detectAndCompute 来获取关键点。它将返回两个值:关键点和描述符。
让我们确定关键点并打印在每个图像中找到的关键点总数:
import cv2
import matplotlib.pyplot as plt
%matplotlib inline
# read images
img1 = cv2.imread('eiffel_2.png')
img2 = cv2.imread('eiffel_1.png')
img1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
img2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
#sift
sift = cv2.xfeatures2d.SIFT_create()
keypoints_1, descriptors_1 = sift.detectAndCompute(img1,None)
keypoints_2, descriptors_2 = sift.detectAndCompute(img2,None)
len(keypoints_1), len(keypoints_2) # 283, 540
接下来,让我们尝试将图像 1 中的特征与图像 2 中的特征进行匹配。 我们将使用 BFmatcher 模块中的函数 match()。此外,我们将在两个图像中匹配的特征之间画线。这可以使用 OpenCV 中的 drawMatches 函数来完成:
import cv2
import matplotlib.pyplot as plt
%matplotlib inline
# read images
img1 = cv2.imread('eiffel_2.png')
img2 = cv2.imread('eiffel_1.png')
img1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
img2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
#sift
sift = cv2.xfeatures2d.SIFT_create()
keypoints_1, descriptors_1 = sift.detectAndCompute(img1,None)
keypoints_2, descriptors_2 = sift.detectAndCompute(img2,None)
#feature matching
bf = cv2.BFMatcher(cv2.NORM_L1, crossCheck=True)
matches = bf.match(descriptors_1,descriptors_2)
matches = sorted(matches, key = lambda x:x.distance)
img3 = cv2.drawMatches(img1, keypoints_1, img2, keypoints_2, matches[:50], img2, flags=2)
plt.imshow(img3),plt.show()
为了清楚起见,我在这里只绘制了 50 个匹配项。您可以根据自己的喜好增加数量。要找出匹配了多少关键点,我们可以打印变量匹配的长度。