文章来源:http://blog.csdn.net/lhanchao/article/details/52345845
之前在学习三维重建的过程中,了解过SIFT算法,现在老师要求详细的了解SIFT算法,看看能不能对它进行改进,于是又详细的看了一遍SIFT算法。记录一下。
SIFT(Scale Invariant Feature Transform)全称尺度不变特征变换,是1999年Lowe提出的一种局部特征描述算子,在2004年得到了改善。
SIFT算子是把图像中检测到的特征点用一个128维的特征向量进行描述,因此一幅图像经过SIFT算法后表示为一个128维的特征向量集,该特征向量集具有对图像缩放,平移,旋转不变的特征,对于光照、仿射和投影变换也有一定的不变性,是一种非常优秀的局部特征描述算法。
SIFT算法的流程分别为:
下面将会依次对这几步进行介绍。
为了在尺度空间中找到稳定不变的极值点,在SIFT算法中使用了高斯差分(DOG)函数D(x,y,σ)D(x,y,\sigma),定义为D(x,y,σ)=[G(x,y,kσ)−G(x,y,σ)]∗I(x,y)=L(x,y,kσ)−L(x,y,σ)D(x,y,\sigma) = [G(x,y,k\sigma) - G(x,y,\sigma)] * I(x,y)\\=L(x,y,k\sigma) - L(x,y,\sigma)其中kσk\sigma和σ\sigma是连续的两个图像的平滑尺度,所得到的差分图像再高斯差分金字塔中。
选择高斯差分函数的原因如下:
1. 计算简单,因为L(x,y,σ)L(x,y,\sigma)是一定需要计算的,而D(x,y,σ)D(x,y,\sigma)只需要执行减法。
2. 高斯拉普拉斯算子LoG(Laplacian of Gaussian),即图像的二阶导数,能够在不同的尺度下检测到图像的斑点特征,从而检测到图像中尺度变化下的位置不动点,但是LoG的运算效率不高。而DoG是LoG的近似。DoG和LoG的关系如下述所示:σ∇2G=∂G∂σ≈G(x,y,kσ)−G(x,y,σ)kσ−σ(5)\sigma\nabla^2G = \frac{\partial G}{\partial\sigma} \approx \frac{G(x,y,k\sigma) - G(x,y,\sigma)}{k\sigma - \sigma}\tag{5} 因此,有G(x,y,kσ)−G(x,y,yσ)≈(k−1)σ2∇2G(6)G(x,y,k\sigma) - G(x,y,y\sigma) \approx (k-1)\sigma^2\nabla^2G\tag{6}而σ2∇2G\sigma^2\nabla^2G正是尺度归一化算子的表达形式。在所有的尺度中k−1k-1是一个常数,当kk趋近于1的时候误差趋近于0,但实际上这种误差对于极值的位置检测并没有什么影响
3. 通过前人的实验证明LoG提取的特征稳定性最强。
高斯金字塔和高斯差分金字塔如下图所示:
这里的几个参数定义如下:
1. 金字塔的组数(number of octaves):大多数情况下为4,但是实际上这个值与图像的大小有关,我在网上查到的资料大多数为⌊log2(min(M,N))⌋−3⌊log_2(min(M,N))⌋ - 3或者⌊log2(min(M,N))⌋−2⌊log_2(min(M,N))⌋ - 2,具体实现看效果确定吧。
2. 每层的组数:S1=s+3S_1 = s+3这里ss为极值检测需要的层数,一般取3到5。
3. 参数:k=21sk = 2^{\frac{1}{s}},σ0=1.6\sigma_0 = 1.6
——————————————————————分割线——————————————————————
下面我们详细讲一下高斯金字塔的生成过程:
设我们输入的图像的尺度为0.5(Lowe论文中设定),由该图像进行高斯模糊得到第0组的第0层图像作为基准图像,设它的尺度为σ0\sigma_0,即Lowe论文中的1.6,我们称σ0\sigma_0为基准层尺度,由第0层生成第1层图像的尺度为kσ0k\sigma_0,第1层生成第2层的尺度为k2σ0k^2\sigma_0,依次类推。
那么第0组中的图像的尺度为σ=krσ0,r=0,1,...,s−2(7)\sigma = k^r\sigma_0 ,r=0,1,...,s-2\tag{7}当第0组构建完以后,再构建第1组,第1组中的第0层图像是根据第0组的倒数第三张图像进行隔点采样得到的。由公式7我们可以知道,第0组的倒数第三层的尺度为ksσ0k^s\sigma_0,而k=21sk = 2^{\frac{1}{s}}因此其尺度为2σ02\sigma_0,所以第1组的第0张图像的尺度依然为2σ02\sigma_0。
但是第1组中的图像是第0组中的图像经过隔点采样后得到的,因此相对于输入图像分辨率来说,其尺度为2σ02\sigma_0,而对于第1组中的图像分辨率来说,其尺度为σ0\sigma_0。因此我们称σ0\sigma_0为基准层尺度。
上述总结规律如下:
第oo组中的第r个图像相对于输入图像的尺度为:σ=2okrσ0,o=0,1,2,...;r=0,1,2,...,s+2(8)\sigma = 2^ok^r\sigma_0 ,o = 0,1,2,...;r = 0,1,2,...,s+2\tag{8}该图像相对于本组中的基准图像的尺度为:σ=krσ0(9)\sigma = k^r\sigma_0\tag{9}
而DoG金字塔的生成过程就比较简单了,就是由高斯金字塔相邻的两层相减得到DoG金字塔中的一层,然后依次得到。由高斯金字塔中每组有s+3s+3层,所以高斯差分金字塔中每组有s+2s+2层。
——————————————————分割线——————————————————
金字塔中每一组(octave)中的每层(scale)的平滑尺度都不同,下一组的第一层都是由上一层的倒数第三张的图像隔点降采样得到的。
这样做的目的是使DoG满足尺度连续性,下面讲具体的原因:
在高斯金字塔中第一组中的不同层中的平滑尺度分别为σ,kσ,k2σ,k3σ,…,ks+2σ\sigma,k\sigma,k^2\sigma,k^3\sigma,…,k^{s+2}\sigma,把k=21sk=2^{\frac{1}{s}}带入上面的数列中,则第一组中不同层的平滑尺度分别为σ,21sσ,22sσ,23sσ,…,2ssσ,2s+1sσ,2s+2sσ\sigma,2^{\frac{1}{s}}\sigma,2^{\frac{2}{s}}\sigma,2^{\frac{3}{s}}\sigma,…,2^{\frac{s}{s}}\sigma,2^{\frac{s+1}{s}}\sigma,2^{\frac{s+2}{s}}\sigma一共有s+3s+3层,那么取得的高斯差分金子塔有s+2s+2层,平滑尺度分别为σ,21sσ,22sσ,23sσ,…,2ssσ,2s+1sσ\sigma,2^{\frac{1}{s}}\sigma,2^{\frac{2}{s}}\sigma,2^{\frac{3}{s}}\sigma,…,2^{\frac{s}{s}}\sigma,2^{\frac{s+1}{s}}\sigma,最终有极值的只有平滑程度为21sσ,22sσ,23sσ,…,2ssσ2^{\frac{1}{s}}\sigma,2^{\frac{2}{s}}\sigma,2^{\frac{3}{s}}\sigma,…,2^{\frac{s}{s}}\sigma的图层。
由第二组的第一层的平滑尺度为2σ2\sigma可知,应该从第一组的倒数第三层开始下采样。按照这样的操作第二组的最终有极值的几层的平滑程度分别为2s+1s,2s+2s,…,2s+ss2^{\frac{s+1}{s}},2^{\frac{s+2}{s}},…,2^{\frac{s+s}{s}},与第一组的有极值的层的平滑尺度正好相接,满足连续性。剩余的基层操作与上面相同。
如上图的最右方所示,只有当前点与其周围26个点值相比,如果是最大值或者最小值则该点为极值点,否则不是。这种比较计算量比较小,因为大多数的点在比较的前几步就已经被pass掉了
这里还有一个问题,对于除第一组以外的其他组中得到的极值点的位置,如何映射到原图中的位置呢?这里我觉得可能是根据位置的对应关系,但是具体还不清楚,等我把sift的实现源码看完后再解释。(这样理解是对的!)
计算机中存储的图像数据是离散的,而我们之前找到的极值点也就是离散空间中的极值点,但是离散空间中的极值点并不是真实的连续空间中的极值点。所以需要对DoG空间进行拟合处理,以找到极值点的精确位置和尺度。另外,我们还需要去除那些在边缘位置的极值点,以提高关键点的稳定性。
在Lowe的论文中,使用的是泰勒展开式作为拟合函数。通过SIFT算法详解(1)综述与尺度空间检测中提到的,我们得到的极值点是一个三维向量,包括它所在的尺度σ\sigma以及所在尺度图像中的位置坐标,即X=(x,y,σ)X = (x,y,\sigma).因此需要三维的泰勒展开式进行展开,设X0=(x0,y0,σ0)X_0 = (x_0,y_0,\sigma_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(\left[
有些极值点的位置是在图像的边缘位置的,因为图像的边缘点很难定位,同时也容易受到噪声的干扰,我们把这些点看做是不稳定的极值点,需要进行去除。
由于图像中的物体的边缘位置的点的主曲率一般会比较高,因此我们可以通过主曲率来判断该点是否在物体的边缘位置。某像素点位置处的主曲率可以由二维的Hessian矩阵HH计算得到H=[Dxx(x,y)Dxy(x,y)Dxy(x,y)Dyy(x,y)]H = \left[
为了实现特征点的旋转不变性,因此需要计算特征点的角度。在计算特征点的方向时是根据特征点所在的高斯尺度图像中的局部特征计算出的。该高斯尺度σ\sigma是已知的,并且该尺度是相对于该图像所在的组的基准图像的。所谓的局部特征就是特征点的邻域区域内所有像素的梯度幅角和梯度幅值,这里邻域区域定义为在该图像中以特征点为圆心,以rr为半径的圆形区域r=3∗1.5σ(17)r = 3*1.5\sigma\tag{17}这里的σ\sigma就是上面提到的相对于该组的基准图像的尺度。
像素的梯度幅值计算公式为m(x,y)=(L(x+1,y)−L(x−1,y))2+(L(x,y+1)−L(x,y−1))2−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−√(18)m(x,y) = \sqrt{(L(x+1,y) - L(x-1,y))^2 + (L(x,y+1)-L(x,y-1))^2}\tag{18}像素的梯度幅角的计算公式为θ(x,y)=arctan(L(x,y+1)−L(x,y−1)L(x+1,y)−L(x−1,y))(19)\theta(x,y) = arctan\left(\frac{L(x,y+1)-L(x,y-1)}{L(x+1,y)-L(x-1,y)}\right)\tag{19}因为在特征点的邻域范围内并不是所有的像素的权值都是相同的,因此还需要对该邻域范围内的像素点进行加权,这里采用的是高斯加权,该高斯加权的方差σm\sigma_m为σm=1.5σ\sigma_m = 1.5\sigma,这里的σ\sigma也是相对于该组的基准图像的尺度。
在完成邻域范围内的梯度幅值和幅角的计算以后,需要建立直方图来对邻域内各个像素点的幅角进行记录。在这里直方图一共分为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)H(i) = \frac{h(i-2)+h(i+2)}{16}+ \frac{4*(h(i-1)+h(i+1))}{16}+\frac{6*h(i)}{16}\tag{20}在这里ii的取值范围为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)B = i + \frac{H(i-1)-H(i+1)}{2*(H(i-1)+H(i+1)-2H(i))}\tag{21}θ=360−10∗B(22)\theta = 360 - 10*B\tag{22}同理,这里的ii也是0到35取值。
我们需要求出该柱体的具体的值,因为在Lowe的论文中,最高柱体的80%是一个阈值,用来判断该特征点的辅方向。
这样我们已经求出了SIFT特征点,以及特征点的方向,下面就是对于特征点的描述符的生成过程。
经过前面的一系列的操作,我们已经可以得到了图像中的所有特征点,并且计算了他们的方向。接下来要做的就是特征点描述符的生成了,有了特征点描述符,我们就可以准确的描述特征点,相当于特征点的特征,后面我们进行特征点匹配时用到的特征就是特征点描述符。
特征点描述符是跟特征点所在的尺度相关的,因此我们需要在特征点所在的尺度图像中生成特征点的描述符。在Lowe的论文中,把特征点的邻域区域划分为d∗dd*d个正方形区域,Lowe论文中取d=4d = 4。每个正方形的区域的边长为3σ3\sigma,这里的σ\sigma与SIFT算法详解(2)中定义的一样,也是相对于该组中的基准层的尺度。由于实际情况下,编程时用到双线性差值,因此这里用到的特征点邻域的边长实际为3σ(d+1)3\sigma(d+1),因此邻域中一共有(3σ(d+1))2(3\sigma(d+1))^2个像素点。
为了保证特征点的方向不变性,我们需要把特征点及其邻域进行旋转,旋转角度即为特征点的角度。由于是对正方形进行旋转,为了使旋转的区域包含整个正方形,则旋转的半径应为这个正方形的对角线的一半,即r=3σ(d+1)2√2r = \frac{3\sigma(d+1)\sqrt{2}}{2}因此特征点邻域实际上有(2r+1)2(2r+1)^2个像素点。
像素点的旋转过程如下[x′y′]=[cosθsinθ−sinθcosθ][xy]\left[
经过上面的计算,就可以得到128柱的直方图{p1,p2,...,p128}\{p_1,p_2,...,p_{128}\},为了去除光照的影响,需要进行归一化处理qi=pip21+p22+...+p2128−−−−−−−−−−−−−−√,i=1,2,3,...,128q_i = \frac{p_i}{\sqrt{p_1^2+p_2^2+...+p_{128}^2}},i = 1,2,3,...,128
实际上为了去除非线性光照的变化,在实现的过程中对于已经归一化好的描述符{q1,q2,...,q128}\{q_1,q_2,...,q_{128}\}需要设定一个阈值,一般阈值为0.2,当qiq_i超过0.2以后,则qi=0.2q_i = 0.2
————————————————————————————分割线————————————————————————————
最后放一点资料,主要是博客网址,其实现在SIFT算法研究的已经比较透彻了,网上有好多分析SIFT算法的文章,自己去找,英文好的话可以直接去读Lowe的论文。
这里放一些我写这几篇博客主要参考的网址,因为我写博客主要的目的是让我确认我确实理解这些算法了,因为只有理解了才能顺畅的介绍下来,其实我这几篇博客我参考的人有很多,主要有:
赵春江的专栏:Opencv2.4.9源码分析——SIFT
这篇写的SIFT算法非常非常详细,作者真是非常厉害!不过有些许错误,不过理解了SIFT以后发现这点错误也比较容易,这里就不列举了
小魏的修行路:SIFT原理与源码分析
这一系列文章也写得非常好,这两篇博文可以参照着看。
Rachel Zhang的介绍SIFT的博文
我的博客主要是用我认为好理解的话把SIFT算法的流程又说了一遍,写的马马虎虎。