xw20084898的专栏

只有一条路不能选择——那就是放弃的路;只有一条路不能拒绝—— 那就是成长的路。...

SIFT特征分析与源码解读

SIFT(Scale-invariant feature transform)是一种检测局部特征的算法,该算法通过求一幅图中的特征点(interest points,or corner points)及其有关scale 和 orientation 的描述子得到特征并进行图像特征点匹配,获得了良好效果,详细解析如下:

算法描述

SIFT特征不只具有尺度不变性,即使改变旋转角度,图像亮度或拍摄视角,仍然能够得到好的检测效果。整个算法分为以下几个部分:

1. 构建尺度空间

这是一个初始化操作,尺度空间理论目的是模拟图像数据的多尺度特征

高斯卷积核是实现尺度变换的唯一线性核,于是一副二维图像的尺度空间定义为:


其中 G(x,y,σ) 是尺度可变高斯函数 

(x,y)是空间坐标,是尺度坐标。σ大小决定图像的平滑程度,大尺度对应图像的概貌特征,小尺度对应图像的细节特征。大的σ值对应粗糙尺度(低分辨率),反之,对应精细尺度(高分辨率)。为了有效的在尺度空间检测到稳定的关键点,提出了高斯差分尺度空间(DOG scale-space)。利用不同尺度的高斯差分核与图像卷积生成。

下图所示不同σ下图像尺度空间:



关于尺度空间的理解说明:2kσ中的2是必须的,尺度空间是连续的。在  Lowe的论文中 ,将第0层的初始尺度定为1.6(最模糊),图片的初始尺度定为0.5(最清晰). 在检测极值点前对原始图像的高斯平滑以致图像丢失高频信息,所以 Lowe 建议在建立尺度空间前首先对原始图像长宽扩展一倍,以保留原始图像信息,增加特征点数量。尺度越大图像越模糊。 


图像金字塔的建立:对于一幅图像I,建立其在不同尺度(scale)的图像,也成为子八度(octave),这是为了scale-invariant,也就是在任何尺度都能够有对应的特征点,第一个子八度的scale为原图大小,后面每个octave为上一个octave降采样的结果,即原图的1/4(长宽分别减半),构成下一个子八度(高一层金字塔)。



尺度空间的所有取值,i为octave的塔数(第几个塔),s为每塔层数

由图片size决定建几个塔,每塔几层图像(S一般为3-5)0塔的第0层是原始图像(或你double后的图像),往上每一层是对其下一层进行Laplacian变换(高斯卷积,其中σ值渐大,例如可以是σ, k*σ, k*k*σ),直观上看来越往上图片越模糊。塔间的图片是降采样关系,例如1塔的第0层可以由0塔的第3down sample得到,然后进行与0塔类似的高斯卷积操作。


2. LoG近似DoG找到关键点<检测DOG尺度空间极值点>

为了寻找尺度空间的极值点,每一个采样点要和它所有的相邻点比较,看其是否比它的图像域和尺度域的相邻点大或者小。如图所示,中间的检测点和它同尺度的8个相邻点和上下相邻尺度对应的9×2个点共26个点比较,以确保在尺度空间和二维图像空间都检测到极值点。 一个点如果在DOG尺度空间本层以及上下两层的26个领域中是最大或最小值时,就认为该点是图像在该尺度下的一个特征点,如图所示。


同一组中的相邻尺度(由于k的取值关系,肯定是上下层)之间进行寻找


s=3的情况

 在极值比较的过程中,每一组图像的首末两层是无法进行极值比较的,为了满足尺度变化的连续性(下面有详解)
我们在每一组图像的顶层继续用高斯模糊生成了 3 幅图像,高斯金字塔有每组S+3层图像。DOG金字塔每组有S+2层图像.

==========================================
这里有的童鞋不理解什么叫“为了满足尺度变化的连续性”,现在做仔细阐述:
假设s=3,也就是每个塔里有3层,则k=21/s=21/3,那么按照上图可得Gauss Space和DoG space 分别有3个(s个)和2个(s-1个)分量,在DoG space中,1st-octave两项分别是σ,kσ; 2nd-octave两项分别是2σ,2kσ;由于无法比较极值,我们必须在高斯空间继续添加高斯模糊项,使得形成σ,kσ,k2σ,k3σ,k4σ这样就可以选择DoG space中的中间三项kσ,k2σ,k3σ(只有左右都有才能有极值),那么下一octave中(由上一层降采样获得)所得三项即为2kσ,2k2σ,2k3σ,其首项2kσ=24/3。刚好与上一octave末项k3σ=23/3尺度变化连续起来,所以每次要在Gaussian space添加3项,每组(塔)共S+3层图像,相应的DoG金字塔有S+2层图像。
==========================================

使用Laplacian of Gaussian能够很好地找到找到图像中的兴趣点,但是需要大量的计算量,所以使用Difference of Gaussian图像的极大极小值近似寻找特征点.DOG算子计算简单,是尺度归一化的LoG算子的近似,有关DOG寻找特征点的介绍及方法详见http://blog.csdn.net/abcjennifer/article/details/7639488极值点检测用的Non-Maximal Suppression


3. 除去不好的特征点

通过拟和三维二次函数以精确确定关键点的位置和尺度(达到亚像素精度),同时去除低对比度的关键点和不稳定的边缘响应点(因为DoG算子会产生较强的边缘响应),以增强匹配稳定性、提高抗噪声能力,在这里使用近似Harris Corner检测器。

①空间尺度函数泰勒展开式如下:,对上式求导,并令其为0,得到精确的位置, 得

②在已经检测到的特征点中,要去掉低对比度的特征点和不稳定的边缘响应点。去除低对比度的点:把公式(2)代入公式(1),即在DoG Space的极值点处D(x)取值,只取前两项可得:


若   ,该特征点就保留下来,否则丢弃。

③边缘响应的去除
一个定义不好的高斯差分算子的极值在横跨边缘的地方有较大的主曲率,而在垂直边缘的方向有较小的主曲率。主曲率通过一个2×2 的Hessian矩阵H求出:

导数由采样点相邻差估计得到。

