目录

  • 计算机视觉专栏传送
  • 前言
  • 一、SIFT算法
  • 1.算法简介
  • 2.主要步骤
  • (1)尺度空间的极值检测
  • 1.1 高斯滤波
  • 1.2 高斯差分
  • 1.3 局部极值
  • (2)关键点的精确定位
  • (3)关键点主方向分配
  • (4)关键点描述子的生成
  • 二、Python代码实践
  • 1.测试环境
  • 2.测试代码
  • 3.测试结果
  • 三、算法小结


前言

SIFT算法作为图像局部特征的里程碑式发明被广泛应用于各个领域,David Lowe的思想简单却深邃。

本文将结合笔者的一点简单理解,从问题出发,由理论到实践,充分掌握SIFT中心思想,了解通过python的SIFT算法简单实现。

一、SIFT算法

1.算法简介

SIFT算法,Scale-invariant feature transform,中文含义就是尺度不变特征变换。该算法自1999年由David Lowe提出(Object recognition from local scale-invariant features)以后被广泛的应用于图像识别,图像检索,3D重建等CV的各种领域。

由于在此之前的目标检测算法对图片的大小、旋转非常敏感,而SIFT算法是一种基于局部兴趣点的算法,因此不仅对图片大小和旋转不敏感,而且对光照、噪声等影响的抗击能力也非常优秀。

2.主要步骤

lowe在2004年的文章 Distinctive Image Features from Scale-Invariant Keypoints 中总结了SIFT特征检测算法的主要步骤:

python代码raft算法 sift python_opencv

Lowe将算法主要步骤分为:

  1. 尺度空间的极值检测
  2. 关键点精确定位
  3. 关键点主方向分配
  4. 关键点描述子的生成
(1)尺度空间的极值检测

SIFT算法提出的目标是建立一个描述图像特征的完整体系,这个问题自然可以分解为寻找图像的特征描述图像的特征两个部分。

特征应是区分不同内容的两幅图像和关联相同内容的两幅图像的量化指标。区分不同内容的图像时,人眼的做法是匹配一些图像的关键点。这一点成为SIFT特征的突破口,从而将图像特征的提取和描述的问题转化为寻找图像关键点(keypoints)和建立基于关键点的图像特征描述子(feature descriptor) 的问题。

1.1 高斯滤波

确定图像的关键点需要先排除图像的噪声,排除噪声最经典的办法是使用滤波器对图像进行滤波。使用 高斯滤波器(Gaussian Blur) 可以降低图像中的噪点。

高斯滤波器滤波的过程相当于将一个特殊的模板对图像进行卷积。如图,滤波后的结果图像中纹理和次要细节、噪声等部分得到了有效抑制,仅留下了图像的边缘和轮廓特征。

python代码raft算法 sift python_计算机视觉_02

高斯模糊后得到的图像滤除了一些不必要的细节而保留了重要的图像特征。为确保发明的图像特征算法对图像检索、图像匹配等问题的鲁棒性,需要保证发明的图像特征具有尺度不变性。(即特征对图像比例不敏感)

于是我们对原图像进行不同程度的降采样,再对降采样后的图像进行高斯滤波得到以下结果:

python代码raft算法 sift python_python代码raft算法_03


图中不同的行(即不同的Octave)表示图像经过不同程度的降采样(即缩小比例)。同一行的不同列表示图像经过的高斯滤波方差值不同(同一行从左至右方差依次增大)。

1.2 高斯差分

得到不同尺度和不同高斯模板的降采样高斯滤波结果图像可以发现,虽然已经滤除了大部分的无用信息,但图像的轮廓等特征信息仍有待进一步凸显。

为进一步削弱图像无用信息、凸显特征,我们考虑对得到的不同尺度不同滤波模板的图像组进行处理。算法对同一尺度下不同高斯模板滤波的图像(即上图中同一Octave的图像)进行差分(两两相减)。高斯滤波后再进行差分即进行高斯差分(Difference of Gaussian(DOG))。

DOG通过差分为每个Octave创建了一组新的图像。如下图所示,左侧绿色的图像组每5个为一个Octave,即相同尺度不同高斯核的卷积图像。所有的Octave垂直方向分布组成类似于金字塔的形状被称作 “高斯金字塔”

而下图右侧是高斯金字塔中同一Octave的不同卷积核卷积图像的差分得到的图像集。因为是差分得到的图像,故每个Octave相较于高斯金字塔中Octave数量上会减少一张。因为差分得到的图像集也是垂直方向分布,形状类似于金字塔,故被称作 “高斯差分金字塔”

python代码raft算法 sift python_python代码raft算法_04


