双线性插值简介及C++代码实现

双线性插值

简介

双线性插值是指现在某个方向进行两次线性插值,之后在另外一个方向对插值的结果再进行一次线性插值。以下图为例,已知 Q 11 Q_{11} Q11 Q 12 Q_{12} Q12 Q 21 Q_{21} Q21 Q 22 Q_{22} Q22四个点的坐标和像素值、 P P P点的坐标,求 P P P点的像素值。双线性插值的做法是首先在 x x x方向上做两次线性插值获得 R 1 R_{1} R1点和 R 2 R_{2} R2点的像素值,之后在 y y y方向上做一次线性插值获得 P P P点的像素值。

双线性插值示意图

代码实现

在实际处理的过程中,依次遍历目标 M a t Mat Mat所有像素点 ( x , y ) (x,y) (x,y),找到像素点 ( x , y ) (x,y) (x,y)在原 M a t Mat Mat上的对应点 ( s x , s y ) (s_{x},s_{y}) (sx,sy)。由于 s x s_{x} sx s y s_{y} sy的值可能不是整数,所以取最接近的四个点进行双线性插值。话不多说,直接来看c++代码。

Mat sxxcz1(Mat img, int h, int w) {
    Mat img2(h, w,  CV_8UC3, Scalar::all(0));
    float ori_h = img.rows;
    float ori_w = img.cols;
    float rate_h = ori_h / h;
    float rate_w = ori_w / w;
    for (int i = 0; i < h; i++) {
        float f_h = i * rate_h;
        int s_h = cvFloor(f_h);
        f_h -= s_h;
        
        if (s_h < 0) {
            f_h = 0, s_h = 0;
        } 
        if (s_h >= ori_h - 1) {
            f_h = 1, s_h = ori_h - 2;
        }

        for (int j = 0; j < w; j++) {
            
            float f_w = j * rate_w;
            int s_w = cvFloor(f_w);
            f_w -= s_w;
            
            if (s_w < 0) {
                f_w = 0, s_w = 0;
            } 
            if (s_w >= ori_w - 1) {
                f_w = 1, s_w = ori_w - 2;
            }

            for (int k = 0; k < img.channels(); k++) {
                img2.at<Vec3b>(i, j)[k] = (1 - f_h) * (1 - f_w) * img.at<Vec3b>(s_h, s_w)[k] 
                                        + (1 - f_h) * f_w * img.at<Vec3b>(s_h, s_w + 1)[k]
                                        + f_h * (1 - f_w) * img.at<Vec3b>(s_h + 1, s_w)[k] 
                                        + f_h * f_w * img.at<Vec3b>(s_h + 1, s_w + 1)[k];
            }
        }
    }
    return img2;
}

效果展示如下:
原图

放大2倍
但是在使用上面的代码处理比较简单的图像时,放大后的图像会有明显的违和感。
原图放大图
针对这个问题,查阅了一下官方代码的源码,发现在计算周围四个点的权重时,官方使用的代码更合理,于是对自己的代码进行了修改。修改后的代码如下:

Mat sxxcz2(Mat img, int h, int w) {
    Mat img2(h, w,  CV_8UC3, Scalar::all(0));
    float ori_h = img.rows;
    float ori_w = img.cols;
    float rate_h = ori_h / h;
    float rate_w = ori_w / w;
    for (int i = 0; i < h; i++) {
        float f_h = ((i + 0.5) * rate_h - 0.5);
        int s_h = cvFloor(f_h);
        f_h -= s_h;
        
        if (s_h < 0) {
            f_h = 0, s_h = 0;
        } 
        if (s_h >= ori_h - 1) {
            f_h = 1, s_h = ori_h - 2;
        }  

        short cbufh[2];
		cbufh[0] = cv::saturate_cast<short>((1.f - f_h) * 2048);
		cbufh[1] = 2048 - cbufh[0];
        for (int j = 0; j < w; j++) {
            
            float f_w = ((j + 0.5) * rate_w - 0.5);
            int s_w = cvFloor(f_w);
            f_w -= s_w;
            
            if (s_w < 0) {
                f_w = 0, s_w = 0;
            }
            if (s_w >= ori_w - 1) {
                f_w = 1, s_w = ori_w - 2;
            }

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

            for (int k = 0; k < img.channels(); k++) {
                cout << (1 - f_w) * img.at<Vec3b>(s_h, s_w)[k] << endl;;
                img2.at<Vec3b>(i, j)[k] = (cbufh[0] * cbufw[0] * img.at<Vec3b>(s_h, s_w)[k] 
                                        + cbufh[0] * cbufw[1] * img.at<Vec3b>(s_h, s_w + 1)[k]
                                        + cbufh[1] * cbufw[0] * img.at<Vec3b>(s_h + 1, s_w)[k] 
                                        + cbufh[1] * cbufw[1] * img.at<Vec3b>(s_h + 1, s_w + 1)[k]) >> 22;
            }
        }
    }
    return img2;
}

运行修改后的代码,图像质量明显提升。
修改后代码放大图

总结

通过本文的撰写,自己对双线性插值算法有了更加深刻的理解。在复现代码的过程中,发现自己还是不够关注运行效率等细节上的问题,在今后的工作和生活中还是需要多加锻炼。

  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值