D的主曲率和H的特征值成正比,令α为较大特征值,β为较小的特征值,则

令α=γβ,则

 (r + 1)2/r的值在两个特征值相等的时候最小,随着r的增大而增大,因此,为了检测主曲率是否在某域值r下,只需检测

if (α+β)/ αβ> (r+1)2/r, throw it out.   在Lowe的文章中,取r=10。



4. 给特征点赋值一个128维方向参数

上一步中确定了每幅图中的特征点,为每个特征点计算一个方向,依照这个方向做进一步的计算, 利用关键点邻域像素的梯度方向分布特性为每个关键点指定方向参数,使算子具备旋转不变性。


为(x,y)处梯度的模值和方向公式。其中L所用的尺度为每个关键点各自所在的尺度。至此,图像的关键点已经检测完毕,每个关键点有三个信息:位置,所处尺度、方向,由此可以确定一个SIFT特征区域。


梯度直方图的范围是0~360度,其中每10度一个柱,总共36个柱。随着距
      中心点越远的领域其对直方图的贡献也响应减小.Lowe论文中还提到要使用高斯函数对直方图进行平滑,减少突变的影响。

在实际计算时,我们在以关键点为中心的邻域窗口内采样,并用直方图统计邻域像素的梯度方向。梯度直方图的范围是0~360度,其中每45度一个柱,总共8个柱, 或者每10度一个柱,总共36个柱。Lowe论文中还提到要使用高斯函数对直方图进行平滑,减少突变的影响。直方图的峰值则代表了该关键点处邻域梯度的主方向,即作为该关键点的方向


直方图中的峰值就是主方向,其他的达到最大值80%的方向可作为辅助方向

由梯度方向直方图确定主梯度方向

该步中将建立所有scale中特征点的描述子(128维)

Identify peak and assign orientation and sum of magnitude to key point.
  The user may choose a threshold to exclude key points based on their assigned sum of magnitudes.


关键点描述子的生成步骤


 通过对关键点周围图像区域分块,计算块内梯度直方图,生成具有独特性的向量,这个向量是该区域图像信息的一种抽象,具有唯一性。


5. 关键点描述子的生成

首先将坐标轴旋转为关键点的方向,以确保旋转不变性。以关键点为中心取8×8的窗口。

Figure.16*16的图中其中1/4的特征点梯度方向及scale,右图为其加权到8个主方向后的效果。

图左部分的中央为当前关键点的位置,每个小格代表关键点邻域所在尺度空间的一个像素,利用公式求得每个像素的梯度幅值与梯度方向,箭头方向代表该像素的梯度方向,箭头长度代表梯度模值,然后用高斯窗口对其进行加权运算。


图中蓝色的圈代表高斯加权的范围(越靠近关键点的像素梯度方向信息贡献越大)。然后在每4×4的小块上计算8个方向的梯度方向直方图,绘制每个梯度方向的累加值,即可形成一个种子点,如图右部分示。此图中一个关键点由2×2共4个种子点组成,每个种子点有8个方向向量信息。这种邻域方向性信息联合的思想增强了算法抗噪声的能力,同时对于含有定位误差的特征匹配也提供了较好的容错性。


计算keypoint周围的16*16的window中每一个像素的梯度,而且使用高斯下降函数降低远离中心的权重。

在每个4*4的1/16象限中,通过加权梯度值加到直方图8个方向区间中的一个,计算出一个梯度方向直方图。

这样就可以对每个feature形成一个4*4*8=128维的描述子,每一维都可以表示4*4个格子中一个的scale/orientation. 将这个向量归一化之后,就进一步去除了光照的影响。

5. 根据SIFT进行Match

生成了A、B两幅图的描述子,(分别是k1*128维和k2*128维),就将两图中各个scale(所有scale)的描述子进行匹配,匹配上128维即可表示两个特征点match上了。


实际计算过程中,为了增强匹配的稳健性,Lowe建议对每个关键点使用4×4共16个种子点来描述,这样对于一个关键点就可以产生128个数据,即最终形成128维的SIFT特征向量。此时SIFT特征向量已经去除了尺度变化、旋转等几何变形因素的影响,再继续将特征向量的长度归一化,则可以进一步去除光照变化的影响。 当两幅图像的SIFT特征向量生成后,下一步我们采用关键点特征向量的欧式距离来作为两幅图像中关键点的相似性判定度量。取图像1中的某个关键点,并找出其与图像2中欧式距离最近的前两个关键点,在这两个关键点中,如果最近的距离除以次近的距离少于某个比例阈值,则接受这一对匹配点。降低这个比例阈值,SIFT匹配点数目会减少,但更加稳定。为了排除因为图像遮挡和背景混乱而产生的无匹配关系的关键点,Lowe提出了比较最近邻距离与次近邻距离的方法,距离比率ratio小于某个阈值的认为是正确匹配。因为对于错误匹配,由于特征空间的高维性,相似的距离可能有大量其他的错误匹配,从而它的ratio值比较高。Lowe推荐ratio的阈值为0.8。但作者对大量任意存在尺度、旋转和亮度变化的两幅图片进行匹配,结果表明ratio取值在0. 4~0. 6之间最佳,小于0. 4的很少有匹配点,大于0. 6的则存在大量错误匹配点。(如果这个地方你要改进,最好给出一个匹配率和ration之间的关系图,这样才有说服力)作者建议ratio的取值原则如下:

ratio=0. 4 对于准确度要求高的匹配;
ratio=0. 6 对于匹配点数目要求比较多的匹配; 
ratio=0. 5 一般情况下。
也可按如下原则:当最近邻距离<200时ratio=0. 6,反之ratio=0. 4。ratio的取值策略能排分错误匹配点。



当两幅图像的SIFT特征向量生成后,下一步我们采用关键点特征向量的欧式距离来作为两幅图像中关键点的相似性判定度量。取图像1中的某个关键点,并找出其与图像2中欧式距离最近的前两个关键点,在这两个关键点中,如果最近的距离除以次近的距离少于某个比例阈值,则接受这一对匹配点。降低这个比例阈值,SIFT匹配点数目会减少,但更加稳定。

 

实验结果:








