美文网首页
重现image resize-双线性插值

重现image resize-双线性插值

作者: Zz鱼丸 | 来源:发表于2017-11-21 14:37 被阅读0次

Opencv水太深,代码看的头疼。
从库一层一层往下看,发现opencv采用了许多代码加速,例如并行计算,SSE等。这几天做的是resize的线性插值的方法。

SSE(Streaming SIMD Extensions)是Intel在3D Now!发布一年之后,在PIII中引入的指令集,是MMX的超集。AMD后来在Athlon XP中加入了对这个指令集的支持。这个指令集增加了对8个128位寄存器XMM0-XMM7的支持,每个寄存器可以存储4个单精度浮点数。使用这些寄存器的程序必须使用FXSAVE和FXRSTR指令来保持和恢复状态。但是在PIII对SSE的实现中,浮点数寄存器又一次被新的指令集占用了,但是这一次切换运算模式不是必要的了,只是SSE和浮点数指令不能同时进入CPU的处理线而已。
SSE2是Intel在P4的最初版本中引入的,但是AMD后来在Opteron 和Athlon 64中也加入了对它的支持。这个指令集添加了对64位双精度浮点数的支持,以及对整型数据的支持,也就是说这个指令集中所有的MMX指令都是多余的了,同时也避免了占用浮点数寄存器。这个指令集还增加了对CPU的缓存的控制指令。AMD对它的扩展增加了8个XMM寄存器,但是需要切换到64位模式(AMD64)才可以使用这些寄存器。Intel后来在其EM64T架构中也增加了对AMD64的支持。
SSE3是Intel在P4的Prescott版中引入的指令集,AMD在Athlon 64的第五个版本中也添加了对它的支持。这个指令集扩展的指令包含寄存器的局部位之间的运算,例如高位和低位之间的加减运算;浮点数到整数的转换,以及对超线程技术的支持。
双线性内插值算法描述如下:
  对于一个目的像素,设置坐标通过反向变换得到的浮点坐标为(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也体现出了这一特点。
双线性内插法是利用待求象素四个邻象素的灰度在两个方向上作线性内插,如下图所示:

image.png
对于 (i, j+v),f(i, j) 到 f(i, j+1) 的灰度变化为线性关系,则有:

      f(i, j+v) = [f(i, j+1) - f(i, j)] * v + f(i, j)

同理对于 (i+1, j+v) 则有:

                  f(i+1, j+v) = [f(i+1, j+1) - f(i+1, j)] * v + f(i+1, j)

从f(i, j+v) 到 f(i+1, j+v) 的灰度变化也为线性关系,由此可推导出待求象素灰度的计算式如下:

                  f(i+u, j+v) = (1-u) * (1-v) * f(i, j) + (1-u) * v * f(i, j+1) + u * (1-v) * f(i+1, j) + u * v * f(i+1, j+1)

双线性内插法的计算比最邻近点法复杂,计算量较大,但没有灰度不连续的缺点,结果基本令人满意。它具有低通滤波性质,使高频分量受损,图像轮廓可能会有一点模糊。
基于此原理写代码

/*********************Resize_INTER_LINEAR******************************************/
//功能:利用线性插值修改图像尺寸
//参数:原图像,目标图像,目标长,目标宽
void mcv::Resize_INTER_LINEAR(mcv::Image &src, mcv::Image &dst, unsigned int width, unsigned int heigth)
{
    matDst1 = Mat(Size(width, heigth), matSrc.type(), Scalar::all(0));//为了显示使用opencv,Size(width,heigth)
    dst.height = matDst1.rows;
    dst.width = matDst1.cols;
    dst.channel = matDst1.channels();
    dst.data = matDst1.data;

    //缩放倍数
    double scale_x = (double)src.width / dst.width;
    double scale_y = (double)src.height / dst.height;

    for (int j = 0; j < dst.height; ++j)//f( sy+fy )
    {
        float fy = (float)((j + 0.5) * scale_y - 0.5);
        int sy = floor(fy);//整数部分
        fy -= sy;//小数部分
        sy = min(sy, src.height - 2);//因为有小数部分所以-2
        sy = max(0, sy);

        //由于cbufx[0]和cbufy[1]都放大了2048(2^11)倍,这里右移是为了还原,
        //这样先乘后右移的操作在图像处理中很常见,主要是为了简化计算
        short cbufy[2];
        if ((1.f - fy) * 2048>32767)
            cbufy[0] = 32767;
        else if ((1.f - fy) * 2048 < -32768)
            cbufy[0] = -32768;
        else
            cbufy[0] = (1.f - fy) * 2048;
        //cbufy[0] = cv::saturate_cast<short>((1.f - fy)*2048);
        cbufy[1] = 2048 - cbufy[0];
        //float cbufy[2];
        //cbufy[0] = cv::saturate_cast<float>((1.f - fy));
        //cbufy[1] = 1 - cbufy[0];
        for (int i = 0; i < dst.width; ++i)
        {
            float fx = (float)((i + 0.5) * scale_x - 0.5);
            int sx = floor(fx);
            fx -= sx;

            if (sx < 0) {
                fx = 0, sx = 0;
            }
            if (sx >= src.width - 1) {
                fx = 0, sx = src.width - 2;
            }

            //float cbufx[2];
            //cbufx[0] = cv::saturate_cast<float>((1.f - fx));
            //cbufx[1] = 1 - cbufx[0];
            short cbufx[2];
            if ((1.f - fx) * 2048>32767)
                cbufx[0] = 32767;
            else if ((1.f - fx) * 2048 < -32768)
                cbufx[0] = -32768;
            else
                cbufx[0] = (1.f - fx) * 2048;
            //cbufx[0] = cv::saturate_cast<short>((1.f - fx) * 2048);
            cbufx[1] = 2048 - cbufx[0];

            for (int k = 0; k < src.channel; ++k)
            {
                *(dst.data + j*dst.width*dst.channel + dst.channel * i + k) = (*(src.data + sy*src.width*src.channel + src.channel * sx + k) * cbufx[0] * cbufy[0] +
                    *(src.data + (sy + 1)*src.width*src.channel + src.channel * sx + k) * cbufx[0] * cbufy[1] +
                    *(src.data + sy*src.width*src.channel + src.channel * (sx + 1) + k) * cbufx[1] * cbufy[0] +
                    *(src.data + (sy + 1)*src.width*src.channel + src.channel * (sx + 1) + k) * cbufx[1] * cbufy[1]) >> 22;
            }
        }
    }
}

这样能够实现图像尺寸的线性插值

相关文章

  • 重现image resize-双线性插值

    Opencv水太深,代码看的头疼。从库一层一层往下看,发现opencv采用了许多代码加速,例如并行计算,SSE等。...

  • 【数学】线性插值

    线性插值有一阶、二阶、三阶,对应为单线性插值、双线性插值和三线性插值。三者皆为线性插值,不同之处在于单线性插值为2...

  • 重现image resize - 最邻近算法

    实习第一个任务就是摆脱opencv,写出图像的resize算法。难。。。现在总结一下:由于目前还需要opencv进...

  • 线性插值 np.interp

    计算得出0-299这300个数的线性插值 ok, 300个数字线性插值完成!

  • Stata系列-如何处理缺失值

    对于缺失的数据,为保持样本容量,可以采用“线性插值”的方法 以上是线性插值的简单原理,如果y有指数增长趋势,则应先...

  • 《念奴娇》桂林

    岁岁年年今又是 , 欢欢喜喜过双节 。 若要双节再重现 , 还需等待两三年 。 2017年10月5号

  • 【图像缩放算法】双立方(三次)插值

    当我们进行图像缩放的时候,我们就需要用到插值算法。常见的插值有: 最邻近插值 双线性插值 双立方(三次)插值在这三...

  • 专业术语

    Linear Interpolation 线性插值perplexity 复杂度Confounden 混杂Cons...

  • 线性插值,双线性插值Bilinear Interpolation

    线性插值 先讲一下线性插值:已知数据 (x0, y0) 与 (x1, y1),要计算 [x0, x1] 区间内某一...

  • 双线性插值算法解析与实例

    双线性插值在图像处理和gis分析中用到还比较多,整理下原理并写了个简单的示例 双线性插值,又称为双线性内插。在数学...

网友评论

      本文标题:重现image resize-双线性插值

      本文链接:https://www.haomeiwen.com/subject/oecwvxtx.html