常用圖像插值方法概述

緣由

之前我在公司做了一個在 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