===============================
基本概念及一些补充
什么是局部特征?
  •局部特征从总体上说是图像或在视觉领域中一些有别于其周围的地方
  •局部特征通常是描述一块区域,使其能具有高可区分度
  •局部特征的好坏直接会决定着后面分类、识别是否会得到一个好的结果
 局部特征需具备的特性
  •重复性
  •可区分性
  •准确性
  •数量以及效率
  •不变性
 局部特征提取算法-sift
  •SIFT算法由D.G.Lowe 1999年提出,2004年完善总结。后来Y.Ke将其描述子部分用PCA代替直方图的方式,对其进行改进。
   •SIFT算法是一种提取局部特征的算法,在尺度空间寻找极值点,提取位置,尺度,旋转不变量
  •SIFT特征是图像的局部特征,其对旋转、尺度缩放、亮度变化保持不变性,对视角变化、仿射变换、噪声也保持一定程度的稳定性。
  •独特性好,信息量丰富,适用于在海量特征数据库中进行快速、准确的匹配。
  •多量性,即使少数的几个物体也可以产生大量SIFT特征向量。
  •可扩展性,可以很方便的与其他形式的特征向量进行联合。
尺度空间理论
  •尺度空间理论目的是模拟图像数据的多尺度特征
  •其基本思想是在视觉信息图像信息处理模型中引入一个被视为尺度的参数, 通过连续变化尺度参数获得不同尺度下的视觉处理信息, 然后综合这些信息以深入地挖掘图像的本质特征。
描述子生成的细节
  •以极值点为中心点,并且以此点所处于的高斯尺度sigma值作为半径因子。对于远离中心点的梯度值降低对其所处区域的直方图的贡献,防止一些突变的影响。
  •每个极值点对其进行三线性插值,这样可以把此极值点的贡献均衡的分到直方图中相邻的柱子上
归一化处理
  •在求出4*4*8的128维特征向量后,此时SIFT特征向量已经去除了尺度变化、旋转等几何变形因素的影响。而图像的对比度变化相当于每个像素点乘上一个因子,光照变化是每个像素点加上一个值,但这些对图像归一化的梯度没有影响。因此将特征向量的长度归一化,则可以进一步去除光照变化的影响。
  •对于一些非线性的光照变化,SIFT并不具备不变性,但由于这类变化影响的主要是梯度的幅值变化,对梯度的方向影响较小,因此作者通过限制梯度幅值的值来减少这类变化造成的影响。
PCA-SIFT算法
  •PCA-SIFT与标准SIFT有相同的亚像素位置,尺度和主方向。但在第4步计算描述子的设计,采用的主成分分析的技术。
  •下面介绍一下其特征描述子计算的部分:
    •用特征点周围的41×41的像斑计算它的主元,并用PCA-SIFT将原来的2×39×39维的向量降成20维,以达到更精确的表示方式。
    •它的主要步骤为,对每一个关键点:在关键点周围提取一个41×41的像斑于给定的尺度,旋转到它的主方向 ;计算39×39水平和垂直的梯度,形成一个大小为3042的矢量;用预先计算好的投影矩阵n×3042与此矢量相乘;这样生成一个大小为n的PCA-SIFT描述子。
===============================

辅助资料:





===============================


Reference:

Lowe SIFT 原文:http://www.cs.ubc.ca/~lowe/papers/ijcv04.pdf

SIFT 的C实现:https://github.com/robwhess/opensift/blob/master/src

MATLAB 应用Sift算子的模式识别方法:http://blog.csdn.net/abcjennifer/article/details/7372880

http://blog.csdn.net/abcjennifer/article/details/7365882

http://en.wikipedia.org/wiki/Scale-invariant_feature_transform#David_Lowe.27s_method

http://blog.sciencenet.cn/blog-613779-475881.html

http://www.cnblogs.com/linyunzju/archive/2011/06/14/2080950.html

http://www.cnblogs.com/linyunzju/archive/2011/06/14/2080951.html

http://blog.csdn.net/ijuliet/article/details/4640624

http://www.cnblogs.com/cfantaisie/archive/2011/06/14/2080917.html  (部分图片有误,以本文中的图片为准)

SIFT解析(一)建立高斯金字塔

SIFT(Scale-Invariant Feature Transform,尺度不变特征转换)在目标识别、图像配准领域具有广泛的应用,下面按照SIFT特征的算法流程对其进行简要介绍对SIFT特征做简要介绍。

高斯金字塔是SIFT特征提取的第一步,之后特征空间中极值点的确定,都是基于高斯金字塔,因此SIFT特征学习的第一步是如何建立的高斯金字塔。

明白几个定义:

高斯金字塔 对于高斯金字塔,很容易直观地理解为对同一尺寸的图像,然后进行不同程度的高斯平滑,这些图像构成高斯金字塔,这种是不对的,这描述的图像集合叫做一个八度。金字塔总要有个变“尖”的过程,真正的高斯金字塔要有个平滑以及下采样的过程,因此整个图像平滑以及下采样再平滑,构成的所有图像集合才构成了图像的高斯金字塔。

八度(octave) 简单地说八度就是在特定尺寸(长宽)下,经不同高斯核模糊的图像的集合。八度的集合是高斯金字塔。

为什么要构建高斯金字塔:

整个高斯金字塔,或者说是差分高斯金字塔是我们确定SIFT特征的基础,让我们首先想想高斯金字塔到底干了一件什么事情,他到底模仿的是什么?答案很容易确定,高斯金字塔模仿的是图像的不同的尺度,尺度应该怎样理解?对于一副图像,你近距离观察图像,与你在一米之外观察,看到的图像效果是不同的,前者比较清晰,后者比较模糊,前者比较大,后者比较小,通过前者能看到图像的一些细节信息,通过后者能看到图像的一些轮廓的信息,这就是图像的尺度,图像的尺度是自然存在的,并不是人为创造的。好了,到这里我们明白了,其实以前对一幅图像的处理还是比较单调的,因为我们的关注点只落在二维空间,并没有考虑到“图像的纵深”这样一个概念,如果将这些内容考虑进去我们是不是会得到更多以前在二维空间中没有得到的信息呢?于是高斯金字塔横空出世了,它就是为了在二维图像的基础之上,榨取出图像中自然存在的另一个维度:尺度。因为高斯核是唯一的线性核,也就是说使用高斯核对图像模糊不会引入其他噪声,因此就选用了高斯核来构建图像的尺度。

