1. 程式人生 > >opencv學習筆記二十九:SIFT特徵點檢測與匹配

opencv學習筆記二十九:SIFT特徵點檢測與匹配

SIFT(Scale-invariant feature transform)是一種檢測區域性特徵的演算法,該演算法通過求一幅圖中的特徵點(interest points,or corner points)及其有關scale 和 orientation 的描述子得到特徵並進行影象特徵點匹配,獲得了良好效果,詳細解析如下:

演算法描述

SIFT特徵不只具有尺度不變性,即使改變旋轉角度,影象亮度或拍攝視角,仍然能夠得到好的檢測效果。整個演算法分為以下幾個部分:

1. 構建尺度空間

這是一個初始化操作,尺度空間理論目的是模擬影象資料的多尺度特徵。

高斯卷積核是實現尺度變換的唯一線性核,於是一副二維影象的尺度空間定義為:

其中 G(x,y,σ) 是尺度可變高斯函式 

(x,y)是空間座標,是尺度座標。σ大小決定影象的平滑程度,大尺度對應影象的概貌特徵,小尺度對應影象的細節特徵。大的σ值對應粗糙尺度(低解析度),反之,對應精細尺度(高解析度)。為了有效的在尺度空間檢測到穩定的關鍵點,提出了高斯差分尺度空間(DOG scale-space)。利用不同尺度的高斯差分核與影象卷積生成。

下圖所示不同σ下影象尺度空間:

關於尺度空間的理解說明:2kσ中的2是必須的,尺度空間是連續的。在  Lowe的論文中 ,將第0層的初始尺度定為1.6(最模糊),圖片的初始尺度定為0.5(最清晰). 在檢測極值點前對原始影象的高斯平滑以致影象丟失高頻資訊,所以 Lowe 建議在建立尺度空間前首先對原始影象長寬擴充套件一倍,以保留原始影象資訊,增加特徵點數量。尺度越大影象越模糊。 

影象金字塔的建立:對於一幅影象I,建立其在不同尺度(scale)的影象,也成為子八度(octave),這是為了scale-invariant,也就是在任何尺度都能夠有對應的特徵點,第一個子八度的scale為原圖大小,後面每個octave為上一個octave降取樣的結果,即原圖的1/4(長寬分別減半),構成下一個子八度(高一層金字塔)。

尺度空間的所有取值,i為octave的塔數(第幾個塔),s為每塔層數

由圖片size決定建幾個塔,每塔幾層影象(S一般為3-5層)。0塔的第0層是原始影象(或你double後的影象),往上每一層是對其下一層進行Laplacian變換(高斯卷積,其中σ值漸大,例如可以是σ, k*σ, k*k*σ…),直觀上看來越往上圖片越模糊。塔間的圖片是降取樣關係,例如1塔的第0層可以由0塔的第3層down sample得到,然後進行與0塔類似的高斯卷積操作。

2. LoG近似DoG找到關鍵點<檢測DOG尺度空間極值點>

為了尋找尺度空間的極值點,每一個取樣點要和它所有的相鄰點比較,看其是否比它的影象域和尺度域的相鄰點大或者小。如圖所示,中間的檢測點和它同尺度的8個相鄰點和上下相鄰尺度對應的9×2個點共26個點比較,以確保在尺度空間和二維影象空間都檢測到極值點。 一個點如果在DOG尺度空間本層以及上下兩層的26個領域中是最大或最小值時,就認為該點是影象在該尺度下的一個特徵點,如圖所示。

同一組中的相鄰尺度(由於k的取值關係,肯定是上下層)之間進行尋找

s=3的情況

 在極值比較的過程中,每一組影象的首末兩層是無法進行極值比較的,為了滿足尺度變化的連續性(下面有詳解)

我們在每一組影象的頂層繼續用高斯模糊生成了 3 幅影象,高斯金字塔有每組S+3層影象。DOG金字塔每組有S+2層影象.

==========================================

這裡有的童鞋不理解什麼叫“為了滿足尺度變化的連續性”,現在做仔細闡述:

假設s=3,也就是每個塔裡有3層,則k=21/s=21/3,那麼按照上圖可得Gauss Space和DoG space 分別有3個(s個)和2個(s-1個)分量,在DoG space中,1st-octave兩項分別是σ,kσ; 2nd-octave兩項分別是2σ,2kσ;由於無法比較極值,我們必須在高斯空間繼續新增高斯模糊項,使得形成σ,kσ,k2σ,k3σ,k4σ這樣就可以選擇DoG space中的中間三項kσ,k2σ,k3σ(只有左右都有才能有極值),那麼下一octave中(由上一層降取樣獲得)所得三項即為2kσ,2k2σ,2k3σ,其首項2kσ=24/3。剛好與上一octave末項k3σ=23/3尺度變化連續起來,所以每次要在Gaussian space新增3項,每組(塔)共S+3層影象,相應的DoG金字塔有S+2層影象。

