OpenCV2学习笔记(十三):利用SURF匹配不同图像的特征点

SURF算法是著名的尺度不变特征检测器SIFT(Scale-Invariant Features Transform)的高效变种,它为每个检测到的特征定义了位置和尺度,其中尺度的值可用于定义围绕特征点的窗口大小,使得每个特征点都与众不同。这里便是使用SURF算法提取两幅图像中的特征点描述子,并调用OpenCV中的函数进行匹配,最后输出一个可视化的结果,开发平台为Qt5.3.2+OpenCV2.4.9。以下给出图像匹配的实现步骤:

一、输入两幅图像,使用OpenCV中的cv::FeatureDetector接口实现SURF特征检测,在实际调试中改变阈值可获得不一样的检测结果:

    // 设置两个用于存放特征点的向量
    std::vector<cv::KeyPoint> keypoint1;
    std::vector<cv::KeyPoint> keypoint2;

    // 构造SURF特征检测器
    cv::SurfFeatureDetector surf(3000); // 阈值

    // 对两幅图分别检测SURF特征
    surf.detect(image1,keypoint1);
    surf.detect(image2,keypoint2);

二、OpenCV 2.0版本中引入一个通用类,用于提取不同的特征点描述子。在这里构造一个SURF描述子提取器,输出的结果是一个矩阵,它的行数与特征点向量中的元素个数相同。每行都是一个N维描述子的向量。在SURF算法中,默认的描述子维度为64,该向量描绘了特征点周围的强度样式。两个特征点越相似,它们的特征向量也就越接近,因此这些描述子在图像匹配中十分有用:

    cv::SurfDescriptorExtractor surfDesc;

    // 对两幅图像提取SURF描述子
    cv::Mat descriptor1, descriptor2;
    surfDesc.compute(image1,keypoint1,descriptor1);
    surfDesc.compute(image2,keypoint2,descriptor2);

