影像重取樣演算法之雙線性插值演算法

大雄愛折騰發表於2020-01-30

概述

在雙線性插值演算法中,目標影像中新創造的畫素值,是由源影像位置在它附近的2*2區域4個鄰近畫素的值通過加權平均計算得出的。雙線性內插值演算法放大後的影像質量較高,不會出現畫素值不連續的情況。

數學原理

影像重取樣演算法之雙線性插值演算法
圖片來源於Image rotation with bilinear interpolation

上圖中有四個已知畫素點,分別為P(j,k),P(j,k+1),P(j+1,k),P(j+1,k+1)。現在有一個點D,做一條垂直線相交於Q1和Q2點,D距離Q1為u,Q1距離P(j,k)為t。另外,四個P點組成一個單位正方形,就是邊長為1。

這個演算法的思路是先算出Q1和Q2點的值,然後再算出D的值。

根據距離的權重,我們可以得到

Q1 = P(j,k)*(1-t) + P(j,k+1)*(t)
Q2 = P(j+1,k)*(1-t) + p(j+1,k+1)*(t)

D = Q1*(1-u) + Q2*(u)

// 把Q1和Q2代入上式得
D = P(j,k)*(1-t)*(1-u) + P(j,k+1)*(t)*(1-u) + P(j+1,k)*(1-t)*(u) + p(j+1,k+1)*(t)*(u)
複製程式碼

關鍵程式碼

我在iOS平臺上,使用Objective-C語言實現這個演算法。首先需要獲取到圖片的RGBA資料,也叫RAW資料,這是一個一維陣列,但是在實際處理中我們需要以二維的思路來遍歷。

  1. 獲取圖片RGBA資料
UInt32* pixelData = (UInt32 *)calloc(width * height, sizeof(UInt32));
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(pixelData,
                                                width,
                                                height,
                                                bitsPerComponent,
                                                bytesPerRow,
                                                colorSpace,
                                                kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);

CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgOriginalImage);
複製程式碼
  1. 計算寬、高的縮放常數
float rowRatio = ((float)sourceHeight) / ((float)desHeight);
float colRatio = ((float)sourceWidth) / ((float)desWidth);
複製程式碼
  1. 根據縮放常數,找到當前遍歷位置所在的源圖的位置,這個位置是有小數的,這裡我們直接取整數部分,j代表在原圖的行,k代表在原圖的列,小數部分就是我們要求的u和t,這樣我們就拿到了四個原畫素的位置了。
double srcRow = ((float)row) * rowRatio;
double j = floor(srcRow);
double u = srcRow - j;

double srcCol = ((float)col) * colRatio;
double k = floor(srcCol);
double t = srcCol - k;
複製程式碼
  1. 迴圈所有畫素,計算最終的畫素值
static UInt32* scaleImageWithLinearInterpolation(UInt32* pixelData, int sourceWidth, int sourceHeight, int desWidth, int desHeight) {
    
    float rowRatio = ((float)sourceHeight) / ((float)desHeight);
    float colRatio = ((float)sourceWidth) / ((float)desWidth);
    UInt32* rgba = (UInt32 *)calloc(desWidth * desHeight, sizeof(UInt32));
    int offset=0;
    for(int row = 0; row < desHeight; row++) {
        double srcRow = ((float)row) * rowRatio;
        double j = floor(srcRow);
        double u = srcRow - j;
        
        for (int col = 0; col < desWidth; col++) {
            
            double srcCol = ((float)col) * colRatio;
            double k = floor(srcCol);
            double t = srcCol - k;
            double coffiecent1 = (1.0 - t) * (1.0 - u);
            double coffiecent2 = (1.0 - t) * u;
            double coffiecent3 = t * u;
            double coffiecent4 = (t) * (1.0 - u);
            
            // 四個角的顏色值
            UInt32 inputColor00 = pixelData[(getClip((int)j, sourceHeight - 1 , 0) * sourceWidth + getClip((int)k, sourceWidth - 1, 0))];
            UInt32 inputColor10 = pixelData[(getClip((int)(j+1), sourceHeight - 1 , 0) * sourceWidth + getClip((int)k, sourceWidth - 1, 0))];
            UInt32 inputColor11 = pixelData[(getClip((int)(j+1), sourceHeight - 1 , 0) * sourceWidth + getClip((int)(k+1), sourceWidth - 1, 0))];
            UInt32 inputColor01 = pixelData[(getClip((int)j, sourceHeight - 1 , 0) * sourceWidth + getClip((int)(k+1), sourceWidth - 1, 0))];
            
            // 新的透明度
            UInt32 newA = (UInt32)(
                                coffiecent1 * A(inputColor00) +
                                coffiecent2 * A(inputColor10) +
                                coffiecent3 * A(inputColor11) +
                                coffiecent4 * A(inputColor01)
                                );
            
            // 新的R分量的值
            double r00 = R(inputColor00) * (255.0 / A(inputColor00));
            double r10 = R(inputColor10) * (255.0 / A(inputColor10));
            double r11 = R(inputColor11) * (255.0 / A(inputColor11));
            double r01 = R(inputColor01) * (255.0 / A(inputColor01));
            UInt32 newR = (UInt32)((
                                    coffiecent1 * r00 +
                                    coffiecent2 * r10 +
                                    coffiecent3 * r11 +
                                    coffiecent4 * r01
                                    ) * (newA / 255.0));
            
            // 新的G分量的值
            double g00 = G(inputColor00) * (255.0 / A(inputColor00));
            double g10 = G(inputColor10) * (255.0 / A(inputColor10));
            double g11 = G(inputColor11) * (255.0 / A(inputColor11));
            double g01 = G(inputColor01) * (255.0 / A(inputColor01));
            UInt32 newG = (UInt32)((
                                    coffiecent1 * g00 +
                                    coffiecent2 * g10 +
                                    coffiecent3 * g11 +
                                    coffiecent4 * g01
                                    ) * (newA / 255.0));
            
            // 新的B分量的值
            double b00 = B(inputColor00) * (255.0 / A(inputColor00));
            double b10 = B(inputColor10) * (255.0 / A(inputColor10));
            double b11 = B(inputColor11) * (255.0 / A(inputColor11));
            double b01 = B(inputColor01) * (255.0 / A(inputColor01));
            UInt32 newB = (UInt32)((
                                    coffiecent1 * b00 +
                                    coffiecent2 * b10 +
                                    coffiecent3 * b11 +
                                    coffiecent4 * b01
                                    ) * (newA / 255.0));
            
            rgba[offset] = RGBAMake(newR, newG, newB, newA);
            offset++;
        }
    }
    
    return rgba;
}
複製程式碼

最終效果

影像重取樣演算法之雙線性插值演算法

Source Code

GitHub

參考連結

相關文章