opencv之SURF特征点提取及匹配

1.概述

在基于特征匹配的方法中,Surf算法对物体的旋转、光照等情况有较好的鲁棒性,且教SIFT算法而言计算速度更快。通过Surf算法检测到的特征点其描述符包含了这个点的位置和尺度信息,故对两幅图片进行匹配时可以通过两幅图中特征点匹配对进行匹配。即使物体位置和光照的改变也能够有良好的匹配效果。
在本片文章中将使用SurfDescriptorExtractor及其函数compute来完成特定计算,使用BruteForceMatcher匹配得到的特征向量,使用函数drawMatches来绘制检测到的匹配点。

2.OpenCV API

2.1 SurfDescriptorExtractor

Surf用来封装的用于计算特征描述子的类,其定义如下

typedef SURF cv::xfeatures2d::SurfDescriptorExtractor

是SURF的一个重定义,也是类xfeatures2d的一个成员函数,其类定义如下:
class SurfDescriptorExtractor : public DescriptorExtractor
{
public:
SurfDescriptorExtractor( int nOctaves=4,
int nOctaveLayers=2, bool extended=false );

    virtual void read (const FileNode &fn);
    virtual void write (FileStorage &fs) const;
    virtual int descriptorSize() const;
    virtual int descriptorType() const;
protected:
    ...
}

我们可以看出SurfDescriptorExtractor是从DescriptorExtractor公有继承而来。

2.2BruteForceMatcher

暴力搜索特征点匹配。对于第一集合中的特征描述子,这个匹配寻找在第二个集合中最相近的特征描述子,这种特征描述子匹配支持masking permissible特征描述子集合匹配,其定义如下:

template
class BruteForceMatcher : public DescriptorMatcher
{
public:
    BruteForceMatcher( Distance d = Distance() );
    virtual ~BruteForceMatcher();

    virtual bool isMaskSupported() const;
    virtual Ptr clone( bool emptyTrainData=false ) const;
protected:
    ...
}

从DescriptorMatcher公有继承,同时又有本身的虚成员函数。

2.3 drawMatches

给定两幅图像,绘制寻找到的特征关键点及其匹配。有两种定义形式如下:

void cv::drawMatches    (   InputArray  img1,
                            const std::vector< KeyPoint > &     keypoints1,
                            InputArray  img2,
                            const std::vector< KeyPoint > &     keypoints2,
                            const std::vector< DMatch > &   matches1to2,
                            InputOutputArray    outImg,
                            const Scalar &  matchColor = Scalar::all(-1),
                            const Scalar &  singlePointColor = Scalar::all(-1),
                            const std::vector< char > &     matchesMask = std::vector< char >(), 
                            int     flags = DrawMatchesFlags::DEFAULT 
                        )

void cv::drawMatches    (   InputArray  img1,
                            const std::vector< KeyPoint > &     keypoints1,
                            InputArray  img2,
                            const std::vector< KeyPoint > &     keypoints2,
                            const std::vector< std::vector< DMatch > > &    matches1to2,
                            InputOutputArray    outImg,
                            const Scalar &  matchColor = Scalar::all(-1),
                            const Scalar &  singlePointColor = Scalar::all(-1),
                            const std::vector< std::vector< char > > &  matchesMask = std::vector< std::vector< char > >(), 
                            int     flags = DrawMatchesFlags::DEFAULT 
                        )   

两个函数的区别仅在于DMatch数据接收的数据类型不同
img1:接收的第一幅源图像
**keypoints1:**KeyPoint类型的关键点(特征点),由第一幅图像检测得到
img2:接收的第二幅图像
**keypoints2:**KeyPoint类型的关键点(特征点),由第二幅图像检测得到
**matches1to2:**DMatch类型矢量,表示从第一幅到第二幅图像的匹配点,表示每一个图1中的特征点豆在图2中有一一对应的点
outImg:完成匹配后的输出图像,其内容取决于输出图像的标志位flags
matchColor:两个匹配点进行绘制匹配的颜色,即线和点的颜色,有默认值表示颜色随机生成
singlePointColor:对于没有匹配对的特征点绘制颜色
matchesMask:确定哪些是会绘制出来的掩膜,如果掩膜为空,表示所有匹配都进行绘制
flags:绘制匹配的标志位,有默认值DrawMatchesFlags::DEFAULT。其可选项如下:

enum  { 
          DEFAULT = 0,  //创建输出图像矩阵,对每一个特征点都只绘制其中间部分。
          DRAW_OVER_OUTIMG = 1,     //不创建输出图像矩阵,而是在输出图像上绘制匹配对
          NOT_DRAW_SINGLE_POINTS = 2,   //单点特征点不被绘制
          DRAW_RICH_KEYPOINTS = 4       //对每个特征点,绘制带大小和方向的关键点图像
      }

3.示例代码:

#include 
#include 
#include 
#include 
#include 


using namespace std;
using namespace cv;

