1. 程式人生 > >Opencv2.4.9原始碼分析——SIFT

Opencv2.4.9原始碼分析——SIFT



SIFT(尺度不變特徵變換,Scale-Invariant Feature Transform)是在計算機視覺領域中檢測和描述影象中區域性特徵的演算法,該演算法於1999年被David Lowe提出,並於2004年進行了補充和完善。該演算法應用很廣,如目標識別,自動導航,影象拼接,三維建模,手勢識別,視訊跟蹤等。不幸的是,該演算法已經在美國申請了專利,專利擁有者為Lowe所在的加拿大不列顛哥倫比亞大學,因此我們不能隨意使用它。

由於SIFT演算法在計算機視覺的特徵檢測和特徵描述中表現十分優異,因此該演算法一經提出,就引起了廣泛的關注。國內外對其研究的人很多,相關的資料也很多。在csdn中,有幾位作者的文章對SIFT演算法介紹得很詳細,如網名為:zddhub、Rachel Zhang和xiaowei_cqu。由王永明和王貴錦所編著的,由國防工業出版社出版的《影象區域性不變性特徵與描述》也對該演算法進行了詳細的介紹。上述文章對我幫助很大。

經過一段時間的研究,並結合opencv中的原始碼,自認為對SIFT演算法有了一定的認識和體會,因此也寫了一篇關於SIFT的文章。該文章共分為三部分,首先是SIFT的演算法分析,然後是opencv的原始碼分析,最後是應用例項。在演算法分析中,注意了每個細節的描述;在原始碼分析中,基本做到了每條程式碼都進行了註釋;在應用例項中,列舉了特徵提取和影象匹配兩個例項。

本想把這篇文章發表在這裡,但文章比較長(有30多頁),關鍵是公式太多,複製貼上太麻煩,排版也不好。因此我把這篇文章分別上傳到了csdn和百度文庫,地址是:

http://wenku.baidu.com/view/d7edd2464b73f242336c5ffa.html

http://download.csdn.net/detail/zhaocj/8294793

可以線上閱讀,也可以免費下載(在這裡,鄙視那些設定下載許可權和積分的人!!!)。如果上述方法都不方便,可以留下email,向我索要。文章中錯誤的地方歡迎指正!

為了不使這篇博文過於空洞,我把這篇文章的第三部分貼上在這裡。

首先給出的是特徵點的檢測:

#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;

int main(int argc, char** argv)
{
   Mat img = imread("box_in_scene.png");

   SIFT sift;    //例項化SIFT類

   vector<KeyPoint> key_points;    //特徵點
   // descriptors為描述符,mascara為掩碼矩陣
   Mat descriptors, mascara;
   Mat output_img;    //輸出影象矩陣

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

   namedWindow("SIFT");
   imshow("SIFT", output_img);
   waitKey(0);

   return 0;
}

結果如下圖所示:

上面的程式需要說明一點的是,如果需要改變SIFT演算法的預設引數,可以通過例項化SIFT類的時候更改,例如我們只想檢測20個特徵點,則例項化SIFT的語句為:

SIFT sift(20);

下面給出利用描述符進行影象匹配的例項:

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

using namespace cv;
using namespace std;

int main(int argc, char** argv)
{
   //待匹配的兩幅影象,其中img1包括img2,也就是要從img1中識別出img2
   Mat img1 = imread("box_in_scene.png");
   Mat img2 = imread("box.png");

   SIFT sift1, sift2;

   vector<KeyPoint> key_points1, key_points2;

   Mat descriptors1, descriptors2, mascara;

   sift1(img1,mascara,key_points1,descriptors1);
   sift2(img2,mascara,key_points2,descriptors2);
   
   //例項化暴力匹配器——BruteForceMatcher
   BruteForceMatcher<L2<float>> matcher;  
   //定義匹配器運算元
   vector<DMatch>matches;  
   //實現描述符之間的匹配,得到運算元matches
   matcher.match(descriptors1,descriptors2,matches);

   //提取出前30個最佳匹配結果
   std::nth_element(matches.begin(),     //匹配器運算元的初始位置
	   matches.begin()+29,     // 排序的數量
	   matches.end());       // 結束位置
   //剔除掉其餘的匹配結果
   matches.erase(matches.begin()+30, matches.end());

   namedWindow("SIFT_matches");  
   Mat img_matches;  
   //在輸出影象中繪製匹配結果
   drawMatches(img1,key_points1,         //第一幅影象和它的特徵點
	   img2,key_points2,      //第二幅影象和它的特徵點
	   matches,       //匹配器運算元
	   img_matches,      //匹配輸出影象
	   Scalar(255,255,255));     //用白色直線連線兩幅影象中的特徵點
   imshow("SIFT_matches",img_matches);  
   waitKey(0);

   return 0;
}

結果如下圖所示:

程式是通過距離測度實現兩幅影象描述符之間的比較的,距離越小,匹配性越好,越說明這兩個描述符表示的是同一事物。描述符的匹配結果儲存在匹配器運算元matches中。如果直接使用matches,匹配效果並不好,因為它是儘可能的匹配所有的描述符。因此我們要進行篩選,只保留那些好的結果。在這裡,我們利用排序,選擇距離最小的前30個匹配結果,並進行輸出。另外,matcher.match函式中,兩個描述符的順序一定不能寫反,否則執行會出錯。