经过DOG得到的结果图像组相较于DOG之前更能凸显图像的特征,图像的无用成分进一步缩减,代表图像特征的边缘、轮廓成分被进一步凸显。

下图仅对一个Octave进行差分操作,而在实际的算法过程中这一部分是对得到的所有不同尺度的Octave进行差分操作。

python代码raft算法 sift python_python代码raft算法_05

1.3 局部极值

我们不应当忘记,在一开始我们将问题转换为寻找图像的特征点和建立基于特征点的图像特征描述符的任务。所以在获得高斯差分金字塔后,算法应当利用高斯差分金字塔中的图像特征信息确定图像的特征点

这里,SIFT算法选择高斯差分金字塔中的极值点作为代表图像特征的关键点。需要注意的是:这里所说的极值点不仅仅是在一幅图像中像素值为极值的点,而是该点像素值对于与其同图像的八邻域和上一张图像相同位置点以及八邻域、下一张图像相同位置点以及八邻域共计26个像素值为局部极值。(如下图所示,中心黑色叉点为中心点,其将与周围共计26个绿色标记点比较确定是否为极值点)

python代码raft算法 sift python_python代码raft算法_06


值得一提的是,选出的高斯差分金字塔极值点只是候选的特征点。虽然高斯差分金字塔极值点已经能够较好地代表图像的特征并且具有尺度不变性,但在选取过程中没有考虑图像特征点对于图像噪声的鲁棒性,这样确定出的图像特征点在实际应用时易出现图像匹配不当等问题。

(2)关键点的精确定位

为提高图像特征点对噪声的鲁棒性,SIFT算法的通常做法是对比测试边缘测试

对比测试是指为解决对比度较低的极值点,对所有极值点进行二阶泰勒展开(second-order Taylor expansion)。如果结果值小于0.03,则剔除该关键点。边缘测试是指使用二阶Hessian矩阵来识别具有高边缘度但对少量噪声没有鲁棒性的关键点。

(3)关键点主方向分配

在建立高斯差分金字塔以选取图像特征点时,算法考虑了关键点的尺度不变性。而对于图像特征而言,与尺度不变性同等重要的还有旋转不变性。为使算法定义的图像特征具有旋转不变性,我们需要在定位特征点后对特征点定义 “主方向”,该“主方向”生成特征点周围的局部区域的梯度方向基准,使图像在旋转后仍能与旋转前保持相同的特征描述。

算法将关键点指定大小领域中的所有点计算梯度方向与赋值,并统计所有梯度方向对应的赋值和,作关键点周围i邻域梯度方向直方图。

下图右侧梯度方向直方图为简化版本(只有8个bin,实际操作时算法会统计从0到360°步长为10°的共计36个梯度方向的幅值和,共有36个bin)。梯度方向直方图中最高的bin对应的方向即定义为该关键点的主方向,若存在任一方向的幅值大于主方向幅值的80%,则将其作为辅方向

获得图像关键点主方向后,每个关键点有三个信息(x,y,σ,θ):位置(x,y)、尺度σ、方向θ。

python代码raft算法 sift python_算法_07

(4)关键点描述子的生成

得到特征点二维位置、尺度位置、主方向的具体信息后,算法需要解决的最后一个问题就是生成关键点信息的描述子,即用一个向量描述图像中的特征点信息。

算法将特征点周围的1616邻域分为4个88的区域,再将88的区域划为22区域,即每个小区域为44的范围。统计每个44区域的梯度方向直方图(直方图为8个bin),故共计448=128个bin(每个1616邻域有44个44邻域,每个44邻域有8个bin)。对应生成128维向量(值为梯度方向的幅值)。该128维向量即该点的特征描述子。

python代码raft算法 sift python_算法_08

二、Python代码实践

1.测试环境

(1)pycharm下python3.8.5
(2)opencv-python 4.4.0.42
(3)opencv-contrib-python 4.4.0.42

2.测试代码

import cv2
import matplotlib.pyplot as plt

img1 = cv2.cvtColor(cv2.imread('../ma1.jfif'), cv2.COLOR_BGR2GRAY)
img2 = cv2.cvtColor(cv2.imread('../ma2.jfif'), cv2.COLOR_BGR2GRAY)

sift = cv2.xfeatures2d.SIFT_create()

keypoints_1, descriptors_1 = sift.detectAndCompute(img1, None)
keypoints_2, descriptors_2 = sift.detectAndCompute(img2, None)

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()

3.测试结果


图中马云的单人头像经过旋转和缩放后仍能和原图进行关键点匹配,算法实现成功。

三、算法小结

python代码raft算法 sift python_python_09