ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
##基于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](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(); } ~~~ 效果一,由于原图中飞机的边缘有锯齿状,因此只需观察拐角处,匹配效果良好: ![](https://box.kancloud.cn/2015-12-30_5683a75f83db5.jpg) 效果二,不涉及图像的旋转和变形,只是将一幅图像进行缩放后进行匹配,得出的效果自然是很好: ![](https://box.kancloud.cn/2015-12-30_5683a75fa2360.jpg) 效果三,用两个不同的角度拍摄的图像进行匹配,其中部分特征点匹配有偏差,总体效果良好,在调试过程中还可以通过参数调整获取更好的匹配效果。 ![](https://box.kancloud.cn/2015-12-30_5683a7601273b.jpg) **附注**:另一种匹配方法是使用 cv::FlannBasedMatcher 接口以及函数 FLANN 实现快速高效匹配(快速最近邻逼近搜索函数库(Fast Approximate Nearest Neighbor Search Library))。网上有源代码例程如下: ~~~ #include <stdio.h> #include <iostream> #include "opencv2/core/core.hpp" #include <opencv2/highgui/highgui.hpp> #include <opencv2/legacy/legacy.hpp> #include "opencv2/features2d/features2d.hpp" #include "opencv2/highgui/highgui.hpp" using namespace cv; void readme(); /** @function main */ int main( int argc, char** argv ) { if( argc != 3 ) { readme(); return -1; } Mat img_1 = imread( argv[1], CV_LOAD_IMAGE_GRAYSCALE ); Mat img_2 = imread( argv[2], CV_LOAD_IMAGE_GRAYSCALE ); if( !img_1.data || !img_2.data ) { std::cout<< " --(!) Error reading images " << std::endl; return -1; } //-- Step 1: Detect the keypoints using SURF Detector int minHessian = 400; SurfFeatureDetector detector( minHessian ); std::vector<KeyPoint> keypoints_1, keypoints_2; detector.detect( img_1, keypoints_1 ); detector.detect( img_2, keypoints_2 ); //-- Step 2: Calculate descriptors (feature vectors) SurfDescriptorExtractor extractor; Mat descriptors_1, descriptors_2; extractor.compute( img_1, keypoints_1, descriptors_1 ); extractor.compute( img_2, keypoints_2, descriptors_2 ); //-- Step 3: Matching descriptor vectors using FLANN matcher FlannBasedMatcher matcher; std::vector< DMatch > matches; matcher.match( descriptors_1, descriptors_2, matches ); double max_dist = 0; double min_dist = 100; //-- Quick calculation of max and min distances between keypoints for( int i = 0; i < descriptors_1.rows; i++ ) { double dist = matches[i].distance; if( dist < min_dist ) min_dist = dist; if( dist > max_dist ) max_dist = dist; } printf("-- Max dist : %f \n", max_dist ); printf("-- Min dist : %f \n", min_dist ); //-- Draw only "good" matches (i.e. whose distance is less than 2*min_dist ) //-- PS.- radiusMatch can also be used here. std::vector< DMatch > good_matches; for( int i = 0; i < descriptors_1.rows; i++ ) { if( matches[i].distance < 2*min_dist ) { good_matches.push_back( matches[i]); } } //-- Draw only "good" matches Mat img_matches; drawMatches( img_1, keypoints_1, img_2, keypoints_2, good_matches, img_matches, Scalar::all(-1), Scalar::all(-1), vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS ); //-- Show detected matches imshow( "Good Matches", img_matches ); for( int i = 0; i < good_matches.size(); i++ ) { printf( "-- Good Match [%d] Keypoint 1: %d -- Keypoint 2: %d \n", i, good_matches[i].queryIdx, good_matches[i].trainIdx ); } waitKey(0); return 0; } /** @function readme */ void readme() { std::cout << " Usage: ./SURF_FlannMatcher <img1> <img2>" << std::endl; } ~~~ 以上只是记录这种方法的实现例程,并没有验证代码的正确性。 参考资料: [http://blog.sina.com.cn/s/blog_a98e39a201017pgn.html](http://blog.sina.com.cn/s/blog_a98e39a201017pgn.html) [http://www.cnblogs.com/tornadomeet/archive/2012/08/17/2644903.html](http://www.cnblogs.com/tornadomeet/archive/2012/08/17/2644903.html) (SURF算法的理论介绍) [http://blog.csdn.net/liyuefeilong/article/details/44166069](http://blog.csdn.net/liyuefeilong/article/details/44166069) [http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/features2d/feature_flann_matcher/feature_flann_matcher.html](http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/features2d/feature_flann_matcher/feature_flann_matcher.html)