基於內容的圖像檢索技術是採用某種算法來提取圖像中的特徵,並將特徵存儲起來,組成圖像特徵數據庫。當須要檢索圖像時,採用相同的特徵提取技術提取出待檢索圖像的特徵,並根據某種類似性準則計算獲得特徵數據庫中圖像與待檢索圖像的相關度,最後經過由大到小排序,獲得與待檢索圖像最相關的圖像,實現圖像檢索。圖像檢索的結果優劣取決於圖像特徵提取的好壞,在面對海量數據檢索環境中,咱們還須要考慮到圖像比對(圖像類似性考量)的過程,採用高效的算法快速找到類似圖像也相當重要。html
在構建圖像特徵庫的時候,一般不會使用原始的圖像特徵,這是因爲Raw Feature有不少冗餘信息,並且維度太高在構建特徵數據庫和匹配的時候效率較低。因此,一般要對提取到的原始特徵進行從新編碼。比較經常使用的三種編碼方式:算法
構建圖像特徵數據庫,一般有如下幾個步驟:數據庫
圖像的特徵庫構建完成後,在檢索階段,主要涉及到特徵的類似性度量準則,排序,搜索函數
SIFT特徵的講解已經不少了,以前的博客也有過介紹。本文就藉助vlfeat
對SIFT特徵的提取過程作一個總結。
一個SIFT特徵有兩部分組成:關鍵點(keypoint)和對應特徵描述子(Descriptor)。使用SIFT detector
進行SIFT關鍵點的提取,而後使用SIFT descriptor
計算關鍵點的描述子。也能夠獨立的使用SIFT detector
進行SIFT 關鍵點的提取,或者使用SIFT descriptor
進行別的關鍵點描述子的計算。編碼
一個SIFT keypoint是一塊圓形區域而且帶有方向,使用4個參數描述該區域的幾何結構:spa
一個SIFT關鍵點由4個參數肯定:
\[ k(x,y,r,\theta) \]
SIFT在多尺度空間檢測關鍵點,肯定關鍵點的位置和尺度,保證了關鍵點的尺度不變性;利用關鍵點鄰域像素的梯度分佈來肯定關鍵點的方向,保證關鍵點的旋轉(方向)不變性。code
SIFT在高斯尺度空間進行特徵點檢測,一個高斯尺度空間有圖像和不一樣的高斯卷積核卷積獲得:
\[ L(x,y,\sigma) = G(x,y,\sigma) * I(x,y) \]
\(L(x,y,\sigma)\)表示圖像的高斯尺度空間,\(\sigma\)稱爲尺度空間因子,它是高斯函數標準差,反映了圖像的模糊程度,其值越大圖像越模糊,對應的尺度也就越大。htm
而對圖像關鍵點的檢測比較好的算子是\(\Delta^2G\),高斯拉普拉斯(LoG)。可是該算子的運算量較大,因此一般使用\(DoG\)(Difference of Gaussian)來近似計算LoG。
\(DoG\)的定義爲:
\[ D(x,y,\sigma) = [G(x,y,k\sigma)-G(x,y,\sigma)] * I(x,y) = L(x,y,k\sigma)-L(x,y,\sigma) \]
\(L(x,y,\sigma)\)表示高斯尺度空間,則相鄰兩個高斯尺度空間相減就獲得來的\(DoG\)的響應圖像。blog
因此爲了的獲得\(DoG\)的響應圖像,就須要先構建高斯尺度空間。高斯尺度空間是由圖像金字塔降採樣結合高斯濾波獲得的。高斯尺度空間分爲多個組\(Octave\)(每組圖像的分辨率同樣),每組有多層\(Level\)(使用不一樣的\(\sigma\)進行高斯模糊獲得)。以一個\(512 \times 512\)的圖像\(I\),其構建高斯尺度空間的步驟:(倒立的金字塔)排序
在Lowe的算法實現中\(\sigma_0 = 1.6,o_min = -1\)。\(o_min = -1\)表示金字塔的第0組是原圖像上採樣獲得的,寬和高加一倍。
高斯圖像金字塔構建完成後,將同一組的相鄰兩層相減就獲得了\(DoG\)金字塔。
每組的層數\(S = 3\),也就是說每組能夠獲得兩層的\(DoG\)圖像,以第一組爲例:其尺度爲\(\sigma,k\sigma\),只有兩項是沒法求取極值的,須要左右兩邊都有尺度。因爲沒法比較取得極值,那麼咱們就須要繼續對每組的圖像進行高斯模糊,使得尺度造成\(\sigma,k\sigma,k^2\sigma,k^3\sigma,k^4\sigma\)這樣就能夠選擇中間的三項\(k\sigma,k^2\sigma,k^3\sigma\)
檢測關鍵點,就是在\(DoG\)的圖像空間中尋找極值點,每一個像素點要和其圖像域(同一尺度空間)和尺度域(相鄰的尺度空間)的全部相鄰點進行比較,當其大於(或者小於)全部相鄰點時,改點就是極值點。如圖所示,中間的檢測點要和其所在圖像的\(3 \times 3\)鄰域8個像素點,以及其相鄰的上下兩層的\(3\times 3\)領域18個像素點,共26個像素點進行比較。
刪除兩類極值點
統計關鍵點鄰域像素的梯度方向分佈來肯定關鍵點的方向。具體步驟以下:
計算以特徵點爲中心,以\(3 \times1.5 \sigma\)爲半徑的區域圖像的幅角和幅值,每一個像點\(L(x,y)\)的梯度的模\(m(x,y)\)以及方向\(\theta(x,y)\)可經過下面公式求得
\[ m(x,y) = \sqrt{[L(x+1,y)-L(x-1,y)]^2 + [L(x,y+1)-L(x,y-1)]^2} \\ \theta(x,y) = artan\frac{L(x,y+1)-L(x,y-1)}{L(x+1,y)-L(x-1,y)} \]
統計像素點的幅角和幅值的直方圖,梯度方向的直方圖的橫軸是梯度方向的角度(梯度方向的範圍是0到360度,直方圖每36度一個柱共10個柱,或者沒45度一個柱共8個柱),縱軸是梯度方向對應梯度幅值的累加,在直方圖的峯值就是特徵點的主方向。在梯度直方圖中,當存在一個至關於主峯值80%能量的柱值時,則能夠將這個方向認爲是該特徵點輔助方向。因此,一個特徵點可能檢測到多個方向(也能夠理解爲,一個特徵點可能產生多個座標、尺度相同,可是方向不一樣的特徵點)。
獲得特徵點的主方向後,對於每一個特徵點能夠獲得三個信息\(k(x,y,r,\theta)\),即位置、尺度和方向。由此能夠肯定一個SIFT特徵區域,一個SIFT特徵區域由三個值表示,中心表示特徵點位置,半徑表示關鍵點的尺度,箭頭表示主方向。
具備多個方向的關鍵點能夠被複製成多份,而後將方向值分別賦給複製後的特徵點,一個特徵點就產生了多個座標、尺度相等,可是方向不一樣的特徵點。
在檢測部分已經獲得了SIFT關鍵點的位置,尺度和方向信息,生成關鍵點的描述子,就是使用一個向量來描述關鍵點及其鄰域像素的信息。
由如下步驟生成描述子:
爲了保證旋轉不變性,將關鍵點爲中心的鄰域像素的座標軸進行旋轉,將\(x\)軸旋轉相當鍵點主方向,以下圖:
分塊計算鄰域內像素的梯度方向直方圖,以關鍵點爲中心的\(16\times16\)的區域內,劃分\(4\times4\)個塊,分別計算每一個塊的梯度直方圖,以下圖:
每一個塊的梯度直方方向直方圖的計算方式,和求關鍵點主方向時相似:此時每一個區域的梯度直方圖在0-360之間劃分爲8個方向區間,每一個區間爲45度,即每一個種子點有8個方向的梯度強度信息,最後將獲得的\(4\times4\times8=128\)維的特徵向量。
vlfeat
是一個開源的輕量級的計算機視覺庫,主要實現圖像局部特徵的提取和匹配以及一些經常使用的聚類算法。其對sift特徵提取的各個步驟進行了封裝,使用的方法以下:
vl_sift_new
初始化VlSiftFilt
,設置sift提取時參數信息,如:圖像的大小,Octave的個數,每一個Octave的中的層數,起始的Octave的index. 各個參數的具體含義能夠參考上面sift特徵提取的方法。vl_sift_set_peak_thresh
設置接受極值點是一個關鍵點的最小對比度。 該值越小,提取到的關鍵點就越多。y vl_sift_set_edge_thresh()
設置一個極值點是在邊緣上的閾值。 該值越小,提取到的關鍵點就越多。這兩個參數對最終提取到的特徵點個數有很大的影響。
vl_sift_process_first_octave()
和vl_sift_process_next_octave()
來計算下一個DoG尺度空間。vl_sift_detect
進行關鍵點提取vl_sift_calc_keypoint_orientations
計算關鍵點的方向,可能多於一個l_sift_calc_keypoint_descriptor
計算每一個方向的特徵描述子。vl_sift_delete
釋放資源。具體代碼以下:
// 初始化 const string file = "../0.jpg"; Mat img = imread(file,IMREAD_GRAYSCALE); Mat color_img = imread(file); Mat float_img; img.convertTo(float_img,CV_32F); int rows = img.rows; int cols = img.cols; VlSiftFilt* vl_sift = vl_sift_new(cols,rows,4,3,0); vl_sift_set_peak_thresh(vl_sift,0.04); vl_sift_set_edge_thresh(vl_sift,10); vl_sift_pix *data = (vl_sift_pix*)(float_img.data); vector<VlSiftKeypoint> kpts; vector<float*> descriptors; vl_sift_extract(vl_sift,data,kpts,descriptors); /* Extract sift using vlfeat parameters: vl_sfit, VlSiftFilt* data , image pixel data ,to be convert to float kpts, keypoint list descriptors, descriptor. Need to free the memory after using. */ void vl_sift_extract(VlSiftFilt *vl_sift, vl_sift_pix* data, vector<VlSiftKeypoint> &kpts,vector<float*> &descriptors) { // Detect keypoint and compute descriptor in each octave if(vl_sift_process_first_octave(vl_sift,data) != VL_ERR_EOF){ while(true){ vl_sift_detect(vl_sift); VlSiftKeypoint* pKpts = vl_sift->keys; for(int i = 0; i < vl_sift->nkeys; i ++) { double angles[4]; // 計算特徵點的方向,包括主方向和輔方向,最多4個 int angleCount = vl_sift_calc_keypoint_orientations(vl_sift,angles,pKpts); // 對於方向多於一個的特徵點,每一個方向分別計算特徵描述符 // 而且將特徵點複製多個 for(int i = 0 ; i < angleCount; i ++){ float *des = new float[128]; vl_sift_calc_keypoint_descriptor(vl_sift,des,pKpts,angles[0]); descriptors.push_back(des); kpts.push_back(*pKpts); } pKpts ++; } // Process next octave if(vl_sift_process_next_octave(vl_sift) == VL_ERR_EOF) { break ; } } } }
vlfeat中sift提取接受的是float
類型的數據,因此要先將讀到的數據圖像轉換爲float
。
和OpenCV中的sift提取的對比結果以下:
幾年前寫過一篇關於SIFT的文章,SIFT特徵詳解 當時可能是從理論上。如今在作圖像檢索的時候,發現仍是有不少東西理解的不是很清晰,好比:關鍵點的多個方向,不穩定極值點的剔除以及梯度方向直方圖計算等等。
正在作一個圖像檢索的項目,陸續將項目的中學到一些知識總結下來,下一篇是關於均值聚類的,對提取到的圖像特徵進行聚類生成視覺特徵(Visul Feature)表。本系列的代碼會陸續更新到Github上,歡迎start/fork 。