下图两幅图像是典型的图像高斯金字塔,这就是模仿的图像离你远去时在你视网膜上的成像,图像分别以动态方式表示。


高斯金字塔的构建步骤:

根据Lowe的论文,高斯金字塔的构建还是比较简单的,高斯卷积和是尺度变换的唯一的线性核。

高斯金字塔构建过程中,一般首先将图像扩大一倍,在扩大的图像的基础之上构建高斯金字塔,然后对该尺寸下图像进行高斯模糊,几幅模糊之后的图像集合构成了一个八度,然后对该八度下的最模糊的一幅图像进行下采样的过程,长和宽分别缩短一倍,图像面积变为原来四分之一。这幅图像就是下一个八度的初始图像,在初始图像图像的基础上完成属于这个八度的高斯模糊处理,以此类推完成整个算法所需要的所有八度构建,这样这个高斯金字塔就构建出来了。构建出的金字塔如下图所示:


什么是尺度空间:

以上已经从人视觉感知的角度让大家感性认识了“尺度”,上文也提到使用高斯核来实现尺度的变换,那么具体实现过程中,尺度体现在哪里?是如何量化的呢?怎么在高斯金字塔中,两个变量很重要,即第几个八度(o)和八度中的第几层(s),这两个量合起来(o,s)就构成了高斯金字塔的尺度空间。尺度空间也不难理解,首先一个八度中图像的长和宽是相等的,即变量o控制的是塔中尺寸这个尺度;区分同一个尺寸尺度下的图像,就需要s了,s控制了一个八度中不同的模糊程度。这样(o,s)就能够确定高斯金字塔中的唯一一幅图像了,这是个三维空间,两维坐标,一维是图像。

根据lowe的论文,(o,s)作用于一幅图像是通过公式

      

确定的。通过公式也可以看出,尺度空间是连续的,两个变量控制着δ的值,其中在第一个八度中有 1<(o+s/S)<=2 ,同理在第二个八度中有2<(o+s/S)<=3,以此类推,δ中的关键部分(o+s/S)部分是逐渐增大的(具体实现时,有些高斯金字塔中这个值是增大,但不是逐渐均匀增大,只能说是连续的)。 


上图中第一个八度的中图像的尺度分别是δ,kδ,k^2δ......,第二个八度的尺度分别是2δ,2kδ,2k^2δ........,同理第三个八度的尺度分别是4δ,4kδ,4k^2δ........。这个序列是通过下式来确定的:


所以每增加一级八度,δ都要扩大2倍,在一个八度中,k的上标s来区分不同的高斯核。

至此,高斯金字塔中的尺度空间已经说得差不多了,包括尺度是什么,包括高斯金字塔中尺度的连续性,后文将详细说明尺度空间的连续性。下图形象说明了什么是尺度空间:



构建差分高斯金字塔

构建高斯金字塔是为了后续构建差分高斯金字塔。对同一个八度的两幅相邻的图像做差得到插值图像,所有八度的这些插值图像的集合,就构成了差分高斯金字塔。过程如下图所示,差分高斯金字塔的好处是为后续的特征点的提取提供了方便。

到这里,高斯金字塔构建的主要部分、关键点都弄好了,一些非常重要的认知就要呼之欲出了,下面解释整个空间的尺度连续性!这是差分高斯金字塔的重中之重!

尺度空间的连续性

这里注意,连续性的主语既不是高斯金字塔,也不是差分高斯金字塔,而是尺度空间。在弄清楚这个问题之前,我们还需要解决一个问题,即为什么高斯金字塔中每个八度有s+3幅高斯图像?s的意思是将来我们在差分高斯金字塔中求极值点的时候,我们要在每个八度中求s层点,通过lowe论文可知,每一层极值点是在三维空间(图像二维,尺度一维)中比较获得,因此为了获得s层点,那么在差分高斯金字塔中需要有s+2图像,好了,继续上溯,如果差分高斯金字塔中有s+2幅图像,那么高斯金字塔中就必须要有s+3幅图像了,因为差分高斯金字塔是由高斯金字塔相邻两层相减得到的。好了,到了这里似乎真相大白,但是我们上面的推导有一个致命的问题,我们上来就假设“我们要在每个八度中求s层点”,为什么要s层点呢?这才是这个小节的主题:是为了保持尺度的连续性!下面进行详细的分析:

以一个八度中的图像为例说明(此处最好结合OpenCV中金字塔构建部分的源码<下文已列出,可以参照>)

高斯金字塔和差分高斯金字塔那几个公式还要在这里贴出来一下:

高斯函数G对图像I的模糊函数:


高斯差分函数:


通过以上这两个公式,可以确定一个八度中(以第一个八度为例)高斯图像和差分高斯图像的尺度如下(以lowe论文为例,s=3,所以每个八度中会有3+3=6幅图像),每一幅图像的尺度也在图像标示了出来。

在lowe的论文中s=3,因此有

因此,当前八度中各高斯图像的尺度依次为:

σ,2^(1/3)σ,    2^(2/3)σ,     2^(3/3)σ,    2^(4/3)σ,     2^(5/3)σ;

    当前八度中各差分高斯图像的尺度依次为:

σ,2^(1/3)σ,    2^(2/3)σ,     2^(3/3)σ,   2^(4/3)σ。

同理,我们可以推断出,下一个八度中各高斯图像的尺度依次为:

2×σ,2×2^(1/3)σ,2×2^(2/3)σ,2×2^(3/3)σ,2×2^(4/3)σ,2×2^(5/3)σ;

 下一个八度中各差分高斯图像的尺度依次为:

2×σ,2×2^(1/3)σ,2×2^(2/3)σ,2×2^(3/3)σ,2×2^(4/3)σ。

