天天看點

雙線性插值算法及需要注意事項

原文部落格位址:http://handspeaker.iteye.com/blog/1545126

最近在程式設計時用到了雙線性插值算法,對圖像進行縮放。網上有很多這方面的資料,介紹的也算明白。但是,這些文章隻介紹了算法,并沒有具體說怎麼實作以及怎麼實作最好,舉個例子,你可以按照網上文章的算法自己寫一個雙線性插值程式,用它對一張圖檔進行處理,然後再用matlab或者openCV的resize函數對同一張圖檔進行處理,得到的結果是不一樣的,如果源圖檔較小,效果差距就更大。以下是對于雙線性插值的講解以及上述現象的解釋:

1.雙線性插值

假設源圖像大小為mxn,目标圖像為axb。那麼兩幅圖像的邊長比分别為:m/a和n/b。注意,通常這個比例不是整數,程式設計存儲的時候要用浮點型。目标圖像的第(i,j)個像素點(i行j列)可以通過邊長比對應回源圖像。其對應坐标為(i*m/a,j*n/b)。

顯然,這個對應坐标一般來說不是整數,而非整數的坐标是無法在圖像這種離散資料上使用的。雙線性插值通過尋找距離這個對應坐标最近的四個像素點,來計算該點的值(灰階值或者RGB值)。如果你的對應坐标是(2.5,4.5),那麼最近的四個像素是(2,4)、(2,5)、(3,4),(3,5)。

若圖像為灰階圖像,那麼(i,j)點的灰階值可以通過一下公式計算:

f(i,j)=w1*p1+w2*p2+w3*p3+w4*p4;

其中,pi(i=1,2,3,4)為最近的四個像素點,wi(i=1,2,3,4)為各點相應權值。關于權值的計算,在維基百科和百度百科上寫的很明白。

2.存在的問題

這部分的前提是,你已經明白什麼是雙線性插值并且在給定源圖像和目标圖像尺寸的情況下,可以用筆計算出目标圖像某個像素點的值。當然,最好的情況是你已經用某種語言實作了網上一大堆部落格上原創或轉載的雙線性插值算法,然後發現計算出來的結果和matlab、openCV對應的resize()函數得到的結果完全不一樣。

那這個究竟是怎麼回事呢?

其實答案很簡單,就是坐标系的選擇問題,或者說源圖像和目标圖像之間的對應問題。

按照網上一些部落格上寫的,源圖像和目标圖像的原點(0,0)均選擇左上角,然後根據插值公式計算目标圖像每點像素,假設你需要将一幅5x5的圖像縮小成3x3,那麼源圖像和目标圖像各個像素之間的對應關系如下:

隻畫了一行,用做示意,從圖中可以很明顯的看到,如果選擇右上角為原點(0,0),那麼最右邊和最下邊的像素實際上并沒有參與計算,而且目标圖像的每個像素點計算出的灰階值也相對于源圖像偏左偏上。

那麼,讓坐标加1或者選擇右下角為原點怎麼樣呢?很不幸,還是一樣的效果,不過這次得到的圖像将偏右偏下。

最好的方法就是,兩個圖像的幾何中心重合,并且目标圖像的每個像素之間都是等間隔的,并且都和兩邊有一定的邊距,這也是matlab和openCV的做法。如下圖:

如果你不懂我上面說的什麼,沒關系,隻要在計算對應坐标的時候改為以下公式即可,

int x=(i+0.5)*m/a-0.5

int y=(j+0.5)*n/b-0.5

instead of 

int x=i*m/a

int y=j*n/b

利用上述公式,将得到正确的雙線性插值結果

總結:

總結一下,我得到的教訓有這麼幾條。

1.網上的一些資料有的時候并不靠譜,自己還是要多做實驗。

2.不要小瞧一些簡單的、基本的算法,讓你寫你未必會寫,而且其中可能還藏着一些玄妙。

3.要多動手程式設計,多體會算法,多看大牛寫的源碼(雖然有的時候很吃力,但是要堅持看)。

二)轉自http://www.cnblogs.com/Imageshop/archive/2011/11/12/2246808.html

 在圖像進行中,雙線性插值算法的使用頻率相當高,比如在圖像的縮放中,在所有的扭曲算法中,都可以利用該算法改進處理的視覺效果。首先,我們看看該算法的簡介。

     在數學上,雙線性插值算法可以看成是兩個變量間的線性插值的延伸。執行該過程的關鍵思路是先在一個方向上執行線性插值,然後再在另外一個方向上插值。下圖示意出這個過程的大概意思。

