概述
在雙線性插值演算法中,目標影像中新創造的畫素值,是由源影像位置在它附近的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資料,這是一個一維陣列,但是在實際處理中我們需要以二維的思路來遍歷。
- 獲取圖片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);
複製程式碼
- 計算寬、高的縮放常數
float rowRatio = ((float)sourceHeight) / ((float)desHeight);
float colRatio = ((float)sourceWidth) / ((float)desWidth);
複製程式碼
- 根據縮放常數,找到當前遍歷位置所在的源圖的位置,這個位置是有小數的,這裡我們直接取整數部分,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;
複製程式碼
- 迴圈所有畫素,計算最終的畫素值
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;
}
複製程式碼
最終效果