标签:
个人博客原文:http://www.sun11.me/blog/2016/sift-implementation-in-matlab/
这是一次作业,内容是给出两张图像,检测特征点和匹配特征点。要求不能用诸如OpenCV的现成特征点检测函数。于是就只能造轮子了,写了这个Matlab版的sift。(其实就是把c语言的opensift翻译成了matlab
以下是算法流程,其实网上的类似博文已经很多了,只不过我看的过程中也看得不很明白,只能对照着好几个看,所以干脆自己又写了一遍。下面的图均来自于参考资料中,然而参考资料的图也是来自于参考资料的参考资料中。
定理1 对图像做
σ=σ1 的高斯平滑,再做一次σ=σ2 的高斯平滑,等效于对原图像做一次σ=σ21+σ22??????√ 的高斯平滑。
高斯卷积核是实现尺度变换的唯一线性核(Koenderink, 1984; Lindeberg, 1994)。
一幅图像的尺度空间被定义为对其做可变尺度的高斯卷积:
对于给定的彩色图像,转化为灰度图像,用不同大小的
为了得到更多的特征点,将图像扩大为原来的两倍。假设原图像已有
上一个octave的图像的长度和宽度分别是下一个octave的图像的两倍。因此图像组数(octaves)可由图像大小决定,将其设为
设第m个octave的第n张图像相对于原始图像的参数
如上图所示,在第一个octave中尺度为
但实际上我们需要做出更多不同尺度的高斯平滑图像,这是因为在后续高斯差分金字塔的极值检测中,需要前后两级尺度都存在图像。如图中红框所示,高斯差分金字塔中每个octave有s幅图像,则需要高斯金字塔中每个octave包含s+3幅图像。其中第s+1幅图像用作下一个octave第一幅图像的降采样。
具体实现中并未对单幅图像多次进行高斯平滑,而是由上一幅图像进行高斯平滑得到下一幅图像并迭代之,按照定理1计算
对两幅高斯金字塔的图像作差。
如上图,与前后两幅图像及自身的共26个邻域像素点比较灰度值检测极值。
检测到的极值点是离散的,通过三元二次函数拟合来精确确定关键点的位置和尺度,达到亚像素精度。以某关键点为中心的尺度空间函数
其中等号右边第一个D为某关键点处的灰度值,
若
精确极值点处函数值:
若
高斯差分函数有较强的边缘响应,对于比较像边缘的点应该去除掉。这样的点的特征为在某个方向有较大主曲率,而在垂直的方向主曲率很小。
设r为大主曲率与小主曲率的比值,H为关键点处的Hessian矩阵,则有(具体推导可见Lowe的论文):
若满足:
说明此处r较小,认为此关键点不位于边缘,否则则去除该点。
根据关键点的局部特性为每个关键点指定一个方向,可以具备旋转不变性。关键点局部特性在检测到关键点的高斯差分金字塔图像临近的高斯金字塔图像中计算。在关键点3σ邻域窗口计算梯度和方向分布,计算方式如下:
此处的x正方向向右,y正方向向上。其中L为关键点在上述精确定位后所处尺度的灰度值,m(x,y)为梯度的幅值,
直方图最大值的方向代表该关键点的主方向,对于其他峰值,若大于或等于主方向值的80%,则再分配一个方向。所以对于一个关键点,可能会有多个对应的方向,将带有方向的关键点定义为feature,则一个关键点可能对应多个feature。由于第一个octave是双倍大小的图像,feature的坐标和尺度应转换到原始图像所在的octave处理。最后用抛物插值精确定位feature的方向。
对于x为-1,0,1,y为l,c,r的三个点来说,抛物插值得到极值点的x为:
上一步已得到具有主方向的关键点,即feature,下一步是对feature的邻域进行采样,形成对该局部图像的描述,然后可用某种度量方法对描述进行匹配。
Lowe提出的sift描述子是一个
这16个图像区域中的每一个区域均为
如下图所示,图中
为了使描述符具有旋转不变性,将坐标轴旋转至关键点主方向。设i,j分别为采样点相对关键点的行偏移量和列偏移量,i = -radius:radius,j = -radius:radius,关键点左上角i和j均为负数。关键点主方向为
定理2 在右手平面直角坐标系中,向量(x,y)逆时针旋转
θ ,得到的向量(x’,y’)为:[x′y′]=[cosθsinθ?sinθcosθ][xy]
在左图以关键点为中心建立右手平面直角坐标系o-uv,u的正方向与左图
解得
得到新的行、列偏移量后,将
采样点的梯度幅值按照
其中a,b为关键点在高斯金字塔图像中的位置坐标。
上述过程中构造了一个三维的bin空间,如4.1中右图所示,维度包括r_bin,c_bin和o_bin。注意最上层格子和最底层格子是相连的,因为0度等于360度。所有带有三维坐标的梯度幅值都将分配到三维格子里。
为了减少一个梯度幅值从一个格子漂移(shift)到另一个格子引起的描述子突变,需要对梯度值做三线性插值。也就是根据三维坐标计算距离周围格子的距离,按距离的倒数计算权重,将梯度幅值按权重分配到临近的格子里。
某点在三维bin空间的坐标为
其中i,j,k均可取0或1,weightedValue下标加1的目的是使下标从1开始。
为简化计算,可改为:
将上述直方图数组按顺序排列可转换为一个128维的向量。
为了减少光照变化的影响,对该向量进行归一化处理。非线性光照变化仍可能导致梯度幅值的较大变化,然而影响梯度方向的可能性较小。因此对于超过阈值0.2的梯度幅值设为0.2,然后再进行一次归一化。最后将描述子按照对应高斯金字塔图像的尺度大小排序。
描述子向量已经归一化,所以可直接用向量之间的夹角进行匹配,相当于球面距离。图像A 的描述子匹配图像B最近的两个描述子点积之比小于0.6,则认为匹配成功。
因为用的是Matlab,所以不注重性能。然而又不得不注重性能,因为第一次跑通程序的时候跑了一晚上都没跑完一半!也就是一张图片的描述子都没算完。后来发现是因为在运行次数最多的for循环(描述子计算中的梯度计算)里用到了cell数组。把对这个cell数组的查询操作提到两重循环前以后,这个程序好像跑了半个小时左右跑出结果了。然而还是太慢,于是我又用Matlab的计时分析工具分析了程序最耗时的地方:
程序里用了很多全局变量,是因为我把函数分成了文件而不是放在一个文件,为了节省点内存(以及方便)只能这么做(虽然据说Matlab在不改变变量的情况下函数传值等于引用,然而我并不清楚究竟是怎样的)。把for循环换成parfor的时候提示,parfor里似乎不推荐用全局变量,而且实际运行的时候全局变量似乎也会影响性能,于是我把全局变量复制成了局部的再放进parfor里。
我还发现一个奇葩的问题,在运行次数最多的计算梯度的函数里用zeros(1,2)创建一个数组竟然也耗时非常多,改成[0 0]就好了。
经过这些修改后,在开启parallel pool的情况下运行时间缩短到了7分钟左右。(然而Lowe的C语言版本只要十几秒
这次作业老师给的是两张768x1024的图片,分别检测到5288和4798个特征点,最后匹配了906对点。用Lowe的siftDemoV4跑出来的结果是1252对匹配。
这个程序的参数基本都是参照opensift,但最后的匹配用的是Lowe的方案。Lowe的实现毕竟不太一样,运行的结果和opensift有一些差异。以下是匹配siftDemoV4.zip
里的scene.pgm
和book.pgm
的结果:
Item | siftDemoV4 | opensift | sw-sift |
---|---|---|---|
scene.pgm | 1021 | 746 | 766 |
book.pgm | 882 | 740 | 741 |
Matched | 98 | 84 | 58 |
sw-sift和opensift的区别主要是在高斯平滑和匹配算法上。opensift的高斯平滑用的是OpenCV的CVSmooth函数,匹配用的是欧式距离(而且把描述子乘以512从double类型转成了int)。和opensift相比,sw-sift检测到的特征点数量很接近,但是匹配数量较少,所以可改进的地方主要是匹配算法(然而我不想改了==)。另外,我发现高斯平滑的核矩阵大小对结果有很大影响,根据
代码发布在github: sw-sift,请注意sift是有专利的。
David G. Lowe, “Distinctive image features from scale-invariant keypoints,” International Journal of Computer Vision, 60, 2 (2004), pp. 91-110. [PDF] [CODE]
Rob Hess, OpenSIFT源码: https://github.com/robwhess/opensift
zddhub, SIFT算法详解: http://blog.csdn.net/zddblog/article/details/7521424
Rachel Zhang, SIFT特征提取分析: http://blog.csdn.net/abcjennifer/article/details/7639681
JiePro, SIFT算法:特征描述子: http://www.cnblogs.com/JiePro/p/sift_4.html
标签:
原文地址:http://blog.csdn.net/sununs11/article/details/51476130