opencv学习笔记二十八:SURF特征点检测与匹配

标签: surf关键点检测  surf特征检测

Speeded Up Robust Features(SURF,加速稳健特征),是一种稳健的局部特征点检测和描述算法。最初由Herbert Bay发表在2006年的欧洲计算机视觉国际会议(Europen Conference on Computer Vision,ECCV)上,并在2008年正式发表在Computer Vision and Image Understanding期刊上。

Surf是对David Lowe在1999年提出的Sift算法的改进,提升了算法的执行效率,为算法在实时计算机视觉系统中应用提供了可能。与Sift算法一样,Surf算法的基本路程可以分为三大部分:局部特征点的提取、特征点的描述、特征点的匹配。

但Surf在执行效率上有两大制胜法宝——一个是积分图在Hessian(黑塞矩阵)上的使用,一个是降维的特征描述子的使用。了解这两大法宝是如何出奇制胜前,先回顾一下传统Sift算法的基本思路及其优缺点。

 

Sift是一种基于尺度空间的,对图像缩放、旋转、甚至仿射变换保持不变性的图像局部特征描述算子。

 

一、特征点提取

Sift的特征点是在DOG金字塔尺度空间中提取的,尺度空间的构建涉及到高斯卷积、图像下采样和高斯差分操作。在尺度空间中先初步提取出在尺度空间和二维图像空间上都是局部极值点的兴趣点,再滤除掉能量低的不稳定的和错误的兴趣点,得到最终稳定的特征点。

 

二、特征点描述

特征点描述包括特征点方向分配和128维向量描述两个步骤。

特征的的方向分配:Sift求取特征点周围邻域内所有像素的梯度方向,生成梯度方向直方图,并归一化为0~360°的梯度方向直方图到36个方向内,取梯度直方图的主要分量所代表的方向作为特征点的方向。

128维向量描述:这个仍然是基于梯度方向直方图展开的,去特征点周围邻域4*4个块,每块提取出8个梯度方向,共计128个方向作为特征的描述子。

 

三、特征点的匹配

特征点的匹配是通过计算两组特征点的128维的特征点的欧氏距离实现的。欧氏距离越小,则相似度越高,当欧式距离小于设定阈值时,可以判定为匹配成功。

 

Sift算法的优点是特征稳定,对旋转、尺度变换、亮度保持不变性,对视角变换、噪声也有一定程度的稳定性;缺点是实时性不高,并且对于边缘光滑目标的特征点提取能力较弱。

 

Surf改进了特征的提取和描述方式,用一种更为高效的方式完成特征的提取和描述,具体实现流程如下:

 

1. 构建Hessian(黑塞矩阵),生成所有的兴趣点,用于特征的提取;

2. 构建尺度空间

3. 特征点定位

4. 特征点主方向分配

5. 生成特征点描述子

6. 特征点匹配

 

 

1. 构建Hessian(黑塞矩阵),生成所有的兴趣点,用于特征的提取;

构建Hessian矩阵的目的是为了生成图像稳定的边缘点(突变点),跟Canny、拉普拉斯边缘检测的作用类似,为下文的特征提取做好基础。构建Hessian矩阵的过程对应于Sift算法中的高斯卷积过程。

黑塞矩阵(Hessian Matrix)是一个多元函数的二阶偏导数构成的方阵,描述了函数的局部曲率。由德国数学家Ludwin Otto Hessian于19世纪提出。

对一个图像f(x,y),其Hessian矩阵如下:

在构造Hessian矩阵前需要对图像进行高斯滤波,经过滤波后的Hessian矩阵表述为:

当Hessian矩阵的判别式取得局部极大值时,判定当前点是比周围邻域内其他点更亮或更暗的点,由此来定位关键点的位置。

我们知道在离散数字图像中,一阶导数是相邻像素的灰度差:

 

 

二阶导数是对一阶导数的再次求导:

 

 

反过来看Hessian矩阵的判别式,其实就是当前点对水平方向二阶偏导乘以垂直方向的二阶偏导再减去当前点水平、垂直二阶偏导的二次方:

 

 

