2017年11月16日 星期四

[原始碼] C / C++ 線性插補 bilinear 與注意事項

[原始碼] C / C++ 線性插補 bilinear 與注意事項

linear 意思就是依照比例去補數值,可以參考維基百科也說得很詳細
https://www.wikiwand.com/en/Bilinear_interpolation
簡單來說就是現在有兩個點 A=0 與 B=10
現在 A 的座標是 0,B的座標1,那個我要取出AB這個位置的點該怎麼辦呢
就是依照比例取因為比較靠近A所以分到的A比較多,離B比較遠分到的就比較少
現在讓我們來計算算法如下
AB = A*dx2 + B*dx1
大致上就這樣而已,如果是二維空間那個就要算三次這個公式意思如下
有ABCD四個點,把他當成2條一維就是AB跟CD,分別先算出他們中間的點
然後再把AB跟CD這兩個點在做一次運算就可以算出中間那個點了
float AB = A*dx2 + B*dx1;
float CD = C*dx2 + D*dx1;
float X  = AB*dy2 + CD*dy1;
linear是一維的意思,bilinear就是二維的意思,在上去還有三維都是差不多意思推倒

實作代碼

實作的時候要注意幾點

獲取鄰點

獲取鄰近點的時候不能用+1或-1,假設現在的點的 0 和 1,需要獲取的點是 1,先使用 floor 無條件捨去獲得1在加1就變成2了;正確的來說因為正好就落在點上,就直接把AB都帶那個點就可以了算出來是正確的
使用 ceil() 這樣就可以正確獲得了

獲取比例

這時候也是一樣,不過情況倒是反過來了,一定要用1去減,否則如果出現兩個一樣的點,比例兩邊都是0。
/*****************************************************************
Name : 
Date : 2017/11/16
By   : CharlotteHonG
Final: 2017/11/16
*****************************************************************/
#include <iostream>
#include <cmath>
using namespace std;

float linear(int* arr, float pos){
    // 獲取鄰點(不能用 1+)
    size_t c0 = floor(pos);
    size_t c1 = ceil(pos);
    // 獲取比例(只能用 1-)
    float dx1 = pos - c0;
    float dx2 = 1 - dx1;
    // 乘出比例(要交叉)
    float X = arr[c0]*dx2 + arr[c1]*dx1;
    return X;
}
float bilinear(int* arr, size_t w, float y, float x){
    // 獲取鄰點(不能用 1+, 選中結尾時 x0=x1=W-1 才是對的)
    size_t x0 = floor(x);
    size_t x1 = ceil(x);
    size_t y0 = floor(y);
    size_t y1 = ceil(y);
    // 獲取比例(只能用 1-, 選中結尾時 1:0 才是對的)
    float dx1 = x - x0;
    float dx2 = 1 - dx1;
    float dy1 = y - y0;
    float dy2 = 1 - dy1;
    // 獲取點
    const float& A = arr[y0*w + x0];
    const float& B = arr[y0*w + x1];
    const float& C = arr[y1*w + x0];
    const float& D = arr[y1*w + x1];
    // 乘出比例(要交叉)
    float AB = A*dx2 + B*dx1;
    float CD = C*dx2 + D*dx1;
    float X = AB*dy2 + CD*dy1;
    return X;
}
//================================================================
int main(int argc, char const *argv[]){
    int arr[] = {0, 10};
    cout << linear(arr, 0.1f) << endl;
    int arr2[] = {0, 0, 10, 10};
    cout << bilinear(arr2, 2, 0.1f, 0.1f) << endl;
    return 0;
}
//================================================================

縮放比例

以下 dst 指的是處理過後的圖,src 指的是原圖。
縮小比例的時候,要注意新圖的點要向中間對齊,不然最右邊那一整排跟最下面一種排會沒有被計算到。
// 縮小向中間對齊
r=srcW/dstW;
double srcX = (dstX+r) * ((double)srcW/dstW) - r;
double srcY = (dstY+r) * ((double)srcH/dstH) - r;
原理其實就是原本會向左上角也就是原點對齊,然後再加上偏差值讓他向中間對齊。
不過這樣向中間對齊有個缺陷,邊緣的向素值會丟失,數值丟失還算小問題,比較大的問題是連帶圖片會稍微放大,假設你做個10次放大縮小,你會發現可能原圖邊緣一圈整個不見了。
// 縮小的倍率
double r1W = ((double)src.width )/(dst.width );
double r1H = ((double)src.height)/(dst.height);

// 縮小時候的誤差
double deviW = ((src.width-1.0)  - (dst.width -1.0)*(r1W)) /dst.width;
double deviH = ((src.height-1.0) - (dst.height-1.0)*(r1H)) /dst.height;

// 縮小保持邊緣對齊
srcX = i*(r1W+deviW);
srcY = j*(r1H+deviH);
公式從原本的向原點對齊,會產生一定誤差,把這個誤差分攤給每個位置。
放大比例的時候則是要注意最外圍的一圈正好要疊在點上面
// 放大保持邊緣對齊
double srcX = (dstX)* ((width-1.0) / (newW-1.0));
double srcY = (dstY)* ((height-1.0) / (newH-1.0));
另外上述算是為了表達沒有加上(double)轉型修飾,是因為有 +1.0 會自動隱式轉型,所以不用補。但是如果是 (int)/(double) 就要補,這個是後面的會隱式轉型成 (int)。

參考

沒有留言:

張貼留言