金字塔的底部是待處理圖像的高分辨率表示,而頂部是低分辨率的近似。我們將一層一層的圖像比喻成金字塔,層級越高,則圖像越小,分辨率越低
一、兩個金字塔
- 高斯金字塔(Gaussianpyramid): 用來向下採樣,主要的圖像金字塔
- 拉普拉斯金字塔(Laplacianpyramid): 用來從金字塔低層圖像重建上層未採樣圖像,在數字圖像處理中也即是預測殘差,可以對圖像進行最大程度的還原,配合高斯金字塔一起使用。
高斯金字塔不同(DoG)又稱爲拉普拉斯金字塔,給出計算方式前,先加強一下定義
記得在上面我們定義了G0,G1,G2
G0下采樣獲得G1
G1上採樣獲得Upsample(G1),注意Upsample(G1)不等於G0,上採樣和下采樣不是可逆過程,這是因爲下采樣損失了圖片信息
在此,給出計算拉普拉斯金字塔(DOG)的公式:L(i) = G(i) –Upsample(G(i+1))
二、採樣
- 對圖像向上採樣:pyrUp函數
- <1>對圖像G_i進行高斯內核卷積
- <2>將所有偶數行和列去除
- 對圖像向下採樣:pyrDown函數
- <1>將圖像在每個方向擴大爲原來的兩倍,新增的行和列以0填充
- <2>使用先前同樣的內核(乘以4)與放大後的圖像卷積,獲得 “新增像素”的近似值
注意:這裏的向下與向上採樣,是對圖像的尺寸而言的(和金字塔的方向相反),向上就是圖像尺寸加倍,向下就是圖像尺寸減半。而如果我們按上圖中演示的金字塔方向來理解,金字塔向上圖像其實在縮小,這樣剛好是反過來了。
注意:PryUp和PryDown不是互逆的,即PryUp不是降採樣的逆操作。這種情況下,圖像首先在每個維度上擴大爲原來的兩倍,新增的行(偶數行)以0填充。然後給指定的濾波器進行卷積(實際上是一個在每個維度都擴大爲原來兩倍的過濾器)去估計“丟失”像素的近似值。
PryDown( )是一個會丟失信息的函數。爲了恢復原來更高的分辨率的圖像,我們要獲得由降採樣操作丟失的信息,這些數據就和拉普拉斯金字塔有關係了。
三、函數介紹
<span style="font-size:18px;">C++: void pyrUp(InputArray src, OutputArraydst, const Size& dstsize=Size(), int borderType=BORDER_DEFAULT )
</span>
- 第一個參數,InputArray類型的src,輸入圖像,即源圖像,填Mat類的對象即可。
- 第二個參數,OutputArray類型的dst,輸出圖像,和源圖片有一樣的尺寸和類型。
- 第三個參數,const Size&類型的dstsize,輸出圖像的大小;有默認值Size(),即默認情況下,由Size(src.cols*2,src.rows*2)來進行計算,且一直需要滿足下列條件:
- 第四個參數,int類型的borderType,又來了,邊界模式,一般我們不用去管它。
C++: void pyrDown(InputArray src,OutputArray dst, const Size& dstsize=Size(), int borderType=BORDER_DEFAULT)
- 第一個參數,InputArray類型的src,輸入圖像,即源圖像,填Mat類的對象即可。
- 第二個參數,OutputArray類型的dst,輸出圖像,和源圖片有一樣的尺寸和類型。
- 第三個參數,const Size&類型的dstsize,輸出圖像的大小;有默認值Size(),即默認情況下,由Size Size((src.cols+1)/2, (src.rows+1)/2)來進行計算,且一直需要滿足下列條件:
該pyrDown函數執行了高斯金字塔建造的向下採樣的步驟。首先,它將源圖像與如下內核做卷積運算:
接着,它便通過對圖像的偶數行和列做插值來進行向下採樣操作。
pyrUp函數執行高斯金字塔的採樣操作,其實它也可以用於拉普拉斯金字塔的。
首先,它通過插入可爲零的行與列,對源圖像進行向上取樣操作,然後將結果與pyrDown()乘以4的內核做卷積,就是這樣。
<span style="font-size:18px;">C++: void resize(InputArray src,OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR )</span>
- 第一個參數,InputArray類型的src,輸入圖像,即源圖像,填Mat類的對象即可。
- 第二個參數,OutputArray類型的dst,輸出圖像,當其非零時,有着dsize(第三個參數)的尺寸,或者由src.size()計算出來。
- 第三個參數,Size類型的dsize,輸出圖像的大小;如果它等於零,由下式進行計算:
其中,dsize,fx,fy都不能爲0。
- 第四個參數,double類型的fx,沿水平軸的縮放係數,有默認值0,且當其等於0時,由下式進行計算:
- 第五個參數,double類型的fy,沿垂直軸的縮放係數,有默認值0,且當其等於0時,由下式進行計算:
- 第六個參數,int類型的interpolation,用於指定插值方式,默認爲INTER_LINEAR(線性插值)。
可選的插值方式如下:
- INTER_NEAREST - 最近鄰插值
- INTER_LINEAR - 線性插值(默認值)
- INTER_AREA - 區域插值(利用像素區域關係的重採樣插值)
- INTER_CUBIC –三次樣條插值(超過4×4像素鄰域內的雙三次插值)
- INTER_LANCZOS4 -Lanczos插值(超過8×8像素鄰域的Lanczos插值)
若要縮小圖像,一般情況下最好用CV_INTER_AREA來插值,
而若要放大圖像,一般情況下最好用CV_INTER_CUBIC(效率不高,慢,不推薦使用)或CV_INTER_LINEAR(效率較高,速度較快,推薦使用)。
四、插值介紹
1、最鄰近元法
這是最簡單的一種插值方法,不需要計算,在待求象素的四鄰象素中,將距離待求象素最近的鄰象素灰度賦給待求象素。設i+u, j+v(i, j爲正整數, u, v爲大於零小於1的小數,下同)爲待求象素座標,則待求象素灰度的值 f(i+u, j+v) 如下圖所示:
如果(i+u, j+v)落在A區,即u<0.5, v<0.5,則將左上角象素的灰度值賦給待求象素,同理,落在B區則賦予右上角的象素灰度值,落在C區則賦予左下角象素的灰度值,落在D區則賦予右下角象素的灰度值。
最鄰近元法計算量較小,但可能會造成插值生成的圖像灰度上的不連續,在灰度變化的地方可能出現明顯的鋸齒狀。
2、雙線性內插法
雙線性內插法是利用待求象素四個鄰象素的灰度在兩個方向上作線性內插,如下圖所示:
對於 (i, j+v),f(i, j) 到 f(i, j+1) 的灰度變化爲線性關係,則有:
f(i, j+v) = [f(i, j+1) - f(i, j)] * v + f(i, j)
同理對於 (i+1, j+v) 則有:
f(i+1, j+v) = [f(i+1, j+1) - f(i+1, j)] * v + f(i+1, j)
從f(i, j+v) 到 f(i+1, j+v) 的灰度變化也爲線性關係,由此可推導出待求象素灰度的計算式如下:
f(i+u, j+v) = (1-u) * (1-v) * f(i, j) + (1-u) * v * f(i, j+1) + u * (1-v) * f(i+1, j) + u * v * f(i+1, j+1)
雙線性內插法的計算比最鄰近點法複雜,計算量較大,但沒有灰度不連續的缺點,結果基本令人滿意。它具有低通濾波性質,使高頻分量受損,圖像輪廓可能會有一點模糊。
3、三次內插法
該方法利用三次多項式S(x)求逼近理論上最佳插值函數sin(x)/x, 其數學表達式爲:
待求像素(x, y)的灰度值由其周圍16個灰度值加權內插得到,如下圖:
待求像素的灰度計算式如下:
f(x, y) = f(i+u, j+v) = ABC
其中:
三次曲線插值方法計算量較大,但插值後的圖像效果最好。
五、綜合示例
<span style="font-size:18px;">#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace std;
using namespace cv;
Mat g_srcImage, g_dstImage, g_tmpImage;
int main( )
{
ShowHelpText();
g_srcImage = imread("lena.jpg");
if( !g_srcImage.data ) { printf("Oh,no,讀取srcImage錯誤~! \n"); return false; }
namedWindow( WINDOW_NAME, CV_WINDOW_AUTOSIZE );
imshow(WINDOW_NAME, g_srcImage);
g_tmpImage = g_srcImage;
g_dstImage = g_tmpImage;
int key =0;
while(1)
{
key=waitKey(9) ;
//根據key變量的值,進行不同的操作
switch(key)
{
case 27:
return 0;
break;
case 'q':
return 0;
break;
case 'a'://按鍵A按下,調用pyrUp函數
pyrUp( g_tmpImage, g_dstImage, Size( g_tmpImage.cols*2, g_tmpImage.rows*2 ) );
printf( ">檢測到按鍵【A】被按下,開始進行基於【pyrUp】函數的圖片放大:圖片尺寸×2 \n" );
break;
case 'w'://按鍵W按下,調用resize函數
resize(g_tmpImage,g_dstImage,Size( g_tmpImage.cols*2, g_tmpImage.rows*2 ));
printf( ">檢測到按鍵【W】被按下,開始進行基於【resize】函數的圖片放大:圖片尺寸×2 \n" );
break;
case '1'://按鍵1按下,調用resize函數
resize(g_tmpImage,g_dstImage,Size( g_tmpImage.cols*2, g_tmpImage.rows*2 ));
printf( ">檢測到按鍵【1】被按下,開始進行基於【resize】函數的圖片放大:圖片尺寸×2 \n" );
break;
case '3': //按鍵3按下,調用pyrUp函數
pyrUp( g_tmpImage, g_dstImage, Size( g_tmpImage.cols*2, g_tmpImage.rows*2 ));
printf( ">檢測到按鍵【3】被按下,開始進行基於【pyrUp】函數的圖片放大:圖片尺寸×2 \n" );
break;
//======================【圖片縮小相關鍵值處理】=======================
case 'd': //按鍵D按下,調用pyrDown函數
pyrDown( g_tmpImage, g_dstImage, Size( g_tmpImage.cols/2, g_tmpImage.rows/2 ));
printf( ">檢測到按鍵【D】被按下,開始進行基於【pyrDown】函數的圖片縮小:圖片尺寸/2\n" );
break;
case 's' : //按鍵S按下,調用resize函數
resize(g_tmpImage,g_dstImage,Size( g_tmpImage.cols/2, g_tmpImage.rows/2 ));
printf( ">檢測到按鍵【S】被按下,開始進行基於【resize】函數的圖片縮小:圖片尺寸/2\n" );
break;
case '2'://按鍵2按下,調用resize函數
resize(g_tmpImage,g_dstImage,Size( g_tmpImage.cols/2, g_tmpImage.rows/2 ),(0,0),(0,0),2);
printf( ">檢測到按鍵【2】被按下,開始進行基於【resize】函數的圖片縮小:圖片尺寸/2\n" );
break;
case '4': //按鍵4按下,調用pyrDown函數
pyrDown( g_tmpImage, g_dstImage, Size( g_tmpImage.cols/2, g_tmpImage.rows/2 ) );
printf( ">檢測到按鍵【4】被按下,開始進行基於【pyrDown】函數的圖片縮小:圖片尺寸/2\n" );
break;
}
//經過操作後,顯示變化後的圖
imshow( WINDOW_NAME, g_dstImage );
//將g_dstImage賦給g_tmpImage,方便下一次循環
g_tmpImage = g_dstImage;
}
return 0;
}
</span>
六、matlab
<span style="font-size:18px;">I = imread('d:\lena.jpg');
A = imresize(I, 1.5, 'nearest');
B = imresize(I, 1.5, 'bilinear');
C = imresize(I, 1.5, 'bicubic');
subplot(2,2,1), imshow(I), title('original');
subplot(2,2,2), imshow(A), title('nearest');
subplot(2,2,3), imshow(B), title('bilinear');
subplot(2,2,4), imshow(C), title('bicubic'); </span>
圖像識別算法交流 QQ羣:145076161,歡迎圖像識別與圖像算法,共同學習與交流