==========================================

使用Laplacian of Gaussian能夠很好地找到找到影象中的興趣點,但是需要大量的計算量,所以使用Difference of Gaussian影象的極大極小值近似尋找特徵點.DOG運算元計算簡單,是尺度歸一化的LoG運算元的近似,有關DOG尋找特徵點的介紹及方法詳見http://blog.csdn.net/abcjennifer/article/details/7639488,極值點檢測用的Non-Maximal Suppression。

3. 除去不好的特徵點

這一步本質上要去掉DoG局部曲率非常不對稱的畫素。

通過擬和三維二次函式以精確確定關鍵點的位置和尺度(達到亞畫素精度),同時去除低對比度的關鍵點和不穩定的邊緣響應點(因為DoG運算元會產生較強的邊緣響應),以增強匹配穩定性、提高抗噪聲能力,在這裡使用近似Harris Corner檢測器。

①空間尺度函式泰勒展開式如下:,對上式求導,並令其為0,得到精確的位置, 得

②在已經檢測到的特徵點中,要去掉低對比度的特徵點和不穩定的邊緣響應點。去除低對比度的點:把公式(2)代入公式(1),即在DoG Space的極值點處D(x)取值,只取前兩項可得:

若   ,該特徵點就保留下來,否則丟棄。

③邊緣響應的去除 一個定義不好的高斯差分運算元的極值在橫跨邊緣的地方有較大的主曲率,而在垂直邊緣的方向有較小的主曲率。主曲率通過一個2×2 的Hessian矩陣H求出:

導數由取樣點相鄰差估計得到。

D的主曲率和H的特徵值成正比,令α為較大特徵值,β為較小的特徵值,則

令α=γβ,則

 (r + 1)2/r的值在兩個特徵值相等的時候最小,隨著r的增大而增大,因此,為了檢測主曲率是否在某域值r下,只需檢測

if (α+β)/ αβ> (r+1)2/r, throw it out.   在Lowe的文章中,取r=10。

4. 給特徵點賦值一個128維方向引數

上一步中確定了每幅圖中的特徵點,為每個特徵點計算一個方向,依照這個方向做進一步的計算, 利用關鍵點鄰域畫素的梯度方向分佈特性為每個關鍵點指定方向引數,使運算元具備旋轉不變性。

為(x,y)處梯度的模值和方向公式。其中L所用的尺度為每個關鍵點各自所在的尺度。至此,影象的關鍵點已經檢測完畢,每個關鍵點有三個資訊:位置,所處尺度、方向,由此可以確定一個SIFT特徵區域。

梯度直方圖的範圍是0~360度,其中每10度一個柱,總共36個柱。隨著距

      中心點越遠的領域其對直方圖的貢獻也響應減小.Lowe論文中還提到要使用高斯函式對直方圖進行平滑,減少突變的影響。

在實際計算時,我們在以關鍵點為中心的鄰域視窗內取樣,並用直方圖統計鄰域畫素的梯度方向。梯度直方圖的範圍是0~360度,其中每45度一個柱,總共8個柱, 或者每10度一個柱,總共36個柱。Lowe論文中還提到要使用高斯函式對直方圖進行平滑,減少突變的影響。直方圖的峰值則代表了該關鍵點處鄰域梯度的主方向,即作為該關鍵點的方向。

直方圖中的峰值就是主方向,其他的達到最大值80%的方向可作為輔助方向

由梯度方向直方圖確定主梯度方向

該步中將建立所有scale中特徵點的描述子(128維)

Identify peak and assign orientation and sum of magnitude to key point.

  The user may choose a threshold to exclude key points based on their assigned sum of magnitudes.

關鍵點描述子的生成步驟

 通過對關鍵點周圍影象區域分塊,計算塊內梯度直方圖,生成具有獨特性的向量,這個向量是該區域影象資訊的一種抽象,具有唯一性。

5. 關鍵點描述子的生成

首先將座標軸旋轉為關鍵點的方向,以確保旋轉不變性。以關鍵點為中心取8×8的視窗。

