1. 程式人生 > >學習筆記之——vs2015+opencv2.4.13實現SIFT、SURF、ORB

學習筆記之——vs2015+opencv2.4.13實現SIFT、SURF、ORB

       此博文為本人寫的第一篇博文,寫博文的主要目的呢有兩個:第一就是對自己做過的工作進行總結;第二就是希望跟志同道合的人相互學習交流~

        本篇博文主要是我自學SIFT、SURF、ORB三種演算法(三種特徵描述子)過程的筆記以及執行的程式碼。博文主要是對於三種演算法的  歸納以及加入我自己的一些思考與理解。當然裡面還有一些出現的問題不知道怎麼解決的,也放到博文裡面(希望有大神可以在評論不吝賜教),後續也會繼續更新,請廣大讀者批評指正謝謝。

        在opencv3中,這三個運算元都轉移到一個名為xfeature2d的第三方庫中,而在opencv2中這三個運算元在nonfree庫中,為此我還特意把opencv3.2改為opencv2.4.9和2.4.13。

       關於在vs下配置opencv可參考https://blog.csdn.net/poem_qianmo/article/details/19809337。個人感覺這個教程比較好,注意版本號不一樣修改對應的lib就好了。

一.SIFT

Scale Invariant Feature Transform(SIFT尺度不變特徵變換。SIFT特徵用於描述影象這種的區域性特徵。是一種關鍵點(或者叫做特徵點)的檢測和描述的演算法。SIFT演算法應用於影象特徵點的提取,首先建立影象的尺度空間表示,接著在尺度空間中搜索影象的極值點,通過這些極值點(也稱關鍵點,特徵點。包含三個主要資訊:位置、尺度、方向),從而建立特徵描述向量。通過特徵描述向量來做影象識別與檢測方面的問題。

SIFT由David Lowe提出,且已經申請了專利保護。

SIFT的演算法流程圖如下圖所示:


SIFT具有以下特點:

(1)SIFT特徵具有旋轉、尺度、平移、視角及亮度的不變性。

(2)SIFT特徵對引數調整魯棒性好,在進行特徵描述時,根據場景需要可調整適宜的特徵點數量,以便進行特徵分析。

區域性不變性包括尺度不變性與旋轉不變性。尺度不變性描述的是物體視覺上的遠近與目標的認知分析無關。而旋轉不變性描述的是物體發生旋轉操作與目標的認知分析無關,它強調目標特徵的多角度資訊特徵

尺度不變性

SIFT的尺度不變性是指:若不同的尺度下都有相同的關鍵點,那麼在不同的尺度的輸入影象下都可以檢測出關鍵點來進行匹配。

尺度不變性:人不管物體離得遠還是近,都能夠對其進行辨認。

將物體的不同尺度下的影象都提供給機器,讓機器能夠對物體在不同的尺度下有一個統一的認知。在建立統一認知的過程中,要考慮的就是在影象不同的尺度下都存在的特徵點。這一過程在實際中,通過影象金字塔,將不同解析度的資訊都儲存下來。

影象的尺度空間表達就是影象在所有尺度下的描述。

SIFT演算法的步驟

本人主要是參考以下四篇部落格來學習SIFT特徵,下面的步驟是結合自己的理解來寫的。

https://blog.csdn.net/abcjennifer/article/details/7639681#comments
https://blog.csdn.net/h2008066215019910120/article/details/17229439
https://blog.csdn.net/Kevin_cc98/article/details/78528619

https://blog.csdn.net/zddblog/article/details/7521424

1.構建尺度空間

(首先在影象的預處理中包括了對影象作灰度變換以及歸一化處理,歸一化處理是為了應對光照強度變化的魯棒性)。

尺度空間理論的基本思想是:在影象資訊處理模型中引入一個被視為尺度的引數,通過連續變化尺度引數獲得多尺度下的尺度空間表示序列,對這些序列進行尺度空間主輪廓的提取,並以該主輪廓作為一種特徵向量,實現邊緣、角點檢測和不同解析度上的特徵提取等。而在SIFT中尺度空間理論的目的是模擬影象資料的多尺度特徵。(尺度空間的構建說白了就是通過影象金字塔對原影象進行降取樣獲得)

Laplacion(拉普拉斯)運算元在邊緣檢測中得到了廣泛的應用。 由於拉斯運算元通過對影象進行微分操作實現邊緣檢測,所以對離散點和噪聲比較敏感,於是,先對影象進行高斯卷積濾波降噪處理,再採用拉斯運算元進行邊緣檢測,就可以提高運算元對噪聲和離散點的魯棒性。這就是LOG運算元(高斯尺度規範化或叫高斯拉普拉斯(Laplacion of Gaussian))的由來。(具體的推導與描述可以參見:https://blog.csdn.net/Kevin_cc98/article/details/78528619)

對於二維影象的尺度空間L(x,y,σ)可以定義為(LOG運算元)(直觀理解,一個影象的尺度空間就是通過下式來描述):


其中,I(x,y)為原影象,xy為空間座標;G(x,y,σ)為尺度因子為σ的高斯卷積核函式。高斯卷積核是唯一可以產生多尺度空間的核,且是唯一線性核。而尺度因子描述的是影象的平滑程度,小尺度對應於影象中的細節部分(高解析度),大尺度對應於影象的輪廓部分(低解析度)。


(關於高斯濾波之類的,這裡有比較好的描述https://blog.csdn.net/zddblog/article/details/7521424)

尺度空間模型在實現的時候,使用高斯金字塔表示。高斯金字塔的構建分為兩部分:

    ①對影象做不同程度的降噪(對於每個octave裡的不同圖片做高斯濾波)

    ②對影象做隔點取樣(就是降取樣獲得不同的octave)


通過對原始影象的降取樣可以獲得影象金字塔,而為了讓尺度體現其連續性,高斯金字塔在傳統影象金字塔簡單的降取樣的基礎上加上了高斯濾波。對高斯金字塔中的每層(每個octave)的第一張圖使用不同引數做高斯模糊處理,使得金字塔的每層(每個octave)都含有多張高斯模糊影象(也叫層interval)。

為了保證尺度不變(scale-invariant),對於一幅影象I,通過影象金字塔建立其在不同尺度(scale)的影象(就是每一層octave),每層octave都有對應的特徵點,也即是在每個尺度下都有對應的特徵點。第一個octave為原影象的大小,後面的每一個octave都為上一個octave降取樣的結果,為上一個octave的1/4(長寬分別減半)。

通過隔點取樣獲得尺度空間,而在隔點取樣之前,會通過高斯低通濾波器(抗混疊)進行濾波處理。而在SIFT中,採用的高斯核其方差是可以變化的,每次取樣前(到下一層octave前),都通過不同的方程對影象進行一系列的高斯卷積處理,這一系列的濾波構成影象金字塔中每一層octave中的一系列圖。

那麼層中方差和層與層之間方差有什麼關係呢。層中方差滿足σ, k*σ, k*k*σ的關係,層間方差σ, 1*σ, 2*σ,比如第一層是σ, k*σ, k*k*σ,第二層就為2*σ,2* k*σ, 2*k*k*σ。但是這一處理還沒結束,上面處理得到是高斯尺度空間,只有把層中的每兩樓還要進行相減才能得到最後的DOG尺度空間(後面有說到DOG,高斯差分尺度空間,它是為了有效的在尺度空間檢測到穩定的關鍵點)。

影象金字塔是一系列以金字塔形狀排列的、解析度逐步降低的影象集合。(上一層影象是下一層影象寬度和長度的一半)。通過採用影象金字塔,將不同解析度的資訊儲存下來進而實現尺度不變。

在Lowe的論文中,建議在建立尺度空間前先對原始影象長寬擴充套件一倍。由於在檢測極值點前對原始影象的高斯平滑會導致影象丟失高頻資訊,所以通過影象金字塔的向上取樣技術可以將原始影象長寬擴充套件一倍,使得原始影象資訊可以得到保留,增加特徵點數量。

影象金字塔的說明

關於影象金字塔可以參考博文(https://blog.csdn.net/poem_qianmo/article/details/26157633)本人學習opencv主要也是跟著淺墨的部落格以及他的書籍來學的,在此對他表示十分的感謝。

但是這裡要強調一點,我一開始對於影象金字塔存在一個誤區,就是覺得一張圖,按照傳統的分為好多層塔,上一層塔是對其下一層塔的降取樣獲得,但在SIFT的影象金字塔應該是這樣的:在影象金字塔中分為很多層,每一層叫做一個octave(中文直譯“八度音階”),每一個octave中又有幾張尺度不同的圖片,在sift演算法中,同一個octave層中的圖片尺寸(即大小)相同,但是尺度(即模糊程度)不同,而不同的octave層中的圖片尺寸大小也不相同,因為它是由上一層圖片降取樣得到的。

注意區分關鍵字:尺寸指大小,而尺度指模糊程度

由圖片的size決定是幾層塔(幾個octave),每個octave裡面有幾張(層)圖(一般是3~5張)。0塔的第0張圖是原始影象(或者你double)後的影象,往上每一層圖是對其下一層進行laplacian變換。塔間(octave間)的圖片只是降取樣關係。例如:1塔的第0層可以由0塔的第3層降取樣得到。而0塔的第三層是第二層進行拉斯變換得到的。

(注意這兩段的“層”“張”“塔”“octave”之間混用的關係)。

由於在實驗中發現,尺度歸一化的高斯拉普拉斯函式的極大值和極小值相對於其他特徵提取函式(角點等等)能夠產生最穩定的影象特徵。然後發現,高斯差分函式與尺度歸一化的高斯拉普拉斯函式非常近似。在計算影象特徵中的關鍵點時,對於連續的影象,為了有效的在尺度空間檢測到穩定的關鍵點,提出了高斯差分尺度空間(DOG scale-space)(一說是LOG可以很好地找到關鍵點,但是運算量過大,可以通過DOG影象的極大極小值來近似計算)

使用高斯金字塔每層(每個octave)中相鄰上下兩層影象相減,得到高斯差分影象,然後進行極值檢測。


2.提取特徵點

SITD特徵點包含尺度以及方向。特徵點是由DOG空間的區域性極值點組成的。特徵點的檢測分為疑似關鍵點的檢測以及去除偽關鍵點。

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


同一組中的相鄰尺度(由於k的取值關係,肯定是上下層)之間進行尋找,如圖所示(S=3時):


在極值比較的過程中,每一組(octave)首末兩張圖是無法進行比較的,為了滿足尺度變換的連續性。在每一層(octave)的 頂層繼續用高斯模糊生成了3幅影象。所以,在DOG金字塔每組需S+2層影象,而DOG金字塔由高斯金字塔相鄰兩層相減得到,則高斯金字塔每組需S+3層影象,實際計算時S在3到5之間。當S=3時:


去除不好的特徵點。由於初步初步獲得的疑似關鍵點在大部分的場景中並不能直接進行特徵描述,高斯差分運算元對邊緣及噪聲相對敏感,會產生偽邊緣資訊和偽極值響應資訊。不符合要求的點主要有兩種:對比度低的特徵點和不穩定的邊緣響應點。兩者的本質就是DOG局部曲率非常不對稱。

在以下博文都有詳細介紹,我個人覺得,只需要知道它有這一步,以及這一步大概怎麼做的,為什麼要這樣做就好了,所以此處不詳細列出原理(偷笑大概也是我看不懂的原因吧哈哈哈哈),有興趣的讀者可以詳細閱讀以下連結:

https://blog.csdn.net/abcjennifer/article/details/7639681#comments

https://blog.csdn.net/zddblog/article/details/7521424

特徵點的方向。通過尺度空間獲得了尺度不變性特徵的提取,還要實現旋轉不變性,需要對特徵點的方向重新分配。利用關鍵點鄰域畫素的梯度方向分佈特性為每個關鍵點指定方向引數,使運算元具備旋轉不變性。


上式為(x,y)處梯度的模以及方向。其中尺度L所用的尺度為每個關鍵點各自所在的尺度。

由梯度方向直方圖確定主梯度方向。梯度方向的範圍為0~360度,對其鄰域內的梯度方向進行統計。在梯度方向上將整個圓周360度平均分為36份,每份10度。在以關鍵點為中心的鄰域視窗內進行取樣,並用直方圖統計鄰域畫素的梯度方向。在Lowe的論文中還提到了要使用高斯函式對直方圖進行平滑,減少突變的影響。直方圖的峰值則代表了該關鍵點處鄰域梯度的主方向,即作為該關鍵點的方向。(直方圖中的峰值就是主方向,其他的達到最大值80%的方向可作為輔助方向)。至此,每個影象的關鍵點有三個資訊:位置,所處尺度,方向,由此可以確定一個SIFT特徵區域。


(對於同一梯度值的多個峰值的關鍵點位置,在相同位置和尺度將會有多個關鍵點被建立但方向不同。僅有15%的關鍵點被賦予多個方向,但可以明顯的提高關鍵點匹配的穩定性。實際程式設計實現中,就是把該關鍵點複製成多份關鍵點,並將方向值分別賦給這些複製後的關鍵點,並且,離散的梯度方向直方圖要進行插值擬合處理,來求得更精確的方向角度值)

特徵向量的生成。上面只是獲得了特徵點,還要加一步生成特徵向量(見後面程式)。關鍵點描述子的生成是指把影象中所描述的關鍵點性質特徵生成向量的過程。與此同時,採用位置,尺度和方向的描述不足以實現兩幅圖的匹配,同時為進一步實現旋轉不變以及關照等的影響,實際中還採用了128維的向量對特徵點進行描述,在匹配的時候就僅比較兩幅圖的128維向量就可以了。

(1)首先將座標軸旋轉為關鍵點的方向,以確保旋轉不變性。


(2)生成128維特徵描述子

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

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

將上圖的區域擴充套件到16*16(如下圖所示,就可以獲得所需要的128維向量)。在每個4*4的1/16象限中,通過加權梯度值加到直方圖8個方向區間中的一個,計算出一個梯度方向直方圖。這樣就可以對每個feature形成一個4*4*8=128維的描述子,每一維都可以表示4*4個格子中一個的scale/orientation。


個人理解:以特徵點為中心在附近選16*16的畫素。16*16的畫素陣列中,每4*4小塊做8個方向的梯度方向直方圖,然後就獲得一個4*4的區間,每個區間裡面有8個統計的方向。所以為4*4*8=128維特徵。

(3)將這個128維的向量歸一化之後,就進一步去除了光照的影響。

關鍵點描述子的生成步驟


放兩張圖來顯示特徵點與特徵向量的區別:第一張是影象的關鍵點的特徵,採用的是一個個圓來描述,直徑代表尺寸,一條半徑是方向,圓中心是位置。第二張是上圖特徵的128個描述子,取16*16方格,計算梯度,統計,從圖中看出各個尺度的方格大小是不一樣的。

(博文https://blog.csdn.net/h2008066215019910120/article/details/17229439的這兩張圖很好的描述了特點與特徵向量的區別)



3.根據特徵向量(描述子)進行匹配

opencv中,特徵向量的匹配分為FlannBasedMatcher和BruteForceMatcher兩種匹配器(後文會描述到,這裡就先不介紹,不然一下知識點太多容易混亂)。反正就是你獲得兩張圖片的特徵向量後,你要對它進行匹配咯吐舌頭(數學推導我也沒有看hhh)

程式

先給出測試的圖片:


程式碼如下:

#include <opencv2\highgui\highgui.hpp>
#include<opencv2\nonfree\nonfree.hpp>
#include<opencv2\legacy\legacy.hpp>

using namespace std;
using namespace cv;

//計算影象的SIFT特徵及匹配


int main()
{
	
	Mat srcImage1 = imread("hand1.jpg", 1);
	Mat srcImage2 = imread("hand2.jpg", 1);

	//CV_Assert用於判斷輸入資料的合法性,當該函式為false時,返回一個錯誤資訊
	CV_Assert(srcImage1.data != NULL && srcImage2.data != NULL);


	//轉換為灰度圖並做歸一化
	Mat grayMat1, grayMat2;
	cvtColor(srcImage1, grayMat1, CV_BGR2GRAY);
	normalize(grayMat1, grayMat1, 0, 255, NORM_MINMAX);

	cvtColor(srcImage2, grayMat2, CV_BGR2GRAY);
	normalize(grayMat2, grayMat2, 0, 255, NORM_MINMAX);



	//定義SIFT描述子
	SiftFeatureDetector detector;
	//這個物件顧名思義就是SIFT特徵的探測器,用它來探測圖片中SIFT點的特徵,存到一個KeyPoint型別的vector中。

	/*keypoint只是儲存了opencv的sift庫檢測到的特徵點的一些基本資訊,但sift所提取出來的特徵向量其實不是在這個裡面,特徵向量通過SiftDescriptorExtractor 提取,
	結果放在一個Mat的資料結構中。這個資料結構才真正儲存了該特徵點所對應的特徵向量。*/

	//得到keypoint只是達到了關鍵點的位置,方向等資訊,並無該特徵點的特徵向量,要想提取得到特徵向量就還要進行SiftDescriptorExtractor 的工作。


	SiftDescriptorExtractor extractor;
	//建立了SiftDescriptorExtractor 物件後,通過該物件,對之前SIFT產生的特徵點進行遍歷,找到該特徵點所對應的128維特徵向量。
	//SiftDescriptorExtractor對應於SIFT演算法中特徵向量提取的工作,通過他對關鍵點周圍鄰域內的畫素分塊進行梯度運算,得到128維的特徵向量。



	//特徵點的檢測,並放入keypoint型別的vector中
	vector< KeyPoint> keypoints1;
	detector.detect(grayMat1, keypoints1);

	vector< KeyPoint> keypoints2;
	detector.detect(grayMat2, keypoints2);



	//計算特徵點描述子
	Mat descriptors1;
	extractor.compute(grayMat1, keypoints1, descriptors1);

	Mat descriptors2;
	extractor.compute(grayMat2, keypoints2, descriptors2);



	//特徵點匹配
	//兩幅圖片的特徵向量被提取出來後,我們就可以使用BruteForceMatcher物件對兩幅圖片的descriptor進行匹配,得到匹配的結果到matches中
	vector<DMatch> matches;
	BruteForceMatcher< L2<float> > matcher;
	matcher.match(descriptors1, descriptors2, matches);



	//二分排序
	int N = 80;
	nth_element(matches.begin(), matches.begin() + N - 1, matches.end());
	//方法是,nth位置的元素放置的值就是把所有元素排序後在nth位置的值.把所有不大於nth的值放到nth的前面,把所有不小於nth的值放到nth後面.
	matches.erase(matches.begin() + N, matches.end());//去除特徵點不匹配情況。erase(pos,n); 刪除從pos開始的n個字元。



	//繪製檢測結果
	Mat resultMat;
	drawMatches(srcImage1, keypoints1, srcImage2, keypoints2, matches, resultMat);
	imshow("jieguo", resultMat);

	waitKey( );
	return 0;
}

程式是可以正常執行的,出來的結果如下:


但是一旦按下“ESC”或者“0”時,就會出現以下錯誤:


如果只是按“shift+F5”是不會報錯的。

至今這個問題還沒有找到答案。。。。重新建立工程配置了opencv2.4.9也會出現同樣的問題。要是有大佬知道怎麼解決請寫在評論處可憐可憐可憐

(個人感覺應該是呼叫sift過程中出現的問題,因為我曾經把上面的sift主體部分改裝成函式封裝,一執行就直接在函式結束的地方終端,不能返回影象。。。。。)

下面再給出另外一個SITF特徵點檢測的程式:

測試圖片如下


#include "opencv2/core/core.hpp"  
#include "highgui.h"  
#include "opencv2/imgproc/imgproc.hpp"  
#include "opencv2/features2d/features2d.hpp"  
#include "opencv2/nonfree/nonfree.hpp"  

using namespace cv;
using namespace std;

//運用sift類
int main()
{
	Mat img = imread("666666.jpg", 1);

	SIFT sift(200);//設定了200個特徵點
	

	vector <KeyPoint> key_points;//存放特徵點,存放檢測出來的特徵點


	Mat descriptors, mascara;// descriptors為描述符,mascara為掩碼矩陣  


	sift(img, mascara, key_points, descriptors);//執行sift運算


	Mat output_img; //輸出影象矩陣
	//在輸出影象上繪製特徵點
	drawKeypoints(img,     //輸入影象  
		key_points,      //特徵點向量  
		output_img,      //輸出影象  
		Scalar::all(-1),      //繪製特徵點的顏色,為隨機  
							  //以特徵點為中心畫圓,圓的半徑表示特徵點的大小,直線表示特徵點的方向  
		DrawMatchesFlags::DRAW_RICH_KEYPOINTS);

	imshow("sift tu", output_img);


	waitKey(0);
	return 0;
}

結果如下:


其實本質上跟上面的程式是一樣的。只是這個程式比較簡易,就粘貼出來。

補充說明一下KeyPoint類,該類是一個為特徵點檢測而生的資料結構,用於表示特徵點。

二.SURF

先給出幾篇參考的博文:

https://blog.csdn.net/ssw_1990/article/details/72789873

https://blog.csdn.net/tostq/article/details/49472709

SURF英文全稱為Speeded Up Robust Features,直接翻譯就是“加速版的具有魯棒性的特徵”。是由Herbert Bay等人在2006年提出的。是一種類似於SIFT的特徵點檢測及描述的演算法,是SIFT的加速版。SIFT演算法的最大缺點是如果不借助硬體或專門的影象處理器很難達到實時。而SURF演算法的實現原理借鑑了SIFT中DOG簡化近似的思想,採用海森矩陣(Hessian matrix)行列式近似值影象。

SURF通過Hessian矩陣的行列式來確定興趣點的位置,在根據興趣點鄰域的Haar小波響應來確定描述子。

SURF演算法的步驟

1.通過Hessian矩陣構建高斯影象金字塔尺度空間

構建Hessian矩陣

首先同SIFT方法一樣,SURF也必須考慮如何確定興趣點位置,不過SIFT採用是DOG來代替LOG運算元,找到其在尺度和影象內區域性極值視為特徵點,而SURF方法是基於Hessian矩陣的,而它通過積分影象極大地減少運算時間,並稱之為FAST-Hessian下面來介紹一下Hessian矩陣:它是一個自變數為向量的實值函式的二階偏導陣列成的方塊矩陣。設影象畫素函式為f(x,y),則影象中某個畫素點的Hessian矩陣為:


對於影象中的每個畫素點都可以求出一個Hessian矩陣。Hessian矩陣的判別式為:


判別式的值是H矩陣的特徵值。Hessian矩陣描述函式的局部曲率(SIFT去除偽關鍵點也用到了Hessian矩陣)。當畫素點的Hessian矩陣是正定矩陣,則該點是一個區域性極小值;當畫素點的Hessian矩陣是負定矩陣,則該點是一個區域性極大值點;當畫素點的Hessian矩陣是不定矩陣,在該畫素點不是極值點。故此,可以利用判定結果的符號將所有點分類,根據判別式取值正負,來判別該點是或不是極值點。

由於SURF特徵點需要具備尺度不變性(或者說尺度無關性),故此在構造Hessian矩陣前先對影象進行高斯濾波(高斯卷積核是唯一可以產生多尺度空間的核,且是唯一線性核



通過Hessian矩陣的行列式值來判斷特徵點。由於求Hessian矩陣時要先進行高斯平滑,然後求二階導數,這一過程在離散的畫素點中是用模板卷積形成的。故此在SURF中用盒函式來近似這一個高斯二階梯度模板。使用盒子濾波運算進行簡化,使得簡化後的模板只是幾個矩形區域組成,矩形區域內填充同一值。在簡化模板中白色區域的值為正數,黑色區域的值為負數,灰度區域的值為0,如下所示:



這部分看得不是很懂,就直接粘貼出來了。反正記住SURF演算法使用矩形盒型濾波器來近似模擬高斯函式的二階偏導數。使用矩陣盒濾波器可以加快計算速度,利用矩形盒濾波器與影象卷積後設定權重係數,進而來計算det(H)值,當其值為負數時,表明矩陣的兩個特徵值異號,該點判定為非極值點;當其值為正數時,表明矩陣兩個特徵值同時為正或負,該點可能是區域性極值點。

構建尺度空間

通過上面的步驟,構建了一幅近似於Hessian的行列式圖(類似於SIFT中的DOG圖)。SURF特徵描述為實現影象尺度的不變性,同樣也採用影象金字塔模型,但是與SIFT不同的是,SURF的金字塔影象始終保持源影象尺寸,而尺度變換則是通過改變矩形盒濾波器大小及高斯函式尺度來實現。

尺度空間通常通過高速金字塔來實施。一般的方法是通過不同的高斯函式,對影象進行平滑濾波,然後重取樣影象以獲得更高一層的金字塔影象。而SURF方法通過盒函式和積分影象,因此不需要進行取樣操作,直接應用不同大小的濾波器就可以了。下圖說明了這一情況,左圖是sift演算法,其是影象大小減少,而模板不變(這裡只是指每組間,組內層之間還是要變的)。而SURF演算法(右圖)剛好相反,其是影象大小不變,而模板大小擴大。


在SURF中,圖片的大小是一直不變的,不同octave層的待檢測圖片是改變高斯模糊尺寸大小得到的。演算法允許尺度空間多層影象同時被處理,不需要二次抽樣,從而提高了演算法的效能。SURF通過使原始影象保持不變而只改變了濾波器大小,節省了降取樣的過程,使得其處理速度得到提升。SURF通過採用不斷增大盒子濾波模板尺寸的間接方法。通過不同尺寸盒子濾波模板與積分影象求取Hessian矩陣行列式的響應影象。

2.利用非極大值抑制初步確定特徵點

通過不同尺寸盒子濾波模板與積分影象求取Hessian矩陣行列式的響應影象,類似於SIFT使用3×3×3鄰域非最大值抑制,即每個畫素點與其三維鄰域中的26個點進行大小比較獲得初步不同尺度的特徵點

3.精確定位極值點

與SIFT類似,採用三維線性插值法得到亞畫素級的特徵點,同時也去掉小於一定閾值的點。

4.選取特徵點的主方向

為了保證特徵向量具有旋轉不變性,需要對每一個特徵點分配一個主方向。

這一步與sift也大有不同。Sift選取特徵點主方向是採用在特徵點領域內統計其梯度直方圖,取直方圖bin值最大的以及超過最大bin值80%的那些方向做為特徵點的主方向。

而在surf中,不統計其梯度直方圖,而是統計特徵點領域內的harr小波特徵。即在特徵點的領域(比如說,半徑為6s的圓內,s為該點所在的尺度)內,統計60度扇形內所有點的水平haar小波特徵和垂直haar小波特徵總和,haar小波的尺寸變長為4s,這樣一個扇形得到了一個值。然後60度扇形以一定間隔進行旋轉,最後將最大值那個扇形的方向作為該特徵點的主方向。該過程的示意圖如下:


5.構造SURF特徵點描述運算元

同sift演算法一樣,SURF也是通過建立興趣點附近區域內的資訊來作為描述子的,不過sift是利用鄰域點的方向,而SURF則是利用Haar小波響應。

SURF首先在興趣點附近建立一個20s大小的方形區域,為了獲得旋轉不變性,同sift演算法一樣,我們需要將其先旋轉到主方向,然後再將方形區域劃分成16個(4*4)子域。對每個子域(其大小為5s*5s)我們計算25(5*5)個空間歸一化的取樣點的Haar小波響應dx和dy。

之後我們將每個子區域(共4*4)的dx,dy相加,因此每個區域都有一個描述子(如下式),為了增加魯棒性,我們可以給描述子再新增高斯權重(尺度為3.3s,以興趣點為中心)



所以最後在所有的16個子區域內的四位描述子結合,將得到該興趣點的64位描述子


由於小波響應對於光流變化偏差是不變的,所以描述子具有了光流不變性,而對比性不變可以通過將描述子歸一化為單位向量得到。

另外也建立128位的SURF描述子,其將原來小波的結果再細分,比如dx的和將根據dy的符號,分成了兩類,所以此時每個子區域內都有8個分量,SURF-128有非常好效果,如下圖所示。

在surf中,也是在特徵點周圍取一個正方形框,框的邊長為20s(s是所檢測到該特徵點所在的尺度)。該框帶方向,方向當然就是第4步檢測出來的主方向了。然後把該框分為16個子區域,每個子區域統計25個畫素的水平方向和垂直方向的haar小波特徵,這裡的水平和垂直方向都是相對主方向而言的。該haar小波特徵為水平方向值之和,水平方向絕對值之和,垂直方向之和,垂直方向絕對值之和。該過程的示意圖如下所示:


這樣每個小區域就有4個值,所以每個特徵點就是16*4=64維的向量,相比sift而言,少了一半,這在特徵匹配過程中會大大加快匹配速度。

程式碼演示:

opencv中SURF部分設計到三個類:SURF、SurfFeatureDetector、SurfDescriptorExtractor

根據features2d.hpp標頭檔案中的兩句定義:

typedef SURF SurfFeatureDetector;
typedef SURF SurfDescriptorExtractor;

其中,typedef宣告是為現有型別建立一個新的名字,類型別名,即SURF類有了兩個新名字SurfFeatureDetector以及SurfDescriptorExtractor。也就是說,SurfFeatureDetector類和SurfDescriptorExtractor類,其實就是SURF類,他們三者等價。下圖給出了SURF相關類之間的關係:


SURF特徵檢測及匹配程式碼:(參考《OPENCV影象處理程式設計例項》一書,並結合自己的理解作出的註釋)

(建議看程式的時候結合著上文SIFT來看。)