双线性插值的实现

图像在进行仿射变换后由于变换后的某些像素点在原图像中并不存在,因此需采用灰度内插为新位置赋灰度值,常用的内插方法包括:最近邻内插法、双线性内插法以及双三次内插技术,综合插值效果及效率,双线性内插法得到了更广泛的应用。

双线性插值的算法实现是这样的:首先对源图像与目标图像做对比,比如源图像大小为1100*1100,目标图像大小为500*500,则二者之间的宽度比和高度比均可得到,为2.2,也就是说目标图像中的(1,1)映射到源图像中是(2.2,2.2),而实际图像中的像素点位置都是整数,没有浮点数,因此我们可以使用与此数值接近的四个点通过插值得到目标图像中(1,1)位置的像素值,这四个点是:Q11(2,2),Q21(2,3),Q12(3,2),Q22(3,3)。从通用性考虑,Q11的横纵坐标分别为x1,y1,同理Q21的横纵坐标分别为x1,y2,Q12的横纵坐标分别为x2,y1,Q22的横纵坐标分别为x2,y2。而目标图像所映射到源图像中的为(2.2,2.2)即为(x,y),这样通过四个点映射到(x,y)即可得到目标图像(1,1)处的像素值,首先在x方向进行两次线性插值计算:

f(x,y1) = (x2-x)/(x2-x1) * f(x1,y1) + (x-x1)/(x2-x1) *f(x2,y1)

f(x,y2)=  (x2-x)/(x2-x1) * f(x1,y2) + (x-x1)/(x2-x1)*f(x2,y2)

然后在y方向进行一次插值计算:

f(x,y) = (y2-y)/(y2-y1)*f(x,y1) + (y-y2)/(y2-y1)*f(x,y2)

由于x2-x1 = 1, y2-y1 = 1,最终可得

f(x,y) = (x2-x)(y2-y)f(x1,y1) + (x-x1)(y-y1)f(x2,y2)  + (x-x1)(y2-y)f(x2,y1) + (x2-x)(y-y1)f(x1,y2)

令x-x1=u,y-y1 =v,则上式又可转化为:

f(x,y) = (1-u)(1-v)f(x1,y1) + uvf(x2,y2) + u(1-v)f(x2,y1) + (1-u)vf(x1,y2)

式中x,y为目标图像中的像素点位置,(x1,y1),(x1,y2),(x2,y1),(x2,y2)为对应的源图像中的四个像素点位置,那么如何得到这四个点的坐标值呢?若按原始坐标变换关系,有:

srcX = dstX * (srcWidth/dstWidth)

srcY = dstY* (srcHeight/dstHeight)

正如我们举的例子中,对应于目标图像(1,1),可得到其对应源图像的像素位置:

x = 1 *(1100/500) = 2.2 

y = 1 * (1100/500) =2.2

为了使目标图像和源图像中心对齐,上述公式改写为:

srcX = (dstX +0.5) * (srcWidth/dstWidth) - 0.5

srcY = (dstY+0.5)* (srcHeight/dstHeight) - 0.5

另外为了提高运算速度,建议将浮点运算转换成整数运算,opencv中选择的放大倍数是2048,这可通过左移11位得到。最终得到的结果则需右移22位(u,v的乘积)。

基于此,得到双线性插值的算法如下:

Mat matDst1,matSrc;
	matSrc = cv::imread("opencv.jpg",IMREAD_COLOR);
	int channel = matSrc.channels();
	matDst1 = Mat(1000,1000,CV_8UC3);
	float scale_x = matSrc.cols/1000.0,scale_y = matSrc.rows/1000.0;

	uchar* dataDst = matDst1.data; //目标图像
    int stepDst = matDst1.step;
    uchar* dataSrc = matSrc.data;//源图像
    int stepSrc = matSrc.step; //一行的宽度
    int iWidthSrc = matSrc.cols; 
    int iHieghtSrc = matSrc.rows;  

    for (int j = 0; j < matDst1.rows; ++j) //高度方向
    {
        float fy = (float)((j + 0.5) * scale_y - 0.5);
        int sy = cvFloor(fy);
        fy -= sy; //u = fy
        sy = std::min(sy, iHieghtSrc - 2);//边界限制
        sy = std::max(0, sy);

        short cbufy[2];
        cbufy[0] = cv::saturate_cast<short>((1.f - fy) * 2048);// 1-u 扩大2048倍,即左移11位
        cbufy[1] = 2048 - cbufy[0];

        for (int i = 0; i < matDst1.cols; ++i)//宽度方向
        {
            float fx = (float)((i + 0.5) * scale_x - 0.5);
            int sx = cvFloor(fx);
            fx -= sx;
			//以下两个if与上面的sy的两条语句(min和max)是一样的
            if (sx < 0) {
                fx = 0, sx = 0;
            }
            if (sx >= iWidthSrc - 1) {
                fx = 0, sx = iWidthSrc - 2;
            }

            short cbufx[2];
            cbufx[0] = cv::saturate_cast<short>((1.f - fx) * 2048);
            cbufx[1] = 2048 - cbufx[0];

            for (int k = 0; k < matSrc.channels(); ++k)
            {
                *(dataDst+ j*stepDst + 3*i + k) = (*(dataSrc + sy*stepSrc + 3*sx + k) * cbufx[0] * cbufy[0] + 
                    *(dataSrc + (sy+1)*stepSrc + 3*sx + k) * cbufx[0] * cbufy[1] + 
                    *(dataSrc + sy*stepSrc + 3*(sx+1) + k) * cbufx[1] * cbufy[0] + 
                    *(dataSrc + (sy+1)*stepSrc + 3*(sx+1) + k) * cbufx[1] * cbufy[1]) >> 22;
            }
        }
    }
	
    cv::imwrite("linear_1.jpg", matDst1);

上述代码通过编译,运行成功后将生成的新图像保存为jpg图片。

猜你喜欢

转载自blog.csdn.net/lovekdy/article/details/85601999