http://blog.csdn.net/abcjennifer/article/details/7639681/
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的情況
使用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所用的尺度為每個關鍵點各自所在的尺度。至此,圖像的關鍵點已經檢測完畢,每個關鍵點有三個信息:
在實際計算時,我們在以關鍵點為中心的鄰域窗口內采樣,並用直方圖統計鄰域像素的梯度方向。梯度直方圖的范圍是0~360度,其中每45度一個柱,總共8個柱, 或者每10度一個柱,總共36個柱。Lowe論文中還提到要使用高斯函數對直方圖進行平滑,減少突變的影響。直方圖的峰值則代表了該關鍵點處鄰域梯度的主方向,即作為該關鍵點的方向。
直方圖中的峰值就是主方向,其他的達到最大值80%的方向可作為輔助方向
由梯度方向直方圖確定主梯度方向
該步中將建立所有scale中特征點的描述子(128維)
關鍵點描述子的生成步驟
5. 關鍵點描述子的生成
首先將坐標軸旋轉為關鍵點的方向,以確保旋轉不變性。以關鍵點為中心取8×8的窗口。
Figure.16*16的圖中其中1/4的特征點梯度方向及scale,右圖為其加權到8個主方向后的效果。
圖左部分的中央為當前關鍵點的位置,每個小格代表關鍵點鄰域所在尺度空間的一個像素,利用公式求得每個像素的梯度幅值與梯度方向,箭頭方向代表該像素的梯度方向,箭頭長度代表梯度模值,然后用高斯窗口對其進行加權運算。
圖中藍色的圈代表高斯加權的范圍(越靠近關鍵點的像素梯度方向信息貢獻越大)。然后在每4×4的小塊上計算8個方向的梯度方向直方圖,繪制每個梯度方向的累加值,即可形成一個種子點,如圖右部分示。此圖中一個關鍵點由2×2共4個種子點組成,每個種子點有8個方向向量信息。這種鄰域方向性信息聯合的思想增強了算法抗噪聲的能力,同時對於含有定位誤差的特征匹配也提供了較好的容錯性。

在每個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匹配點數目會減少,但更加穩定。
實驗結果:
Python+opencv實現:
import cv2 import numpy as np #import pdb #pdb.set_trace()#turn on the pdb prompt #read image img = cv2.imread('D:\privacy\picture\little girl.jpg',cv2.IMREAD_COLOR) gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) cv2.imshow('origin',img); #SIFT detector = cv2.SIFT() keypoints = detector.detect(gray,None) img = cv2.drawKeypoints(gray,keypoints) #img = cv2.drawKeypoints(gray,keypoints,flags = cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS) cv2.imshow('test',img); cv2.waitKey(0) cv2.destroyAllWindows()
C實現:
// FeatureDetector.cpp : Defines the entry point for the console application. // // Created by Rachel on 14-1-12. // Copyright (c) 2013年 ZJU. All rights reserved. // #include "stdafx.h" #include "highgui.h" #include "cv.h" #include "vector" #include "opencv\cxcore.hpp" #include "iostream" #include "opencv.hpp" #include "nonfree.hpp" #include "showhelper.h" using namespace cv; using namespace std; int _tmain(int argc, _TCHAR* argv[]) { //Load Image Mat c_src1 = imread( "..\\Images\\3.jpg"); Mat c_src2 = imread("..\\Images\\4.jpg"); Mat src1 = imread( "..\\Images\\3.jpg", CV_LOAD_IMAGE_GRAYSCALE); Mat src2 = imread( "..\\Images\\4.jpg", CV_LOAD_IMAGE_GRAYSCALE); if( !src1.data || !src2.data ) { std::cout<< " --(!) Error reading images " << std::endl; return -1; } //sift feature detect SiftFeatureDetector detector; std::vector<KeyPoint> kp1, kp2; detector.detect( src1, kp1 ); detector.detect( src2, kp2 ); SiftDescriptorExtractor extractor; Mat des1,des2;//descriptor extractor.compute(src1,kp1,des1); extractor.compute(src2,kp2,des2); Mat res1,res2; int drawmode = DrawMatchesFlags::DRAW_RICH_KEYPOINTS; drawKeypoints(c_src1,kp1,res1,Scalar::all(-1),drawmode);//在內存中畫出特征點 drawKeypoints(c_src2,kp2,res2,Scalar::all(-1),drawmode); cout<<"size of description of Img1: "<<kp1.size()<<endl; cout<<"size of description of Img2: "<<kp2.size()<<endl; //write the size of features on picture CvFont font; double hScale=1; double vScale=1; int lineWidth=2;// 相當於寫字的線條 cvInitFont(&font,CV_FONT_HERSHEY_SIMPLEX|CV_FONT_ITALIC, hScale,vScale,0,lineWidth);//初始化字體,准備寫到圖片上的 // cvPoint 為起筆的x,y坐標 IplImage* transimg1 = cvCloneImage(&(IplImage) res1); IplImage* transimg2 = cvCloneImage(&(IplImage) res2); char str1[20],str2[20]; sprintf(str1,"%d",kp1.size()); sprintf(str2,"%d",kp2.size()); const char* str = str1; cvPutText(transimg1,str1,cvPoint(280,230),&font,CV_RGB(255,0,0));//在圖片中輸出字符 str = str2; cvPutText(transimg2,str2,cvPoint(280,230),&font,CV_RGB(255,0,0));//在圖片中輸出字符 //imshow("Description 1",res1); cvShowImage("descriptor1",transimg1); cvShowImage("descriptor2",transimg2); BFMatcher matcher(NORM_L2); vector<DMatch> matches; matcher.match(des1,des2,matches); Mat img_match; drawMatches(src1,kp1,src2,kp2,matches,img_match);//,Scalar::all(-1),Scalar::all(-1),vector<char>(),drawmode); cout<<"number of matched points: "<<matches.size()<<endl; imshow("matches",img_match); cvWaitKey(); cvDestroyAllWindows(); return 0; }
===============================
輔助資料:
===============================
Reference:
Lowe SIFT 原文:http://www.cs.ubc.ca/~lowe/papers/ijcv04.pdf
SIFT 的C實現:https://github.com/robwhess/opensift/blob/master/src
MATLAB 應用Sift算子的模式識別方法:http://blog.csdn.net/abcjennifer/article/details/7372880
http://blog.csdn.net/abcjennifer/article/details/7365882
http://en.wikipedia.org/wiki/Scale-invariant_feature_transform#David_Lowe.27s_method
http://blog.sciencenet.cn/blog-613779-475881.html
http://www.cnblogs.com/linyunzju/archive/2011/06/14/2080950.html
http://www.cnblogs.com/linyunzju/archive/2011/06/14/2080951.html
http://blog.csdn.net/ijuliet/article/details/4640624
http://www.cnblogs.com/cfantaisie/archive/2011/06/14/2080917.html (部分圖片有誤,以本文中的圖片為准)