影象縮放——雙線性插值演算法
在數學上,雙線性插值是有兩個變數的插值函式的線性插值擴充套件,其核心思想是在兩個方向分別進行一次線性插值。如果選擇一個座標系統使得 的四個已知點座標分別為 (0, 0)、(0, 1)、(1, 0) 和 (1, 1),那麼插值公式就可以化簡為:
用矩陣運算來表示的話就是:
影象的空間變換,也稱幾何變換或幾何運算,包括影象的平移、旋轉、映象變換、轉置、縮放等。空間變換可如下表示:設(u,v)為源影象上的點,(x,y)為目標影象上的點,則空間變換就是將源影象上(u,v)處的顏色值與目標影象上(x,y)處的顏色對應起來。
計算機所處理的影象都是指點陣圖,也就是用一個畫素矩陣來描述一副影象。假如影象的象素矩陣如下所示:(這個矩陣中,圖象處理中最常用的座標系是:x從左到右,從0開始,y從上到下,也是從0開始)
234 38 22
67 44 12
89 65 63
如果想把這副圖放大為 4X4大小的影象,那麼第一步肯定想到的是先把4X4的矩陣先畫出來再說,好了矩陣畫出來了,如下所示,當然,矩陣的每個畫素都是未知數,等待著我們去填充:
? ? ? ?
? ? ? ?
? ? ? ?
? ? ? ?
然後要往這個空的矩陣裡面填值了,要填的值從哪裡來來呢?是從源圖中來,好,先填寫目標圖最左上角的象素,座標為(0,0),那麼該座標對應源圖中的座標可以由如下公式得出:
srcX = dstX * (srcWidth / dstWidth) , srcY = dstY * (srcHeight / dstHeight)
套用公式,就可以找到對應的原圖的座標了(0*(3/4),0*(3/4))=>(0*0.75,0*0.75)=>(0,0)
找到了源圖的對應座標,就可以把源圖中座標為(0,0)處的234象素值填進去目標圖的(0,0)這個位置了。
接下來,如法炮製,尋找目標圖中座標為(1,0)的象素對應源圖中的座標,套用公式:
(1*0.75,0*0.75)=>(0.75,0)
結果發現,得到的座標裡面竟然有小數,這可怎麼辦?計算機裡的影象可是數字影象,象素就是最小單位了,象素的座標都是整數,從來沒有小數座標。這時候採用的一種策略就是採用四捨五入的方法(也可以採用直接舍掉小數位的方法),把非整數座標轉換成整數,好,那麼按照四捨五入的方法就得到座標(1,0),完整的運算過程就是這樣的:
(1*0.75,0*0.75)=>(0.75,0)=>(1,0)
那麼就可以再填一個象素到目標矩陣中了,同樣是把源圖中座標為(1,0)處的畫素值38填入目標圖中的座標。
依次填完每個象素,一幅放大後的影象就誕生了,畫素矩陣如下所示:
234 38 22 22
67 44 12 12
89 65 63 63
89 65 63 63
這種放大影象的方法叫做最臨近插值演算法,這是一種最基本、最簡單的影象縮放演算法,效果也是最不好的,放大後的影象有很嚴重的馬賽克,縮小後的影象有很嚴重的失真;效果不好的根源就是其簡單的最臨近插值方法引入了嚴重的影象失真,比如,當由目標圖的座標反推得到的源圖的的座標是一個浮點數的時候,採用了四捨五入的方法,直接採用了和這個浮點數最接近的象素的值,這種方法是很不科學的,當推得座標值為 0.75的時候,不應該就簡單的取為1,既然是0.75,比1要小0.25 ,比0要大0.75 ,那麼目標象素值其實應該根據這個源圖中虛擬的點四周的四個真實的點來按照一定的規律計算出來的,這樣才能達到更好的縮放效果。
雙線型內插值演算法就是一種比較好的影象縮放演算法,它充分的利用了源圖中虛擬點四周的四個真實存在的畫素值來共同決定目標圖中的一個畫素值,因此縮放效果比簡單的最鄰近插值要好很多,計算量比零階插值大,但縮放後圖像質量高,不會出現畫素值不連續的情況。
雙線性內插值演算法描述如下:
對於一個目的畫素,設定座標通過反向變換得到的浮點座標為(i+u,j+v) (其中i、j均為浮點座標的整數部分,u、v為浮點座標的小數部分,是取值[0,1)區間的浮點數),則這個畫素得值 f(i+u,j+v) 可由原影象中座標為 (i,j)、(i+1,j)、(i,j+1)、(i+1,j+1)所對應的周圍四個畫素的值決定,即:
f(i+u,j+v) = (1-u)(1-v)f(i,j) + (1-u)vf(i,j+1) + u(1-v)f(i+1,j) + uvf(i+1,j+1)
其中f(i,j)表示源影象(i,j)處的的畫素值,以此類推。
假如目標圖的象素座標為(1,1),那麼反推得到的對應於源圖的座標是(0.75 , 0.75), 這其實只是一個概念上的虛擬象素,實際在源圖中並不存在這樣一個象素,那麼目標圖的象素(1,1)的取值不能夠由這個虛擬象素來決定,而只能由源圖的這四個象素共同決定:(0,0)(0,1)(1,0)(1,1),而由於(0.75,0.75)離(1,1)要更近一些,那麼(1,1)所起的決定作用更大一些,這從公式1中的係數uv=0.75×0.75就可以體現出來,而(0.75,0.75)離(0,0)最遠,所以(0,0)所起的決定作用就要小一些,公式中係數為(1-u)(1-v)=0.25×0.25也體現出了這一特點;
演算法步驟詳述:
假設原始影象大小為size=m×n,其中m與n分別是原始影象的行數與列數。若影象的縮放因子是t(t>0),則目標影象的大小size=t×m×t×n。對於目標影象的某個畫素點P(x,y)通過P*1/t可得到對應的原始影象座標P’( x1,y1),其中x1=x/t,y1=y/t,由於x1,y1都不是整數所以並不存在這樣的點,這樣可以找出與它相鄰的四個點的灰度f1、f2、f3、f4,使用雙線性插值演算法就可以得到這個畫素點P’(x1,y1)的灰度,也就是畫素點P(x,y)的灰度。
一個完整的雙線性插值演算法可描述如下:
(1)通過原始影象和比例因子得到新影象的大小,並建立新影象。
(2)由新影象的某個畫素(x,y)對映到原始影象(x’,y’)處。
(3)對x’,y’取整得到(xx,yy)並得到(xx,yy)、(xx+1,yy)、(xx,yy+1)和(xx+1,yy+1)的值。
(4)利用雙線性插值得到畫素點(x,y)的值並寫回新影象。
(5)重複步驟(2)直到新影象的所有畫素寫完。
核心部分程式碼如下:
//函式名Bilinear
//引數float k
//返回值無
//作用利用雙線性插值來實現影象縮放
void CChildView::Bilinear(float k)
{
int nBpp=m_imPicture .GetBPP ();
int widthNew,heightNew;//新影象的寬度和高度
float widthScale=(float)(1.0/k),heightScale=(float)(1.0/k);
float xx,yy;
int a,b;
int rr,gg,bb;//儲存R、G、B分量
//得到新影象的寬度和高度
widthNew=(int)(m_imPicture .GetWidth ()*k);
heightNew =(int)(m_imPicture .GetHeight ()*k);
//利用新影象的寬度和高度來建立新影象
m_imNewPicture .Destroy ();
m_imNewPicture .Create (widthNew ,heightNew ,nBpp);
//得到新、老影象的每行的位元組數
int nPitch=m_imPicture .GetPitch ();
int nPitchNew=m_imNewPicture .GetPitch ();
//得到新、老影象的資料指標
LPBYTE pBitsNew=(LPBYTE)m_imNewPicture .GetBits ();
LPBYTE pBits=(LPBYTE)m_imPicture .GetBits ();
if(m_imPicture.GetBPP ()!=24){
MessageBox ("必須是24點陣圖像或8點陣圖像");
m_imNewPicture .Destroy ();
Invalidate();
return ;
}
for(int x=(int)k;x<widthNew -k;x++){
for(int y=(int)k;y<heightNew -k;y++){
xx=x*widthScale ;
yy=y*heightScale ;
if(xx<=1e-8){
xx=0;
}
if(xx>m_imPicture .GetWidth ()-2)
xx=(float)(m_imPicture .GetWidth ()-2);
if(yy<=1e-8)
yy=0;
if(yy>m_imPicture .GetHeight ()-2)
yy=(float)(m_imPicture .GetHeight ()-2);
a=(int)xx;
b=(int)yy;
//分別得到對應畫素的R、G、B值並用雙線性插值得到新畫素的R、G、B值
int r11,r12,r21,r22;
r11=*(pBits+b*nPitch+3*a+2);
r12=*(pBits+b*nPitch+3*(a+1)+2);
r21=*(pBits+(b+1)*nPitch+3*a+2);
r22=*(pBits+(b+1)*nPitch+3*(a+1)+2);
rr=(int)(r11*(a+1-xx)*(b+1-yy)+r12*(a+1-xx)*(yy-b)
+r21*(xx-a)*(b+1-yy)+r22*(xx-a)*(yy-b));
int g11,g12,g21,g22;
g11=*(pBits+b*nPitch+3*a+1);
g12=*(pBits+b*nPitch+3*(a+1)+1);
g21=*(pBits+(b+1)*nPitch+3*a+1);
g22=*(pBits+(b+1)*nPitch+3*(a+1)+1);
gg=(int)(g11*(a+1-xx)*(b+1-yy)+g12*(a+1-xx)*(yy-b)
+g21*(xx-a)*(b+1-yy)+g22*(xx-a)*(yy-b));
int b11,b12,b21,b22;
b11=*(pBits+b*nPitch+3*a);
b12=*(pBits+b*nPitch+3*(a+1));
b21=*(pBits+(b+1)*nPitch+3*a);
b22=*(pBits+(b+1)*nPitch+3*(a+1));
bb=(int)(b11*(a+1-xx)*(b+1-yy)+b12*(a+1-xx)*(yy-b)
+b21*(xx-a)*(b+1-yy)+b22*(xx-a)*(yy-b));
//將得到的新R、G、B值寫到新影象中
*(pBitsNew +y*nPitchNew +x*3)=min(255,bb);
*(pBitsNew +y*nPitchNew +x*3+1)=min(255,gg);
*(pBitsNew +y*nPitchNew +x*3+2)=min(255,rr);
}
}
m_imPicture .Destroy ();
Invalidate ();
}