图像缩放与插值

一. 最近邻插值

这是最简单的插值办法, 为了方便描述, 先考虑一维的情况:

最近邻插值将连续坐标x近似为最近的整数u0, 输出值g_out(x) = g_in(u0) , 其中u0 = round(x)

假设给定一个输入 123, 60, 255 长度为3, 那么每个点对应的坐标分别是0, 1, 2

如果现在要放大到长度为5, 那么每个点对应的原坐标分别为 -0.2, 0.4, 1.0, 1.6, 2.2

选择最近邻的值作为插值, 对应的输出就是: 123, 123, 60, 255, 255

void nearest_neighbor(uchar *input, uchar *output, int len_in, int len_out)
{
    double scale = (double) len_in / len_out, xn = scale * 0.5;
    int i;
    for (i = 0; i < len_out; ++i)
    {
        *output++ = *(input + (int)xn);
        xn += scale;
    }
}

图像缩放中近邻插值实现起来很方便, 代码很容易写, 处理速度也特别快, 但效果实在太差

用QImage做了个测试

void ImgProcess::nearestZoom(
        const QImage &imgSrc, QImage &imgDest,
        const QSize &sizeSrc, const QSize &sizeDest)
{
    int
            depthSrc = imgSrc.depth(),
            depthDest = imgDest.depth();

    if(depthSrc != depthDest)
    { //different depth
        return ;
    }

    if(depthSrc != 32)
    { // not 32-bit color
        return;
    }

    typedef int Pix;

    Pix
            *pSrcCurrentLine, *pDestCurrentPix,

            *pSrcData = (Pix *)imgSrc.bits(),
            *pDestData = (Pix *)imgDest.bits();

    int
            wSrc = sizeSrc.width(),
            hSrc = sizeSrc.height(),
            wDest = sizeDest.width(),
            hDest = sizeDest.height();

    double
            xSrc, ySrc,

            xScale = (double)wSrc/wDest,
            yScale = (double)hSrc/hDest,
            initXSrc = xScale * 0.5,
            initYSrc = yScale * 0.5;

    pDestCurrentPix = pDestData;
    ySrc = initYSrc;

    for(int i = 0; i< hDest; ++i)
    {
        xSrc = initXSrc;
        pSrcCurrentLine = pSrcData + wSrc * (int)ySrc;

        for(int j = 0; j< wDest; ++j)
        {
            *pDestCurrentPix++ = *(pSrcCurrentLine + (int)xSrc);
            xSrc += xScale;
        }
        ySrc += yScale;
    }
}

下面是缩放前后对比(缩放比例分别是0.8, 1.25)

origin(原图)

near1(*0.8)

near2(*1.25)

可以看到, 效果非常不好, 失真很严重

二. 线性插值

还是先考虑一维的情形:

估计值由相邻两个输入值决定, 每个输入值的权重与其和估计值位置的临近程度成正相关

g_out(x) = (u0+1-x) * g_in(u0) + (x-u0) * g_in(u0+1) , 其中u0 = round(x)

void linear(uchar *input, uchar *output, int len_in, int len_out)
{
    int max_in = len_in - 1, max_out = len_out -1;
    double scale = (double) max_in / max_out;
    double xf, weight_xn, weight_xn1;
    int i, xn, xn1;
    for (i = 0, xf = 0.0; i < max_out; ++i)
    {
        xn = (int)xf, xn1 = xn + 1;
        weight_xn1 = xf -xn;
        weight_xn = 1 - weight_xn;
        *output++ = *(input + xn) * weight_xn + *(input + xn1) * weight_xn1;
        xf += scale;
    }
    *output = *(input + max_in);
}

如果是二维就需要两个方向进行插值, 估计值由临近四个点决定

g_out(u,v)

=(u1-u)*(v1-v)*g_in(u0,v0)

+(u-u0)*(v-v0)*g_in(u1,v1)

+(u-u0)*(v1-v)*g_in(u1,v0)

+(u1-u)*(v-v0)*g_in(u0,v1)

其中u0 = round(u), v0 = round(v), u1 = u0+1, v1 = v0 +1

线性插值的缩放效果比最近邻插值就好得多了, 下面还是QImage测试的输出图像:

linear1(*0.8)

linear2(*1.25)

三. 三次插值

抽空再写…