图像处理之SURF特征点检测2

187 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 6 天

1 SURF类相关OpenCV源码剖析

OpenCV中关于SURF算法的部分,常常涉及到的是SURF、SurfFeatureDetector、 SurfDescriptorExtractor 这三个类,这一小节我们就来看看它们究竟是什么来头。

在·······\opencv\sources\modules\nonfree\include\opencv2\nonfree路径下的 features2d.hpp头文件中,可以发现这样两句定义:

typedef SURF SurfFeatureDetector; typedef SURF SurfDescriptorExtractor;

我们都知道,typedef声明是为现有类型创建一个新的名字,类型别名。这就表示,SURF类忽然同时有了两个新名字 SurfFeatureDetector 和 SurfDescriptorExtractor。

也就是说,我们平常使用的 SurfFeatureDetector 类和 SurfDescriptorExtractor 类,其实就是SURF类,它们三者等价。

然后在这两句定义的上方,可以看到SURF类的类声明全貌,在这里由于篇幅原因,只贴出其轮廓。

class CV_EXPORTS_W SURF : public Feature2D

{

// ......

};

可以观察到,SURF类公共继承自Feature2D类,我们再次进行转到定义,可以在路径···\opencv\build\include \opencv2\features2d\features2d.hpp 看到 Feature2D 类的声明。

class CV_EXPORTS_W Feature2D : public FeatureDetector, public DescriptorExtractor

{

//......

};

显然,Feature2D类又是公共继承自FeatureDetector以及 DescriptorExtractor类。 继续刨根问底,看看其父类FeatureDetector 以及 DescriptorExtractor类的定义。

首先是FeatureDetector类。

class CV_EXPORTS_W FeatureDetector:public virtual Algorithm

{

// ......

};

这里,我们看到了以后经常会用到的 detect()方法重载的两个原型,原来是SURF类经过两层的继承,从FeatureDetector类继承而来的。

CV_WRAP void detect(const Mat& image, CV_OUT vector& keypoints,const Mat& mask=Mat())const;

void detect(const vector& images, vector<vector>& keypoints, const vector& masks=vector())const;

同样,看看SURF类的另一个“爷爷”DescriptorExtractor类的声明。

class CV_EXPORTS_W DescriptorExtractor : public virtual Algorithm

{

//......

};

上述代码表明 FeatureDetector 类和 DescriptorExtractor 类都虚继承自 Algorithm基类。

终于,我们找到SURF类“德高望重”的祖先-OpenCV中的Algorithm基类。其原型声明如下。

class CV_EXPORTS_W Algorithm

{

// ......

};

2 绘制关键点:drawKeypoints()函数

因为接下来的示例程序需要用到 drawKeypoints 函数,在这里顺便讲一讲。顾名思义,此函数用于绘制关键点。

C++:

void drawKeypoints(const Mat&image, const vector& keypoints,Mat& outImage,constscalar& color=Scalar::all(-1), int flags=DrawMatchesFlags::DEFAULT)

  • 第一个参数,const Mat&类型的src,输入图像。
  • 第二个参数,const vector<KeyPoint>&类型的keypoints,根据源图像得到的 特征点。它是一个输出参数。
  • 第三个参数,Mat&类型的outImage,输出图像,其内容取决于第五个参数标识符falgs。
  • 第四个参数,const Scalar&类型的 color,关键点的颜色,有默认值Scalar::all(-1)。
  • 第五个参数,int 类型的 flags,绘制关键点的特征标识符,有默认值DrawMatchesFlags::DEFAULT。可以在下面这个结构体中选取值。
struct DrawMatchesFlags
{
    enum     
    {
        DEFAULT0,//创建输出图像矩阵(使用Mat::create)。使用现存的输出图像绘制匹配对和特征点。且对每一个关键点,只绘制中间点
        DRAW_OVER_OUTIMG=1,//不创建输出图像矩阵,而是在输出图像上绘制匹配对
        NOT_DRAW_SINGLE_POINTS=2,//单点特征点不被绘制
        DRAW_RICH_KEYPOINTS=4//对每一个关键点,绘制带大小和方向的关键点圆圈
    };
};

3 KeyPoint类

KeyPoint类是一个为特征点检测而生的数据结构,用于表示特征点。

class KeyPoint
{
    Point2f pt;//坐标
    float size;//特征点邻域直径 float
    angle;//特征点的方向,值为[零,三百六十),负值表示不使用float
    response;
    int octave;//特征点所在的图像金字塔的组int class_id;//用于聚类的id
}

4 示例程序:SURF特征点检测

这个示例程涉及到如下三个方面:

  • 使用FeatureDetector接口来发现感兴趣点。
  • 使用 SurfFeatureDetector以及其函数 detect 来实现检测过程
  • 使用函数drawKeypoints绘制检测到的关键点。

详细注释的源代码如下。


//---------------------------------【头文件、命名空间包含部分】----------------------------
//		描述:包含程序所使用的头文件和命名空间
//---------------------------------------------------------------------------------------
#include "opencv2/core/core.hpp"
#include "opencv2/features2d/features2d.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/nonfree/nonfree.hpp"
#include <iostream>
using namespace cv;


//-----------------------------------【main( )函数】--------------------------------------------
//   描述:控制台应用程序的入口函数,我们的程序从这里开始执行
//-----------------------------------------------------------------------------------------------
int main( int argc, char** argv )
{
	//【0】改变console字体颜色    
	system("color 2F");    

	//【1】载入源图片并显示
	Mat srcImage1 = imread("1.jpg", 1 );
	Mat srcImage2 = imread("2.jpg", 1 );
	if( !srcImage1.data || !srcImage2.data )//检测是否读取成功
	{ printf("读取图片错误,请确定目录下是否有imread函数指定名称的图片存在~! \n"); return false; } 
	imshow("原始图1",srcImage1);
	imshow("原始图2",srcImage2);

	//【2】定义需要用到的变量和类
	int minHessian = 400;//定义SURF中的hessian阈值特征点检测算子
	SurfFeatureDetector detector( minHessian );//定义一个SurfFeatureDetector(SURF) 特征检测类对象
	std::vector<KeyPoint> keypoints_1, keypoints_2;//vector模板类是能够存放任意类型的动态数组,能够增加和压缩数据

	//【3】调用detect函数检测出SURF特征关键点,保存在vector容器中
	detector.detect( srcImage1, keypoints_1 );
	detector.detect( srcImage2, keypoints_2 );

	//【4】绘制特征关键点.
	Mat img_keypoints_1; Mat img_keypoints_2;
	drawKeypoints( srcImage1, keypoints_1, img_keypoints_1, Scalar::all(-1), DrawMatchesFlags::DEFAULT );
	drawKeypoints( srcImage2, keypoints_2, img_keypoints_2, Scalar::all(-1), DrawMatchesFlags::DEFAULT );

	//【5】显示效果图
	imshow("特征点检测效果图1", img_keypoints_1 );
	imshow("特征点检测效果图2", img_keypoints_2 );

	waitKey(0);
	return 0;
}

原图

2.png

效果图

3.png

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 6 天