http://blog.csdn.net/xiaowei_cqu/article/details/8067881
《SIFT原理與源碼分析》系列文章索引:http://blog.csdn.net/xiaowei_cqu/article/details/8069548
尺度空間理論
尺度越大圖像越模糊。
爲什麼要討論尺度空間?
圖像的尺度空間表達就是圖像在所有尺度下的描述。
尺度空間表達與金字塔多分辨率表達
高斯模糊
高斯核是唯一可以產生多尺度空間的核(《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也只需要一句代碼:
- GaussianBlur(dbl, dbl, Size(), sig_diff, sig_diff);
- GaussianBlur(dbl, dbl, Size(), sig_diff, sig_diff);
我這裏詳寫了一下是因爲這塊兒對分析算法效率比較有用,而且高斯模糊的算法真的很漂亮~
金字塔多分辨率
金字塔是早期圖像多尺度的表示形式。圖像金字塔化一般包括兩個步驟:使用低通濾波器平滑圖像;對平滑圖像進行降採樣(通常是水平,豎直方向1/2),從而得到一系列尺寸縮小的圖像。
上圖中(a)是對原始信號進行低通濾波,(b)是降採樣得到的信號。
而對於二維圖像,一個傳統的金字塔中,每一層圖像由上一層分辨率的長、寬各一半,也就是四分之一的像素組成:
多尺度和多分辨率
尺度空間表達和金字塔多分辨率表達之間最大的不同是:
- 尺度空間表達是由不同高斯核平滑卷積得到,在所有尺度上有相同的分辨率;
- 而金字塔多分辨率表達每層分辨率減少固定比率。
DoG(Difference of Gaussian)
高斯拉普拉斯LoG金字塔
高斯差分DoG金字塔
- // 構建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]);
- }
- }
- }
- }
- // 構建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);
- }
- }
- }
這個比較簡單,就是一個subtract()函數。
至此,SIFT第一步就完成了。參見《SIFT原理與源碼分析》