图像上采样算法(双线性插值)的实现

  • Post author:
  • Post category:其他


图像进行几何变换的时候,没有办法给一些像素点直接赋值,这才有了插值算法

先说最邻近插值,该算法的原理很简单,仅仅是将原图像的坐标系映射到另一个坐标系上,找到相邻的像素点进行赋值,一个新的像素点仅由原图的一个像素点决定,算法简单,效率高,但是该算法处理后的图像,边缘的马赛克放大后十分明显,双线性插值就很好的解决了这一问题。

双线性插值是线性插值的一种扩展,它是在线性插值的基础上分别进行两次x轴上的单线性和一次y轴上单线性,区别于最临近插值,双线性插值处理后的图像边缘会变得较为柔和,放大后的图像不会出现马赛克,但是细节会大量丢失,图像缩放带来的损失很低(但是任然存在),双线性插值的一个点由它附近的四个点来决定,用这四个点来拟合出需要插入的新像素点的rgb值,计算方法如下。


双线性插值(图片来源百度百科

)


\frac{y-y_{0}}{​{x-x_{0}}} = \frac{y_{1}-y_{0}}{x_{1} - x_{0}}=>y=\frac{x-x_{0}}{x_{1}-x_{0}}+\frac{x_{1}-x}{x_{1}-x_{0}}y_{0}


线性插值


四个坐标的表示如下:

双线性就是两次


x


的线性加一次


y


方向的线性,有以下
f(Q_{xx})
代表某一点的灰度值(参上图)


在x方向上做插值:


f(R_{1}) = f(x,y_{1})\approx \frac{x_{2}-x}{x_{2}-x_{1}}f(Q_{11})+\frac{x-x_{1}}{x_{2}-x_{1}}f(Q_{21})


f(R_{2}) = f(x,y_{2})\approx \frac{x_{2}-x}{x_{2}-x_{1}}f(Q_{12})+\frac{x-x_{1}}{x_{2}-x_{1}}f(Q_{22})


在y方向上做插值:


f(x, y)\approx\frac{y_{2}-y}{y_{2}-y_{1}}f(R_{1})+\frac{y-y_{1}}{y_{2}-y_{1}}f(R_{2})


综上:


f(x, y)\approx\frac{y_{2}-y}{y_{2}-y_{1}}(\frac{x_{2}-x}{x_{2}-x_{1}}f(Q_{11})+\frac{x-x_{1}}{x_{2}-x_{1}}f(Q_{21}))+\frac{y-y_{1}}{y_{2}-y_{1}}(\frac{x_{2}-x}{x_{2}-x_{1}}f(Q_{12}+\frac{x-x_{1}}{x_{2}-x_{1}}f(Q_{22}))

这里的分母由于都是两个相邻的点计算出来的,那么它一定恒等于1,比如x0=4, x1=5,那么分母就是x1-x0为1,而该公式所有的分母都符合这一规则,故,公式所有的分母都可以简化为1。

其实有了公式以后,我们只需要套公式运算就可以了,但是这里会有一个问题:即,新的坐标系和原图像坐标系的几何中心对称问题,在坐标系变换中,通常的变换是:

srcX=dstX(Src_{width}/dst_{width})

srcY=dstY(src_{height}/dst_{height})

这里引用大佬通俗易懂的解释:

原图像的有些点没有参与计算。举个例子,把9∗9的原图像缩小成3∗3,原图像的原点(0,0)和目标图像的原点(0,0)都为左上角,目标图像右上角的坐标为(0,2),对应原图像的坐标为(0∗(9/3),2∗(9/3))=(0,6)。目标图像右边已经没有点了,(0,6)右边的像素点也就用不到了,原图像和目标图像的像素之间的对应关系如下:


图片来源51CTO博客

从图片可以看出,只有圈出来的红色部分参与运算了。目标图像的每个像素点的灰度值相对于原图像偏左上方,右下角的元素实际上没有参与运算。

为了让原图像和目标图像的中心对齐,我们规定另外一种变换方式 :

srcX=dstX(src_{width}/dst_{width}) + 0.5(src_{width}/dst_{width}-1)

srcY=dstY(src_{height}/dst_{height}) + 0.5(src_{height}/dst_{height}-1)

就是在原来的变换后面加了调节因子:

0.5(src_{width}/dst_{width}-1)

这种变换下,目标图像的中心点(1,1),对应了原图像的中心点(4,4),两个图像的几何中心重合,能充分利用原图像的点,并且目标图像的每个像素点之间都是等间隔的,也都和两边有一定的边距。实际上,在openCv中也是这种变换方式。


图片来源51CTO博客

代码:

int is_a_negative_number(int i){
    if(i < 0){
        return 0;
    }
    return i;
}
Mat Bilinear_interpolation(Mat image, int height, int width){
    // initializer matrix
    Mat empty = cv::Mat::zeros(height, width, CV_8UC3);
    cout << "bilinear empty matrix dimension is " << empty.channels() << endl;

    // get scale rate
    double destination_rate_h = image.rows / (double)height;
    double destination_rate_w = image.cols / (double)width;

    for (int i = 0; i < height; ++i) {
        for (int j = 0; j < width; ++j) {
            auto &ptr = empty.at<Vec3b>(i, j);

//            double src_x = (j + 0.5) * destination_rate_w - 0.5;
//            double src_y = (i + 0.5) * destination_rate_h - 0.5;

            double src_x = j * destination_rate_w + 0.5 * (destination_rate_w - 1);
            double src_y = i * destination_rate_h + 0.5 * (destination_rate_h - 1);

            int src_x0 = (int)floor(src_x);
            int src_x1 = min(src_x0 + 1, image.cols - 1);

            int src_y0 = (int) floor(src_y);
            int src_y1 = min(src_y0 + 1, image.rows - 1);

            auto &originx1 = image.at<Vec3b>(is_a_negative_number(src_y0), is_a_negative_number(src_x0));
            auto &originx2 = image.at<Vec3b>(is_a_negative_number(src_y0), is_a_negative_number(src_x1));

            auto &originy1 = image.at<Vec3b>(is_a_negative_number(src_y1), is_a_negative_number(src_x0));
            auto &originy2 = image.at<Vec3b>(is_a_negative_number(src_y1), is_a_negative_number(src_x1));
            for (int k = 0; k < empty.channels(); k++) {
                double temp0 = (src_x1 - src_x) * originx1[k] + (src_x - src_x0) * originx2[k];
                double temp1 = (src_x1 - src_x) * originy1[k] + (src_x - src_x0) * originy2[k];
                ptr[k] = (int)((src_y1 - src_y) * temp0 + (src_y - src_y0) * temp1);
            }
        }
    }
    imshow("bilinear", empty);
    return empty;
}



版权声明:本文为qq_52363737原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。