在基于特征匹配的方法中,Surf算法对物体的旋转、光照等情况有较好的鲁棒性,且教SIFT算法而言计算速度更快。通过Surf算法检测到的特征点其描述符包含了这个点的位置和尺度信息,故对两幅图片进行匹配时可以通过两幅图中特征点匹配对进行匹配。即使物体位置和光照的改变也能够有良好的匹配效果。
在本片文章中将使用SurfDescriptorExtractor及其函数compute来完成特定计算,使用BruteForceMatcher匹配得到的特征向量,使用函数drawMatches来绘制检测到的匹配点。
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公有继承而来。
暴力搜索特征点匹配。对于第一集合中的特征描述子,这个匹配寻找在第二个集合中最相近的特征描述子,这种特征描述子匹配支持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公有继承,同时又有本身的虚成员函数。
给定两幅图像,绘制寻找到的特征关键点及其匹配。有两种定义形式如下:
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 //对每个特征点,绘制带大小和方向的关键点图像
}
#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检测器
vector keypoint1, 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);
vector matches;
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;
}
运行结果
最近邻搜索的问题在图像识别、数据压缩、模式识别和分类、机器学习、文档检索系统、统计和数据分析等方面是一个重大问题,在高维空间中解决这个问题似乎是一个非常难以执行的任务,没有算法明显优于标准的蛮力搜索,因此越来越多的人把兴趣点转向执行最近邻搜索的一类算法。
FLANN(Fast Library for Approximate Nearest Neighbors)时目前最完整的近似近邻开源库,不但实现了一系列查找算法还包含了一种自动选取最快算法的机制。
opencv中提供了函数FlannBasedMatcher()函数,其继承关系如下图所示:
#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);
vector keypoints1, 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;
vector matches;
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
vector good_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;
}
运行结果如下所示: