常用圖像插值方法概述
緣由
之前我在公司做了一個在 JNI 層縮放 Bitmap 的需求。
需求本身很簡單,就是按各種比例縮小或者放大圖像,要求縮放後無明顯鋸齒,不失真。
很容易就想到了雙線性插值算法,於是在 Github 上找了開源代碼。即
https://github.com/AndroidDeveloperLB/AndroidJniBitmapOperations
裏的函數:
Java_com_jni_bitmap_1operations_JniBitmapHolder_jniScaleBIBitmap
這個 Github 項目的歷史有 6 年了,也有接近 500 的 star。但接入後還是覺得效果不如預期,有時候失真嚴重。後來發現是因爲函數實現裏的幾行代碼邏輯有問題。於是向作者提了 PullRequest, 已經被 merge 了。
拓展
傳統的圖像插值算法主要有以下幾種:最鄰近插值 / 雙線性插值 / 雙三次插值 / lanczos 插值。以上算法效果按順序越來越好,但計算量也是越來越大。
最鄰近插值法
效果上比較粗糙,容易失真
實現最簡單,就是取最接近插值點的像素的值。
雙線性插值法
效果上比較平滑
在 X 和 Y 方向分別進行一次線性插值, 採樣點的權重與和插值點的距離負相關。
設要插值的像素座標爲 (X.a,Y.y), 大寫和小寫分別表示座標的整數部分和小數部分,f(x) 爲讀取像素值的函數,那麼雙線性插值的結果爲
f(X.x,Y.y) = [(1-x)*f(X,Y) + x*f(X+1,Y)](1-y) + [(1-x)*f(X,Y+1) + x*f(X+1,Y+1)]y
= (1-x)(1-y)*f(X,Y) + x(1-y)*f(X+1,Y) + (1-x)y*f(X,Y+1) + xy*f(X+1,Y+1)
可以觀察到,採樣點的權重就是橫向權重與縱向權重的乘積。
由於效果和耗時都適中,所以應用廣泛,比如 OpenGL 裏的 GL_LINEAR 就是雙線性過濾的意思。
雙線性插值圖示
雙三次插值法
效果上比雙線性插值更少鋸齒, 更平滑
比雙線性的採樣點更多,即取插值點周圍的 16 個採樣點的加權平均求得插值點的像素值。並且計算權重的過濾函數是三次多項式。
三線性插值圖示
採樣的過程可以用矩陣乘法表示如下
bicubic 插值矩陣表示
其中 i,j 是座標的整數部分。v,u 是座標的小數部分。f(x) 爲讀取像素的函數。S(x) 爲權重函數。
而權重函數的公式爲:
其中 a 的取值說明如下
-0.5 三次Hermite樣條
-0.75 常用值
-1 逼近y = sin(x*PI)/(x*PI)
-2 常用值
權重函數對應的圖像如下
lanczos 插值
效果上比雙三次插值更清晰銳利。但在圖像的高頻信號區域 (像素值陡變的地方, 比如素描的線條邊緣),會有振鈴效應 (Ringing Artifact), 這種情況下建議改用雙線性過濾
原理和雙三次插值法差不多。插值卷積核尺寸爲 4*4 時,計算過程對應的矩陣表示和上面的 bicubic 插值矩陣表示一樣。
計算權重的函數如下:
其中 a = kernelWidth * 0.5。即在卷積核爲 4 * 4 時, a= 2
權重函數對應的圖像如下
Lanczos 和雙三次插值的耗時, 是雙線性插值的 2 倍左右, 這個性能在絕大多數移動端場景下都是 OK 的。
其他補充
OpenGL 領域還有 MipMap 三線性過濾法 GL_LINEAR_MIPMAP_LINEAR。
也可以接入 FFmpeg 直接使用 libswscale 庫裏的各種過濾算法。
開源庫 OpenCV 也提供了各種圖像插值算法的 C++ 實現。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/o2tZLt2YYCRxHhq0Jo4FLg