之前在学习三维重建的过程中,了解过SIFT算法,现在老师要求详细的了解SIFT算法,看看能不能对它进行改进,于是又详细的看了一遍SIFT算法。记录一下。
一、SIFT算法综述
SIFT(Scale Invariant Feature Transform)全称尺度不变特征变换,是1999年Lowe提出的一种局部特征描述算子,在2004年得到了改善。
SIFT算子是把图像中检测到的特征点用一个128维的特征向量进行描述,因此一幅图像经过SIFT算法后表示为一个128维的特征向量集,该特征向量集具有对图像缩放,平移,旋转不变的特征,对于光照、仿射和投影变换也有一定的不变性,是一种非常优秀的局部特征描述算法。
SIFT算法的流程分别为:
- 尺度空间极点检测
- 关键点精确定位
- 关键点的方向确定
- 特征向量的生成
下面将会依次对这几步进行介绍。
二、尺度空间极点检测
2.1 尺度空间
特征点的检测就需要知道特征点的位置和尺度,需要位置的原因显而易见,而需要尺度的原因则是因为真实世界中的物体只有在一定尺度下才有意义。我们寻找的特征点就是要找到在连续的尺度空间下位置不发生改变的点。
构建尺度空间的目的就是找到在尺度变化中具有不变性的位置,可以使用连续的尺度变化,即在尺度空间中所有可能的尺度变化中找到稳定的特征点,通过这种方式找到的极点可以保证在图像缩放和旋转变化中具有不变性。
经过前人证明,尺度空间内核是高斯函数。因此假设 I(x,y) 是原始图像, G(x,y,σ) 是尺度空间可变的高斯函数,则一个图像的尺度空间可以定义为
L(x,y,σ)=G(x,y,σ)∗I(x,y)(1)
其中,
∗ 表示的是卷积运算,
σ 表示尺度空间的大小,
σ 越大则表示越模糊,表示图像的概貌,
σ 越小则表示越清晰,表示图像的细节。高斯函数
G(x,y,σ) 定义为
G(x,y,σ)=12πσ2e−(x2+y2)/2σ2(2)
经过一系列的尺度空间变换和二倍的下采样,最终得到高斯金字塔。
需要注意的是公式1中的图像
I(x,y) 具有无限的分辨率,也就是说他的尺度
σ=0 ,即
I(x,y)=L(x,y,0) 。也就是说公式1得到的尺度空间图像
L(x,y,σ) 是由尺度尺度空间为0的图像
L(x,y,0 生成的,但是现实生活中是不存在尺度空间为0,即具有无限分辨率的图像的。在Lowe的论文中,他们给定原图一个很小的尺度空间,为0.5。因此由一个小尺度空间图像
L(x,y,σ1) 生成一个大的尺度空间图像
L(x,y,σ2) 的过程为
L(x,y,σ2)=G(x,y,σ22−σ21−−−−−−√)∗L(x,y,σ1)(3)
其中,
G(x,y,σ22−σ21−−−−−−√)=12π(σ22−σ21)e−x2+y22(σ22−σ21)(4)
由于实际中尺度为0的图像是无法得到的,因此我们得到的尺度图像的基准图像一定是由公式3得到的,这在SIFT算法的实现过程中尤为重要,不理解这里以后SIFT算法的实现看起来回比较吃力。
2.2 高斯差分
为了在尺度空间中找到稳定不变的极值点,在SIFT算法中使用了高斯差分(DOG)函数 D(x,y,σ) ,定义为
D(x,y,σ)=[G(x,y,kσ)−G(x,y,σ)]∗I(x,y)=L(x,y,kσ)−L(x,y,σ)
其中
kσ 和
σ 是连续的两个图像的平滑尺度,所得到的差分图像再高斯差分金字塔中。
选择高斯差分函数的原因如下:
1. 计算简单,因为
L(x,y,σ) 是一定需要计算的,而
D(x,y,σ) 只需要执行减法。
2. 高斯拉普拉斯算子LoG(Laplacian of Gaussian),即图像的二阶导数,能够在不同的尺度下检测到图像的斑点特征,从而检测到图像中尺度变化下的位置不动点,但是LoG的运算效率不高。而DoG是LoG的近似。DoG和LoG的关系如下述所示:
σ∇2G=∂G∂σ≈G(x,y,kσ)−G(x,y,σ)kσ−σ(5)
因此,有
G(x,y,kσ)−G(x,y,yσ)≈(k−1)σ2∇2G(6)
而
σ2∇2G 正是尺度归一化算子的表达形式。在所有的尺度中
k−1 是一个常数,当
k 趋近于1的时候误差趋近于0,但实际上这种误差对于极值的位置检测并没有什么影响
3. 通过前人的实验证明LoG提取的特征稳定性最强。
2.3 高斯金字塔与高斯差分金字塔
高斯金字塔和高斯差分金字塔如下图所示:
这里的几个参数定义如下:
1. 金字塔的组数(number of octaves):大多数情况下为4,但是实际上这个值与图像的大小有关,我在网上查到的资料大多数为 ⌊log2(min(M,N))⌋−3 或者 ⌊log2(min(M,N))⌋−2 ,具体实现看效果确定吧。
2. 每层的组数: S1=s+3 这里 s 为极值检测需要的层数,一般取3到5。
3. 参数: k=21s , σ0=1.6
——————————————————————分割线——————————————————————
下面我们详细讲一下高斯金字塔的生成过程:
设我们输入的图像的尺度为0.5(Lowe论文中设定),由该图像进行高斯模糊得到第0组的第0层图像作为基准图像,设它的尺度为 σ0 ,即Lowe论文中的1.6,我们称 σ0 为基准层尺度,由第0层生成第1层图像的尺度为 kσ0 ,第1层生成第2层的尺度为 k2σ0 ,依次类推。
那么第0组中的图像的尺度为
σ=krσ0,r=0,1,...,s−2(7)
当第0组构建完以后,再构建第1组,第1组中的第0层图像是根据第0组的倒数第三张图像进行隔点采样得到的。由公式7我们可以知道,第0组的倒数第三层的尺度为
ksσ0 ,而
k=21s 因此其尺度为
2σ0 ,所以第1组的第0张图像的尺度依然为
2σ0 。
但是第1组中的图像是第0组中的图像经过隔点采样后得到的,因此相对于输入图像分辨率来说,其尺度为
2σ0 ,而对于第1组中的图像分辨率来说,其尺度为
σ0 。因此我们称
σ0 为基准层尺度。
上述总结规律如下:
第
o 组中的第r个图像相对于输入图像的尺度为:
σ=2okrσ0,o=0,1,2,...;r=0,1,2,...,s+2(8)
该图像相对于本组中的基准图像的尺度为:
σ=krσ0(9)
而DoG金字塔的生成过程就比较简单了,就是由高斯金字塔相邻的两层相减得到DoG金字塔中的一层,然后依次得到。由高斯金字塔中每组有
s+3 层,所以高斯差分金字塔中每组有
s+2 层。
——————————————————分割线——————————————————
金字塔中每一组(octave)中的每层(scale)的平滑尺度都不同,下一组的第一层都是由上一层的倒数第三张的图像隔点降采样得到的。
这样做的目的是使DoG满足尺度连续性,下面讲具体的原因:
在高斯金字塔中第一组中的不同层中的平滑尺度分别为
σ,kσ,k2σ,k3σ,…,ks+2σ ,把
k=21s 带入上面的数列中,则第一组中不同层的平滑尺度分别为
σ,21sσ,22sσ,23sσ,…,2ssσ,2s+1sσ,2s+2sσ
一共有
s+3 层,那么取得的高斯差分金子塔有
s+2 层,平滑尺度分别为
σ,21sσ,22sσ,23sσ,…,2ssσ,2s+1sσ
,最终有极值的只有平滑程度为
21sσ,22sσ,23sσ,…,2ssσ 的图层。
由第二组的第一层的平滑尺度为
2σ 可知,应该从第一组的倒数第三层开始下采样。按照这样的操作第二组的最终有极值的几层的平滑程度分别为
2s+1s,2s+2s,…,2s+ss ,与第一组的有极值的层的平滑尺度正好相接,满足连续性。剩余的基层操作与上面相同。
2.4 极值点的选定
如上图的最右方所示,只有当前点与其周围26个点值相比,如果是最大值或者最小值则该点为极值点,否则不是。这种比较计算量比较小,因为大多数的点在比较的前几步就已经被pass掉了
这里还有一个问题,对于除第一组以外的其他组中得到的极值点的位置,如何映射到原图中的位置呢?这里我觉得可能是根据位置的对应关系,但是具体还不清楚,等我把sift的实现源码看完后再解释。(这样理解是对的!)
三、特征点的精确定位
计算机中存储的图像数据是离散的,而我们之前找到的极值点也就是离散空间中的极值点,但是离散空间中的极值点并不是真实的连续空间中的极值点。所以需要对DoG空间进行拟合处理,以找到极值点的精确位置和尺度。另外,我们还需要去除那些在边缘位置的极值点,以提高关键点的稳定性。
3.1 特征点精确定位
在Lowe的论文中,使用的是泰勒展开式作为拟合函数。通过SIFT算法详解(1)综述与尺度空间检测中提到的,我们得到的极值点是一个三维向量,包括它所在的尺度 σ 以及所在尺度图像中的位置坐标,即 X=(x,y,σ) .因此需要三维的泰勒展开式进行展开,设 X0=(x0,y0,σ0) ,则其展开式的矩阵形式为:
f(⎡⎣⎢xyσ⎤⎦⎥)≈f(⎡⎣⎢x0y0σ0⎤⎦⎥)+[∂f∂x∂f∂y∂f∂σ]⎛⎝⎜⎡⎣⎢xyσ⎤⎦⎥−⎡⎣⎢x0y0σ0⎤⎦⎥⎞⎠⎟+12([xyσ]−[x0y0σ0])⎡⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢∂2f∂x∂x∂2f∂x∂y∂2f∂x∂σ∂2f∂x∂y∂2f∂y∂y∂2f∂y∂σ∂2f∂x∂σ∂2f∂y∂σ∂2f∂σ∂σ⎤⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎛⎝⎜⎡⎣⎢xyσ⎤⎦⎥−⎡⎣⎢x0y0σ0⎤⎦⎥⎞⎠⎟(10)
若写成矢量形式,则为
f(X)=f(X0)+∂fT∂X(X−X0)+12(X−X0)T∂2f∂X2(X−X0)(11)
在这里
X0 表示离散的差值中心,
X 表示拟合后连续空间的差值点坐标,则设
X^=X−X0 ,表示偏移量,带入11式,另求得的导数为0,则有
X^=−∂2f−1∂X2∂f∂X(12)
把该极值点带入到原公式中,则有结果
f(X^)=f(X0)+12∂fT∂XX^(13)
只要12式中得到的偏移量大于0.5,则认为偏移量过大,需要把位置移动到拟合后的新位置,继续进行迭代求偏移量,若迭代过一定次数后偏移量仍然大于0.5,则抛弃该点。如果迭代过程中有偏移量小于0.5,则停止迭代。
另外,如果13式中得到
f(X^) 过小,则抛弃该点,Lowe论文中阈值为0.03(设灰度值为0~1)
3.2 去除不稳定极值点
有些极值点的位置是在图像的边缘位置的,因为图像的边缘点很难定位,同时也容易受到噪声的干扰,我们把这些点看做是不稳定的极值点,需要进行去除。
由于图像中的物体的边缘位置的点的主曲率一般会比较高,因此我们可以通过主曲率来判断该点是否在物体的边缘位置。某像素点位置处的主曲率可以由二维的Hessian矩阵 H 计算得到
H=[Dxx(x,y)Dxy(x,y)Dxy(x,y)Dyy(x,y)]
设该矩阵的两个特征值分别为
α和β ,其中
α=γβ ,有如下公式:
Tr(H)=α+β(14)
Det(H)=αβ(15)
其中
Tr(H) 表示矩阵的直迹,
Det(H) 表示的矩阵的行列式。
首先需要去除行列式为负的点。接下来需要去掉主曲率比较大的点,Lowe中使用如下判断规则:
Tr(H)2Det(H)=(γβ+β)2γβ2=(γ+1)2γ
这里
γ 越大,则表示该点越有可能在边缘,因此要检查主曲率是否超过一定的阈值
γ0 ,只需要判断
Tr(H)2Det(H)<(γ0+1)2γ0(16)
Lowe论文中阈值为10。
四、特征点方向的计算
为了实现特征点的旋转不变性,因此需要计算特征点的角度。在计算特征点的方向时是根据特征点所在的高斯尺度图像中的局部特征计算出的。该高斯尺度 σ 是已知的,并且该尺度是相对于该图像所在的组的基准图像的。所谓的局部特征就是特征点的邻域区域内所有像素的梯度幅角和梯度幅值,这里邻域区域定义为在该图像中以特征点为圆心,以 r 为半径的圆形区域
r=3∗1.5σ(17)
这里的
σ 就是上面提到的相对于该组的基准图像的尺度。
像素的梯度幅值计算公式为
m(x,y)=(L(x+1,y)−L(x−1,y))2+(L(x,y+1)−L(x,y−1))2−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−√(18)
像素的梯度幅角的计算公式为
θ(x,y)=arctan(L(x,y+1)−L(x,y−1)L(x+1,y)−L(x−1,y))(19)
因为在特征点的邻域范围内并不是所有的像素的权值都是相同的,因此还需要对该邻域范围内的像素点进行加权,这里采用的是高斯加权,该高斯加权的方差
σm 为
σm=1.5σ ,这里的
σ 也是相对于该组的基准图像的尺度。
在完成邻域范围内的梯度幅值和幅角的计算以后,需要建立直方图来对邻域内各个像素点的幅角进行记录。在这里直方图一共分为36个柱,每个柱表示10度。把邻域内的所有像素点按所在的幅角范围进行分类,这里以0度~9度为例,把邻域内的所有幅角在该范围内的像素点的幅角乘以加权后的值相加作为该柱的高度。
把直方图建立好后,为了防止噪声的干扰,需要对直方图进行平滑。在OpenCV中的SIFT源码是使用下式进行平滑的
H(i)=h(i−2)+h(i+2)16+4∗(h(i−1)+h(i+1))16+6∗h(i)16(20)
在这里
i 的取值范围为0到35,循环取值,如i为35时,i+1 = 0,i+2 = 1。
需要寻找直方图中的最高柱作为该特征点的主方向,因为该柱体表示的是一个范围,要精确求值需要如下的拟合
B=i+H(i−1)−H(i+1)2∗(H(i−1)+H(i+1)−2H(i))(21)
θ=360−10∗B(22)
同理,这里的
i 也是0到35取值。
我们需要求出该柱体的具体的值,因为在Lowe的论文中,最高柱体的80%是一个阈值,用来判断该特征点的辅方向。
这样我们已经求出了SIFT特征点,以及特征点的方向,下面就是对于特征点的描述符的生成过程。
经过前面的一系列的操作,我们已经可以得到了图像中的所有特征点,并且计算了他们的方向。接下来要做的就是特征点描述符的生成了,有了特征点描述符,我们就可以准确的描述特征点,相当于特征点的特征,后面我们进行特征点匹配时用到的特征就是特征点描述符。
特征点描述符是跟特征点所在的尺度相关的,因此我们需要在特征点所在的尺度图像中生成特征点的描述符。在Lowe的论文中,把特征点的邻域区域划分为
d∗d 个正方形区域,Lowe论文中取
d=4 。每个正方形的区域的边长为
3σ ,这里的
σ 与SIFT算法详解(2)中定义的一样,也是相对于该组中的基准层的尺度。由于实际情况下,编程时用到双线性差值,因此这里用到的特征点邻域的边长实际为
3σ(d+1) ,因此邻域中一共有
(3σ(d+1))2 个像素点。
为了保证特征点的方向不变性,我们需要把特征点及其邻域进行旋转,旋转角度即为特征点的角度。由于是对正方形进行旋转,为了使旋转的区域包含整个正方形,则旋转的半径应为这个正方形的对角线的一半,即
r=3σ(d+1)2√2
因此特征点邻域实际上有
(2r+1)2 个像素点。
像素点的旋转过程如下
[x′y′]=[cosθsinθ−sinθcosθ][xy]
其中
[x′y′] 为旋转后的结果,
[xy] 为旋转前的坐标。
在这里,我们仍然需要计算像素点的梯度幅值和梯度幅角,用以生成直方图,同样,这里根据像素点与特征点的距离,像素点的加权值也是不一样的,这里仍然采用高斯加权,高斯处理的方法为
d22 。其实我们可以先在旋转前计算出各个像素点的梯度幅值和幅角,然后在进行旋转,拿这些梯度幅值和幅角进行对应。
最终形成的是一个128柱的直方图(
d∗d∗8,这里d=4,8是把360度分成8份 ),这个直方图的结果也就是我们的特征描述符。
这里需要注意一下,因为我们把特征点的邻域区域划分为了
d∗d 份,每份中的小正方形为
3σ∗3σ 。但是我们在统计直方图的时候,是把每个正方形看做一个整体进行统计的(即8个方向),那么一个显而易见的问题出现了:一个正方形中有很多像素点,这些像素点对于这个正方形的整体的贡献也是有权值的,因此还需要在此进行高斯加权。
因此每个像素点对于整个直方图的贡献的计算如下所述:
- 计算各个像素点的梯度幅值和梯度幅角;
- 根据该像素点距离特征点的距离进行加权(即第一次高斯加权),该像素点的幅值乘以加权值;
- 根据该像素点在所在的小正方形区域内据中心的距离进行加权(即第二次高斯加权),把2中的结果再乘以一个权值。
经过上面的计算,就可以得到128柱的直方图 {p1,p2,...,p128} ,为了去除光照的影响,需要进行归一化处理
qi=pip21+p22+...+p2128−−−−−−−−−−−−−−√,i=1,2,3,...,128
实际上为了去除非线性光照的变化,在实现的过程中对于已经归一化好的描述符
{q1,q2,...,q128} 需要设定一个阈值,一般阈值为0.2,当
qi 超过0.2以后,则
qi=0.2
————————————————————————————分割线————————————————————————————
最后放一点资料,主要是博客网址,其实现在SIFT算法研究的已经比较透彻了,网上有好多分析SIFT算法的文章,自己去找,英文好的话可以直接去读Lowe的论文。
这里放一些我写这几篇博客主要参考的网址,因为我写博客主要的目的是让我确认我确实理解这些算法了,因为只有理解了才能顺畅的介绍下来,其实我这几篇博客我参考的人有很多,主要有:
赵春江的专栏:Opencv2.4.9源码分析——SIFT
这篇写的SIFT算法非常非常详细,作者真是非常厉害!不过有些许错误,不过理解了SIFT以后发现这点错误也比较容易,这里就不列举了
小魏的修行路:SIFT原理与源码分析
这一系列文章也写得非常好,这两篇博文可以参照着看。
Rachel Zhang的介绍SIFT的博文
我的博客主要是用我认为好理解的话把SIFT算法的流程又说了一遍,写的马马虎虎。