可以观察到,其中红色标注数据所代表的层,是差分高斯金字塔中获得极值点的层,也就是说只有在这些层上才发生与上下两层比较获得极值点的操作。下面将这些红色数据连成一串:2^(1/3)σ, 2^(2/3)σ, 2^(3/3)σ,2×2^(1/3)σ,2×2^(2/3)σ,2×2^(3/3)σ......。发现了什么?对了,这些数据时连续的,我们通过在每个八度中多构造三幅高斯图像,达到了尺度空间连续的效果,这一效果带来的直接的好处是在尺度空间的极值点确定过程中,我们不会漏掉任何一个尺度上的极值点,而是能够综合考虑量化的尺度因子

所确定的每一个尺度!

下一个八度的第一幅图像如何确定

这个问题,是上面问题(尺度空间的连续性)的延伸,我们可以通过反推OpenCV中这一部分的源代码,来理解这个问题。

当前八度中的第一幅图像是通过前一个八度的倒数第三幅图像得到。OpenCV这段源码有个很重要的问题:不同的八度间的尺度不是会有一个2的差异吗?为什么本部分源码并没有体现这一点,而是在对每一个八度处理中都是用相同的数组sig[]。首先明确一下sig数组内.存储的并不是一个绝对的模糊核,而是相对的模糊核,这一点很重要,既然是相对的模糊核,那么第一幅图像的核就很重要了,所以尺度的连续就看每个八度的第一幅图像了。
对于以下列出的高斯金字塔的构建过程来看,每个八度中的第一幅图像并没有一个2倍的尺度跃进过程。但是,这个2倍的跃进式隐含在整个高斯金字塔的构建过程中了!
再看倒数第三幅图像,这幅图像的尺度是2^(3/3)*δ,3/3=1,也就是说,在这个八度中,第一幅图像的尺度是δ,而倒数第三幅图像的尺度是2*δ,正好发生了一个2的跃进!这就是以这幅图像作为基准进行下采样的原因,如此的话,下一个八度的第一幅图像的初始尺度就是2*δ了。

这就是真相,这就是为什么选用倒数第三幅图像进行下采样的原因。

  1. void SIFT::buildGaussianPyramid( const Mat& base, vector<Mat>& pyr, int nOctaves ) const  
  2. {  
  3.     vector<double> sig(nOctaveLayers + 3);  
  4.     pyr.resize(nOctaves*(nOctaveLayers + 3));  
  5.     // precompute Gaussian sigmas using the following formula:  
  6.     //  \sigma_{total}^2 = \sigma_{i}^2 + \sigma_{i-1}^2  
  7.     sig[0] = sigma;  
  8.     double k = pow( 2., 1. / nOctaveLayers );  
  9.     forint i = 1; i < nOctaveLayers + 3; i++ )  
  10.     {  
  11.         double sig_prev = pow(k, (double)(i-1))*sigma;  
  12.         double sig_total = sig_prev*k;  
  13.         sig[i] = std::sqrt(sig_total*sig_total - sig_prev*sig_prev);  
  14.     }  
  15.     forint o = 0; o < nOctaves; o++ )  
  16.     {  
  17.         forint i = 0; i < nOctaveLayers + 3; i++ )  
  18.         {  
  19.             Mat& dst = pyr[o*(nOctaveLayers + 3) + i];  
  20.             if( o == 0  &&  i == 0 )  
  21.                 dst = base;  
  22.             // base of new octave is halved image from end of previous octave  
  23.             else if( i == 0 )/*每一个八度中第一幅图像的确定过程*/  
  24.             {  
  25.                   const Mat& src = pyr[(o-1)*(nOctaveLayers + 3) + nOctaveLayers];  
  26.                   resize(src, dst, Size(src.cols/2, src.rows/2), 0, 0, INTER_NEAREST);  
  27.             }   
  28.            else  
  29.            {  
  30.                     const Mat& src = pyr[o*(nOctaveLayers + 3) + i-1];   
  31.                     GaussianBlur(src, dst, Size(), sig[i], sig[i]);   
  32.            }  
  33.         }   
  34.      }  
  35. }  
  36. void SIFT::buildDoGPyramid( const vector<Mat>& gpyr, vector<Mat>& dogpyr ) const  
  37. {   
  38.         int nOctaves = (int)gpyr.size()/(nOctaveLayers + 3);  
  39.         dogpyr.resize( nOctaves*(nOctaveLayers + 2) );   
  40.         forint o = 0; o < nOctaves; o++ )  
  41.         {   
  42.                forint i = 0; i < nOctaveLayers + 2; i++ )   
  43.                {   
  44.                      const Mat& src1 = gpyr[o*(nOctaveLayers + 3) + i];   
  45.                      const Mat& src2 = gpyr[o*(nOctaveLayers + 3) + i + 1];   
  46.                      Mat& dst = dogpyr[o*(nOctaveLayers + 2) + i];   
  47.                      subtract(src2, src1, dst, noArray(), DataType<sift_wt>::type);   
  48.                }  
  49.         }  
  50. }  


以上SIFT源码均摘自OpenCV nonfree模块,lowe对SIFT拥有版权。

SIFT金字塔构建完毕,需要在金字塔中寻找特征点了,请关注本博客SIFT系列下一篇文章:SIFT解析(二)特征点位置确定

(转载请注明作者和出处 :honpey  http://blog.csdn.net/wendy709468104/article/details/8639617 )




  1. <pre></pre>  
  2. <pre></pre>  
  3. <pre></pre>  
  4. <pre></pre>  
  5. <pre></pre>  
  6. <pre></pre>  
  7. <pre></pre>  
  8. <pre></pre>  
  9. <pre></pre>  
  10. <pre></pre>  
  11. <pre></pre>  
  12. <pre></pre>  
  13. <pre></pre>  
  14. <pre></pre>  
  15. <pre></pre>  
  16. <pre></pre>  
 

SIFT解析(二)特征点位置确定


最近微博上有人发起投票那篇论文是自己最受益匪浅的论文,不少人说是lowe的这篇介绍SIFT的论文。确实,在图像特征识别领域,SIFT的出现是具有重大意义的,SIFT特征以其稳定的存在,较高的区分度推进了诸多领域的发展,比如识别和配准。上一篇文章,解析了SIFT特征提取的第一步高斯金字塔的构建,并详细分析了高斯金字塔以及差分高斯金字塔如何完成一个连续的尺度空间的构建。构建高斯金字塔不是目的,目的是如何利用高斯金字塔找到极值点。

lowe在论文中阐述了为什么使用差分高斯金字塔:

1)差分高斯图像可以直接由高斯图像相减获得,简单高效