int main()
{
    Mat srcImage1 = imread("Surf_description_1.jpg",IMREAD_GRAYSCALE);
    Mat srcImage2 = imread("Surf_description_2.jpg",IMREAD_GRAYSCALE);

    //判断文件是否读取成功
    if (srcImage1.empty() || srcImage2.empty())
    {
        cout << "图像加载失败!";
        return -1;
    }
    else
        cout << "图像加载成功..." << endl << endl;

    //检测两幅图像中的特征点
    int minHessian = 2000;      //定义Hessian矩阵阈值

    SurfFeatureDetector detector(minHessian);       //定义Surf检测器
    vectorkeypoint1, keypoint2;           //定义两个KeyPoint类型矢量存储检测到的特征点
    detector.detect(srcImage1, keypoint1);
    detector.detect(srcImage2, keypoint2);

    //计算特征向量的描述子
    SurfDescriptorExtractor descriptorExtractor;
    Mat descriptors1, descriptors2;

    descriptorExtractor.compute(srcImage1, keypoint1, descriptors1);
    descriptorExtractor.compute(srcImage2, keypoint2, descriptors2);

    //使用BruteForceMatcher进行描述符匹配
    BFMatcher matcher(NORM_L2);
    vectormatches;
    matcher.match(descriptors1, descriptors2, matches);

    //绘制匹配特征点
    Mat matchImage;
    drawMatches(srcImage1, keypoint1, srcImage2, keypoint2, matches, matchImage);

    //显示匹配的图像
    namedWindow("Match", WINDOW_AUTOSIZE);
    imshow("Match", matchImage);

    waitKey(0);

    return 0;
}

运行结果

4.使用FLANN匹配

最近邻搜索的问题在图像识别、数据压缩、模式识别和分类、机器学习、文档检索系统、统计和数据分析等方面是一个重大问题,在高维空间中解决这个问题似乎是一个非常难以执行的任务,没有算法明显优于标准的蛮力搜索,因此越来越多的人把兴趣点转向执行最近邻搜索的一类算法。
FLANN(Fast Library for Approximate Nearest Neighbors)时目前最完整的近似近邻开源库,不但实现了一系列查找算法还包含了一种自动选取最快算法的机制。
opencv中提供了函数FlannBasedMatcher()函数,其继承关系如下图所示:

4.1示例代码

#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;
using namespace cv;

int main()
{
    Mat srcImage1 = imread("Surf_description_1.jpg");
    Mat srcImage2 = imread("Surf_description_2.jpg");

    //判断文件是否加载成功
    if (srcImage1.empty() || srcImage2.empty())
    {
        cout << "图像加载失败";
        return -1;
    }
    else
        cout << "图像加载成功..." << endl << endl;

    //检测特征点
    int minHessian = 700;
    SurfFeatureDetector detector(minHessian);

    vectorkeypoints1, keypoints2;
    detector.detect(srcImage1, keypoints1);
    detector.detect(srcImage2, keypoints2);

    //计算特征点描述子
    SurfDescriptorExtractor extractor;
    Mat descriptor1, descriptor2;

    extractor.compute(srcImage1, keypoints1, descriptor1);
    extractor.compute(srcImage2, keypoints2, descriptor2);

    //使用FLANN进行匹配
    FlannBasedMatcher matcher;
    vectormatches;
    matcher.match(descriptor1, descriptor2, matches);

    double max_dist = 0;
    double min_dist = 100;
    for (int i = 0; i < descriptor1.rows; i++)
    {
        double dist = matches[i].distance;
        if (dist < min_dist)min_dist = dist;
        if (dist > max_dist)max_dist = dist;
    }

    cout << "Max dist: " << max_dist << endl;
    cout << "Min dist: " << min_dist << endl << endl;

    //绘制好的匹配点即匹配点距离小于2*min_dst
    vectorgood_matches;
    for (int i = 0; i < descriptor1.rows; i++)
    {
        if (matches[i].distance<=max(2*min_dist,0.02))
        {
            good_matches.push_back(matches[i]);
        }
    }

    Mat matchImage;
    drawMatches(srcImage1, keypoints1, srcImage2, keypoints2, good_matches, matchImage, Scalar::all(-1), Scalar(-1), vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);

    namedWindow("匹配图", WINDOW_AUTOSIZE);
    imshow("匹配图", matchImage);

    //输出好的特征点匹配对
    for (int i = 0; i < (int)good_matches.size(); i++)
    {
        //queryIdx为query描述子的索引,match函数中前面数集的索引
        //trainIdx为train描述子的索引,match函数中后面数集的索引
        printf("--Good Match [%d] Keypoint 1: %d -- Keypoint 2: %d \n", i, good_matches[i].queryIdx, good_matches[i].trainIdx);
    }

    waitKey(0);
    return 0;
}

运行结果如下所示:

你可能感兴趣的:(OpenCV进阶提高)