尺度空间理论与图像金字塔
我們之所以決定使用卷積后的特征是因為圖像具有一種“靜態性”的屬性。也就是說使用卷積就是為了提取顯著特征,減少特征維數,減少計算量。
在對圖像進行卷積操作后的只管現象:圖像變得模糊了,可是我們依然能夠分辨出是什么!所以卷積就是提取顯著特征!
1尺度空間理論
每個物體,我們總可以用一些詞語或部件來描述它,比如人臉的特征:兩個眼睛、一個鼻子和一個嘴巴。對于圖像而言,我們需要計算機去理解圖像,描述圖像就需要計算機去取得圖像的特征,對圖像比較全面的描述即一個二維矩陣,矩陣內的每個值代表圖像的亮度。有時候我們需要讓計算機更簡化的來描述一個圖像,抓住一些顯著特征,這些特征要具有一些良好的性質,比如局部不變性。局部不變性一般包括兩個方面:尺度不變性與旋轉不變性。- 尺度不變性:人類在識別一個物體時,不管這個物體或遠或近,都能對它進行正確的辨認,這就是所謂的尺度不變性。尺度空間理論經常與生物視覺關聯, ? ?有人也稱圖像局部不變性特征為基于生物視覺的不變性方法。
旋轉不變性:當這個物體發生旋轉時,我們照樣可以正確地辨認它,這就是所謂的旋轉不變性。全局特征:從整個圖像中抽取的特征。較多的運用在圖像檢索領域,如圖像顏色直方圖。
局部特征:從圖像的局部區域中抽取的特征(這個局部區域往往是圖像中的一個像素及它周圍的鄰域)。
一種好的局部特征應該具有下面的特性:
- 可重復性:同一個物體在不同時間,不同角度拍到圖像中,檢測到的特征對應的越多越好。
- 獨特性:特征在該物體上表現為獨特性,能與場景下其他物體區分。
- 局部性:特征往往是物體某個局部的特點,這樣才可以避免遮擋時不能匹配的問題。
- 數量性:檢測到的特征數目一定要多,密集度最好能在一定程度上反映圖像的內容。
- 準確性:得到的特征應該能被精確定位,能夠精確到像素。
- 高效性:特征檢測算法運算要快。
- 2 具體實施過程
$$G_l(i,j)=\sum^{2}_{m=-2}\sum_{n=-2}^2\omega(m,n)G_{l-1}(2i+m,2j+n)$$
$$(1\le l \le N,0 \le i \le R_l,0 \le j \le C_l)$$
式中,$N$為高斯金字塔頂層 撥動號;$R_l$和$G_l$分別為高斯金字塔第$l$層的行數和列數;$\omega(m,n)$是一個二維可分離的$5 \times 5$窗口函數,表達式為: $$\omega=\frac{1}{256} \begin{bmatrix} 1 & 4 & 6 & 4 & 1\\ 4 & 16 & 24 & 16 & 4\\ 6 & 24 &36 & 24 & 6\\ 4 & 16 & 24 & 16 & 4\\ 1 & 4 & 6 & 4 & 1 \\ \end{bmatrix} =\frac{1}{16}\begin{bmatrix} 1 & 4 & 6 & 4 &1 \end{bmatrix}\times\frac{1}{16}\begin{bmatrix} 1 \\ 4 \\ 6 \\ 4 \\1 \end{bmatrix} $$
寫成上面的形式是為了說明,二維窗口的卷積算子,可以寫成兩個方向上的1維卷積核(二項核)的乘積。上面卷積形式的公式實際上完成了2個步驟:1)高斯模糊;2)降維。按上述步驟生成的$G_0,G_1,\dots ,G_N$就構成了圖像的高斯金字塔,其中$G_0$為金字塔的底層(與原圖像相同),$G_N$為金字塔的頂層???/span>見高斯金字塔的當前層圖像是對其前一層圖像先進行高斯低通濾波,然后做隔行和隔列的降采樣(去除偶數行與偶數列)而生成的。當前層圖像的大小依次為前一層圖像大小的1/4。下面是用OpenCV中的圖像金字塔相關函數寫的一個生成圖像金字塔的示例程序。程序中,不但生成了圖像金字塔,而且生成了圖像的拉普拉斯金字塔(接下來的內容)。enum pyrType { PYR_GUASS, PYR_LAPLACE };
void genPyr(const Mat& imgSrc, vector<Mat>& outPutArray, int TYPE, int level) {outPutArray.assign(level + 1, Mat());outPutArray[0] = imgSrc.clone(); // the 0 level is the image. for (int i = 0; i != level; i++){pyrDown(outPutArray[i], outPutArray[i + 1]);}if (PYR_GUASS == TYPE){return;}for (int i = 0; i != level; i++){Mat UpSampleImg;pyrUp(outPutArray[i + 1], UpSampleImg, outPutArray[i].size());outPutArray[i] -= UpSampleImg;} }將$G_l$進行內插(這里內插用的不是雙線性而是用的與降維時相同的濾波核)得到放大圖像$G_l^{\ast}$,使$G_l^{\ast}$的尺寸與$G_{l-1}$的尺寸相同,表示為:
$$ G_l^{\ast}(i,j)=4\sum_{m=-2}^2\sum_{n=-2}^2\omega(m,n)G_l(\frac{i+m}{2},\frac{j+n}{2}) \\ (0 \le l \le N,0 \le i \le R_l,0 \le j \le G_l) $$
上面的系數4,是因為每次能參與加權的項,的權值和為4/256,這個與我們用的$\omega$的值有關。 式中,
$$ G_l(\frac{i+m}{2},\frac{j+n}{2})=\begin{cases} G_l(\frac{i+m}{2},\frac{j+n}{2}), & 當\frac{i+m}{2},\frac{j+n}{2}為整數時 \\ 0, & 其他 \end{cases} $$
$$ \begin{cases} LP_l=G_l-G_{l+1}^{\ast}, & 當0 \le l \le N時 \\ LP_N=G_N, & 當l=N時 \end{cases} $$
式中,$N$為拉普拉斯金字塔頂層號,$LP_l$是拉普拉斯金字塔分解的第$l$層圖像。
由$LP_0,LP_1, \dots,LP_l,\dots,LP_N$構成的金字塔即為拉普拉斯金字塔。它的每一層圖像是高斯金字塔本層圖像與其高一級的圖像經內插放大后圖像的差,此過程相當于帶通濾波,因此拉普拉斯金字塔又稱為帶通金字塔分解。下圖為小貓圖像的拉普拉斯金字塔圖像:圖像的金字塔化能高效地(計算效率也較高)對圖像進行多尺度的表達,但它缺乏堅實的理論基礎,不能分析圖像中物體的各種尺度(雖然我們有小貓的金字塔圖像,我們還是不知道原圖像內小貓的大小)。 尺度空間理論本質 以及其理論支撐:信號的尺度空間剛提出是就是通過一系列單參數、寬度遞增的高斯濾波器將原始信號濾波得到到組低頻信號。那么一個很明顯的疑問是:除了高斯濾波之外,其他帶有參數t的低通濾波器是否也可以用來生成一個尺度空間。
后來Koenerink、Lindeberg[Scale-space theory in computer vision]、Florack等人用精確的數學形式通過不同的途徑都證明了高斯核是實現尺度變換的唯一變換核。
雖然很多研究者從可分性、旋轉不變性、因果性等特性推出高斯濾波器是建立線性尺度空間的最優濾波器。然后在數字圖像處理中,需要對核函數進行采樣,離散的高斯函數并不滿足連續高斯函數的的一些優良的性質。所以后來出現了一些非線性的濾波器組來建立尺度空間,如B樣條核函數。
使用高斯濾波器對圖像進行尺度空間金塔塔圖的構建,讓這個尺度空間具有下面的性質:
信號在尺度t上的表達可以看成是原信號在空間上的一系列加權平均,權重就是具有不同尺度參數的高斯核。
信號在尺度t上的表達也對應于用一個無方向性的孔徑函數(特征長度為$\sigma=\sqrt{t}$)來觀測信號的結果。這時候信號中特征長度小于$\sigma$的精細結構會被抑制[理解為一維信號上小于$\sigma$的波動會被平滑掉。
也叫高斯核族的半群(Semi-Group)性質:兩個高斯核的卷積等同于另外一個不同核參數的高斯核卷積。
$$g(\mu,\sigma_1)\ast g(\mu,\sigma_2)=g(\mu,\sqrt{\sigma_1^2+\sigma_2^2})$$
這個性質的意思就是說不同的高斯核對圖像的平滑是連續的。
這個特征可以從人眼的視覺原理去理解,人在看一件 物體時,離得越遠,物體的細節看到的越少,細節特征是在減少的。
高斯核對圖像進行濾波具有壓制局部細節的性質。
這里只是一個公式推導的問題,對原來的信號加一個變換函數,對變換后的信號再進行高斯核的尺度空間生成,新的信號的極值點等特征是不變的。
Young對經生理學的研究中發現,哺乳動物的視網膜和視覺皮層的感受區域可以很好地用4階以內的高斯微分來建模。
一般我們采集到的圖像中,我們并不知道我們感興趣的目標在圖像中的尺度,在這樣的情況下,我們對圖像進行分析時就無法選擇合適的參數,比如邊緣檢測,可能由于參數不當,而造成過多的局部細節。
3 為什么用高斯核
圖像的金字塔化能高效地(計算效率也較高)對圖像進行多尺度的表達,但它缺乏堅實的理論基礎,不能分析圖像中物體的各種尺度(雖然我們有小貓的金字塔圖像,我們還是不知道原圖像內小貓的大小)。
信號的尺度空間剛提出是就是通過一系列單參數、寬度遞增的高斯濾波器將原始信號濾波得到到組低頻信號。那么一個很明顯的疑問是:除了高斯濾波之外,其他帶有參數t的低通濾波器是否也可以用來生成一個尺度空間。
后來Koenerink、Lindeberg[Scale-space theory in computer vision]、Florack等人用精確的數學形式通過不同的途徑都證明了高斯核是實現尺度變換的唯一變換核。
雖然很多研究者從可分性、旋轉不變性、因果性等特性推出高斯濾波器是建立線性尺度空間的最優濾波器。然后在數字圖像處理中,需要對核函數進行采樣,離散的高斯函數并不滿足連續高斯函數的的一些優良的性質。所以后來出現了一些非線性的濾波器組來建立尺度空間,如B樣條核函數。
使用高斯濾波器對圖像進行尺度空間金塔塔圖的構建,讓這個尺度空間具有下面的性質:
1)加權平均和有限孔徑效應
信號在尺度t上的表達可以看成是原信號在空間上的一系列加權平均,權重就是具有不同尺度參數的高斯核。
信號在尺度t上的表達也對應于用一個無方向性的孔徑函數(特征長度為σ=t√)來觀測信號的結果。這時候信號中特征長度小于σ的精細結構會被抑制[理解為一維信號上小于σ的波動會被平滑掉。]。
2)層疊平滑
也叫高斯核族的半群(Semi-Group)性質:兩個高斯核的卷積等同于另外一個不同核參數的高斯核卷積。
g(μ,σ1)?g(μ,σ2)=g(μ,σ21+σ22???????√)這個性質的意思就是說不同的高斯核對圖像的平滑是連續的。
3)局部極值遞性
這個特征可以從人眼的視覺原理去理解,人在看一件物體時,離得越遠,物體的細節看到的越少,細節特征是在減少的。
高斯核對圖像進行濾波具有壓制局部細節的性質。
4)尺度伸縮不變性。
這里只是一個公式推導的問題,對原來的信號加一個變換函數,對變換后的信號再進行高斯核的尺度空間生成,新的信號的極值點等特征是不變的。
Young對經生理學的研究中發現,哺乳動物的視網膜和視覺皮層的感受區域可以很好地用4階以內的高斯微分來建模。
4 尺度的選擇
一般我們采集到的圖像中,我們并不知道我們感興趣的目標在圖像中的尺度,在這樣的情況下,我們對圖像進行分析時就無法選擇合適的參數,比如邊緣檢測,可能由于參數不當,而造成過多的局部細節。
如下圖所示:紅色圓圈內的斑點的大小(直徑)比例對應著兩幅圖像之間尺度比例(scale ratio)。如果對兩幅圖像采用相同的固定尺度的LoG檢測器檢測,很難將這兩個斑點檢測出來。LoG檢測器相當于一個匹配濾波器,只有當LoG的尺度與圖片中斑點結構尺度相當時才會有較強的響應。如果用與左圖中斑點結構相當大小尺度LoG算子,在中的大斑點的對應的LoG響應很小不能被檢測出來,反之亦然。因此固定尺度的LoG斑點檢測器不具有尺度不變性。使用尺度空間進行多尺度檢測可以將兩幅圖像中不同尺度的斑點檢測出來。但是由于斑點結構是在一定尺度范圍之內存在的,比如用5~8尺度的LoG可能都能檢測出來右邊圖像中的斑點結構,所以在尺度空間中進行斑點檢測會有重復檢測的缺點。
?
在實際操作中,我們需要定義一個特征響應函數,在不同的尺度空間上尋找一個極值點。比如小貓的金字塔圖像分析時,我們定義了一個大小為[w,h]的小貓的模板,用這個模板去與金字塔系列圖像匹配,一定有匹配度最佳(即特征響應最強)。
需要注意的是,圖像結構往往是在粗糙的尺度上被檢測到,此時位置信息未必是最準確的,因此通常圖像的尺度分析包含兩個階段:首先在粗尺度上進行特征(結構)檢測,然后再在細尺度上進行精確定位。
5.高斯金字塔
Gaussian Pyramid
Imagine the pyramid as a set of layers in which the higher the layer, the smaller the size.
Every layer is numbered from bottom to top, so layer??(denoted as??is smaller than layer??().
To produce layer??in the Gaussian pyramid, we do the following:
Convolve??with a Gaussian kernel:
Remove every even-numbered row and column.
You can easily notice that the resulting image will be exactly one-quarter the area of its predecessor. Iterating this process on the input image??(original image) produces the entire pyramid.
The procedure above was useful to downsample an image. What if we want to make it bigger?:
- First, upsize the image to twice the original in each dimension, wit the new even rows and columns filled with zeros ()
- Perform a convolution with the same kernel shown above (multiplied by 4) to approximate the values of the “missing pixels”
These two procedures (downsampling and upsampling as explained above) are implemented by the OpenCV functions?pyrUp?and?pyrDown, as we will see in an example with the code below:
Note
?When we reduce the size of an image, we are actually?losing?information of the image.
Code
This tutorial code’s is shown lines below. You can also download it from?here
#include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #include <math.h> #include <stdlib.h> #include <stdio.h>using namespace cv;/// Global variables Mat src, dst, tmp; char* window_name = "Pyramids Demo";/** * @function main */ int main( int argc, char** argv ) {/// General instructionsprintf( "\n Zoom In-Out demo \n " );printf( "------------------ \n" );printf( " * [u] -> Zoom in \n" );printf( " * [d] -> Zoom out \n" );printf( " * [ESC] -> Close program \n \n" );/// Test image - Make sure it s divisible by 2^{n}src = imread( "../images/chicky_512.jpg" );if( !src.data ){ printf(" No data! -- Exiting the program \n");return -1; }tmp = src;dst = tmp;/// Create windownamedWindow( window_name, CV_WINDOW_AUTOSIZE );imshow( window_name, dst );/// Loopwhile( true ){int c;c = waitKey(10);if( (char)c == 27 ){ break; }if( (char)c == 'u' ){ pyrUp( tmp, dst, Size( tmp.cols*2, tmp.rows*2 ) );printf( "** Zoom In: Image x 2 \n" );}else if( (char)c == 'd' ){ pyrDown( tmp, dst, Size( tmp.cols/2, tmp.rows/2 ) );printf( "** Zoom Out: Image / 2 \n" );}imshow( window_name, dst );tmp = dst;}return 0; }
Explanation
Let’s check the general structure of the program:
Load an image (in this case it is defined in the program, the user does not have to enter it as an argument)
/// Test image - Make sure it s divisible by 2^{n} src = imread( "../images/chicky_512.jpg" ); if( !src.data ){ printf(" No data! -- Exiting the program \n");return -1; }
Create a Mat object to store the result of the operations (dst) and one to save temporal results (tmp).
Mat src, dst, tmp; /* ... */ tmp = src; dst = tmp;
Create a window to display the result
namedWindow( window_name, CV_WINDOW_AUTOSIZE ); imshow( window_name, dst );
Perform an infinite loop waiting for user input.
while( true ) {int c;c = waitKey(10);if( (char)c == 27 ){ break; }if( (char)c == 'u' ){ pyrUp( tmp, dst, Size( tmp.cols*2, tmp.rows*2 ) );printf( "** Zoom In: Image x 2 \n" );}else if( (char)c == 'd' ){ pyrDown( tmp, dst, Size( tmp.cols/2, tmp.rows/2 ) );printf( "** Zoom Out: Image / 2 \n" );}imshow( window_name, dst );tmp = dst; }
Our program exits if the user presses?ESC. Besides, it has two options:
Perform upsampling (after pressing ‘u’)
pyrUp( tmp, dst, Size( tmp.cols*2, tmp.rows*2 )
We use the function?pyrUp?with 03 arguments:
- tmp: The current image, it is initialized with the?src?original image.
- dst: The destination image (to be shown on screen, supposedly the double of the input image)
- Size( tmp.cols*2, tmp.rows*2 )?: The destination size. Since we are upsampling,?pyrUp?expects a size double than the input image (in this case?tmp).
Perform downsampling (after pressing ‘d’)
pyrDown( tmp, dst, Size( tmp.cols/2, tmp.rows/2 )
Similarly as with?pyrUp, we use the function?pyrDown?with 03 arguments:
- tmp: The current image, it is initialized with the?src?original image.
- dst: The destination image (to be shown on screen, supposedly half the input image)
- Size( tmp.cols/2, tmp.rows/2 )?: The destination size. Since we are upsampling,?pyrDown?expects half the size the input image (in this case?tmp).
Notice that it is important that the input image can be divided by a factor of two (in both dimensions). Otherwise, an error will be shown.
Finally, we update the input image?tmp?with the current image displayed, so the subsequent operations are performed on it.
tmp = dst;
Results
After compiling the code above we can test it. The program calls an image?chicky_512.jpg?that comes in the?tutorial_code/image?folder. Notice that this image is?, hence a downsample won’t generate any error (). The original image is shown below:
First we apply two successive?pyrDown?operations by pressing ‘d’. Our output is:
Note that we should have lost some resolution due to the fact that we are diminishing the size of the image. This is evident after we apply?pyrUp?twice (by pressing ‘u’). Our output is now:
六:補充說明【OpenCV】SIFT原理與源碼分析:DoG尺度空間構造
《SIFT原理與源碼分析》系列文章索引:http://blog.csdn.net/xiaowei_cqu/article/details/8069548
尺度空間理論
自然界中的物體隨著觀測尺度不同有不同的表現形態。例如我們形容建筑物用“米”,觀測分子、原子等用“納米”。更形象的例子比如Google地圖,滑動鼠標輪可以改變觀測地圖的尺度,看到的地圖繪制也不同;還有電影中的拉伸鏡頭等等……尺度空間中各尺度圖像的模糊程度逐漸變大,能夠模擬人在距離目標由近到遠時目標在視網膜上的形成過程。
尺度越大圖像越模糊。
為什么要討論尺度空間?
用機器視覺系統分析未知場景時,計算機并不預先知道圖像中物體的尺度。我們需要同時考慮圖像在多尺度下的描述,獲知感興趣物體的最佳尺度。另外如果不同的尺度下都有同樣的關鍵點,那么在不同的尺度的輸入圖像下就都可以檢測出來關鍵點匹配,也就是尺度不變性。圖像的尺度空間表達就是圖像在所有尺度下的描述。
尺度空間表達與金字塔多分辨率表達
高斯模糊
高斯核是唯一可以產生多尺度空間的核(《Scale-space theory: A basic tool for analysing structures at different scales》)。一個圖像的尺度空間L(x,y,σ) ,定義為原始圖像I(x,y)與一個可變尺度的2維高斯函數G(x,y,σ)卷積運算。?
二維空間高斯函數:
尺度空間:
尺度是自然客觀存在的,不是主觀創造的。高斯卷積只是表現尺度空間的一種形式。
二維空間高斯函數是等高線從中心成正太分布的同心圓:
分布不為零的點組成卷積陣與原始圖像做變換,即每個像素值是周圍相鄰像素值的高斯平均。一個5*5的高斯模版如下所示:
高斯模版是圓對稱的,且卷積的結果使原始像素值有最大的權重,距離中心越遠的相鄰像素值權重也越小。
在實際應用中,在計算高斯函數的離散近似時,在大概3σ距離之外的像素都可以看作不起作用,這些像素的計算也就可以忽略。所以,通常程序只計算(6σ+1)*(6σ+1)就可以保證相關像素影響。
高斯模糊另一個很厲害的性質就是線性可分:使用二維矩陣變換的高斯模糊可以通過在水平和豎直方向各進行一維高斯矩陣變換相加得到。
O(N^2*m*n)次乘法就縮減成了O(N*m*n)+O(N*m*n)次乘法。(N為高斯核大小,m,n為二維圖像高和寬)
其實高斯這一部分只需要簡單了解就可以了,在OpenCV也只需要一句代碼:
[cpp]?view plain?copy- GaussianBlur(dbl,?dbl,?Size(),?sig_diff,?sig_diff);??
金字塔多分辨率
金字塔是早期圖像多尺度的表示形式。圖像金字塔化一般包括兩個步驟:使用低通濾波器平滑圖像;對平滑圖像進行降采樣(通常是水平,豎直方向1/2),從而得到一系列尺寸縮小的圖像。
上圖中(a)是對原始信號進行低通濾波,(b)是降采樣得到的信號。
而對于二維圖像,一個傳統的金字塔中,每一層圖像由上一層分辨率的長、寬各一半,也就是四分之一的像素組成:
多尺度和多分辨率
尺度空間表達和金字塔多分辨率表達之間最大的不同是:
- 尺度空間表達是由不同高斯核平滑卷積得到,在所有尺度上有相同的分辨率;
- 而金字塔多分辨率表達每層分辨率減少固定比率。
DoG(Difference of Gaussian)
高斯拉普拉斯LoG金字塔
結合尺度空間表達和金字塔多分辨率表達,就是在使用尺度空間時使用金字塔表示,也就是計算機視覺中最有名的拉普拉斯金子塔(《The Laplacian pyramid as a compact image code》)。高斯拉普拉斯LoG(Laplace of Guassian)算子就是對高斯函數進行拉普拉斯變換:核心思想還是高斯,這個不多敘述。
高斯差分DoG金字塔
DoG(Difference of Gaussian)其實是對高斯拉普拉斯LoG的近似,也就是對的近似。SIFT算法建議,在某一尺度上的特征檢測可以通過對兩個相鄰高斯尺度空間的圖像相減,得到DoG的響應值圖像D(x,y,σ)。然后仿照LoG方法,通過對響應值圖像D(x,y,σ)進行局部最大值搜索,在空間位置和尺度空間定位局部特征點。其中:k為相鄰兩個尺度空間倍數的常數。
上圖中(a)是DoG的三維圖,(b)是DoG與LoG的對比。
金字塔構建
構建高斯金字塔
為了得到DoG圖像,先要構造高斯金字塔。我們回過頭來繼續說高斯金字塔~高斯金字塔在多分辨率金字塔簡單降采樣基礎上加了高斯濾波,也就是對金字塔每層圖像用不同參數的σ做高斯模糊,使得每層金字塔有多張高斯模糊圖像。金字塔每層多張圖像合稱為一組(Octave),每組有多張(也叫層Interval)圖像。另外,降采樣時,金字塔上邊一組圖像的第一張圖像(最底層的一張)是由前一組(金字塔下面一組)圖像的倒數第三張隔點采樣得到。以下是OpenCV中構建高斯金字塔的代碼,我加了相應的注釋:[cpp]?view plain?copy
- //?構建nOctaves組(每組nOctaves+3層)高斯金字塔??
- void?SIFT::buildGaussianPyramid(?const?Mat&?base,?vector<Mat>&?pyr,?int?nOctaves?)?const??
- {??
- ????vector<double>?sig(nOctaveLayers?+?3);??
- ????pyr.resize(nOctaves*(nOctaveLayers?+?3));??
- ??
- ????//?precompute?Gaussian?sigmas?using?the?following?formula:??
- ????//??\sigma_{total}^2?=?\sigma_{i}^2?+?\sigma_{i-1}^2、??
- ????//?計算對圖像做不同尺度高斯模糊的尺度因子??
- ????sig[0]?=?sigma;??
- ????double?k?=?pow(?2.,?1.?/?nOctaveLayers?);??
- ????for(?int?i?=?1;?i?<?nOctaveLayers?+?3;?i++?)??
- ????{??
- ????????double?sig_prev?=?pow(k,?(double)(i-1))*sigma;??
- ????????double?sig_total?=?sig_prev*k;??
- ????????sig[i]?=?std::sqrt(sig_total*sig_total?-?sig_prev*sig_prev);??
- ????}??
- ??
- ????for(?int?o?=?0;?o?<?nOctaves;?o++?)??
- ????{??
- ????????//?DoG金子塔需要nOctaveLayers+2層圖像來檢測nOctaves層尺度??
- ????????//?所以高斯金字塔需要nOctaveLayers+3層圖像得到nOctaveLayers+2層DoG金字塔??
- ????????for(?int?i?=?0;?i?<?nOctaveLayers?+?3;?i++?)??
- ????????{??
- ????????????//?dst為第o組(Octave)金字塔??
- ????????????Mat&?dst?=?pyr[o*(nOctaveLayers?+?3)?+?i];??
- ????????????//?第0組第0層為原始圖像??
- ????????????if(?o?==?0??&&??i?==?0?)??
- ????????????????dst?=?base;??
- ??????????????
- ????????????//?base?of?new?octave?is?halved?image?from?end?of?previous?octave??
- ????????????//?每一組第0副圖像時上一組倒數第三幅圖像隔點采樣得到??
- ????????????else?if(?i?==?0?)??
- ????????????{??
- ????????????????const?Mat&?src?=?pyr[(o-1)*(nOctaveLayers?+?3)?+?nOctaveLayers];??
- ????????????????resize(src,?dst,?Size(src.cols/2,?src.rows/2),??
- ???????????????????????0,?0,?INTER_NEAREST);??
- ????????????}??
- ????????????//?每一組第i副圖像是由第i-1副圖像進行sig[i]的高斯模糊得到??
- ????????????//?也就是本組圖像在sig[i]的尺度空間下的圖像??
- ????????????else??
- ????????????{??
- ????????????????const?Mat&?src?=?pyr[o*(nOctaveLayers?+?3)?+?i-1];??
- ????????????????GaussianBlur(src,?dst,?Size(),?sig[i],?sig[i]);??
- ????????????}??
- ????????}??
- ????}??
- }??
高斯金字塔的組數為:
代碼10-17行是計算高斯模糊的系數σ,具體關系如下:
其中,σ為尺度空間坐標,s為每組中層坐標,σ0為初始尺度,S為每組層數(一般為3~5)。根據這個公式,我們可以得到金字塔組內各層尺度以及組間各圖像尺度關系。
組內相鄰圖像尺度關系:
相鄰組間尺度關系:
所以,相鄰兩組的同一層尺度為2倍的關系。
最終尺度序列總結為:
o為金字塔組數,n為每組金字塔層數。
構建DoG金字塔
構建高斯金字塔之后,就是用金字塔相鄰圖像相減構造DoG金字塔。下面為構造DoG的代碼:[cpp]?view plain?copy
- //?構建nOctaves組(每組nOctaves+2層)高斯差分金字塔??
- void?SIFT::buildDoGPyramid(?const?vector<Mat>&?gpyr,?vector<Mat>&?dogpyr?)?const??
- {??
- ????int?nOctaves?=?(int)gpyr.size()/(nOctaveLayers?+?3);??
- ????dogpyr.resize(?nOctaves*(nOctaveLayers?+?2)?);??
- ??
- ????for(?int?o?=?0;?o?<?nOctaves;?o++?)??
- ????{??
- ????????for(?int?i?=?0;?i?<?nOctaveLayers?+?2;?i++?)??
- ????????{??
- ????????????//?第o組第i副圖像為高斯金字塔中第o組第i+1和i組圖像相減得到??
- ????????????const?Mat&?src1?=?gpyr[o*(nOctaveLayers?+?3)?+?i];??
- ????????????const?Mat&?src2?=?gpyr[o*(nOctaveLayers?+?3)?+?i?+?1];??
- ????????????Mat&?dst?=?dogpyr[o*(nOctaveLayers?+?2)?+?i];??
- ????????????subtract(src2,?src1,?dst,?noArray(),?CV_16S);??
- ????????}??
- ????}??
- } ?
SIFT簡介
Scale Invariant Feature Transform,尺度不變特征變換匹配算法,是由David G. Lowe在1999年(《Object Recognition from Local Scale-Invariant Features》)提出的高效區域檢測算法,在2004年(《Distinctive Image Features from Scale-Invariant Keypoints》)得以完善。
SIFT特征對旋轉、尺度縮放、亮度變化等保持不變性,是非常穩定的局部特征,現在應用很廣泛。而SIFT算法是將Blob檢測,特征矢量生成,特征匹配搜索等步驟結合在一起優化。我會更新一系列文章,分析SIFT算法原理及OpenCV 2.4.2實現的SIFT源碼:
- DoG尺度空間構造(Scale-space extrema detection)http://blog.csdn.net/xiaowei_cqu/article/details/8067881
- 關鍵點搜索與定位(Keypoint localization)http://blog.csdn.net/xiaowei_cqu/article/details/8087239
- 方向賦值(Orientation assignment)http://blog.csdn.net/xiaowei_cqu/article/details/8096072
- 關鍵點描述(Keypoint descriptor)http://blog.csdn.net/xiaowei_cqu/article/details/8113565
- OpenCV實現:特征檢測器FeatureDetectorhttp://blog.csdn.net/xiaowei_cqu/article/details/8652096
- SIFT中LoG和DoG的比較http://blog.csdn.net/xiaowei_cqu/article/details/27692123
SIFT in OpenCV
OpenCV中的SIFT函數主要有兩個接口。構造函數:
[cpp]?view plain?copy- SIFT::SIFT(int?nfeatures=0,?int?nOctaveLayers=3,?double?contrastThreshold=0.04,?double?edgeThreshold=??
- 10,?double?sigma=1.6)??
nOctaveLayers:金字塔中每組的層數(算法中會自己計算這個值,后面會介紹)。
contrastThreshold:過濾掉較差的特征點的對閾值。contrastThreshold越大,返回的特征點越少。
edgeThreshold:過濾掉邊緣效應的閾值。edgeThreshold越大,特征點越多(被多濾掉的越少)。
sigma:金字塔第0層圖像高斯濾波系數,也就是σ。
重載操作符:
[cpp]?view plain?copy- void?SIFT::operator()(InputArray?img,?InputArray?mask,?vector<KeyPoint>&?keypoints,?OutputArray??
- descriptors,?bool?useProvidedKeypoints=false)??
img:8bit灰度圖像
mask:圖像檢測區域(可選)
keypoints:特征向量矩陣
descipotors:特征點描述的輸出向量(如果不需要輸出,需要傳cv::noArray())。
useProvidedKeypoints:是否進行特征點檢測。ture,則檢測特征點;false,只計算圖像特征描述。
函數源碼
構造函數SIFT()主要用來初始化參數,并沒有特定的操作:[cpp]?view plain?copy- SIFT::SIFT(?int?_nfeatures,?int?_nOctaveLayers,??
- ???????????double?_contrastThreshold,?double?_edgeThreshold,?double?_sigma?)??
- ????:?nfeatures(_nfeatures),?nOctaveLayers(_nOctaveLayers),??
- ????contrastThreshold(_contrastThreshold),?edgeThreshold(_edgeThreshold),?sigma(_sigma)??
- ????//?sigma:對第0層進行高斯模糊的尺度空間因子。??
- ????//?默認為1.6(如果是軟鏡攝像頭捕獲的圖像,可以適當減小此值)??
- {??
- }??
主要操作還是利用重載操作符()來執行:[cpp]?view plain?copy
- void?SIFT::operator()(InputArray?_image,?InputArray?_mask,??
- ??????????????????????vector<KeyPoint>&?keypoints,??
- ??????????????????????OutputArray?_descriptors,??
- ??????????????????????bool?useProvidedKeypoints)?const??
- //?mask?:Optional?input?mask?that?marks?the?regions?where?we?should?detect?features.??
- //?Boolean?flag.?If?it?is?true,?the?keypoint?detector?is?not?run.?Instead,??
- //?the?provided?vector?of?keypoints?is?used?and?the?algorithm?just?computes?their?descriptors.??
- //?descriptors?–?The?output?matrix?of?descriptors.??
- //?Pass?cv::noArray()?if?you?do?not?need?them.??????????????
- {??
- ????Mat?image?=?_image.getMat(),?mask?=?_mask.getMat();??
- ??
- ????if(?image.empty()?||?image.depth()?!=?CV_8U?)??
- ????????CV_Error(?CV_StsBadArg,?"image?is?empty?or?has?incorrect?depth?(!=CV_8U)"?);??
- ??
- ????if(?!mask.empty()?&&?mask.type()?!=?CV_8UC1?)??
- ????????CV_Error(?CV_StsBadArg,?"mask?has?incorrect?type?(!=CV_8UC1)"?);??
- ??
- ??????????
- ????//?得到第1組(Octave)圖像??
- ????Mat?base?=?createInitialImage(image,?false,?(float)sigma);??
- ????vector<Mat>?gpyr,?dogpyr;??
- ????//?每層金字塔圖像的組數(Octave)??
- ????int?nOctaves?=?cvRound(log(?(double)std::min(?base.cols,?base.rows?)?)?/?log(2.)?-?2);??
- ??
- ????//?double?t,?tf?=?getTickFrequency();??
- ????//?t?=?(double)getTickCount();??
- ??????
- ????//?構建金字塔(金字塔層數和組數相等)??
- ????buildGaussianPyramid(base,?gpyr,?nOctaves);??
- ????//?構建高斯差分金字塔??
- ????buildDoGPyramid(gpyr,?dogpyr);??
- ??
- ????//t?=?(double)getTickCount()?-?t;??
- ????//printf("pyramid?construction?time:?%g\n",?t*1000./tf);??
- ??????
- ????//?useProvidedKeypoints默認為false??
- ????//?使用keypoints并計算特征點的描述符??
- ????if(?!useProvidedKeypoints?)??
- ????{??
- ????????//t?=?(double)getTickCount();??
- ????????findScaleSpaceExtrema(gpyr,?dogpyr,?keypoints);??
- ????????//除去重復特征點??
- ????????KeyPointsFilter::removeDuplicated(?keypoints?);???
- ??
- ????????//?mask標記檢測區域(可選)??
- ????????if(?!mask.empty()?)??
- ????????????KeyPointsFilter::runByPixelsMask(?keypoints,?mask?);??
- ??
- ????????//?retainBest:根據相應保留指定數目的特征點(features2d.hpp)??
- ????????if(?nfeatures?>?0?)??
- ????????????KeyPointsFilter::retainBest(keypoints,?nfeatures);??
- ????????//t?=?(double)getTickCount()?-?t;??
- ????????//printf("keypoint?detection?time:?%g\n",?t*1000./tf);??
- ????}??
- ????else??
- ????{??
- ????????//?filter?keypoints?by?mask??
- ????????//?KeyPointsFilter::runByPixelsMask(?keypoints,?mask?);??
- ????}??
- ??
- ????//?特征點輸出數組??
- ????if(?_descriptors.needed()?)??
- ????{??
- ????????//t?=?(double)getTickCount();??
- ????????int?dsize?=?descriptorSize();??
- ????????_descriptors.create((int)keypoints.size(),?dsize,?CV_32F);??
- ????????Mat?descriptors?=?_descriptors.getMat();??
- ??
- ????????calcDescriptors(gpyr,?keypoints,?descriptors,?nOctaveLayers);??
- ????????//t?=?(double)getTickCount()?-?t;??
- ????????//printf("descriptor?extraction?time:?%g\n",?t*1000./tf);??
- ????}??
- }??
函數中用到的構造金字塔:?buildGaussianPyramid(base, gpyr, nOctaves);等步驟請參見文章后續系列。
[1] opencv教程:圖像金字塔
[2] 計算機視覺——算法與應用 3.5節 金字塔與小波
[3] 現代數字圖像 1.1節 圖像多分辨率真金字塔
[4] OpenCV的5種圖像內插方法
[5]? http://www.360doc.com/content/15/1115/12/25664332_513338038.shtml
[6] http://blog.csdn.net/xiaowei_cqu/article/details/8067881
[7] http://blog.csdn.net/xiaowei_cqu/article/details/8069548
[8]
總結
以上是生活随笔為你收集整理的尺度空间理论与图像金字塔的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小米电视机芯片哪个最好?
- 下一篇: OpenCV3.2.0+VS2017在w