2) 差分高斯函数是尺度规范化的高斯拉普拉斯函数的近似,而高斯拉普拉斯函数的极大值和极小值点是一种非常稳定的特征点(与梯度特征、Hessian特征和Harris角点相比)

有了这些基础,我们就可以放开手脚从差分高斯金字塔中找点了。

特征点的确定主要包括两个过程:确定潜在特征点,精确确定特征点的位置和去除不稳定特征点。

确定潜在特征点

上文已经阐述,高斯拉普拉斯函数的极大值和极小值点是一种非常稳定的特征点,因此我们从差分高斯金字塔中寻找这些潜在特征点。差分高斯金字塔是一个三维空间(平面图像二维,尺度一维),因此我们在三维空间中在寻找极大值点和极小值点。具体方法是比较当前特征点的灰度值和其他26个点的灰度值的大小,这26个点包括:当前尺度下该点的8邻域以及前一尺度和后一尺度下与该点最近的9个点(9*2+8=26),如下图所示:


OpenCV该部分源码:

  1. void SIFT::findScaleSpaceExtrema( const vector<Mat>& gauss_pyr, const vector<Mat>& dog_pyr,  
  2.                                   vector<KeyPoint>& keypoints ) const  
  3. {  
  4.     ......  
  5.     forint o = 0; o < nOctaves; o++ )//每一个八度  
  6.         forint i = 1; i <= nOctaveLayers; i++ )//对八度中的存在具有第1至第nOctaveLayers层高斯差分图像提取特征点  
  7.         {  
  8.             ......  
  9.             forint r = SIFT_IMG_BORDER; r < rows-SIFT_IMG_BORDER; r++)//图像二维空间.行  
  10.             {  
  11.                 ......  
  12.                 forint c = SIFT_IMG_BORDER; c < cols-SIFT_IMG_BORDER; c++)//图像二维空间.列  
  13.                 {  
  14.                     .......  
  15.                      // 当前点与26个点比较,比较两次,分别确定是否是极大值,是否是极小值  
  16.                      if( std::abs(val) > threshold &&  
  17.                        ((val > 0 && val >= currptr[c-1] && val >= currptr[c+1] &&  
  18.                          val >= currptr[c-step-1] && val >= currptr[c-step] && val >= currptr[c-step+1] &&  
  19.                          val >= currptr[c+step-1] && val >= currptr[c+step] && val >= currptr[c+step+1] &&  
  20.                          val >= nextptr[c] && val >= nextptr[c-1] && val >= nextptr[c+1] &&  
  21.                          val >= nextptr[c-step-1] && val >= nextptr[c-step] && val >= nextptr[c-step+1] &&  
  22.                          val >= nextptr[c+step-1] && val >= nextptr[c+step] && val >= nextptr[c+step+1] &&  
  23.                          val >= prevptr[c] && val >= prevptr[c-1] && val >= prevptr[c+1] &&  
  24.                          val >= prevptr[c-step-1] && val >= prevptr[c-step] && val >= prevptr[c-step+1] &&  
  25.                          val >= prevptr[c+step-1] && val >= prevptr[c+step] && val >= prevptr[c+step+1]) ||  
  26.                         (val < 0 && val <= currptr[c-1] && val <= currptr[c+1] &&  
  27.                          val <= currptr[c-step-1] && val <= currptr[c-step] && val <= currptr[c-step+1] &&  
  28.                          val <= currptr[c+step-1] && val <= currptr[c+step] && val <= currptr[c+step+1] &&  
  29.                          val <= nextptr[c] && val <= nextptr[c-1] && val <= nextptr[c+1] &&  
  30.                          val <= nextptr[c-step-1] && val <= nextptr[c-step] && val <= nextptr[c-step+1] &&  
  31.                          val <= nextptr[c+step-1] && val <= nextptr[c+step] && val <= nextptr[c+step+1] &&  
  32.                          val <= prevptr[c] && val <= prevptr[c-1] && val <= prevptr[c+1] &&  
  33.                          val <= prevptr[c-step-1] && val <= prevptr[c-step] && val <= prevptr[c-step+1] &&  
  34.                          val <= prevptr[c+step-1] && val <= prevptr[c+step] && val <= prevptr[c+step+1])))  
  35.                     {  
  36.                            ......  
  37.                           
  38.                     }  
  39.                 }  
  40.             }  
  41.         }  
  42. }  

尺度空间中的极值点已经确定出来了,下面有两个问题需要解决:

(1)这些点是最终我们确定的SIFT特征点集的超集,该超集里包含许多“间谍”-----不稳定的特征点,因此必须去掉这些不稳定的特征点。这些不稳定的特征点主要包含两类:低对比度的点(对噪声敏感)和边缘点。

(2)这一步骤中极值点的坐标还是离散的整数值,如何精确确定特征点的位置。

由于在计算上(2)问题的解决可以捎带解决(1)中低对比度点的问题,因此我们先讨论问题(2)。本部分的OpenCV源码位于sift.cpp文件的adjustLocalExtrema函数中,本文最后会贴出此部分源码,下面首先分析如何解决以上两个问题。

精确确定特征点的位置:

由于图像是一个离散的空间,特征点的位置的坐标都是整数,但是极值点的坐标并不一定就是整数,如下图所示。


因此,如何从离散空间中估计出极值点的精确位置是重要的。为了精确确定极值点坐标,Brown和Lowe使用了三元二次函数,通过迭代确定极值点的位置,具有良好的效果。

主要是根据泰勒公式,泰勒公式作用:用值已知的点A估计点A附近的某点B的值。

求上式极值,对其求导,导数等于0,得到


去除不稳定特征点

去除对比度低的点

以上求出了极值点的精确的位置,将求出的 x 带入原式,得:

 

我们就利用这个函数去除对比度低的点,lowe文中,当D(x)<=0.03时,去除这个特征点。

