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函式中,兩個描述符的順序一定不能寫反,否則執行會出錯。