Figure.16*16的圖中其中1/4的特徵點梯度方向及scale,右圖為其加權到8個主方向後的效果。

圖左部分的中央為當前關鍵點的位置,每個小格代表關鍵點鄰域所在尺度空間的一個畫素,利用公式求得每個畫素的梯度幅值與梯度方向,箭頭方向代表該畫素的梯度方向,箭頭長度代表梯度模值,然後用高斯視窗對其進行加權運算。

圖中藍色的圈代表高斯加權的範圍(越靠近關鍵點的畫素梯度方向資訊貢獻越大)。然後在每4×4的小塊上計算8個方向的梯度方向直方圖,繪製每個梯度方向的累加值,即可形成一個種子點,如圖右部分示。此圖中一個關鍵點由2×2共4個種子點組成,每個種子點有8個方向向量資訊。這種鄰域方向性資訊聯合的思想增強了演算法抗噪聲的能力,同時對於含有定位誤差的特徵匹配也提供了較好的容錯性。

計算keypoint周圍的16*16的window中每一個畫素的梯度,而且使用高斯下降函式降低遠離中心的權重。

在每個4*4的1/16象限中,通過加權梯度值加到直方圖8個方向區間中的一個,計算出一個梯度方向直方圖。

這樣就可以對每個feature形成一個4*4*8=128維的描述子,每一維都可以表示4*4個格子中一個的scale/orientation. 將這個向量歸一化之後,就進一步去除了光照的影響。

5. 根據SIFT進行Match

生成了A、B兩幅圖的描述子,(分別是k1*128維和k2*128維),就將兩圖中各個scale(所有scale)的描述子進行匹配,匹配上128維即可表示兩個特徵點match上了。

實際計算過程中,為了增強匹配的穩健性,Lowe建議對每個關鍵點使用4×4共16個種子點來描述,這樣對於一個關鍵點就可以產生128個數據,即最終形成128維的SIFT特徵向量。此時SIFT特徵向量已經去除了尺度變化、旋轉等幾何變形因素的影響,再繼續將特徵向量的長度歸一化,則可以進一步去除光照變化的影響。 當兩幅影象的SIFT特徵向量生成後,下一步我們採用關鍵點特徵向量的歐式距離來作為兩幅影象中關鍵點的相似性判定度量。取影象1中的某個關鍵點,並找出其與影象2中歐式距離最近的前兩個關鍵點,在這兩個關鍵點中,如果最近的距離除以次近的距離少於某個比例閾值,則接受這一對匹配點。降低這個比例閾值,SIFT匹配點數目會減少,但更加穩定。為了排除因為影象遮擋和背景混亂而產生的無匹配關係的關鍵點,Lowe提出了比較最近鄰距離與次近鄰距離的方法,距離比率ratio小於某個閾值的認為是正確匹配。因為對於錯誤匹配,由於特徵空間的高維性,相似的距離可能有大量其他的錯誤匹配,從而它的ratio值比較高。Lowe推薦ratio的閾值為0.8。但作者對大量任意存在尺度、旋轉和亮度變化的兩幅圖片進行匹配,結果表明ratio取值在0. 4~0. 6之間最佳,小於0. 4的很少有匹配點,大於0. 6的則存在大量錯誤匹配點。(如果這個地方你要改進,最好給出一個匹配率和ration之間的關係圖,這樣才有說服力)作者建議ratio的取值原則如下:

ratio=0. 4 對於準確度要求高的匹配; ratio=0. 6 對於匹配點數目要求比較多的匹配;  ratio=0. 5 一般情況下。 也可按如下原則:當最近鄰距離<200時ratio=0. 6,反之ratio=0. 4。ratio的取值策略能排分錯誤匹配點。

當兩幅影象的SIFT特徵向量生成後,下一步我們採用關鍵點特徵向量的歐式距離來作為兩幅影象中關鍵點的相似性判定度量。取影象1中的某個關鍵點,並找出其與影象2中歐式距離最近的前兩個關鍵點,在這兩個關鍵點中,如果最近的距離除以次近的距離少於某個比例閾值,則接受這一對匹配點。降低這個比例閾值,SIFT匹配點數目會減少,但更加穩定。

opencv實現步驟: 

首先包含標頭檔案和新增名稱空間:

#include<opencv2/opencv.hpp> #include<opencv2/xfeatures2d.hpp>

using namespace cv; using namespace cv::xfeatures2d; using namespace std;

【1】例項化一個SIFT特徵檢測類物件的結構指標