提取出两幅图像各自的特征点描述子后,需要进行比较(匹配)。可以调用OpenCV中的类cv::BruteForceMatcher构造一个匹配器。cv::BruteForceMatcher是类cv::DescriptorMatcher的一个子类,定义了不同的匹配策略的共同接口,结果返回一个cv::DMatch向量,它将被用于表示一对匹配的描述子。(关于cv::BruteForceMatcher 请参考: http://blog.csdn.net/panda1234lee/article/details/11094483?utm_source=tuicool

三、在一批特征点匹配结果中筛选出评分(或者称距离)最理想的25个匹配结果,这通过std::nth_element实现。

void nth_element(_RandomAccessIterator _first, _RandomAccessIterator _nth, _RandomAccessIterator _last)  

该函数的作用为将迭代器指向的从_first 到 _last 之间的元素进行二分排序,以_nth 为分界,前面都比 _Nth 小(大),后面都比之大(小),因此适用于找出前n个最大(最小)的元素。

四、最后一步,将匹配的结果可视化。OpenCV提供一个绘制函数以产生由两幅输入图像拼接而成的图像,而匹配的点由直线相连:

    // 以下操作将匹配结果可视化
    cv::Mat imageMatches;
    cv::drawMatches(image1,keypoint1,  // 第一张图片和检测到的特征点
                    image2,keypoint2,  // 第二张图片和检测到的特征点
                    matches,            // 输出的匹配结果
                    imageMatches,       // 生成的图像
                    cv::Scalar(128,128,128)); // 画直线的颜色

要注意SIFT、SURF的函数在OpenCV的nonfree模块中而不是features2d,cv::BruteForceMatcher类存放在legacy模块中,因此函数中需要包含头文件:

#include <opencv2/legacy/legacy.hpp>
#include <opencv2/nonfree/nonfree.hpp>

完整代码如下:

#include <QCoreApplication>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/legacy/legacy.hpp>
#include <opencv2/nonfree/nonfree.hpp>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
        // 以下两图比之
    // 输入两张要匹配的图
    cv::Mat image1= cv::imread("c:/Fig12.18(a1).jpg",0);
    cv::Mat image2= cv::imread("c:/Fig12.18(a2).jpg",0);
    if (!image1.data || !image2.data)
        qDebug() << "Error!";

    cv::namedWindow("Right Image");
    cv::imshow("Right Image", image1);
    cv::namedWindow("Left Image");
    cv::imshow("Left Image", image2);

    // 存放特征点的向量
    std::vector<cv::KeyPoint> keypoint1;
    std::vector<cv::KeyPoint> keypoint2;

    // 构造SURF特征检测器
    cv::SurfFeatureDetector surf(3000); // 阈值

    // 对两幅图分别检测SURF特征
    surf.detect(image1,keypoint1);
    surf.detect(image2,keypoint2);

    // 输出带有详细特征点信息的两幅图像
    cv::Mat imageSURF;
    cv::drawKeypoints(image1,keypoint1,
                      imageSURF,
                      cv::Scalar(255,255,255),
                      cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
    cv::namedWindow("Right SURF Features");
    cv::imshow("Right SURF Features", imageSURF);
    cv::drawKeypoints(image2,keypoint2,
                      imageSURF,
                      cv::Scalar(255,255,255),
                      cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
    cv::namedWindow("Left SURF Features");
    cv::imshow("Left SURF Features", imageSURF);

    // 构造SURF描述子提取器
    cv::SurfDescriptorExtractor surfDesc;

    // 对两幅图像提取SURF描述子
    cv::Mat descriptor1, descriptor2;
    surfDesc.compute(image1,keypoint1,descriptor1);
    surfDesc.compute(image2,keypoint2,descriptor2);

    // 构造匹配器
    cv::BruteForceMatcher< cv::L2<float> > matcher;

    // 将两张图片的描述子进行匹配,只选择25个最佳匹配
    std::vector<cv::DMatch> matches;
    matcher.match(descriptor1, descriptor2, matches);

    std::nth_element(matches.begin(),    // 初始位置
                     matches.begin()+24, // 排序元素的位置
                     matches.end());     // 终止位置
    // 移除25位后的所有元素
    matches.erase(matches.begin()+25, matches.end());

    // 以下操作将匹配结果可视化
    cv::Mat imageMatches;
    cv::drawMatches(image1,keypoint1,  // 第一张图片和检测到的特征点
                    image2,keypoint2,  // 第二张图片和检测到的特征点
                    matches,            // 输出的匹配结果
                    imageMatches,       // 生成的图像
                    cv::Scalar(128,128,128)); // 画直线的颜色
    cv::namedWindow("Matches"); //, CV_WINDOW_NORMAL);
    cv::imshow("Matches",imageMatches);

    return a.exec();
}

效果一,由于原图中飞机的边缘有锯齿状,因此只需观察拐角处,匹配效果良好:

效果二,不涉及图像的旋转和变形,只是将一幅图像进行缩放后进行匹配,得出的效果自然是很好:

效果三,用两个不同的角度拍摄的图像进行匹配,其中部分特征点匹配有偏差,总体效果良好,在调试过程中还可以通过参数调整获取更好的匹配效果。

参考资料:

http://blog.sina.com.cn/s/blog_a98e39a201017pgn.html

http://www.cnblogs.com/tornadomeet/archive/2012/08/17/2644903.html (SURF算法的理论介绍)

http://blog.csdn.net/liyuefeilong/article/details/44166069

时间: 2024-10-26 18:36:02

OpenCV2学习笔记(十三):利用SURF匹配不同图像的特征点的相关文章

OpenCV2学习笔记(六):检测图像颜色小程序

设计一个界面,用来检测一幅图像的颜色分布,开发平台为Qt5.3.2+OpenCV2.4.9. 该程序的主要步骤如下: 1. 载入图像,选定一种颜色: 2. 设定阈值,在该值范围内判定像素属于预设的颜色: 3. 在界面的Label中输出结果. 首先,新建一个Qt Widgets Application,其中基类选择为QWidget,在创建完项目后,添加一个检测图像颜色的类ColorDetector.并在在Qt项目的.pro文件中添加: INCLUDEPATH+=C:\OpenCV\install\

学习笔记:利用GDI+生成简单的验证码图片

小分享:我有几张阿里云优惠券,用券购买或者升级阿里云相应产品最多可以优惠五折!领券地址:https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=ohmepe03 学习笔记:利用GDI+生成简单的验证码图片 1 /// <summary> 2 /// 单击图片时切换图片 3 /// </summary> 4 /// <param name="sender">&

java之jvm学习笔记十三(jvm基本结构)

java之jvm学习笔记十三(jvm基本结构) 这一节,主要来学习jvm的基本结构,也就是概述.说是概述,内容很多,而且概念量也很大,不过关于概念方面,你不用担心,我完全有信心,让概念在你的脑子里变成图形,所以只要你有耐心,仔细,认真,并发挥你的想象力,这一章之后你会充满自信.当然,不是说看完本章,就对jvm了解了,jvm要学习的知识实在是非常的多.在你看完本节之后,后续我们还会来学jvm的细节,但是如果你在学习完本节的前提下去学习,再学习其他jvm的细节会事半功倍. 为了让你每一个知识点都有迹

OpenCV2学习笔记:图像的读取与显示

1. 图像读取:imread() Mat imread(const string& ?lename, int ?ags=1 ) 参数介绍: filename: 待加载的文件名称. flags: 此标志用来指定被加载图像的颜色类型(color type).这个标志的取值可以有: -- CV_LOAD_IMAGE_ANYDEPTH : 如果设置这个标志的话,如果图像为16位或32位深度的图像,则返回对应深度的图像:否则,将图像转换为8位深度图像再返回. -- CV_LOAD_IMAGE_COLOR

Swift学习笔记十三:继承

一个类可以继承(inherit)另一个类的方法(methods),属性(property)和其它特性 一.基本语法 class Human{ var name :String init(){ name = "human" println(name) } func description(){ println("name:\(name)") } } class Student:Human{ var score = 0 init(){ super.init() name

laravel3学习笔记(十三)

原作者博客:ieqi.net ==================================================================================================== 类的自动载入 使用 PHP 自身的 include 或者 require 族的函数载入重用代码一般情况下很难帮助我们更好的组织工程代码,对于此,从便捷和性能的方面考虑, Laravel3 为我们提供了类的自动载入功能.自动载入可以让我们在需要的时候才载入所需要的类文件

Go语言学习笔记十三: Map集合

Go语言学习笔记十三: Map集合 Map在每种语言中基本都有,Java中是属于集合类Map,其包括HashMap, TreeMap等.而Python语言直接就属于一种类型,写法上比Java还简单. Go语言中Map的写法比Java简单些,比Python繁琐. 定义Map var x map[string]string x : = make(map[string]string) 写法上有些奇怪,map为关键字,右侧中括号内部为key的类型,中括号外部为value的类型.一般情况下使用逗号或者冒号

GDI+学习笔记(七)保存简单图像

请尊重本人的工作成果,转载请留言,并说明转载地址,谢谢.地址如下: http://blog.csdn.net/fukainankai/article/details/27710883 前几节中,我们利用GDI+在窗口中绘制了各种各样的图形.图像,这一节,我们将会将这些图像保存成简单图像.所谓简单图像,指的是bmp/jpg/png等图像或者单帧的gif图像.保存成多帧的gif图像稍微复杂一点,本节中暂时不做说明.保存成动态的tiff文件也比较简单,但这里也不做说明,下次有机会和gif一起介绍. 另

[傅里叶变换及其应用学习笔记] 十三. 分布的傅里叶变换

这份是本人的学习笔记,课程为网易公开课上的斯坦福大学公开课:傅里叶变换及其应用. 分布傅里叶变换的定义 在傅里叶变换领域中,测试函数$\varphi$选择了速降函数(Schwartz Functions).与之对应的分布$T$通常被称为缓增分布(Tempered Distributions). $<T,\varphi>$ 上式表示了,给定测试函数$\varphi$,分布$T$对测试函数$\varphi$进行作用,得到的结果为一个数值,该过程也被称为匹配(Pair).这种作用是通过积分来实现的.