1. 程式人生 > >Opencv Surf運算元特徵提取與最優匹配

Opencv Surf運算元特徵提取與最優匹配

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繪製方式,特徵點包含了位置、尺度、方向資訊:


特徵點的匹配是經過篩選的最優匹配點,匹配對數較少,並且不顯示匹配失敗的特徵點:


不篩選最優匹配點的話,會有比較多的特徵點配對成功,當然誤匹配會更多: