本博客内容来源于网络以及其他书籍,结合自己学习的心得进行重编辑,因为看了很多文章不便一一标注引用,如图片文字等侵权,请告知删除。
前言
本篇的上一篇文章描写的是SIFT算法SIFT算法学习笔记(上) ,本篇博主在SIFT算法详细内容还很清晰的情况下,再描述一下SIFT的优化算法-- SURF算法。因为SURF是SIFT的优化算法,所以本文只描述与SIFT不同的地方,与SIFT相同的地方就不再重复描述,因而在读本文之前,希望能够看完SIFT算法的文章,或则对SIFT足够理解。那么开始吧!!!
![](https://img.haomeiwen.com/i11133696/dd90b27505bb91fe.gif)
SURF 简介
SURF(Speeded-Up Robust Features)加速稳健特征,是一种稳健的局部特征点检测和描述算法。最初由Herbert Bay发表在2006年的欧洲计算机视觉国际会议ECCV上,并在2008年正式发表在Computer Vision and Image Understanding期刊上。
Surf是可以说是对Sift算法的改进,该算子在保持 SIFT 算子优良性能特点的基础上,同时解决了 SIFT 计算复杂度高、耗时长的缺点,提升了算法的执行效率,为算法在实时计算机视觉系统中应用提供了可能,sift在一般的计算机对于日常使用的图片想做到实时基本上是不可能的。
下面我们来看一下SURF到底对SIFT做了什么改进,是怎么继续保证SIFT的优秀的效果的以及SURF和SIFT有什么效果上的区别。
SURF 流程
与Sift算法一样,Surf算法的基本路程也可以分为四大部分:尺度空间建立、特征点定位、特征点方向确定,特征点描述。
基本的流程都是一样的,那么SURF是怎么改进其执行效率的呢?主要还是在两个关键的优化点:
- 积分图在Hessian(黑塞矩阵)上的使用
- 降维的特征描述子的使用
在梳理SURF的流程之前,我们先了解几个概念:积分图,Hessian矩阵,harr小波特征。
-
积分图
我们知道图像是由一系列的离散像素点组成, 所以图像的积分其实就是所有的像素点求和. 图像积分图中每个点的值是原图像中该点左上角的所有像素值之和。
![](https://img.haomeiwen.com/i11133696/3fa5a0580409eeb4.png)
![](https://img.haomeiwen.com/i11133696/69b52729609b932c.png)
![](https://img.haomeiwen.com/i11133696/9efb9fd5ba0ed8bb.png)
其中I(x,y)为像素点(x,y)的像素值。增量的计算方式,可以保证其计算速度。概念很简单,理解没有问题,具体怎么用我们之后在流程中再解释。
-
Hessian矩阵
这个好像很熟悉?我们的确在之前的几篇文章中,见过很多次这个矩阵了,包括在SIFT去除边缘响应。现在我们详细解释一下。
所谓 Hessian矩阵 就是一个多元函数的二阶偏导数构成的方阵,描述了函数的局部曲率。此外还有Jacob矩阵也是描述函数的局部曲率的,是一个多元函数的一阶偏导数构成的方阵。
![](https://img.haomeiwen.com/i11133696/b15e0a0c28c9c54d.jpg)
Hessian 就是描述的一个点周围像素梯度大小的变化率,其极值就是生成图像稳定的边缘点(突变点),其两个特征值,代表这其在两个相互垂直方向是的梯度的变化率,当两个特征值越大时,其图像中的像素点的像素值波动越大。我们用两个特征值相加即 Hessian的判别式(即行列式的值)来量化。
-
harr小波特征
这个还挺难解释的,个人也还是不是理解的那么好,而且越看越迷糊,下次通过一个文章来写吧。先立个flag
在我们了解了上述内容,我们来看接下来的步骤。
1. 构建尺度空间
同SIFT算法一样,SURF算法的尺度空间由𝑂组𝑆层组成,不同的是,SIFT算法下一组图像的长宽均是上一组的一半,同一组不同层图像之间尺寸一样,但是所使用的尺度空间因子(高斯模糊系数𝜎)逐渐增大;而在SURF算法中,不同组间图像的尺寸都是一致的,不同的是不同组间使用的盒式滤波器的模板尺寸逐渐增大,同一组不同层图像使用相同尺寸的滤波器,但是滤波器的尺度空间因子(即高斯模糊系数𝜎)逐渐增大。![](https://img.haomeiwen.com/i11133696/02dfdae20ebe7034.jpg)
左图表示的是传统方式建立一个如图所看到的的金字塔结构。图像的大小是变化的。而且运 算会重复使用高斯函数对子层进行平滑处理。右图说明Surf算法使原始图像保持不变而仅仅改变滤波器大小。Surf採用这样的方法节省了降採样过程,其处理速度自然也就提上去了。
-
SURF中盒式滤波器解释
在构建尺度空间的时候,我们使用刚才解释的Hessian矩阵,但是在构造Hessian矩阵前需要对图像进行高斯滤波,经过滤波后的Hessian矩阵表述为:
图中,Lxx(x,𝜎) 是对高斯函数进行x方向的二次导数,其他的类似。
Hessian矩阵判别式中的L(x,y)是原始图像的高斯卷积,由于高斯核实服从正态分布的,从中心点往外,系数越来越低,为了提高运算速度,Surf使用了盒式滤波器来近似替代高斯滤波器,所以在Dxy上乘了一个加权系数0.9,目的是为了平衡因使用盒式滤波器近似所带来的误差:
其实,当Hessian矩阵的判别式取得局部极大值时,判定当前点是比周围邻域内其他点更亮或更暗的点,由此来定位关键点的位置。那怎么用用合适滤波器来近似高斯滤波器呢?有为什么能提高速度呢?
上边两幅图是9*9高斯滤波器模板分别在图像上垂直方向上二阶导数Dyy和Dxy对应的值,下边两幅图是使用盒式滤波器对其近似,灰色部分的像素值为0,黑色为-2,白色为1。
盒式滤波器对图像的滤波转化成计算图像上不同区域间像素和的加减运算问题,这正是积分图的强项,只需要简单几次查找积分图就可以完成。这样就大大的提高了构造尺度空间的速度。
支持我们已经解释了surf是怎么构造尺度空间的并能达到和sift类似的效果,为什么要按这种方式构造尺度的,有怎么提高构造尺度空间的速度的。
2. 特征点定位
这一部分和SIFT查找方式一样,找到尺度空间的局域极大值,然后删除响应比较弱的关键点以及错误定位的关键点,以及进行亚像素分析。
3. 特征点方向确认
Sift特征点方向分配是采用在特征点邻域内统计其梯度直方图,取直方图最大值的以及超过最大值80%的那些方向作为特征点的主方向。
![](https://img.haomeiwen.com/i11133696/50dc7ae10551388a.jpg)
这样做的有点就是统计的方向更加精确,但是计算时间会略有上升,但是毕竟计算的只有关键点,可忽略不计。
4. 生成特征点描述向量
在SIFT中,是提取特征点周围44个区域块,统计每小块内8个梯度方向,用着44*8=128维向量作为Sift特征的描述子。
SURF算法中,也是在特征点周围取一个4*4的矩形区域块,但是所取得矩形区域方向是沿着特征点的主方向。每个子区域统计25个像素的水平方向和垂直方向的haar小波特征,这里的水平和垂直方向都是相对主方向而言的。该haar小波特征为水平方向值之后、垂直方向值之后、水平方向绝对值之后以及垂直方向绝对值之和4个方向。该过程示意图如下:这样SURF提取的就是一个444 的64维的特征向量。
OpenCV SURF特征效果展示[代码]
#include <opencv2/opencv.hpp>
#include <iostream>
#include <opencv2/xfeatures2d.hpp>
void extracte_surf(cv::Mat input,std::vector<cv::KeyPoint> &keypoint,cv::Mat &descriptor){
cv::Ptr<cv::Feature2D> f2d = cv::xfeatures2d::SURF::create();
f2d->detect(input,keypoint);
cv::Mat image_with_kp;
f2d->compute(input,keypoint,descriptor);
cv::drawKeypoints(input, keypoint, image_with_kp, cv::Scalar::all(-1),cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
cv::imwrite("surf"+std::to_string(keypoint.size())+".png",image_with_kp);
}
void match_two_image(cv::Mat image1,cv::Mat image2, std::vector<cv::KeyPoint> keypoint1,std::vector<cv::KeyPoint> keypoint2,cv::Mat descriptor1,cv::Mat descriptor2){
cv::FlannBasedMatcher matcher;
std::vector<cv::DMatch> matches, goodmatches;
matcher.match(descriptor1,descriptor2, matches);
cv::Mat good_matches_image;
double max_dist = 0; double min_dist = 1000;
for (int i = 0; i < descriptor1.rows; i++) {
if (matches[i].distance > max_dist) {
max_dist = matches[i].distance;
}
if (matches[i].distance < min_dist) {
min_dist = matches[i].distance;
}
}
for (int i = 0; i < descriptor1.rows; i++) {
if (matches[i].distance < 6 * min_dist) {
goodmatches.push_back(matches[i]);
}
}
cv::drawMatches(image1, keypoint1, image2, keypoint2,
goodmatches, good_matches_image, cv::Scalar::all(-1), cv::Scalar::all(-1),
std::vector<char>(), cv::DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
cv::imwrite("good_matches_image.png",good_matches_image);
{
std::vector <cv::KeyPoint> RAN_KP1, RAN_KP2;
std::vector<cv::Point2f> keypoints1, keypoints2;
for (int i = 0; i < goodmatches.size(); i++) {
keypoints1.push_back(keypoint1[goodmatches[i].queryIdx].pt);
keypoints2.push_back(keypoint2[goodmatches[i].trainIdx].pt);
RAN_KP1.push_back(keypoint1[goodmatches[i].queryIdx]);
RAN_KP2.push_back(keypoint2[goodmatches[i].trainIdx]);
}
std::vector<uchar> RansacStatus;
cv::findFundamentalMat(keypoints1, keypoints2, RansacStatus, cv::FM_RANSAC);
std::vector <cv::KeyPoint> ransac_keypoints1, ransac_keypoints2;
std::vector <cv::DMatch> ransac_matches;
int index = 0;
for (size_t i = 0; i < goodmatches.size(); i++)
{
if (RansacStatus[i] != 0)
{
ransac_keypoints1.push_back(RAN_KP1[i]);
ransac_keypoints2.push_back(RAN_KP2[i]);
goodmatches[i].queryIdx = index;
goodmatches[i].trainIdx = index;
ransac_matches.push_back(goodmatches[i]);
index++;
}
}
cv::Mat after_ransac_sift_match;
cv::drawMatches(image1, ransac_keypoints1, image2, ransac_keypoints2,
ransac_matches, after_ransac_sift_match, cv::Scalar::all(-1), cv::Scalar::all(-1),
std::vector<char>(), cv::DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
cv::imwrite("after_ransac_surf_match.png",after_ransac_sift_match);
}
}
int main(int argc, char *argv[])
{
cv::Mat image1 = cv::imread(argv[1]);
cv::Mat image2 = cv::imread(argv[2]);
std::vector<cv::KeyPoint> keypoint1,keypoint2;
cv::Mat descriptor1, descriptor2;
extracte_surf(image1,keypoint1,descriptor1);
extracte_surf(image2,keypoint2,descriptor2);
match_two_image(image1,image2,keypoint1,keypoint2,descriptor1,descriptor2);
return 0;
}
图片还是SIFT文章中使用的图片
原图1 | 原图2 |
---|---|
![]() |
![]() |
原图1提取SURF | 原图2提取SURF |
![]() |
![]() |
![](https://img.haomeiwen.com/i11133696/cc60dcef994e19ad.png)
总结
surf如何保证那些sift类似的那些特点的,按照sift的想法还是很容易分析的,这里就不一一分析。
从实验结果上看,surf的特征点的提取效果还是不错的,特征点的数目也很多,而且时间上有大大的优化。论文《A comparison of SIFT, PCA-SIFT and SURF 》对三种方法给出了性能上的比较,结论是:SIFT在尺度和旋转变换的情况下效果最好,SURF在亮度变化下匹配效果最好,在模糊方面优于SIFT,而尺度和旋转的变化不及SIFT,旋转不变上比SIFT差很多。速度上看,SURF是SIFT速度的3倍。
重要的事情说三遍:
如果我的文章对您有所帮助,那就点赞加个关注呗 ( * ^ __ ^ * )
如果我的文章对您有所帮助,那就点赞加个关注呗 ( * ^ __ ^ * )
如果我的文章对您有所帮助,那就点赞加个关注呗 ( * ^ __ ^ * )
任何人或团体、机构全部转载或者部分转载、摘录,请保留本博客链接或标注来源。博客地址:开飞机的乔巴
作者简介:开飞机的乔巴(WeChat:zhangzheng-thu),现主要从事机器人抓取视觉系统以及三维重建等3D视觉相关方面,另外对slam以及深度学习技术也颇感兴趣,欢迎加我微信或留言交流相关工作。
网友评论