去除边缘点

差分高斯金字塔中的极值点会有许多边缘点,边缘点对一些噪声不稳定,因此需要去除这些边缘相应点。

差分高斯金字塔中会有一些不是很好的极值点,这些点的特征是:在跨越边缘的方向有较大的主曲率,在与边缘相切的方向主曲率较小。在本步骤中,需要去除这些不好的边缘相应。主曲率可以通过2阶Hessian方阵获得:

D函数中某点的主曲率和该点的H矩阵的特征值是成比例的,因此我们可以通过H矩阵的特征值来确定某点在差分高斯金字塔中的主曲率。

设矩阵H的特征值分别为α(较大)和β(较小),有如下公式:

通过以上两式,α和β就可以计算出来了,但是,不急!

如上文所述,那些不好的边缘点:跨越边缘的方向有较大的主曲率,与边缘相切的方向主曲率较小。因此,我们通过α/β的比率函数并确定阈值来体现表征那些不好的边缘点,α/β越大,说明这个点就越糟糕,就越应该被删掉,但是这样就要真真切切计算α和β的值,前面让大家不急了,是的,先不用着急计算,设定r=α/β(即 α=rβ),使用如下公式:


以上函数是关于r的增函数(已经假设α是特征值中较大的一个),r 越大,以上函数值就越大,反之,以上函数值越大,r 就是越大的,因此我们可以通过已知的Tr(H)和Det(H)“曲线地”去判断 r的大小!所以在本步骤中,去除不好的边缘点的阈值是:


lowe论文中设定r=10。

到这里,在差分高斯金字塔中提取的特征点就完成了提纯的步骤。

下面是OpenCV源码中特征点精确位置的确定过程以及特征点提纯过程,主要实现函数为sift.cpp中adjustLocalExtrema函数:

  1. // Interpolates a scale-space extremum's location and scale to subpixel  
  2. // accuracy to form an image feature. Rejects features with low contrast.  
  3. // Based on Section 4 of Lowe's paper.  
  4. static bool adjustLocalExtrema( const vector<Mat>& dog_pyr, KeyPoint& kpt, int octv,  
  5.                                 int& layer, int& r, int& c, int nOctaveLayers,  
  6.                                 float contrastThreshold, float edgeThreshold, float sigma )  
  7. {  
  8.     const float img_scale = 1.f/(255*SIFT_FIXPT_SCALE);  
  9.     const float deriv_scale = img_scale*0.5f;  
  10.     const float second_deriv_scale = img_scale;  
  11.     const float cross_deriv_scale = img_scale*0.25f;  
  12.   
  13.     float xi=0, xr=0, xc=0, contr=0;  
  14.     int i = 0;  
  15.   
  16.     // 如上文所述,迭代计算特征点的精确位置  
  17.     for( ; i < SIFT_MAX_INTERP_STEPS; i++ )  
  18.     {  
  19.         int idx = octv*(nOctaveLayers+2) + layer;  
  20.         const Mat& img = dog_pyr[idx];  
  21.         const Mat& prev = dog_pyr[idx-1];  
  22.         const Mat& next = dog_pyr[idx+1];  
  23.   
  24.         Vec3f dD((img.at<sift_wt>(r, c+1) - img.at<sift_wt>(r, c-1))*deriv_scale,  
  25.                  (img.at<sift_wt>(r+1, c) - img.at<sift_wt>(r-1, c))*deriv_scale,  
  26.                  (next.at<sift_wt>(r, c) - prev.at<sift_wt>(r, c))*deriv_scale);  
  27.   
  28.         float v2 = (float)img.at<sift_wt>(r, c)*2;  
  29.         float dxx = (img.at<sift_wt>(r, c+1) + img.at<sift_wt>(r, c-1) - v2)*second_deriv_scale;  
  30.         float dyy = (img.at<sift_wt>(r+1, c) + img.at<sift_wt>(r-1, c) - v2)*second_deriv_scale;  
  31.         float dss = (next.at<sift_wt>(r, c) + prev.at<sift_wt>(r, c) - v2)*second_deriv_scale;  
  32.         float dxy = (img.at<sift_wt>(r+1, c+1) - img.at<sift_wt>(r+1, c-1) -  
  33.                      img.at<sift_wt>(r-1, c+1) + img.at<sift_wt>(r-1, c-1))*cross_deriv_scale;  
  34.         float dxs = (next.at<sift_wt>(r, c+1) - next.at<sift_wt>(r, c-1) -  
  35.                      prev.at<sift_wt>(r, c+1) + prev.at<sift_wt>(r, c-1))*cross_deriv_scale;  
  36.         float dys = (next.at<sift_wt>(r+1, c) - next.at<sift_wt>(r-1, c) -  
  37.                      prev.at<sift_wt>(r+1, c) + prev.at<sift_wt>(r-1, c))*cross_deriv_scale;  
  38.   
  39.         Matx33f H(dxx, dxy, dxs,  
  40.                   dxy, dyy, dys,  
  41.                   dxs, dys, dss);//通过当前像素点以及周围像素点差值出H矩阵  
  42.   
  43.         Vec3f X = H.solve(dD, DECOMP_LU);  
  44.   
  45.         xi = -X[2];  
  46.         xr = -X[1];  
  47.         xc = -X[0];  
  48.   
  49.         //有任何一个维度的偏移超过0.5,会更新当前像素点  
  50.         //如果每一个维度的偏移都没有超过0.5,当前像素的位置加上偏移就是最终的精确点  
  51.        if( std::abs(xi) < 0.5f && std::abs(xr) < 0.5f && std::abs(xc) < 0.5f )  
  52.             break;  
  53.   
  54.         if( std::abs(xi) > (float)(INT_MAX/3) ||  
  55.             std::abs(xr) > (float)(INT_MAX/3) ||  
  56.             std::abs(xc) > (float)(INT_MAX/3) )  
  57.             return false;  
  58.   
  59.         c += cvRound(xc);  
  60.         r += cvRound(xr);  
  61.         layer += cvRound(xi);  
  62.   
  63.         if( layer < 1 || layer > nOctaveLayers ||  
  64.             c < SIFT_IMG_BORDER || c >= img.cols - SIFT_IMG_BORDER  ||  
  65.             r < SIFT_IMG_BORDER || r >= img.rows - SIFT_IMG_BORDER )  
  66.             return false;  
  67.     }  
  68.     //迭代结束  
  69.    // ensure convergence of interpolation  
  70.     if( i >= SIFT_MAX_INTERP_STEPS )  
  71.         return false;  
  72.   
  73.     {  
  74.         int idx = octv*(nOctaveLayers+2) + layer;  
  75.         const Mat& img = dog_pyr[idx];  
  76.         const Mat& prev = dog_pyr[idx-1];  
  77.         const Mat& next = dog_pyr[idx+1];  
  78.         Matx31f dD((img.at<sift_wt>(r, c+1) - img.at<sift_wt>(r, c-1))*deriv_scale,  
  79.                    (img.at<sift_wt>(r+1, c) - img.at<sift_wt>(r-1, c))*deriv_scale,  
  80.                    (next.at<sift_wt>(r, c) - prev.at<sift_wt>(r, c))*deriv_scale);  
  81.         float t = dD.dot(Matx31f(xc, xr, xi));  
  82.   
  83.         contr = img.at<sift_wt>(r, c)*img_scale + t * 0.5f;  
  84.         if( std::abs( contr ) * nOctaveLayers < contrastThreshold )//去除低对比度的点  
  85.             return false;  
  86.   
  87.         // principal curvatures are computed using the trace and det of Hessian  
  88.         float v2 = img.at<sift_wt>(r, c)*2.f;  
  89.         float dxx = (img.at<sift_wt>(r, c+1) + img.at<sift_wt>(r, c-1) - v2)*second_deriv_scale;  
  90.         float dyy = (img.at<sift_wt>(r+1, c) + img.at<sift_wt>(r-1, c) - v2)*second_deriv_scale;  
  91.         float dxy = (img.at<sift_wt>(r+1, c+1) - img.at<sift_wt>(r+1, c-1) -  
  92.                      img.at<sift_wt>(r-1, c+1) + img.at<sift_wt>(r-1, c-1)) * cross_deriv_scale;  
  93.         float tr = dxx + dyy;  
  94.         float det = dxx * dyy - dxy * dxy;  
  95.   
  96.         if( det <= 0 || tr*tr*edgeThreshold >= (edgeThreshold + 1)*(edgeThreshold + 1)*det )//去除边缘噪声点  
  97.             return false;  
  98.     }  
  99.   
  100.     kpt.pt.x = (c + xc) * (1 << octv);  
  101.     kpt.pt.y = (r + xr) * (1 << octv);  
  102.     kpt.octave = octv + (layer << 8) + (cvRound((xi + 0.5)*255) << 16);  
  103.     kpt.size = sigma*powf(2.f, (layer + xi) / nOctaveLayers)*(1 << octv)*2;  
  104.     kpt.response = std::abs(contr);  
  105.   
  106.     return true;  
  107. }  
