一、SIFT算法概述:
SIFT(Scale Invariant Feature Transform)全称尺度不变特征变换,SIFT算子是把图像中检测到的特征点用一个128维的特征向量进行描述,因此一幅图像经过SIFT算法后表示为一个128维的特征向量集,该特征向量集具有对图像缩放,平移,旋转不变的特征,对于光照、仿射和投影变换也有一定的不变性,是一种非常优秀的局部特征描述算法。
SIFT算法步骤分为:
(1).尺度空间极点检测
(2).关键点精确定位
(3).确定关键点方向
(4).生成特征向量
二、尺度空间极点检测
2.1 尺度空间
特征点的检测就是寻找特征点的位置和尺度,需要位置的原因显而易见,而需要尺度的原因则是因为真实世界中的物体只有在一定尺度下才有意义。我们寻找的特征点就是要找到在连续的尺度空间下位置不发生改变的点。
构建尺度空间的目的就是找到在尺度变化中具有不变性的位置,可以使用连续的尺度变化,即在尺度空间中所有可能的尺度变化中找到稳定的特征点,通过这种方式找到的极点可以保证在图像缩放和旋转变化中具有不变性。
经过前人证明,尺度空间内核是高斯函数。因此假设I(x,y)是原始图像,G(x,y,σ)是尺度空间可变的高斯函数,则一个图像的尺度空间可以定义为:
其中,∗表示的是卷积运算,σ表示尺度空间的大小,σ越大则表示越模糊,表示图像的概貌,σ越小则表示越清晰,表示图像的细节。高斯函数G(x,y,σ)定义为:
经过一系列的尺度空间变换和二倍的下采样,最终得到高斯金字塔。
需要注意的是公式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)的过程为:
2.2 高斯差分(DOG)
高斯差分可以在尺度空间中找到稳定不变的极值点,高斯差分(DOG)函数D(x,y,σ)定义为:
其中kσ和σ是连续的两个图像的平滑尺度,所得到的差分图像再高斯差分金字塔中。
为何选择高斯差分函数:
2.3 高斯金字塔与高斯差分金字塔
高斯金字塔和高斯差分金字塔如下图所示:
这里的几个参数定义如下:
4. 金字塔的组数(number of octaves):大多数情况下为4,但是实际上这个值与图像的大小有关,我在网上查到的资料大多数为⌊log2(min(M,N))⌋−3或者⌊log2(min(M,N))⌋−2,具体实现看效果确定吧。
5. 每层的组数:S1=s+3这里s为极值检测需要的层数,一般取3到5。
6. 参数: ,σ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
当第0组构建完以后,再构建第1组,第1组中的第0层图像是根据第0组的倒数第三张图像进行隔点采样得到的。由上面公式我们可以知道,第0组的倒数第三层的尺度为,而因此其尺度为2σ0,所以第1组的第0张图像的尺度依然为2σ0。
但是第1组中的图像是第0组中的图像经过隔点采样后得到的,因此相对于输入图像分辨率来说,其尺度为2σ0,而对于第1组中的图像分辨率来说,其尺度为σ0。因此我们称σ0为基准层尺度。
上述总结规律如下:
第o组中的第r个图像相对于输入图像的尺度为:
而DoG金字塔的生成过程就比较简单了,就是由高斯金字塔相邻的两层相减得到DoG金字塔中的一层,然后依次得到。由高斯金字塔中每组有s+3层,所以高斯差分金字塔中每组有s+2层。
金字塔中每一组(octave)中的每层(scale)的平滑尺度都不同,下一组的第一层都是由上一层的倒数第三张的图像隔点降采样得到的。
这样做的目的是使DoG满足尺度连续性。
2.4 极值点的选定
如上图的最右方所示,只有当前点与其周围24个点值相比,如果是最大值或者最小值则该点为极值点,否则不是。这种比较计算量比较小,因为大多数的点在比较的前几步就已经被pass掉了 。
三、特征点的精确定位
计算机中存储的图像数据是离散的,而我们之前找到的极值点也就是离散空间中的极值点,但是离散空间中的极值点并不是真实的连续空间中的极值点。所以需要对DoG空间进行拟合处理,以找到极值点的精确位置和尺度。另外,我们还需要去除那些在边缘位置的极值点,以提高关键点的稳定性。
3.1、位置的插值
下图是二维函数离散空间得到的极值点与连续空间极值点的差别:
利用已知的离散空间点插值得到的连续空间极值点的方法叫做子像素插值(Sub-pixel Interpolation).对尺度空间DOG函数进行曲线拟合(子像素插值),利用DOG函数在尺度空间的泰勒展开式:
公式推导如下:
极值点的偏移量的求解如下:
3.2、去除边缘响应
一个定义不好的高斯差分算子的极值在横跨边缘的地方有较大的主曲率,而在垂直边缘的方向有较小的主曲率。 DOG 算子会产生较强的边缘响应,需要剔除不稳定的边缘响应点。获取特征点处的Hessian 矩阵,主曲率通过一个2x2 的Hessian 矩阵H 求出:
四、特征点方向分配
对上面提取的每个关键点,围绕该点选择一个窗口(圆形区域),窗口内各采样点的梯度方向构成一个方向直方图,根据直方图的峰值确定关键点的方向。关键点的尺度用来选择哪个高斯滤波图像参与计算,还用来决定窗口的大小——为了保证不同尺度下的同一关键点的方向都包含相同的信息量,那么窗口的大小必然不一样:同一个原始图像,尺度越大,窗口应该越大;反之,如果窗口大小不变,尺度越大的图像,该窗口内的信息越少.
做一个梯度方向的直方图,范围是0~360度,其中每10度一个柱,总共36个柱。每个采样点按照其梯度方向θ(x,y)加权统计到直方图,权值为幅度m(x,y )和贡献因子的乘积。贡献因子是采样点到关键点(窗口中心)距离的量度,距离越大,贡献因子越小.直方图的峰值代表了该关键点处邻域梯度的主方向.
Lowe指出,直方图的峰值确定以后,任何大于峰值80%的方向(柱)创建一个具有该方向的关键点,因此,对于多峰值(幅值大小接近)的情形,在同一位置和尺度就会产生多个具有不同方向的关键点。虽然这样的点只占15%,但是它们却能显著地提高匹配的稳定性。用每个峰值和左右两个幅值拟合二次曲线,以定位峰值的实际位置(抛物线的最高点)。峰值方向的精度高于10度。
所以,关键点的方向分配步骤如下:
五、描述符的生成
SIFT 描述子是关键点领域高斯图像梯度统计结果的一种表示。通过对关键点周围图像区域分块,计算块内梯度直方图,生成具有独特性的向量,这个向量是该区域图像信息的一种抽象,具有唯一性。 Lowe 建议描述子使用在关键点尺度空间内4×4的窗口中计算的8 个方向的梯度信息,共4×4×8 =128维向量表征.
1、确定计算描述子所需的图像区域
计算结果四舍五入取整。
2、将坐标轴旋转为关键点的方向,以确保旋转不变性
旋转后领域内采样点的新坐标为:
3、将领域内的采样点分配到对应的子区域内,将子区域内的梯度值分配到8 个方向上,计算其权值
4、插值计算每个种子点八个方向的梯度
如上图所示,将所得采样点在子区域中的下标(x’’,y’’)(图中蓝色窗口内红色点)线性插值,计算其对每个种子点的贡献。如图中的红色点,落在第0 行和第1 行之间,对这两行都有贡献。对第0 行第3 列种子点的贡献因子为dr,对第1 行第3 列的贡献因子为1-dr,同理,对邻近两列的贡献因子为dc 和1-dc,对邻近两个方向的贡献因子为do 和1-do。则最终累加在每个方向上的梯度大小为:
5、描述符向量元素门限化
即把方向直方图每个方向上梯度幅值限制在一定门限值一下(门限一般取0.2)
6、描述符向量元素归一化
特征向量形成后,为了去除光照变化的影响,需要对它们进行归一化处理,对于图像灰度值整体漂移,图像各点的梯度是邻域像素相减得到,所以也能去除。
六、匹配
采用关键点描述子的欧式距离来作为两幅图像的关键点的相似性度量。
七、SIFT算法的缺陷
(1)SIFT在求主方向阶段太过于依赖局部区域像素的梯度方向,有可能使得找到的主方向不准确,后面的特征向量提取以及匹配都严重依赖于主方向,即使不大偏差角度也可以造成后面特征匹配的放大误差,从而匹配不成功;
(2)图像金字塔的层取得不足够紧密也会使得尺度有误差,后面的特征向量提取同样依赖相应的尺度,发明者在这个问题上的折中解决方法是取适量的层然后进行插值。
(3)我们知道同样的景物在不同的照片中可能出现不同的形状、大小、角度、亮度,甚至扭曲;计算机视觉的知识表明通过光学镜头获取的图像,对于平面形状的两个物体它们之间可以建立射影对应,对于像人脸这种曲面物体在不同角度距离不同相机参数下获取的两幅图像,它们之间不是一个线性对应关系,就是说我们即使获得两张图像中的脸上若干匹配好的点对,还是无法从中推导出其他点的对应。
https://blog.csdn.net/CXP2205455256/article/details/41747325
https://blog.csdn.net/lhanchao/article/details/52345845#commentsedit
SIFT代码实现:
#include
#include
#include
#include
using namespace std;
using namespace cv;
using namespace cv::xfeatures2d;
Mat src, gray_src;
const char* output_title = "SIFT";
int main(int argc, char**argv) {
src = imread("D:/picture/opencv/images/home.jpg");
if (src.empty()) {
printf("could not load image...\n");
return -1;
}
namedWindow("input image", CV_WINDOW_AUTOSIZE);
imshow("input image", src);
int numFeatures = 400;
Ptrdetector = SIFT::create(numFeatures);
vectorkeypoints;
detector->detect(src, keypoints, Mat());
printf("Total KeyPoints:%d\n", keypoints.size());
Mat keypoint_img;
drawKeypoints(src, keypoints, keypoint_img, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
namedWindow(output_title, CV_WINDOW_AUTOSIZE);
imshow(output_title, keypoint_img);
waitKey(0);
return 0;
}