SIFT特徵點相對於ORB計算速度較慢,在沒有GPU加速狀況下,沒法知足視覺里程計的實時性要求,或者沒法運行在手機平臺上,可是效果更好,精度更高。在應用時能夠擇優選取,瞭解其本質原理的動機是爲了本身使用時,能夠對其進行修改,針對本身的應用場景優化算法。html
有足夠的時間,能夠去看D. Lowe的論文,理解起來更透徹.node
對於構建的高斯金字塔,金字塔每層多張圖像合稱爲一組(Octave),每組有多張(也叫層Interval)圖像。一般高斯金字塔最底層爲原始圖像第0組,octave之間爲降採樣過程,對應OpenCV裏的函數爲PryDown()。注意這裏降採樣PryDown在直觀上是金字塔向上走,要注意區分。算法
金字塔的第i組octave經過對第i-1組降採樣得到(一般降採樣的比例爲2,也就是高斯模糊後去掉偶數行/列像素)。在每一組octave中還須要使用高斯核σ建立連續的尺度空間圖像: σ, kσ, k2σ,..., kn-1σ,其中,k=21/s,s爲每一組octave中再分的尺度層數。構建連續尺度空間的目的是爲了提取特徵點(角點)時,該特徵點不只在二維圖像空間中是梯度極值點,還須要在第三維的尺度空間也是極值點。函數
固然,檢測角點並非直接在高斯卷積構建的尺度空間中,而是DoG(Difference of Gaussian)高斯差分尺度空間中,所以,爲了保證DOG高斯差分尺度空間變化的連續性(是高斯卷積空間相鄰尺度的差),須要在每一層octave的高斯卷積尺度首尾兩邊多建立一個尺度。源碼分析
這裏注意理解連續性,不是微積分裏面的連續性,指的是尺度空間連續,(σ, kσ, k2σ,..., ks-1σ) ,(2σ, 2kσ, 2k2σ,..., 2ks-1σ) 。從這裏能夠看出,尺度空間主要由σ主導.優化
雖然在高斯拉普拉斯LoG尺度空間中檢測極值點是最精確的,可是因爲計算量比較大,一般使用DoG尺度空間對其進行近似,在DoG尺度空間中進行局部最值搜索。有關LoG能夠參考http://fourier.eng.hmc.edu/e161/lectures/gradient/node8.html.LoG也就是將拉普拉斯算子做用於高斯核函數後造成的新的核函數,拉普拉斯算子的做用是求二階偏導,對噪點響應較敏感,所以須要先用高斯覈對圖像進行平滑.ui
因爲DoG尺度空間是離散的,咱們只能經過比較DoG空間中某個點周圍26個點來獲取一個點是否能夠做爲極值(和ORB同樣,這裏能夠選較少的幾個先比較剔除).spa
可是爲了進一步得到精確的亞像素級別特徵點位置,須要將DoG函數D(x,y,σ)在像素極值點附近二階展開,對亞像素極值求偏導後=0求解..net
第二式帶入第一式後能夠獲得亞像素極值點對應的DoG函數值,也就是對比度contrast:code
在檢測到DoG尺度空間中的極值點(extremum)後,使用兩個閾值來剔除質量不高(unstable)的點,contrastThreshold以及edgeThreshold.首先在DoG尺度空間中在potential的極值點附近二階泰勒展開,尋找更精確的極值點座標,其中,contrastThreshold是爲了剔除對比度不高的極值點,edgeThreshold是爲了剔除邊緣點,由於邊緣點也知足極值點的條件,可是不是須要的角點.
剔除對比度不高(不穩定)的點,|D(x)| < 0.03,這裏假設DoG函數取值範圍[0, 1].
剔除邊緣點借鑑了Harris Corner的檢測方法,計算特徵點鄰域的Hessian矩陣。
一次求導物理意義是變化率/梯度,二次求導物理意義是曲率,所以Hessian矩陣更能體現一個邊緣的特徵程度。
該Hessian矩陣的特徵值表明了邊緣特徵的主曲率。兩個特徵值之間的比例體現了鄰域兩個垂直方向的曲率差異,這裏去掉比例較大(橫跨邊緣和沿着邊緣方向的曲率差異較大,>10)的點。注意這裏不用特徵值分解,而是用矩陣跡和行列式的性質,獲得兩個特徵值比例就行.
在檢測到的特徵點一個圓形鄰域像素集合上計算各自的梯度幅值和梯度方向:
將360度方向劃分爲方向直方圖中的36個bins,獲取直方圖的peak,做爲特徵點的方向。若是存在大於80%*peak幅值的方向,則在同一個尺度特徵點(x,y,σ)上新建一個方向不一樣的特徵點,有多少個新建多少個特徵點.論文中說,相同位置的不會超過15%, 可是能夠顯著加強匹配的穩定性.注意這裏的鄰域是有區分度的,距離特徵點近的像素在計算直方圖時權重較大,使用高斯分佈加權處理。
得到關鍵點主方向後,每一個關鍵點有三個信息(x,y,σ,θ):位置、尺度、方向。
在對鄰域像素點採樣前,要旋轉xy座標系和關鍵點的方向對齊,使得特徵點具有方向不變的特徵(旋轉相機後,特徵點描述子不變)。
計算128維空間中描述子的距離,SIFT的實現中計算的是歐氏距離.
練習:
OpenCV中提供的goodFeatureToTrack()可使用Harris和Shi-Tomasi角點檢測算法對圖像進行角點提取,注意沒有構建尺寸空間,且沒有提供描述子計算,是最簡單的角點提取算法,可是經過對其參數的調整,能夠了解一些實現特色:
#include "common.h" #include <opencv2/nonfree/features2d.hpp> using namespace std; using namespace cv; Mat origin, img; vector<Point2f> keyPoints; string title("FeatureDetector"); int maxCorners(10); int qualityLevel(2); int minDistance(10); int blockSize(3); int useHarrisDetector(0); int k(4); int method(1); void detectCorner(int, void*); int main(){ origin = imread("/home/shang/dataset/opencv/building.jpg",CV_LOAD_IMAGE_COLOR); if(!origin.data){ cerr << "No data!"<< endl; return -1; } cvtColor(origin, img, CV_BGR2GRAY); namedWindow(title); createTrackbar("maxCorners", title, &maxCorners, 200, detectCorner); createTrackbar("qualityLevel (%)", title, &qualityLevel, 100, detectCorner); createTrackbar("minDistance", title, &minDistance, 400, detectCorner); createTrackbar("blockSize", title, &blockSize, 10, detectCorner); createTrackbar("useHarrisDetector", title, &useHarrisDetector, 1, detectCorner); createTrackbar("k when in HarrisDetector", title, &k, 100, detectCorner); createTrackbar("Method", title, &method, 2, detectCorner); detectCorner(0,0); imshow(title, img); while(true){ if(waitKey(0)==27) break; } } void detectCorner(int, void*){ Mat result ; img.copyTo(result); switch (method){ case 1: if(qualityLevel==0) { qualityLevel = 1; setTrackbarPos("qualityLevel (%)", title, 1); } if(blockSize==0) { blockSize = 1; setTrackbarPos("blockSize", title, 1); } goodFeaturesToTrack(result, keyPoints, maxCorners, qualityLevel/100.0, minDistance, noArray(), blockSize, useHarrisDetector, k/100.0 ); cvtColor(result, result, CV_GRAY2RGB); for(vector<Point2f>::const_iterator it=keyPoints.begin(); it!=keyPoints.end(); it++){ circle(result, *it, 8, Scalar(0,0,255),2); } imshow(title, result); break; case 2: SiftFeatureDetector detector(100); vector<KeyPoint> keyPoints; detector.detect(result, keyPoints); cvtColor(result, result, CV_GRAY2RGB); for(vector<KeyPoint>::const_iterator it=keyPoints.begin(); it!=keyPoints.end(); it++){ rectangle(result, Rect(it->pt.x-8,it->pt.y-8,16,16), Scalar(0,255,0),2); } imshow(title, result); break; } }
參考: