上一节中,我们介绍了Harris角点检测。角点在图像旋转的情况下也可以检测到,但是如果减小(或者增加)图像的大小,可能会丢失图像的某些部分,甚至导致检测到的角点发生改变。这样的损失现象需要一种与图像比例无关的角点检测方法来解决。尺度不变特征变换(Scale-Invariant Feature Transform,SIFT)可以解决这个问题。我们使用一个变换来进行特征变换,并且该变换会对不同的图像尺度输出相同的结果。
到底什么是SIFT算法?通俗一点说,SIFT算法利用DoG(差分高斯)来提取关键点(或者说成特征点),DoG的思想是用不同的尺度空间因子(高斯正态分布的标准差$\sigma$)对图像进行平滑,然后比较平滑后图像的区别,差别大的像素就是特征明显的点,即可能是特征点。对得到的所有特征点,我们剔除一些不好的,SIFT算子会把剩下的每个特征点用一个128维的特征向量进行描述。
由上,易知,一幅图像经过SIFT算法后可以表示为一个128维的特征向量集。
SIFT具有一下特征:
- SIFT特征,对旋转、尺度缩放、亮度变化等保持不变性,对视角变换、仿射变化、噪声也保持一定程度的稳定性,是一种非常优秀的局部特征描述算法;
- 独特性好,信息量丰富,适用于海量特征库进行快速、准确的匹配;
- 多量性,即使是很少几个物体也可以产生大量的SIFT特征;
- 高速性,经优化的SIFT匹配算法甚至可以达到实时性的要求;
- 扩展性,可以很方便的与其他的特征向量进行联合。
一 使用DoG和SIFT进行特征提取和描述
我们先用OpenCV库函数演示一下DoG和SIFT特征提取的效果,然后再来讲述一下SIFT的原理。
我们先来介绍一下Different of Gaussians(DoG),DoG是对同一图象使用不同高斯滤波器作差所得到的结果。DoG操作的最终结果会得到感兴趣的区域(关键点),这将通过SIFT来进行特征描述。
我们来看看如何通过SIFT得到充满角点和特征的图像:
# -*- coding: utf-8 -*- """ Created on Wed Aug 22 16:53:16 2018 @author: lenovo """ ''' SIFT算法 ''' import cv2 import numpy as np img = cv2.imread('./image/cali.bmp') img = cv2.resize(img,dsize=(600,400)) #转换为灰度图像 gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #创建一个SIFT对象 sift = cv2.xfeatures2d.SIFT_create() #SIFT对象会使用DoG检测关键点,并且对每个关键点周围的区域计算特征向量。该函数返回关键点的信息和描述符 keypoints,descriptor = sift.detectAndCompute(gray,None) print(type(keypoints),len(keypoints),keypoints[0]) print(descriptor.shape) #在图像上绘制关键点 img = cv2.drawKeypoints(image=img,keypoints = keypoints,outImage=img,color=(255,0,255),flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS) #显示图像 cv2.imshow('sift_keypoints',img) cv2.waitKey(0) cv2.destroyAllWindows()
我们首先创建了一个SIFT对象,SIFT对象会使用DoG检测关键点,并且对每个关键点周围区域计算特征向量。detectAndCompute()函数会返回关键点信息(每一个元素都是一个对象,有兴趣的可以看一下OpenCV源码)和关键点的描述符。然后,我们在图像上绘制关键点,并显示出来。
上面我们用到了一个函数,下面来介绍一下:
cv2.drawKeypoints(image,keypoints,outImage[,color[,flags]])
参数描述:
image:原始图像,可以使三通道或单通道图像;
keypoints:特征点集合list,向量内每一个元素是一个KeyPoint对象,包含了特征点的各种属性信息;
outImage:特征点绘制的画布图像,可以是原图像;
color:颜色设置,绘制的特征点的颜色信息,默认绘制的是随机彩色;
flags:特征点的绘制模式,其实就是设置特征点的哪些信息需要绘制,哪些不需要绘制,有以下几种模式可选:
- cv2.DRAW_MATCHES_FLAGS_DEFAULT:创建输出图像矩阵,使用现存的输出图像绘制匹配对和特征点,对每一个关键点只绘制中间点;
- cv2.DRAW_MATCHES_FLAGS_DRAW_OVER_OUTIMG:不创建输出图像矩阵,而是在输出图像上绘制匹配对;
- cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS:对每一个特征点绘制带大小和方向的关键点图形;
- cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS:单点的特征点不被绘制;
我们传入了DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS标志位,表明对图像每个关键点都绘制圆圈(大小)和方向。
二 SIFT算法原理
1、SIFT特征检测的步骤
- 尺度空间的极值检测:搜索所有高斯尺度空间上的图像,通过高斯差分函数(DoG)来识别潜在的对尺度和选择不变的兴趣点。
- 关键点的定位和过滤:在每个候选的位置上,通过一个拟合精细模型来确定位置尺度,关键点的选取依据他们的稳定程度。
- 特征方向赋值:基于图像局部的梯度方向,分配给每个关键点位置一个或多个方向,后续的所有操作都是对于关键点的方向、尺度和位置进行变换,从而提供这些特征的不变性。
- 特征点描述:在每个特征点周围的邻域内,在选定的尺度上测量图像的局部梯度,这些梯度被变换成一种表示,这种表示允许比较大的局部形状的变形和光照变换。
2、尺度空间
在一定的范围内,无论物体是大还是小,人眼都可以分辨出来。然而计算机要有相同的能力却不是那么简单的事情,在未知的场景中,计算机视觉并不能提供物体的尺度大小,其中的一个方法就是把物体不同尺寸下的图像都提供给机器,让机器能够对物体再不同的尺度下有一个统一的认知。在建立统一认知的过程中,要考虑的就是图像在不同的尺度下都存在的特征点。
2.1 多分辨率图像金字塔
在早期图像的多尺度通常使用图像金字塔表述形式。图像金字塔是同一图象在不同的分辨率下得到的一组结果,其生成过程一半包含两个步骤:
1、对原始图像进行平滑出来;
2、对处理后的图像进行降维采样(通常是水平、垂直方向的1/2);降维采样后得到一系列尺寸不断缩小的图像。显然,一个传统的金字塔中,每一层的图像是其上一层图像长、高的各一半。多分辨率的图像金字塔虽然生成简单,但其本质是降维采样,图像的局部特征则难以保持,也就是无法保持特性的尺度不变性。
2.2 高斯尺度空间
我们还可以通过图像的模糊程度来模拟人在距离物体远近时物体在视网膜上的成像过程,距离物体越近其尺寸越大图像也越清晰,这就是高斯尺度空间,使用不同的参数模糊图像(保持分辨率不变),是尺度空间的另一种表现形式。
我们知道图像和高斯函数进行卷积运算能够对图像进行模糊,使用不同的"高斯核"可得到不同模糊程度图像。
高斯核函数$G(x,y,σ)$(二维高斯函数)的公式可以写成:
$$G(x,y,σ)=\frac{1}{2\pi σ^2}e^{\frac{x^2+y^2}{2σ^2}}$$
$σ$称为尺度空间因子,它是高斯正太分布的标准差,反映了图像被模糊的程度,其值越大图像越模糊,对应的尺度也就越大。
在二维空间中,这个公式生成的曲面的等高线是从中心开始呈正态分布的同心圆,如图所示。分布不为零的像素组成的卷积矩阵与原始图像做变换。每个像素的值都是周围相邻像素值的加权平均。原始像素的值有最大的高斯分布值,所以有最大的权重,相邻像素随着距离原始像素越来越远,其权重也越来越小。这样进行模糊处理比其它的均衡模糊滤波器更高地保留了边缘效果。
一副图像其高斯尺度空间可由其和不同的高斯卷积得到:
$$L(x,y,σ)=G(x,y,σ)*I(x,y)$$
$L(x,y,σ)$代表着图像的高斯尺度空间。下图为图像取不同$\sigma$值得到的高斯尺度空间:
构建尺度空间的目的是为了检测出在不同的尺度下都存在的特征点,而检测特征点较好的算子是$\sigma^2\nabla^2G$(高斯拉普拉斯,LoG,图像的二阶导数),是由2002年Mikolajczyk在详细的实验比较中发现的,同其它的特征提取函数,例如:梯度,Hessian或Harris角特征比较,能够产生最稳定的图像特征。
而Lindeberg早在1994年就发现高斯差分函数(Difference of Gaussian ,简称DOG算子)与尺度归一化的高斯拉普拉斯函数$\sigma^2\nabla^2G$非常近似。使用LoG虽然能较好的检测到图像中的特征点,但是其运算量过大,通常可使用DoG(差分高斯,Difference of Gaussina)来近似计算LoG。其中$D(x,y,\sigma)$和$\sigma^2\nabla^2G$的关系可以从如下公式推导得到:$$\frac{\partial{G}}{\partial{\sigma}}=\sigma^2\nabla^2G$$
利用差分近似代替微分,则有:$$\sigma^2\nabla^2G=\frac{\partial{G}}{\partial{\sigma}}≈\frac{G(x,y,k\sigma)-G(x,y,\sigma)}{k\sigma-\sigma}$$
因此有:$$G(x,y,k\sigma)-G(x,y,\sigma)≈(k-1)\sigma^2\nabla^2G$$
其中k-1是个常数,并不影响极值点位置的求取。高斯拉普拉斯和高斯差分的比较如下:
如图所示,红色曲线表示的是高斯差分算子,而蓝色曲线表示的是高斯拉普拉斯算子。Lowe使用更高效的高斯差分算子代替拉普拉斯算子进行极值检测,设$k$为相邻两个高斯尺度空间的比例因子,则DoG的定义:
$$D(x,y,σ)=[G(x,y,kσ)-G(x,y,σ)]*I(x,y)$$ $$=L(x,y,kσ)-L(x,y,σ)$$
其中,$L(x,y,σ)$是图像的高斯尺度空间。
从上式可以知道,将相邻的两个高斯空间的图像相减就得到了DoG的响应图像。为了得到DoG图像,先要构造高斯尺度空间,而高斯的尺度空间可以在图像金字塔降维采样的基础上加上高斯滤波得到,也就是对图像金字塔的每层图像使用不同的尺度参数$\sigma$进行高斯模糊,使每层金字塔有多张高斯模糊过的图像,然后我们把得到的同一尺寸大小的图像划分为一组。
易知,高斯金字塔有多组(如上图),每组又有多层。一组中的多个层之间的尺度是不一样的(也就是使用的高斯参数$\sigma$是不同的),相邻两层之间的尺度相差一个比例因子$k$,如果每组有$S$层,则$k=2^{\frac{1}{S}}$。上一组图像的最低层图象是由下一组中尺度为$2\sigma$的图像进行因子为2的降维采样得到的(高斯金字塔先底层建立)。高斯金字塔构建完成后,将相邻的高斯金字塔相减就得到了DoG金字塔。
高斯金字塔的组数一般是:
$$O=[log_2min(m,n)-a]$$
$O$代表高斯金字塔的组数,$m$,$n$分别是图像的行和列。减去的系数$a$可以在$0-log_2min(m,n)$之间的任意值,和具体需要的金字塔的顶层图像的大小有关。
高斯模糊参数$\sigma$(尺度空间),可由下面关系得到:
$$\sigma(o,s)=\sigma_0·2^{\frac{o+s}{S}}$$
其中$o$为所在的组,$s$为所在的层,$\sigma_0$为初始的尺度,$S$为每组的层数。
在Lowe论文中$\sigma_0=1.6$,$o_{min}=-1$,$S=3$,$o_{min}=-1$就是首先将原图像的长和宽各扩展一倍(为了保留原始图像信息,增加特征点数量)。
从上面可以得到同一组内相邻的图像尺度关系:
$$\sigma_{s+1}=k·\sigma_s=2^{\frac{1}{S}}·\sigma_s$$
相邻组处于同一层之间的图像尺度关系:
$$\sigma_{o+1}=2\sigma_o$$
下面是高斯尺度空间表示的一个例子:
2.3 高斯金字塔构建案例
以一张$512×512$的图像$I$为例,构建高斯金字塔步骤:(从0开始计数,倒立的金字塔)
1、金字塔的组数,$log_2{512}=9$,减去因子3,构建的金字塔组数为$O=6$,取每组的层数为$S=3$,相邻两个高斯尺度空间的比例因子$k=2^{\frac{1}{S}}=2^{\frac{1}{3}}$;
2、构建第0组,将图像的宽和高都增加一倍,变成$1024×1024$($I_0$)。第0层$I_0*G(x,y,\sigma_0)$,第1层$I_0*G(x,y,k\sigma_0)$,第2层$I_0*G(x,y,k^2\sigma_0)$;
3、构建第1组,对$I_0$降维采样变成$512×512$($I_1$)。第0层$I_1*G(x,y,2\sigma_0)$,第1层$I_1*G(x,y,2k\sigma_0)$,第2层$I_1*G(x,y,2k^2\sigma_0)$;
4、......
5、构建第$o$组,第$s$层$I_o*G(x,y,2^ok^s\sigma_0)$;
高斯金字塔构建成功后,将每一组相邻的两层相减就可以得到DoG金字塔。
3、DoG空间极值检测
为了寻找DoG金字塔尺度空间的极值点,每个像素点要和其图像域(相同大小的图像)和尺度域(相邻的尺度空间)的所有相邻点进行比较,当其大于(或者小于)所有相邻点时,该点就是极值点。如图所示,中间的检测点要和其所在图像的$3×3$邻域8个像素点,以及其相邻的上下两层$3×3$邻域18个像素点,共26个像素点进行比较。
从上面的描述中可以知道,不同组的图像大小不一样,因此每组图像的第一层和最后一层是无法进行比较取得极值的。为了满足尺度变换的连续性,在每一组图像继续使用高斯模糊生成3幅图像,高斯金字塔每组$S+3$层图像,DoG金字塔的每组有$S+2$层图像.
3.1 尺度变化的连续性
设$S=3$,也就是每组有3层,则$k=2^{\frac{1}{S}}=2^{\frac{1}{3}}$。如果按照之前的计算,也就是高斯金字塔每组有3层图像,DoG金字塔每组有2层图像,在DoG金字塔的第一组有两层尺度分别是$\sigma$,$k\sigma$,$k^2\sigma$,第二组有两层尺度分别是$2\sigma$,$2k\sigma$,由于只有两项是无法比较取得极值的(只有左右两边都有值才能有极值),我们必须在高斯空间继续添加高斯模糊项,使得DoG空间尺度形成$\sigma$,$k\sigma$,$k^2\sigma$,$k^3\sigma$,$k^4\sigma$,这样就可以选择中间的三项$k\sigma$,$k^2\sigma$,$k^3\sigma$。对应的下一组由上一组降维采样得到的三项是$2k\sigma$,$2k^2\sigma$,$2k^3\sigma$,其首项$2k\sigma=2·2^{\frac{1}{3}}\sigma=2^{\frac{4}{3}}\sigma$,刚好与上一组的最后一项$k^3\sigma=2^{\frac{3}{3}}\sigma$尺度连接起来。所以需要在每一组图像继续使用高斯模糊生成3幅图像,高斯金字塔每组有$S+3$层图像,DoG金字塔的每组有$S+2$层图像。
下面是局部极值检测的结果:
4、删除不好的极值点(特征点)
到现在,我们已经得到了部分关键点,但是现在存在几个问题,通过比较检测得到的DoG的局部极值点是在离散的空间搜索得到的,由于离散空间是对连续空间采样得到的结果,因此在离散空间找到的极值点不一定是真正意义上的极值点,因此要设法将不满足条件的点剔除掉。可以通过尺度空间DoG函数进行曲线拟合寻找极值点,这一步的本质是去掉DoG局部曲率非常不对称的点,增强匹配稳定性、提高抗噪声能力,以达到精确定位关键点的目的。
利用已知的离散空间点插值得到的连续空间极值点的方法叫做子像素插值(Sub-pixelInterpolation)。
- 低对比度的特征点;
- 不稳定的边缘响应点;
4.1 剔除低对比度的特征点
假设我们在尺度为$\sigma$的尺度图像$D(x,y)$上检测到了一个局部极值点,空间位置为$(x,y,\sigma)$,为了简单方便我们使用$x$表示这个候选点,其极值点偏移量用$Δx$表示,为了提高关键点的稳定性,需要对尺度空间DoG函数进行曲线拟合。利用DoG函数在尺度空间的泰勒展开式(拟合函数)为:
$$D(x+Δx)=D(x)+\frac{\partial{D^T}}{\partial{x}}Δx+\frac{1}{2}Δx^T\frac{\partial^2D}{\partial{x^2}}Δx$$
对$Δx$求导并让方程等于零,可以得到极值点的偏移量为:
$$Δx=-\frac{\partial^2D^{-1}}{\partial{x^2}}\frac{\partial{D(x)}}{\partial{x}}$$
然后再把求得的$Δx$带入到$D(x)$的泰勒展开式中:
$$D(\hat{x})=D(x)+\frac{1}{2}\frac{\partial{D^T}}{\partial{x}}Δx$$
其中$\hat{x}=x+Δx$,设对比度的阈值为$T$,对比度为$D(x)$的绝对值$|D(x)|$,若$|D(\hat{x})|≥T$,则该特征点保留,否则剔除掉。
下面是删除$D(x)$绝对值较小后的结果:
4.2 剔除不稳定的边缘响应点
有些极值点的位置是在图像的边缘位置的,因为图像的边缘点很难定位,同时也容易受到噪声的干扰,我们把这些点看做是不稳定的极值点,需要进行去除。
在边缘梯度的方向上主曲率值比较大,而沿着边缘方向则主曲率值较小。候选特征点的DoG函数$D(x)$的主曲率与$2×2$Hessian矩阵$H$的特征值成正比:
$$H=\begin{bmatrix}D_{xx} & D_{yx} \\ D_{xy} & D_{yy} \end{bmatrix}$$
其中,$D_{xx}$,$D_{xy}$,$D_{yy}$是候选点邻域位置差分求得的。
为了避免求具体的值,可以使用$H$特征值的比例.设$\alpha=\lambda_{max}$为$H$的最大特征值,$\beta=\lambda_{min}$为$H$的最小特征值,则:
$$T_r(H)=D_{xx}+D_{yy}=\alpha+\beta$$
$$Det(H)=D_{xx}+D_{yy}-D^2_{xy}=\alpha·\beta$$
其中,$T_r(H)$为矩阵$H$的迹,$Det(H)$为矩阵$H$的行列式.
设$\gamma=\frac{\alpha}{\beta}$表示最大特征值与最小特征值的比值,则:
$$\frac{T_r(H)^2}{Det(H)}=\frac{(\alpha+\beta)^2}{\alpha\beta}=\frac{(\gamma\beta+\beta)^2}{\gamma\beta^2}=\frac{(\gamma+1)^2}{\gamma}$$
上式的结果与两个特征值的比例有关,和具体的大小无关,当两个特征值相等时其值最小,并且随着$\gamma$的增大而增大。因此为了检测主曲率是否在某个阈值$T_r$下,只需检测:
$$\frac{T_r(H)^2}{Det(H)}>\frac{(T_{\gamma}+1)^2}{T_{\gamma}}$$
如果上式成立,则剔除该特征点,否则保留。(Lowe论文中取$T_{\gamma}=10$)
我们知道边缘$H$矩阵一个特征值很大,一个特征值很小,因此可以通过过滤较大的$\gamma$值达到过滤边缘的目的。具体可参见Harris角点检测算法第十一节、Harris角点检测原理。
下面是移除边缘响应后的结果:
注意:代码实现以及具体细节可以参看文章SIFT定位算法关键步骤的说明。
5、求取特征点的主方向
经过上面的步骤已经找到了在不同尺度下都存在的特征点,为了实现图像旋转不变性,需要给特征点的方向进行赋值。利用特征点邻域像素的梯度来确定其方向参数,再利用图像的梯度直方图求取关键点局部结构的稳定方向。
对于已经检测到的特征点,我们知道该特征点的尺度值$\sigma$,因此根据这一尺度值,也就可以得到这一尺度的高斯图像:
$$L(x,y)=G(x,y,\sigma)*I(x,y)$$
使用有限差分,计算以特征点为中心,$3×1.5\sigma$为半径的区域图像的幅角和幅值,每个点$L(x,y)$的梯度的模$m(x,y)$以及方向$\theta(x,y)$可通过下面公式得到:
$$m(x,y)=\sqrt{[L(x+1,y)-L(x-1,y)]^2+[L(x,y+1)-L(x,y-1)]^2}$$
$$\theta(x,y)=arctan\frac{L(x,y+1)-L(x,y-1)}{L(x+1,y)-L(x-1,y)}$$
计算得到梯度方向后,就要使用直方图统计特征点邻域内像素对应的梯度方向和幅值。梯度方向的直方图的横轴是梯度方向的角度(梯度方向的范围是0到360度,直方图每36度一个柱共10个柱,或者每45度一个柱共8个柱),纵轴是梯度方向对应梯度幅值的累加,在直方图的峰值就是特征点的主方向。
在Lowe的论文还提到了使用高斯函数对直方图进行平滑以增强特征点近的邻域点对关键点方向的作用,并减少突变的影响。为了得到更精确的方向,通常还可以对离散的梯度直方图进行插值拟合。具体而言,关键点的方向可以由和主峰值最近的三个柱值通过抛物线插值得到。在梯度直方图中,当存在一个相当于主峰值80%能量的柱值时,则可以将这个方向认为是该特征点辅助方向。所以,一个特征点可能检测到多个方向(也可以理解为,一个特征点可能产生多个坐标、尺度相同,但是方向不同的特征点)。Lowe在论文中指出15%的关键点具有多方向,而且这些点对匹配的稳定性很关键。
得到特征点的主方向后,对于每个特征点可以得到三个信息$(x,y,\sigma,\theta)$,即位置、尺度和方向。由此可以确定一个SIFT特征区域,一个SIFT特征区域由三个值表示,中心表示特征点位置,半径表示关键点的尺度,箭头表示主方向。具有多个方向的关键点可以被复制成多份,然后将方向值分别赋给复制后的特征点,一个特征点就产生了多个坐标、尺度相等,但是方向不同的特征点。
下面是特征方向和尺度分配的结果(箭头指向代表方向,长度代表尺度):
6、生成特征描述
通过以上的步骤已经找到了SIFT特征点位置、尺度和方向信息,下面就需要使用一组向量来描述关键点也就是生成特征点描述子,这个描述符不只包含特征点,也含有特征点周围对其有贡献的像素点。描述子应具有较高的独立性,以保证匹配率。
特征描述符的生成大致有三个步骤:
- 校正旋转主方向,确保旋转不变性。
- 生成描述子,最终形成一个128维的特征向量
- 归一化处理,将特征向量长度进行归一化处理,进一步去除光照的影响。
为了保证特征矢量的旋转不变性,要以特征点为中心,在附近邻域内将坐标轴旋转$\theta$(特征点的主方向)角度,即将坐标轴旋转为特征点的主方向。
到这里,有人会问:旋转过程中,中图和右图为什么每个像素点的方向不一样?其实,你要明确一点,你所选的小区域,是关键点旋转后的小区域,右图的区域跟旋转前的区域不一样了,右图是重新选取得区域,但是区域大小没变。
旋转后邻域内像素的新坐标为:
$$\begin{bmatrix} x' \\ y' \end{bmatrix} = \begin{bmatrix} cos\theta & –sin\theta \\ sin\theta & cos\theta \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix}$$
旋转后以主方向为中心取$3\sigma{d}\times3\sigma{d}$(下图中$d=4$)的窗口。下图所示,左图的中央为当前关键点的位置,每个小格代表为关键点邻域所在尺度空间的一个像素,求取每个像素的梯度幅值与梯度方向,箭头方向代表该像素的梯度方向,长度代表梯度幅值,然后利用高斯窗口对其进行加权运算。最后在每个$3\sigma\times3\sigma$的小块上(图像中每个正方形的区域的边长为$4$)绘制8个方向的梯度直方图,计算每个梯度方向的累加值,即可形成一个种子点,如右图所示。每个特征点由4个种子点组成,每个种子点有8个方向的向量信息。这种邻域方向性信息联合增强了算法的抗噪声能力,同时对于含有定位误差的特征匹配也提供了比较理性的容错性。
注意:由于实际情况下,编程时用到双线性差值(前面用到了旋转,旋转操作会用到插值处理),因此这里用到的特征点邻域的边长实际为$3\sigma{(d+1)}$。因此邻域中一共有$(3\sigma{(d+1)})^2$个像素点。
与求主方向不同,此时每个种子区域的梯度直方图在0-360之间划分为8个方向区间,每个区间为45度,即每个种子点有8个方向的梯度强度信息。
在实际的计算过程中,为了增强匹配的稳健性,Lowe建议对每个关键点使用$4\times4$共16个种子点来描述,这样一个关键点就可以产生128维的SIFT特征向量。
通过对特征点周围的像素进行分块,计算块内梯度直方图,生成具有独特性的向量,这个向量是该区域图像信息的一种抽象,具有唯一性。
上面讲了那么多,我们来总结一下特征点邻域每个像素点对于整个梯度直方图的贡献的计算如下所述:
- 计算各个像素点的梯度幅值和梯度幅角;
- 根据该像素点距离特征点的距离进行加权(即第一次高斯加权),该像素点的幅值乘以加权值;
- 根据该像素点在所在的小正方形区域内据中心的距离进行加权(即第二次高斯加权),把2中的结果再乘以一个权值。
经过上面的计算,就可以得到128柱的直方图$\{p_1,p_2,...,p_{128}\}$,为了去除光照的影响,需要进行归一化处理:
$$q_i=\frac{p_i}{\sqrt{p_1^2+p_2^2+...+p_{128}^2}},i=1,2,3,...,128$$
实际上为了去除非线性光照的变化,在实现的过程中对于已经归一化好的描述符$\{q_1,q_2,...,q_{128}\}$需要设定一个阈值,一般阈值为0.2,当$q_i$超过0.2以后,则$q_i=0.2$。
7、总结
SIFT特征以其对旋转、尺度缩放、亮度等保持不变性,是一种非常稳定的局部特征,在图像处理和计算机视觉领域有着很重要的作用,其本身也是非常复杂的,下面对其计算过程做一个粗略总结。
1、DoG尺度空间的极值检测。 首先是构造DoG尺度空间,在SIFT中使用不同参数的高斯模糊来表示不同的尺度空间。而构造尺度空间是为了检测在不同尺度下都存在的特征点,特征点的检测比较常用的方法$\sigma^2\nabla^2G$(高斯拉普拉斯LoG),但是LoG的运算量是比较大的,Marr和Hidreth曾指出,可以使用DoG(差分高斯)来近似计算LoG,所以在DoG的尺度空间下检测极值点。
2、删除不稳定的极值点。主要删除两类:低对比度的极值点以及不稳定的边缘响应点。
3、确定特征点的主方向。以特征点的为中心、以$3×1.5\sigma$为半径的领域内计算各个像素点的梯度的幅角和幅值,然后使用直方图对梯度的幅角进行统计。直方图的横轴是梯度的方向,纵轴为梯度方向对应梯度幅值的累加值,直方图中最高峰所对应的方向即为特征点的方向。
4、生成特征点的描述子。 首先将坐标轴旋转为特征点的方向,以特征点为中心的$16\times16$窗口的像素的梯度幅值和方向,将窗口内的像素分成16块,每块是其像素内8个方向的直方图统计,共可形成128维的特征向量。
三 特征点匹配
既然是匹配,当然每个特征点的特征向量(描述符)要相似才能匹配到一起,这里采用的是欧式距离来衡量其相似度。比如说我有2张图片A和B,图片的内容相同,只是图片的大小尺寸不同。假设A图片尺寸比较大,且我们已经采用SIFT算法对图片A和B都进行了特征提取,获得了它们的特征点集合,现在我们的问题是需要把A和B中相应的特征点给对应连线起来。
对B中的特征点x,去寻找A中最相似的点y,最简单的方法就是拿x与A中所有点进行相似度比较,距离最小的那个为匹配点。但是如果图片中特征点数目很多的话,这样效率会很低。所以我们需要把A中特征点向量集合用一种数据结构来描述,这种描述要有利于x在A中的搜索,即减少时间复杂度。在sift匹配中,这种数据结构采用的是kd-tree。关于kd-tree的讲解,可以参考博文k-d tree算法的研究.
# -*- coding: utf-8 -*- """ Created on Wed Aug 22 16:53:16 2018 @author: lenovo """ ''' SIFT算法 ''' import cv2 '''1、加载图片''' img1 = cv2.imread('./image/match1.jpg') img1 = cv2.resize(img1,dsize=(600,400)) gray1 = cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY) img2 = cv2.imread('./image/match2.jpg') img2 = cv2.resize(img2,dsize=(600,400)) gray2 = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY) image1 = gray1.copy() image2 = gray2.copy() '''2、提取特征点''' #创建一个SIFT对象 sift = cv2.xfeatures2d.SIFT_create(400) #SIFT对象会使用DoG检测关键点,并且对每个关键点周围的区域计算特征向量。该函数返回关键点的信息和描述符 keypoints1,descriptor1 = sift.detectAndCompute(image1,None) keypoints2,descriptor2 = sift.detectAndCompute(image2,None) print('descriptor1:',descriptor1.shape,'descriptor2',descriptor2.shape) #在图像上绘制关键点 image1 = cv2.drawKeypoints(image=image1,keypoints = keypoints1,outImage=image1,color=(255,0,255),flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS) image2 = cv2.drawKeypoints(image=image2,keypoints = keypoints2,outImage=image2,color=(255,0,255),flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS) #显示图像 cv2.imshow('sift_keypoints1',image1) cv2.imshow('sift_keypoints2',image2) cv2.waitKey(20) '''3、特征点匹配''' matcher = cv2.FlannBasedMatcher() matchePoints = matcher.match(descriptor1,descriptor2) print(type(matchePoints),len(matchePoints),matchePoints[0]) #提取强匹配特征点 minMatch = 1 maxMatch = 0 for i in range(len(matchePoints)): if minMatch > matchePoints[i].distance: minMatch = matchePoints[i].distance if maxMatch < matchePoints[i].distance: maxMatch = matchePoints[i].distance print('最佳匹配值是:',minMatch) print('最差匹配值是:',maxMatch) #获取排雷在前边的几个最优匹配结果 goodMatchePoints = [] for i in range(len(matchePoints)): if matchePoints[i].distance < minMatch + (maxMatch-minMatch)/3: goodMatchePoints.append(matchePoints[i]) #绘制最优匹配点 outImg = None outImg = cv2.drawMatches(img1,keypoints1,img2,keypoints2,goodMatchePoints,outImg,matchColor=(0,255,0),flags=cv2.DRAW_MATCHES_FLAGS_DEFAULT) cv2.imshow('matche',outImg) cv2.waitKey(0) cv2.destroyAllWindows()
我们选择的这两幅图片亮度和对比度差异都是很大的,而且拍摄所使用的相机也是不同的,拍摄出来的同一物体尺寸也是不用的,左侧是我自己用手机拍摄到的,右侧是从网上下载的,可以看到匹配效果还不错。这也说明了SIFT算法的优越性:对旋转、尺度缩放、亮度变化等保持不变性,对视角变换、仿射变化、噪声也保持一定程度的稳定性。但是如果我们想达到更高的匹配度,我们应该尽量选择两张更为相似的图片。
参考文章:
[1]斑点检测(LoG)
[2]SIFT特征详解(部分内容转自该文)
[3]基于图像配准的图像特征检测之sift算法----sift算法总结
[4]SIFT算法学习总结
[5]特征点匹配——SIFT算法详解
[6]SIFT特征匹配算法介绍——寻找图像特征点的原理
[7]SIFT算法详解
[8]SIFT算法详解
[9]Distinctive Image Features from Scale-Invariant Keypoints
[10]Distinctive Image Features from Scale-Invariant Keypoints-SIFT算法译文
[11]SIFT特征点检测学习一(转载)
[12]九之再续:教你一步一步用c语言实现sift算法、上
[13]九之再续:教你一步一步用c语言实现sift算法、下
[14]Sift特征点匹配过程
[15]尺度空间理论
[16]SIFT定位算法关键步骤的说明
[17]SIFT原理及源码解析(C++)