##基于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();
}
~~~
效果一,由于原圖中飛機的邊緣有鋸齒狀,因此只需觀察拐角處,匹配效果良好:

效果二,不涉及圖像的旋轉和變形,只是將一幅圖像進行縮放后進行匹配,得出的效果自然是很好:

效果三,用兩個不同的角度拍攝的圖像進行匹配,其中部分特征點匹配有偏差,總體效果良好,在調試過程中還可以通過參數調整獲取更好的匹配效果。

**附注**:另一種匹配方法是使用 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)
- 前言
- Win8.1下OpenCV2.4.9+Qt5.3.2開發環境搭建
- OpenCV2學習筆記(一)
- OpenCV2學習筆記(二)
- OpenCV2學習筆記(三)
- OpenCV2學習筆記(四)
- OpenCV2學習筆記(五)
- OpenCV2學習筆記(六)
- OpenCV2學習筆記(七)
- OpenCV2學習筆記(八)
- OpenCV2學習筆記(九)
- OpenCV2學習筆記(十)
- OpenCV2學習筆記(十一)
- OpenCV2學習筆記(十二)
- OpenCV2學習筆記(十三)
- OpenCV2學習筆記(十四)
- OpenCV2學習筆記(十五)
- OpenCV2學習筆記(十六)
- OpenCV2學習筆記(十七)
- OpenCV2學習筆記(十八)
- OpenCV2學習筆記(十九)
- OpenCV2學習筆記(二十)
- OpenCV2學習筆記(二十一)
- OpenCV2學習筆記(二十二)