三十分鐘理解:線性插值,雙線性插值Bilinear Interpolation演算法

三十分鐘理解:線性插值,雙線性插值Bilinear Interpolation演算法

線性插值

先講一下線性插值:已知資料 (x0, y0) 與 (x1, y1),要計算 [x0, x1] 區間內某一位置 x 在直線上的y值(反過來也是一樣,略):

y−y0x−x0=y1−y0x1−x0

\begin{equation}
\frac{y – y_0}{x – x_0} = \frac{y_1 – y_0}{x_1 – x_0}
\end{equation}

y=x1−xx1−x0y0 x−x0x1−x0y1

\begin{equation}
y = \frac{x_1 – x}{x_1 – x_0}y_0 \frac{x – x_0}{x_1 – x_0}y_1
\end{equation}

上面比較好理解吧,仔細看就是用x和x0,x1的距離作為一個權重,用於y0和y1的加權。雙線性插值本質上就是在兩個方向上做線性插值。

雙線性插值

在數學上,雙線性插值是有兩個變數的插值函式的線性插值擴充套件,其核心思想是在兩個方向分別進行一次線性插值[1]。見下圖:

這裡寫圖片描述

假如我們想得到未知函式 f 在點 P = (x, y) 的值,假設我們已知函式 f 在 Q11 = (x1, y1)、Q12 = (x1, y2), Q21 = (x2, y1) 以及 Q22 = (x2, y2) 四個點的值。最常見的情況,f就是一個畫素點的畫素值。首先在 x 方向進行線性插值,得到

這裡寫圖片描述
這裡寫圖片描述

然後在 y 方向進行線性插值,得到

這裡寫圖片描述

綜合起來就是雙線性插值最後的結果:

這裡寫圖片描述
這裡寫圖片描述

由於影象雙線性插值只會用相鄰的4個點,因此上述公式的分母都是1。opencv中的原始碼如下,用了一些優化手段,比如用整數計算代替float(下面程式碼中的*2048就是變11位小數為整數,最後有兩個連乘,因此>>22位),以及源影象和目標影象幾何中心的對齊
SrcX=(dstX 0.5)* (srcWidth/dstWidth) -0.5
SrcY=(dstY 0.5) * (srcHeight/dstHeight)-0.5

這個要重點說一下,源影象和目標影象的原點(0,0)均選擇左上角,然後根據插值公式計算目標影象每點畫素,假設你需要將一幅5×5的影象縮小成3×3,那麼源影象和目標影象各個畫素之間的對應關係如下。如果沒有這個中心對齊,根據基本公式去算,就會得到左邊這樣的結果;而用了對齊,就會得到右邊的結果:

這裡寫圖片描述這裡寫圖片描述

cv::Mat matSrc, matDst1, matDst2;  
matSrc = cv::imread("lena.jpg", 2 | 4);  
matDst1 = cv::Mat(cv::Size(800, 1000), matSrc.type(), cv::Scalar::all(0));  
matDst2 = cv::Mat(matDst1.size(), matSrc.type(), cv::Scalar::all(0));  
double scale_x = (double)matSrc.cols / matDst1.cols;  
double scale_y = (double)matSrc.rows / matDst1.rows;  
uchar* dataDst = matDst1.data;  
int stepDst = matDst1.step;  
uchar* dataSrc = matSrc.data;  
int stepSrc = matSrc.step;  
int iWidthSrc = matSrc.cols;  
int iHiehgtSrc = matSrc.rows;  
for (int j = 0; j < matDst1.rows;   j)  
{  
float fy = (float)((j   0.5) * scale_y - 0.5);  
int sy = cvFloor(fy);  
fy -= sy;  
sy = std::min(sy, iHiehgtSrc - 2);  
sy = std::max(0, sy);  
short cbufy[2];  
cbufy[0] = cv::saturate_cast<short>((1.f - fy) * 2048);  
cbufy[1] = 2048 - cbufy[0];  
for (int i = 0; i < matDst1.cols;   i)  
{  
float fx = (float)((i   0.5) * scale_x - 0.5);  
int sx = cvFloor(fx);  
fx -= sx;  
if (sx < 0) {  
fx = 0, sx = 0;  
}  
if (sx >= iWidthSrc - 1) {  
fx = 0, sx = iWidthSrc - 2;  
}  
short cbufx[2];  
cbufx[0] = cv::saturate_cast<short>((1.f - fx) * 2048);  
cbufx[1] = 2048 - cbufx[0];  
for (int k = 0; k < matSrc.channels();   k)  
{  
*(dataDst  j*stepDst   3*i   k) = (*(dataSrc   sy*stepSrc   3*sx   k) * cbufx[0] * cbufy[0]     
*(dataSrc   (sy 1)*stepSrc   3*sx   k) * cbufx[0] * cbufy[1]     
*(dataSrc   sy*stepSrc   3*(sx 1)   k) * cbufx[1] * cbufy[0]     
*(dataSrc   (sy 1)*stepSrc   3*(sx 1)   k) * cbufx[1] * cbufy[1]) >> 22;  
}  
}  
}  
cv::imwrite("linear_1.jpg", matDst1);  
cv::resize(matSrc, matDst2, matDst1.size(), 0, 0, 1);  
cv::imwrite("linear_2.jpg", matDst2);  

好了,本篇到這裡,歡迎大家分享轉載,註明出處即可。

參考資料

[1] 雙線性插值(Bilinear Interpolation)
[2] OpenCV ——雙線性插值(Bilinear interpolation)
[3] 雙線性插值演算法及需要注意事項
[4] OpenCV中resize函式五種插值演算法的實現過程