Hessian矩阵判别式中的f(x,y)是原始图像的高斯卷积,由于高斯核实服从正态分布的,从中心点往外,系数越来越低,为了提高运算速度,Surf使用了盒式滤波器来近似替代高斯滤波器,所以在Dxy上乘了一个加权系数0.9,目的是为了平衡因使用盒式滤波器近似所带来的误差:

 

 

高斯滤波器和盒式滤波器的示意图如下:


上边两幅图是9*9高斯滤波器模板分别在图像上垂直方向上二阶导数Dyy和Dxy对应的值,下边两幅图是使用盒式滤波器对其近似,灰色部分的像素值为0,黑色为-2,白色为1。

 

那么为什么盒式滤波器可以提高运算速度呢,这就涉及到积分图的使用。盒式滤波器对图像的滤波转化成计算图像上不同区域间像素和的加减运算问题,这正是积分图的强项,只需要简单几次查找积分图就可以完成。

 

2. 构建尺度空间

 

同Sift一样,Surf的尺度空间也是由O组L成组成,不同的是,Sift中下一组图像的尺寸是上一组的一半,同一组间图像尺寸一样,但是所使用的高斯模糊系数逐渐增大;而在Surf中,不同组间图像的尺寸都是一致的,不同的是不同组间使用的盒式滤波器的模板尺寸逐渐增大,同一组间不同层间使用相同尺寸的滤波器,但是滤波器的模糊系数逐渐增大,如下图所示:

 

3. 特征点定位

 

特征点的定位过程Surf和Sift保持一致,将经过Hessian矩阵处理的每个像素点与二维图像空间和尺度空间邻域内的26个点进行比较,初步定位出关键点,再经过滤除能量比较弱的关键点以及错误定位的关键点,筛选出最终的稳定的特征点。

 

 

4. 特征点主方向分配

 

Sift特征点方向分配是采用在特征点邻域内统计其梯度直方图,取直方图bin值最大的以及超过最大bin值80%的那些方向作为特征点的主方向。而在Surf中,采用的是统计特征点圆形邻域内的harr小波特征。即在特征点的圆形邻域内,统计60度扇形内所有点的水平、垂直harr小波特征总和,然后扇形以0.2弧度大小的间隔进行旋转并再次统计该区域内harr小波特征值之后,最后将值最大的那个扇形的方向作为该特征点的主方向。该过程示意图如下:

 

 

5. 生成特征点描述子

 

在Sift中,是取特征点周围4*4个区域块,统计每小块内8个梯度方向,用着4*4*8=128维向量作为Sift特征的描述子。

Surf算法中,也是在特征点周围取一个4*4的矩形区域块,但是所取得矩形区域方向是沿着特征点的主方向。每个子区域统计25个像素的水平方向和垂直方向的haar小波特征,这里的水平和垂直方向都是相对主方向而言的。该haar小波特征为水平方向值之后、垂直方向值之后、水平方向绝对值之后以及垂直方向绝对值之和4个方向。该过程示意图如下:

 

 

把这4个值作为每个子块区域的特征向量,所以一共有4*4*4=64维向量作为Surf特征的描述子,比Sift特征的描述子减少了2倍。

 

6. 特征点匹配

 

与Sift特征点匹配类似,Surf也是通过计算两个特征点间的欧式距离来确定匹配度,欧氏距离越短,代表两个特征点的匹配度越好。不同的是Surf还加入了Hessian矩阵迹的判断,如果两个特征点的矩阵迹正负号相同,代表这两个特征具有相同方向上的对比度变化,如果不同,说明这两个特征点的对比度变化方向是相反的,即使欧氏距离为0,页直接予以排除。

以上摘自该篇博客:https://blog.csdn.net/dcrmg/article/details/52601010

opencv实现步骤: 

首先包含头文件和添加命名空间:

#include<opencv2/opencv.hpp>
#include<opencv2/xfeatures2d.hpp>

using namespace cv;
using namespace cv::xfeatures2d;
using namespace std;

【1】实例化一个SURF特征检测类对象的结构指针

Ptr<SURF> surf = SURF::create(double hessianThreshold=100,int nOctaves = 4, int nOctaveLayers = 3,bool extended = false, bool upright = false);
参数解释:  黑塞矩阵计算的阈值;尺度空间即金字塔的层数;每层使用多少张图像,用不同的高斯核(\sigma)模糊;是否使用扩展模块,是的话使用128维的特征向量否的话使用64维的特征向量;是否计算方向。                