SIFT解析(三)生成特征描述子

以上两篇文章中检测在DOG空间中稳定的特征点,lowe已经提到这些特征点是比Harris角点等特征还要稳定的特征。下一步骤我们要考虑的就是如何去很好地描述这些DOG特征点。

-----------------------------------------------------------------------------------------------------------------------------------------------------------------


------------------------------------------------------------------------------------------------------------------------------------------------------------------

下面好好说说如何来描述这些特征点。许多资料中都提到SIFT是一种局部特征,这是因为在SIFT描述子生成过程中,考虑的是该特征点邻域特征点的分布情况(而没有利用全局信息)。本步骤中主要计算过程包括:确定特征点的方向生成特征描述符

确定特征点方向

在特征点的确定过程中,特征点的坐标以及尺度被确定下来(坐标很重要,尺度更重要,到后来,你会发现尺度这个参量在整个描述子生成过程中越来越重要,因为正是运用尺度参量实现的Scale-Invariant,尺度不变!),根据特征点的尺度选择高斯金字塔中的图像,然后在这幅图像上确定该特征点的方向。这里体现的正是尺度不变的思想。

特征点方向的确定基于统计信息,以该特征点为中心,考察与该特征距离为**像素的梯度方向和梯度的幅值,梯度的方向和幅值的计算公式分别为:

构造该点邻域梯度方向直方图,将一圆周360°划分成36个槽,从0°开始每槽递增10°。根据邻域点的方向、梯度的幅值以及距离特征点的远近构建上述梯度方向直方图,如下图所示:


根据梯度直方图,直方图峰值所对应的的角度就是当前特征点A的方向,同时如果某角度的梯度幅值和>=峰值的80%,那么就产生一个新的特征点B,这个点的坐标、尺度等参数同A,但是角度不同。

至此,特征点的方向确定完毕。

生成图像局部描述符

到了本步骤,图像中每个特征点的坐标、尺度以及方向都确定了,下面开始根据这些信息获得描述子。

与上文中提到的确定特征点方向类似,生成描述子也是根据以特征点为中心的图像局部信息,首先根据特征点的尺度选择高斯金字塔中的图像,然后计算特征点邻域范围内各点的梯度方向和梯度的幅值,并根据上文得到的特征点梯度方向更新这些梯度的方向,以此达到描述子的方向不变性。

方向不变性完成后,开始计算特征描述符了。描述符计算过程同样基于梯度方向直方图,只是这次直方图的槽是以45°划分的(因此每个直方图只有8个槽),而不是10°。具体过程如下图所示:

统计每个4×4块的方向梯度直方图,为了去除光照对描述子的影响,对梯度直方图进行归一化处理。然后将每个直方图槽数据串联即可得到SIFT描述子,lowe提出当梯度方向直方图是4×4维的时候,SIFT描述子具有最好的区分度,因此这就诞生了老生长谈的128维SIFT特征向量,如下图所示:


至此,SIFT特征计算过程结束。



阅读更多
个人分类: 图像特征提取
想对作者说点什么? 我来说一句

SIFT经典论文

2012年11月23日 872KB 下载

没有更多推荐了,返回首页

不良信息举报

SIFT特征分析与源码解读

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