Ptr<SIFT> sift = SIFT::create( int nfeatures =100, int nOctaveLayers = 3,double contrastThreshold = 0.04, double edgeThreshold = 10,double sigma = 1.6);                                                                   引數解釋:  需要找到特徵點的個數;在不同尺度空間使用多少張影象,用不同的高斯核模糊;對比度閾值;邊緣閾值;高斯核模糊的\sigma。                

這裡的create指的是xfeatures2d::SIFT空間下即該特定庫中的函式,在其它庫中也有create函式,其定義就不一樣,所以針對具體的函式在不同庫中有重名的需要指定具體的名稱空間,像上篇部落格中xfeatures2d::SURF也有create函式,用法完全不一樣。

【2】檢測SURF特徵關鍵點

vector<KeyPoint> keypoints1,keypoints2; surf->detect(src1, keypoints1); surf->detect(src2, keypoints2);

點選SURF按F12檢視幫助,可看到class CV_EXPORTS_W SURF : public Feature2D,說明SURF是Feature2D的派生類,所以可呼叫其父類Feature2D中的函式,點選Feature2D,按F12檢視幫助,可看到Feature2D類中有detect和compute函式,一個檢測關鍵點,一個計算特徵向量的。

  1. CV_WRAP virtual void detect( InputArray image,

  2. CV_OUT std::vector<KeyPoint>& keypoints,

  3. CV_WRAP virtual void compute( InputArray image,

  4. CV_OUT CV_IN_OUT std::vector<KeyPoint>& keypoints,

  5. OutputArray descriptors );

【3】繪製特徵關鍵點

    Mat result1, result2;     drawKeypoints(src1, keypoints1, result1, Scalar::all(-1), DrawMatchesFlags::DEFAULT);     drawKeypoints(src2, keypoints2, result2, Scalar::all(-1), DrawMatchesFlags::DEFAULT);     imshow("keypoints1", result1);     imshow("keypoints2", result2);

【4】計算特徵向量     Mat descriptors1, descriptors2;     surf->compute(src1, keypoints1, descriptors1);     surf->compute(src2, keypoints2, descriptors2);

【5】使用BruteForce進行匹配(暴力演算法:用歐氏距離依次找出兩幅影象中相似度最高的兩個特徵向量,並將他們連線)     Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create("BruteForce");     vector< DMatch > matches;     matcher->match(descriptors1, descriptors2, matches);

【6】繪製匹配出的關鍵點     Mat imgMatches;     drawMatches(src1, keypoints1, src2, keypoints2, matches, imgMatches);     imshow("output", imgMatches);

#include<opencv2\opencv.hpp>
#include<opencv2\xfeatures2D.hpp>

using namespace cv;
using namespace cv::xfeatures2d;
using namespace std;

int nfeatures = 100;
Mat src1,src2;
void callback(int, void*);
int main(int arc, char** argv) { 
	src1 = imread("1.jpg");
	src2 = imread("2.jpg");
	namedWindow("output",0);
	//resizeWindow("output", 800, 400);//重置視窗大小
	createTrackbar("nfeatures", "output", &nfeatures,500, callback);;
	callback(0, 0);
	waitKey(0);
	return 0;
} 
void callback(int, void*) {
	//例項化一個SIFT檢測類物件的結構指標
	Ptr<SIFT>sift = SIFT::create(nfeatures);
	vector<KeyPoint>keypoints1, keypoints2;
	//檢測關鍵點
	sift->detect(src1, keypoints1);
	sift->detect(src2, keypoints2);

	printf("total keypoints1:%d\n", (int)keypoints1.size());
	printf("total keypoints2:%d\n", (int)keypoints2.size());

	//繪製關鍵點
	Mat keypoints1_img, keypoints2_img;
	drawKeypoints(src1, keypoints1, keypoints1_img);
	drawKeypoints(src2, keypoints2, keypoints2_img);
	imshow("keypoints1_img", keypoints1_img);
	imshow("keypoints2_img", keypoints2_img);

	//計算描述子即特徵向量
	Mat discriptions1, discriptions2;
	sift->compute(src1,keypoints1, discriptions1);
	sift->compute(src2,keypoints2, discriptions2);

	//例項化一個匹配的物件
	Ptr<DescriptorMatcher>matcher = DescriptorMatcher::create("BruteForce");
	vector<DMatch>matches;
	matcher->match(discriptions1, discriptions2, matches);

	//繪製匹配圖
	Mat match_img;
	drawMatches(src1, keypoints1, src2, keypoints2, matches,match_img);
	imshow("output", match_img);
}

匹配結果如下: