Opencv中Surf算子提取特征,生成特征描述子,匹配特征的流程跟Sift是完全一致的,这里主要介绍一下整个过程中需要使用到的主要的几个Opencv方法。
1. 特征提取
特征提取使用SurfFeatureDetector类中的detect方法,先定义一个SurfFeatureDetector类的对象,通过对象调用detect方法就可以提取输入图像的Surf特征。可以使用不带参数的默认构造函数构建SurfFeatureDetector对象,也可以使用含参数的构造函数:
CV_WRAP SURF(double hessianThreshold,
int nOctaves=4, int nOctaveLayers=2,
bool extended=true, bool upright=false);
从参数分布上可以看出,我们可以自定义Hessian阈值和尺度空间的组和层的个数。
特别要关注一下第一个参数hessianThreshold是图像Hessian矩阵判别式的阈值,值越大检测出的特征点就越少,也就意味着特征点越稳定。
2. 特征点绘制
特征点绘制是为了把检测出来的Surf特征点在原图上绘制出来,这一步是为了把特征点直观的显示出来给我们看,跟整个Surf算子的特征提取和匹配流程没关系。
绘制使用drawKeypoints方法:
void drawKeypoints( const Mat& image, const vector<KeyPoint>& keypoints, CV_OUT Mat& outImage,
const Scalar& color=Scalar::all(-1), int flags=DrawMatchesFlags::DEFAULT );
第一个参数image:原始图像,可以使三通道或单通道图像;
第二个参数keypoints:特征点向量,向量内每一个元素是一个KeyPoint对象,包含了特征点的各种属性信息;
第三个参数outImage:特征点绘制的画布图像,可以是原图像;
第四个参数color:绘制的特征点的颜色信息,默认绘制的是随机彩色;
第五个参数flags:特征点的绘制模式,其实就是设置特征点的那些信息需要绘制,那些不需要绘制,有以下几种模式可选:
DEFAULT:只绘制特征点的坐标点,显示在图像上就是一个个小圆点,每个小圆点的圆心坐标都是特征点的坐标。
DRAW_OVER_OUTIMG:函数不创建输出的图像,而是直接在输出图像变量空间绘制,要求本身输出图像变量就 是一个初始化好了的,size与type都是已经初始化好的变量
NOT_DRAW_SINGLE_POINTS:单点的特征点不被绘制
DRAW_RICH_KEYPOINTS:绘制特征点的时候绘制的是一个个带有方向的圆,这种方法同时显示图像的坐 标,size,和方向,是最能显示特征信息的一种绘制方式。
3. 特征点描述
特征点描述使用SurfDescriptorExtractor类中的compute方法。
4. 特征点匹配
特征点匹配可以使用BruteForceMatcher类或FlannBasedMatcher类的match方法。
5. 最优匹配点筛选
通过以上步骤获取的匹配特征点可能有很多,会有很多误匹配,实际使用中,往往需要有限个数的最优匹配点即可,用于定位、融合、拼接或3D重构等。
最优匹配点的筛选可以通过按一定比例提取出排名靠前的匹配点来实现。
6. 绘制匹配点
绘制匹配点使用drawMatches方法实现。
drawMatches( const Mat& img1, const vector<KeyPoint>& keypoints1,
const Mat& img2, const vector<KeyPoint>& keypoints2,
const vector<DMatch>& matches1to2, Mat& outImg,
const Scalar& matchColor=Scalar::all(-1), const Scalar& singlePointColor=Scalar::all(-1),
const vector<char>& matchesMask=vector<char>(), int flags=DrawMatchesFlags::DEFAULT );
参数很多,重点说一下最后一个参数flags,它跟drawKeypoints方法中flags的含义是一样的。
当仅使用筛选出的最优匹配点进行匹配的时候,意味着会有很多非最优的特征点不会被匹配,这时候可以设置flags=DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS ,意味着那些不是最优特征点的点不会被绘制出来,图上不会再有那么多花花绿绿的小圆圈了,这个简单的设置可以拯救很多重度密集恐怖症患者的生命。
以下是整个流程的Opencv实现:
#include "highgui/highgui.hpp"
#include "opencv2/nonfree/nonfree.hpp"
#include "opencv2/legacy/legacy.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc,char *argv[])
{
Mat image01=imread(argv[1]);
Mat image02=imread(argv[2]);
Mat image1,image2;
image1=image01.clone();
image2=image02.clone();
//提取特征点
SurfFeatureDetector surfDetector(4000); //hessianThreshold,海塞矩阵阈值,并不是限定特征点的个数
vector<KeyPoint> keyPoint1,keyPoint2;
surfDetector.detect(image1,keyPoint1);
surfDetector.detect(image2,keyPoint2);
//绘制特征点
drawKeypoints(image1,keyPoint1,image1,Scalar::all(-1),DrawMatchesFlags::DEFAULT);
drawKeypoints(image2,keyPoint2,image2,Scalar::all(-1),DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
imshow("KeyPoints of image1",image1);
imshow("KeyPoints of image2",image2);
//特征点描述,为下边的特征点匹配做准备
SurfDescriptorExtractor SurfDescriptor;
Mat imageDesc1,imageDesc2;
SurfDescriptor.compute(image1,keyPoint1,imageDesc1);
SurfDescriptor.compute(image2,keyPoint2,imageDesc2);
//特征点匹配并显示匹配结果
//BruteForceMatcher<L2<float>> matcher;
FlannBasedMatcher matcher;
vector<DMatch> matchePoints;
matcher.match(imageDesc1,imageDesc2,matchePoints,Mat());
//提取强特征点
double minMatch=1;
double maxMatch=0;
for(int i=0;i<matchePoints.size();i++)
{
//匹配值最大最小值获取
minMatch=minMatch>matchePoints[i].distance?matchePoints[i].distance:minMatch;
maxMatch=maxMatch<matchePoints[i].distance?matchePoints[i].distance:maxMatch;
}
//最大最小值输出
cout<<"最佳匹配值是: "<<minMatch<<endl;
cout<<"最差匹配值是: "<<maxMatch<<endl;
//获取排在前边的几个最优匹配结果
vector<DMatch> goodMatchePoints;
for(int i=0;i<matchePoints.size();i++)
{
if(matchePoints[i].distance<minMatch+(maxMatch-minMatch)/2)
{
goodMatchePoints.push_back(matchePoints[i]);
}
}
//绘制最优匹配点
Mat imageOutput;
drawMatches(image01,keyPoint1,image02,keyPoint2,goodMatchePoints,imageOutput,Scalar::all(-1),
Scalar::all(-1),vector<char>(),DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
imshow("Mathch Points",imageOutput);
waitKey();
return 0;
}
输入图像的特征点在原图对应位置上做了标注,这里使用了默认的绘制方式,特征点只标注了位置,没有方向和尺度信息:
目标图像特征点的绘制使用了DrawMatchesFlags::DRAW_RICH_KEYPOINTS绘制方式,特征点包含了位置、尺度、方向信息:
特征点的匹配是经过筛选的最优匹配点,匹配对数较少,并且不显示匹配失败的特征点:
不筛选最优匹配点的话,会有比较多的特征点配对成功,当然误匹配会更多: