日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

用线性插值算法实现图像缩放

發布時間:2023/12/18 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 用线性插值算法实现图像缩放 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

用線性插值算法實現圖像縮放

?

猛禽[Mental Studio](個人專欄)(BLOG)

http://mental.mentsu.com

?

在Windows中做過圖像方面程序的人應該都知道Windows的GDI有一個API函數:StretchBlt,對應在VCL中是TCanvas類的StretchDraw方法。它可以很簡單地實現圖像的縮放操作。但問題是它是用了速度最快,最簡單但效果也是最差的“最近鄰域法”,雖然在大多數情況下,它也夠用了,但對于要求較高的情況就不行了。

不久前我做了一個小玩意兒(見《人個信息助理之我的相冊》),用于管理我用DC拍的一堆照片,其中有一個插件提供了縮放功能,目前的版本就是用了StretchDraw,有時效果不能令人滿意,我一直想加入兩個更好的:線性插值法和三次樣條法。經過研究發現三次樣條法的計算量實在太大,不太實用,所以決定就只做線性插值法的版本了。

從數字圖像處理的基本理論,我們可以知道:圖像的變形變換就是源圖像到目標圖像的坐標變換。簡單的想法就是把源圖像的每個點坐標通過變形運算轉為目標圖像的相應點的新坐標,但是這樣會導致一個問題就是目標點的坐標通常不會是整數,而且像放大操作會導致目標圖像中沒有被源圖像的點映射到,這是所謂“向前映射”方法的缺點。所以一般都是采用“逆向映射”法。

但是逆向映射法同樣會出現映射到源圖像坐標時不是整數的問題。這里就需要“重采樣濾波器”。這個術語看起來很專業,其實不過是因為它借用了電子信號處理中的慣用說法(在大多數情況下,它的功能類似于電子信號處理中的帶通濾波器),理解起來也不復雜,就是如何確定這個非整數坐標處的點應該是什么顏色的問題。前面說到的三種方法:最近鄰域法,線性插值法和三次樣條法都是所謂的“重采樣濾波器”。

所謂“最近鄰域法”就是把這個非整數坐標作一個四舍五入,取最近的整數點坐標處的點的顏色。而“線性插值法”就是根據周圍最接近的幾個點(對于平面圖像來說,共有四點)的顏色作線性插值計算(對于平面圖像來說就是二維線性插值)來估計這點的顏色,在大多數情況下,它的準確度要高于最近鄰域法,當然效果也要好得多,最明顯的就是在放大時,圖像邊緣的鋸齒比最近鄰域法小非常多。當然它同時還帶業個問題:就是圖像會顯得比較柔和。這個濾波器用專業術語來說(呵呵,賣弄一下偶的專業^_^)叫做:帶阻性能好,但有帶通損失,通帶曲線的矩形系數不高。至于三次樣條法我就不說了,復雜了一點,可自行參考數字圖像處理方面的專業書籍,如本文的參考文獻。

再來討論一下坐標變換的算法。簡單的空間變換可以用一個變換矩陣來表示:

[x’,y’,w’]=[u,v,w]*T

其中:x’,y’為目標圖像坐標,u,v為源圖像坐標,w,w’稱為齊次坐標,通常設為1,T為一個3X3的變換矩陣。

這種表示方法雖然很數學化,但是用這種形式可以很方便地表示多種不同的變換,如平移,旋轉,縮放等。對于縮放來說,相當于:

???????? ?[Su? 0? 0 ]

[x, y, 1] = [u, v, 1] * | 0? Sv? 0 |

???????? ?[0?? 0? 1 ]

其中Su,Sv分別是X軸方向和Y軸方向上的縮放率,大于1時放大,大于0小于1時縮小,小于0時反轉。

矩陣是不是看上去比較暈?其實把上式按矩陣乘法展開就是:

