图像重采样算法之双线性插值算法

3,471 阅读3分钟

概述

在双线性插值算法中,目标图像中新创造的像素值,是由源图像位置在它附近的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

参考链接