这里的create指的是xfeatures2d::SURF空间下即该特定库中的函数,在其它库中也有create函数,其定义就不一样,所以针对具体的函数在不同库中有重名的需要指定具体的命名空间。

【2】检测SURF特征关键点

vector<KeyPoint> keypoints1,keypoints2;
surf->detect(src1, keypoints1);
surf->detect(src2, keypoints2);

点击SURF按F12查看帮助,可看到class CV_EXPORTS_W SURF : public Feature2D,说明SURF是Feature2D的派生类,所以可调用其父类Feature2D中的函数,点击Feature2D,按F12查看帮助,可看到Feature2D类中有detect和compute函数,一个检测关键点,一个计算特征向量的。

CV_WRAP virtual void detect( InputArray image,
                                 CV_OUT std::vector<KeyPoint>& keypoints,
CV_WRAP virtual void compute( InputArray image,
                                  CV_OUT CV_IN_OUT std::vector<KeyPoint>& keypoints,
                                  OutputArray descriptors );

【3】绘制特征关键点

    Mat result1, result2;
    drawKeypoints(src1, keypoints1, result1, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
    drawKeypoints(src2, keypoints2, result2, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
    imshow("keypoints1", result1);
    imshow("keypoints2", result2);

【4】计算特征向量
    Mat descriptors1, descriptors2;
    surf->compute(src1, keypoints1, descriptors1);
    surf->compute(src2, keypoints2, descriptors2);

【5】使用BruteForce进行匹配(暴力算法:用欧氏距离依次找出两幅图像中相似度最高的两个特征向量,并将他们连线)
    Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create("BruteForce");
    vector< DMatch > matches;
    matcher->match(descriptors1, descriptors2, matches);

【6】绘制匹配出的关键点
    Mat imgMatches;
    drawMatches(src1, keypoints1, src2, keypoints2, matches, imgMatches);
    imshow("output", imgMatches);

#include<opencv2/opencv.hpp>
#include<opencv2/xfeatures2d.hpp>

using namespace cv;
using namespace cv::xfeatures2d;
using namespace std;

int minHessian = 700;//定义SURF中的hessian阈值特征点检测算子
Mat src1,src2;
void callback(int, void*);
int main(int arc, char** argv)
{
	src1 = imread("1.jpg");
	src2 = imread("2.jpg");

	namedWindow("output", CV_WINDOW_AUTOSIZE);
	createTrackbar("threshold", "output", &minHessian, 5000, callback);
	callback(0, 0);
	waitKey(0);
	return 0;
}
void callback(int, void*) {
	//实例化一个SURF特征检测类对象的指针
	Ptr<SURF> surf = SURF::create(minHessian);

	//检测SURF特征关键点
	vector<KeyPoint> keypoints1,keypoints2;//vector模板类是能够存放任意类型的动态数组,能够增加和压缩数据 
	surf->detect(src1, keypoints1);
	surf->detect(src2, keypoints2);

	//绘制特征关键点
	Mat result1, result2;
	drawKeypoints(src1, keypoints1, result1, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
	drawKeypoints(src2, keypoints2, result2, Scalar::all(-1), DrawMatchesFlags::DEFAULT);
	imshow("keypoints1", result1);
	imshow("keypoints2", result2);

	//计算特征向量
	Mat descriptors1, descriptors2;
	surf->compute(src1, keypoints1, descriptors1);
	surf->compute(src2, keypoints2, descriptors2);

	//使用BruteForce(简单匹配算法)进行匹配
	Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create("BruteForce");
	vector< DMatch > matches;
	matcher->match(descriptors1, descriptors2, matches);

	//绘制匹配出的关键点
	Mat imgMatches;
	drawMatches(src1, keypoints1, src2, keypoints2, matches, imgMatches);//进行绘制
	imshow("output", imgMatches);
}

原图像如下:

关键点检测结果如下:

 

 匹配结果如下:

版权声明:本文为qq_24946843原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_24946843/article/details/82620179

智能推荐

phpstudy的mysql版本升级至5.7

phpstudy安装的mysql版本一般都是5.5或5.4的,但是有时候做项目又必须用到mysql5.7版本,所以我们现在来看一下如何在phpstudy的环境下将mysql版本升级至5.7   温馨提醒: 先删掉所有环境变量,如果是之前有的话,不然怎么安装cmd上指向的还是原来的版本。安装完再设新的环境变量。 并且卸载掉mysqld服务mysqld remove。如果不先删除的话,可能会...

RIP/DHCP/ACL综合实验

组播: 加入组的组成员才会接受到消息,只需要将流量发送一次到组播地址 减少控制面流量,减少头部复制, RIP1  广播   有类  不支持认证 RIP2  组播   无类  (支持VLAN)、支持认证 所有距离矢量路由协议:具有距离矢量特征的协议,都会在边界自动汇总 控制平面  路由的产生是控制平面的流量 数据平面  ...

【Sublime】使用 Sublime 工具时运行python文件

使用 Sublime 工具时报Decode error - output not utf-8解决办法   在菜单中tools中第四项编译系统 内最后一项增添新的编译系统 自动新建 Python.sublime-build文件,并添加"encoding":"cp936"这一行,保存即可 使用python2 则注释encoding改为utf-8 ctr...

java乐观锁和悲观锁最底层的实现

1. CAS实现的乐观锁 CAS(Compare And Swap 比较并且替换)是乐观锁的一种实现方式,是一种轻量级锁,JUC 中很多工具类的实现就是基于 CAS 的,也可以理解为自旋锁 JUC是指import java.util.concurrent下面的包, 比如:import java.util.concurrent.atomic.AtomicInteger; 最终实现是汇编指令:lock...

Python 中各种imread函数的区别与联系

  原博客:https://blog.csdn.net/renelian1572/article/details/78761278 最近一直在用python做图像处理相关的东西,被各种imread函数搞得很头疼,因此今天决定将这些imread总结一下,以免以后因此犯些愚蠢的错误。如果你正好也对此感到困惑可以看下这篇总结。当然,要了解具体的细节,还是应该 read the fuc...

猜你喜欢

用栈判断一个字符串是否平衡

注: (1)本文定义:左符号:‘(’、‘[’、‘{’…… 右符号:‘)’、‘]’、‘}’……. (2)所谓的字符串的符号平衡,是指字符串中的左符号与右符号对应且相等,如字符串中的如‘(&r...

JAVA环境变量配置

位置 计算机->属性->高级系统设置->环境变量 方式一 用户变量新建path 系统变量新建classpath 方式二 系统变量 新建JAVA_HOME,值为JDK路径 编辑path,前加 方式三 用户变量新建JAVA_HOME 此路径含lib、bin、jre等文件夹。后运行tomcat,eclipse等需此变量,故最好设。 用户变量编辑Path,前加 系统可在任何路径识别jav...

常用的伪类选择器

CSS选择器众多 CSS选择器及权重计算 最常用的莫过于类选择器,其它的相对用的就不会那么多了,当然属性选择器和为类选择器用的也会比较多,这里我们就常用的伪类选择器来讲一讲。 什么是伪类选择器? CSS伪类是用来添加一些选择器的特殊效果。 常用的为类选择器 状态伪类 我们中最常见的为类选择器就是a标签(链接)上的为类选择器。 当我们使用它们的时候,需要遵循一定的顺序问题,否则将可能出现bug 注意...

ButterKnife的使用介绍及原理探究(六)

前面分析了ButterKnife的源码,了解其实现原理,那么就将原理运用于实践吧。 github地址:       点击打开链接 一、自定义注解 这里为了便于理解,只提供BindView注解。 二、添加注解处理器 添加ViewInjectProcessor注解处理器,看代码, 这里分别实现了init、getSupportedAnnotationTypes、g...

1.写一个程序,提示输入两个字符串,然后进行比较,输出较小的字符串。考试复习题库1|要求:只能使用单字符比较操作。

1.写一个程序,提示输入两个字符串,然后进行比较,输出较小的字符串。 要求只能使用单字符比较操作。 参考代码: 实验结果截图:...