{ x = u * Su

{ y = v * Sv

就這么簡單。^_^

有了上面三個方面的準備,就可以開始編寫代碼實現了。思路很簡單:首先用兩重循環遍歷目標圖像的每個點坐標,通過上面的變換式(注意:因為是用逆向映射,相應的變換式應該是:u = x / Su 和v = y / Sv)取得源坐標。因為源坐標不是整數坐標,需要進行二維線性插值運算:

P = n*b*PA + n * ( 1 – b )*PB + ( 1 – n ) * b * PC + ( 1 – n ) * ( 1 – b ) * PD

其中:n為v(映射后相應點在源圖像中的Y軸坐標,一般不是整數)下面最接近的行的Y軸坐標與v的差;同樣b也類似,不過它是X軸坐標。PA-PD分別是(u,v)點周圍最接近的四個(左上,右上,左下,右下)源圖像點的顏色(用TCanvas的Pixels屬性)。P為(u,v)點的插值顏色,即(x,y)點的近似顏色。

這段代碼我就不寫的,因為它的效率實在太低:要對目標圖像的每一個點的RGB進行上面那一串復雜的浮點運算。所以一定要進行優化。對于VCL應用來說,有個比較簡單的優化方法就是用TBitmap的ScanLine屬性,按行進行處理,可以避免Pixels的像素級操作,對性能可以有很大的改善。這已經是算是用VCL進行圖像處理的基本優化常識了。不過這個方法并不總是管用的,比如作圖像旋轉的時候,這時需要更多的技巧。

無論如何,浮點運算的開銷都是比整數大很多的,這個也是一定要優化掉的。從上面可以看出,浮點數是在變換時引入的,而變換參數Su,Sv通常就是浮點數,所以就從它下手優化。一般來說,Su,Sv可以表示成分數的形式:

Su = ( double )Dw / Sw; Sv = ( double )Dh / Sh

其中Dw, Dh為目標圖像的寬度和高度,Sw, Sh為源圖像的寬度和高度(因為都是整數,為求得浮點結果,需要進行類型轉換)。

將新的Su, Sv代入前面的變換公式和插值公式,可以導出新的插值公式:

因為:

b = 1 – x * Sw % Dw / ( double )Dw;? n = 1 – y * Sh % Dh / ( double )Dh

設:

B = Dw – x * Sw % Dw; N = Dh – y * Sh % Dh

則:

b = B / ( double )Dw; n = N / ( double )Dh

用整數的B,N代替浮點的b, n,轉換插值公式:

P = ( B * N * ( PA – PB – PC + PD ) + Dw * N * PB + DH * B * PC + ( Dw * Dh – Dh * B – Dw * N ) * PD ) / ( double )( Dw * Dh )

這里最終結果P是浮點數,對其四舍五入即可得到結果。為完全消除浮點數,可以用這樣的方法進行四舍五入:

P = ( B * N … * PD + Dw * Dh / 2 ) / ( Dw * Dh )

這樣,P就直接是四舍五入后的整數值,全部的計算都是整數運算了。

簡單優化后的代碼如下:

int __fastcall TResizeDlg::Stretch_Linear(Graphics::TBitmap * aDest, Graphics::TBitmap * aSrc)

{

??? int sw = aSrc->Width - 1, sh = aSrc->Height - 1, dw = aDest->Width - 1, dh = aDest->Height - 1;

??? int B, N, x, y;

??? int nPixelSize = GetPixelSize( aDest->PixelFormat );

??? BYTE * pLinePrev, *pLineNext;

??? BYTE * pDest;

??? BYTE * pA, *pB, *pC, *pD;

??? for ( int i = 0; i <= dh; ++i )

??? {

??????? pDest = ( BYTE * )aDest->ScanLine[i];

??????? y = i * sh / dh;

??????? N = dh - i * sh % dh;

??????? pLinePrev = ( BYTE * )aSrc->ScanLine[y++];

??????? pLineNext = ( N == dh ) ? pLinePrev : ( BYTE * )aSrc->ScanLine[y];

??????? for ( int j = 0; j <= dw; ++j )

??????? {

????????? ??x = j * sw / dw * nPixelSize;

??????????? B = dw - j * sw % dw;

??????????? pA = pLinePrev + x;

??????????? pB = pA + nPixelSize;

??????????? pC = pLineNext + x;

??????????? pD = pC + nPixelSize;

??????????? if ( B == dw )

??????????? {

??????????????? pB = pA;

??????????????? pD = pC;

??????????? }

??????????? for ( int k = 0; k < nPixelSize; ++k )

??????????????? *pDest++ = ( BYTE )( int )(

??????????????????? ( B * N * ( *pA++ - *pB - *pC + *pD ) + dw * N * *pB++

??????????????????? + dh * B * *pC++ + ( dw * dh - dh * B - dw * N ) * *pD++

??????????????????? + dw * dh / 2 ) / ( dw * dh )

??????????????? );

??????? }

??? }

??? return 0;

}

應該說還是比較簡潔的。因為寬度高度都是從0開始算,所以要減一,GetPixelSize是根據PixelFormat屬性來判斷每個像素有多少字節,此代碼只支持24或32位色的情況(對于15或16位色需要按位拆開—因為不拆開的話會在計算中出現不期望的進位或借位,導致圖像顏色混亂—處理較麻煩;對于8位及8位以下索引色需要查調色板,并且需要重索引,也很麻煩,所以都不支持;但8位灰度圖像可以支持)。另外代碼中加入一些在圖像邊緣時防止訪問越界的代碼。

通過比較,在PIII-733的機器上,目標圖像小于1024x768的情況下,基本感覺不出速度比StretchDraw有明顯的慢(用浮點時感覺比較明顯)。效果也相當令人滿意,不論是縮小還是放大,圖像質量比StretchDraw方法有明顯提高。

不過由于采用了整數運算,有一個問題必須加以重視,那就是溢出的問題:由于式中的分母是dw * dh,而結果應該是一個Byte即8位二進制數,有符號整數最大可表示31位二進制數,所以dw * dh的值不能超過23位二進制數,即按2:1的寬高比計算目標圖像分辨率不能超過4096*2048。當然這個也是可以通過用無符號數(可以增加一位)及降低計算精度等方法來實現擴展的,有興趣的朋友可以自己試試。

當然這段代碼還遠沒有優化到極致,而且還有很多問題沒有深入研究,比如抗混疊(anti-aliasing)等,有興趣的朋友可以自行參考相關書籍研究,如果你有什么研究成果,非常歡迎你為我的程序編寫插件實現。


總結

以上是生活随笔為你收集整理的用线性插值算法实现图像缩放的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。