雙線性插值算法及需要注意事項

     用一個簡單的數學表達式可以表示如下:

     f(x,y)=f(0,0)(1-x)(1-y)+f(1,0)x(1-y)+f(0,1)(1-x)y+f(1,1)xy

     合并有關項,可寫為: f(x,y)=(f(0,0)(1-x)+f(1,0)x) (1-y)+(f(0,1)(1-x)+f(1,1)x)y

     由上式可以看出,這個過程存在着大量的浮點數運算,對于圖像這樣大的計算使用者來說,是一個較為耗時的過程。

     考慮到圖像的特殊性,他的像素值的計算結果需要落在0到255之間,最多隻有256種結果,由上式可以看出,一般情況下,計算出的f(x,y)是個浮點數,我們還需要對該浮點數進行取整。是以,我們可以考慮将該過程中的所有類似于1-x、1-y的變量放大合适的倍數,得到對應的整數,最後再除以一個合适的整數作為插值的結果。

      如何取這個合适的放大倍數呢,要從三個方面考慮,第一:精度問題,如果這個數取得過小,那麼經過計算後可能會導緻結果出現較大的誤差。第二,這個數不能太大,太大會導緻計算過程超過長整形所能表達的範圍。第三:速度考慮。假如放大倍數取為12,那麼算式在最後的結果中應該需要除以12*12=144,但是如果取為16,則最後的除數為16*16=256,這個數字好,我們可以用右移來實作,而右移要比普通的整除快多了。 

      綜合考慮上述三條,我們選擇2048這個數比較合适。

      下面我們假定某個算法得到了我們要取樣的坐标分别PosX以及PosY,其中PosX=25.489,PosY=58.698。則這個過程的類似代碼片段如下:

 1 NewX = Int(PosX)                        \'向下取整,NewX=25

 2 NewY = Int(PosY)                        \'向下取整,NewY=58

 3 PartX = (PosX - NewX) * 2048            \'對應表達式中的X

 4 PartY = (PosY - NewY) * 2048            \'對應表達式中的Y

 5 InvX = 2048 - PartX                     \'對應表達式中的1-X

 6 InvY = 2048 - PartY                     \'對應表達式中的1-Y

 7 

 8 Index1 = SamStride * NewY + NewX * 3    \'計算取樣點左上角鄰近的那個像素點的記憶體位址

 9 Index2 = Index1 + SamStride          \'左下角像素點位址

10 ImageData(Speed + 2) = ((Sample(Index1 + 2) * InvX + Sample(Index1 + 5) * PartX) * InvY + (Sample(Index2 + 2) * InvX + 

                          Sample(Index2 + 5) * PartX) * PartY) \ 4194304       \'處理紅色分量

11 ImageData(Speed + 1) = ((Sample(Index1 + 1) * InvX + Sample(Index1 + 4) * PartX) * InvY + (Sample(Index2 + 1) * InvX +

                          Sample(Index2 + 4) * PartX) * PartY) \ 4194304       \'處理綠色分量

12 ImageData(Speed) = ((Sample(Index1) * InvX + Sample(Index1 + 3) * PartX) * InvY + (Sample(Index2) * InvX +

                      Sample(Index2 + 3) * PartX) * PartY) \ 4194304           \'處理藍色分量

      以上代碼中涉及到的變量都為整型(PosX及PosY當然為浮點型)。

      代碼中Sample數組儲存了從中取樣的圖像資料,SamStride為該圖像的掃描行大小。

      觀察上述代碼,除了有2句涉及到了浮點計算,其他都是整數之間的運算。

      在Basic語言中,編譯時如果勾選所有的進階優化,則\ 4194304會被編譯為>>22,即右移22位,如果使用的是C語言,則直接寫為>>22。

      需要注意的是,在進行這代代碼前,需要保證PosX以及PosY在合理的範圍内,即不能超出取樣圖像的寬度和高度範圍。

      通過這樣的改進,速度較直接用浮點類型快至少100%以上,而處理後的效果基本沒有什麼差別。