Bilinear Interpolation of Image

双线性插值(Bilinear Interpolation,又称为双线性内插。在数学上,双线性插值是有两个变量的插值函数的线性插值扩展,其核心思想是在两个方向分别进行一次线性插值。
图像的双线性插值放大算法中,目标图像中新创造的象素值,是由源图像位置在它附近的2*2区域4个邻近象素的值通过加权平均计算得出的。双线性内插值算法放大后的图像质量较高,不会出现像素值不连续的的情况。然而次算法具有低通滤波器的性质,使高频分量受损,所以可能会使图像轮廓在一定程度上变得模糊。
它充分的利用了源图中虚拟点四周的四个真实存在的像素值来共同决定目标图中的一个像素值,因此缩放效果比简单的最邻近插值要好很多。

X方向的线性插值
对于标准的双线性差值算法,X方向的线性插值:

具体到我们所实现的算法中,我们使Q11、Q12、Q21、Q22为光栅上相邻的四点,即P只能落于这四点其中一点上。Δcol是当前像素离像素所属区域原点的水平距离,比如图2,各种不同的颜色代表一个区域,区域原点为区域左上角的像素。

δ (R2) =( Color (Q22) −Color (Q12)) ∙Δcol+Color (Q12) ∙256   (1)
δ (R1) =( Color (Q21) −Color (Q11)) ∙Δcol+Color (Q11) ∙256   (2)
其中:Δcol=(DestColNumber∙((SrcWidth≪8)/DestWidth))&255, Color(X)表示点X的颜色,具体算法使用的是24位真彩色格式。

Y方向的线性插值
做完X方向的插值后再做Y方向的插值,对于一般情况,有:

而我们的具体算法中,Y方向的线性插值方法如(3)所示。Δrow是当前像素离像素所属区域原点的垂直距离,比如图2,各种不同的颜色代表一个区域,区域原点为区域左上角的像素。
Color (P )= (δ (R2) ∙256+ (δ (R2) −δ (R1) )∙Δrow )≫16   (3)
其中:Δrow=(DestRowNumber∙((SrcHeight≪8)/DestHeight))&255,由于前面为了便于计算左移了16位,因此最后需要右移16位保持匹配。

算法描述

for (目标图像第一行的像素++)
{
     // 源图像上Q12, Q22, Q11, Q21的选取见下一节
     获取源图像Q12, Q22, Q11, Q21的颜色;
     // X 方向的插值
     δ(R2) = (Color(Q22) - Color(Q12)) * Δcol+ Color(Q12) * 256;
     δ(R1) = (Color(Q21) - Color(Q11)) * Δcol+ Color(Q11) * 256;
     // 保存 δ(R1)到一个临时数组,因为下一行的δ(R2)等于这一行的δ(R1)
     temp[i++] = δ(R1);
   // Y 方向的插值
     Color(P) = (δ(R2) * 256 + (δ(R2) - δ(R1)) *Δrow) >> 16;
     将 P 输出到目标位图中。
}
for (目标图像第二行到最末行)
{
  for (行上的像素++)
  {
        // 源图像上Q12, Q22, Q11, Q21的选取见下一节
        获取源图像Q12, Q22, Q11, Q21的颜色;
        // X 方向的插值
        δ(R2) = temp[i++]; // 下一行的δ(R2)等于上一行的δ(R1)
        δ(R1) = (Color(Q21) - Color(Q11)) *Δcol+ Color(Q11) * 256;
        // 保存 δ(R1)到一个临时数组,因为下一行的δ(R2)等于这一行的δ(R1)
        temp[i++] = δ(R1);
        // Y 方向的插值
        Color(P) = (δ(R2) * 256 + (δ(R2) - δ(R1)) * Δrow) >> 16;
        将 P 输出到目标位图中。
  }
}

算法中Q12, Q22, Q11, Q21的选取

我们以放大两倍为例,说明选取Q12, Q22, Q11, Q21的过程。源图像3*3区域放大为目标区域6*6区域。设以下为目标图像:

目标图像A像素区域对应的Q21,Q22,Q11,Q12,以红色区域为原点向右下方扩展的2*2区域。

目标图像B像素区域对应的Q21,Q22,Q11,Q12,以蓝色区域为原点向右下方扩展的2*2区域。

目标图像C像素区域对应的Q21,Q22,Q11,Q12,以绿色区域为原点向右下方扩展的2*2区域。

目标图像D像素区域对应的Q21,Q22,Q11,Q12,目标图像处于最后两行的边界情况,将Q21,Q22,Q11,Q12这四个点的值设为一样。

程序流程图
流程图右边虚线框中为相关过程的注解。

      

     

       


通俗的解释:

双线性内插值算法描述如下:
对于一个目的像素,设置坐标通过反向变换得到的浮点坐标为(i+u,j+v) (其中i、j均为浮点坐标的整数部分,u、v为浮点坐标的小数部分,是取值[0,1)区间的浮点数),则这个像素得值 f(i+u,j+v) 可由原图像中坐标为 (i,j)、(i+1,j)、(i,j+1)、(i+1,j+1)所对应的周围四个像素的值决定,即:

f(i+u,j+v) = (1-u)(1-v)f(i,j) + (1-u)vf(i,j+1) + u(1-v)f(i+1,j) + uvf(i+1,j+1)                           公式1

其中f(i,j)表示源图像(i,j)处的的像素值,以此类推。

比如,象刚才的例子,现在假如目标图的象素坐标为(1,1),那么反推得到的对应于源图的坐标是(0.75 , 0.75), 这其实只是一个概念上的虚拟象素,实际在源图中并不存在这样一个象素,那么目标图的象素(1,1)的取值不能够由这个虚拟象素来决定,而只能由源图的这四个象素共同决定:(0,0)(0,1)(1,0)(1,1),而由于(0.75,0.75)离(1,1)要更近一些,那么(1,1)所起的决定作用更大一些,这从公式1中的系数uv=0.75×0.75就可以体现出来,而(0.75,0.75)离(0,0)最远,所以(0,0)所起的决定作用就要小一些,公式中系数为(1-u)(1-v)=0.25×0.25也体现出了这一特点;

java:
/**
 * <p>Title: </p>
 * <p>Description: 双线性插值缩放图片</p>
 * <p>Copyright: Copyright (c) 2010</p>
 * <p>Company: </p>*/
public class ZoomPixel {
    
    public static int interpolation(int[] imageData, int width, int heigth,
            float x, float y) {        
        // 四个最临近象素的坐标(i1, j1), (i2, j1), (i1, j2), (i2, j2)
        int i1, i2;
        int j1, j2;

        int f1, f2, f3, f4; // 四个最临近象素值
        int f12, f34; // 二个插值中间值

        f1 = f2 = f3 = f4 = 0;

        // 定义一个值,当象素坐标相差小于改值时认为坐标相同
        int EXP = 0;

        // 计算四个最临近象素的坐标
        i1 = (int) x;
        i2 = i1 + 1;
        j1 = (int) y;
        j2 = j1 + 1;

        // 根据不同情况分别处理
        if ((x < 0) || (x > width - 1) || (y < 0) || (y > heigth - 1)) {
            return 0x00ffffff; // 要计算的点不在源图范围内,直接返回255。
        } else {
            if (Math.abs(x - width + 1) <= EXP) {
                // 要计算的点在图像右边缘上
                if (Math.abs(y - heigth + 1) <= EXP) {
                    // 要计算的点正好是图像最右下角那一个象素,直接返回该点象素值
                    f1 = (int)imageData[width * j1 + i1];
                    return (int)f1;
                } else {
                    // 在图像右边缘上且不是最后一点,直接一次插值即可
                    f1 = (int)imageData[width * j1 + i1];
                    f3 = (int)imageData[width * j1 + i2];

                    // 返回插值结果
                    return (int) (f1 + (y - j1) * (f3 - f1));
                }
            } else if (Math.abs(y - heigth + 1) <= EXP) {
                // 要计算的点在图像下边缘上且不是最后一点,直接一次插值即可
                f1 = (int)imageData[width * j1 + i1];
                f2 = (int)imageData[width * j2 + i1];

                // 返回插值结果
                return (int) (f1 + (x - i1) * (f2 - f1));
            } else {
                // 计算四个最临近象素值
                f1 = imageData[width * j1 + i1];
                f2 = imageData[width * j1 + i2];
                f3 = imageData[width * j2 + i1];
                f4 = imageData[width * j2 + i2];
                


//                 插值1
                f12 = (int)(f1 + (x - i1) * (f2 - f1));
                // 插值2
                f34 = (int)((f3 + (x - i1) * (f4 - f3)));
                // 插值3
                return  (int)(f12 + (y - j1) * (f34 - f12));
            }
        }

    }    
}

 to use:

    try {
                    
            Image src = Image.createImage("/image.png"); 
            int destW = 400; //缩放后图片的宽度
            int destH = 400; //缩放图片的高度
               
            int srcW = src.getWidth();   
            int srcH = src.getHeight();   
            // create pixel arrays 
            int[] destPixels = new int[destW * destH]; // array to hold destination  
            int[] destRed = new int[destW * destH];
            int[] destGreen = new int[destW * destH];
            int[] destBlue = new int[destW * destH];
            // pixels 
            int[] srcPixels = new int[srcW * srcH]; // array with source's pixels   
            int[] srcRed = new int[srcW * srcH];
            int[] srcGreen = new int[srcW * srcH];
            int[] srcBlue = new int[srcW * srcH];
            src.getRGB(srcPixels, 0, srcW, 0, 0, srcW, srcH);   
            
            for (int i = 0; i < srcPixels.length; i++) {
                srcBlue[i] = srcPixels[i]&0x000000ff;
                srcGreen[i] = (srcPixels[i]>>8)&0x000000ff;
                srcRed[i] = (srcPixels[i]>>16)&0x000000ff;
            }
            // simple point smapled resizing 
            // loop through the destination pixels, find the matching pixel on 
            // the source and use that 
            for (int destY = 0; destY < destH; ++destY) {   
                for (int destX = 0; destX < destW; ++destX) {   
                    float srcX = ((float)destX * (float)srcW) / (float)destW; 
                    float srcY = ((float)destY * (float)srcH) / (float)destH; 
                    try {
                        destRed[destX + destY * destW] = ZoomPixel.interpolation(srcRed, srcW, srcH, srcX, srcY); 
                        destGreen[destX + destY * destW] = ZoomPixel.interpolation(srcGreen, srcW, srcH, srcX, srcY); 
                        destBlue[destX + destY * destW] = ZoomPixel.interpolation(srcBlue, srcW, srcH, srcX, srcY); 

                        int value = 0xff000000;
                        value|=destBlue[destX + destY * destW];
                        value|=(destGreen[destX + destY * destW]<<8);
                        value|=(destRed[destX + destY * destW]<<16);
                        
                        destPixels[destX + destY * destW] = value;                                       
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    }   
            }   
        } catch (Exception e) {
            e.printStackTrace();
        }
 2.
 
原文地址:https://www.cnblogs.com/qiengo/p/2553664.html