数字图像处理(十三)图像放大及双线性插值算法

导言

图像放大是日常学习中经常要用到的两个算法,我们首先讨论缩放的流程以及放大时如何优化双线性插值算法。
采用国际标准测试图像Lena,为了方便,我们将读入的彩色图转为灰度图进行缩放。

图像放大

和图像缩小不同,图像放大是小数据量到大数据量的处理过程,因此需要对许多未知的数据进行估计。
如果一幅 W × H W\times H W×H图像要放大 k 1 × k 2 k_1 \times k_2 k1×k2(即行放大 k 1 k_1 k1倍,列放大 k 1 k_1 k1倍),则放大后的图像大小为 i n t ( W ∗ k 1 ) × i n t ( H ∗ k 1 ) int(W*k_1)\times int(H*k_1) int(Wk1)×int(Hk1),这里取int的原因是乘出来的的数值可能为小数。
具体过程如下图所示:
我们有一个图像大小为4*4,每一个像素值都是1。
在这里插入图片描述

它的坐标矩阵如下所示
在这里插入图片描述

现在我们将原图(下面称为f)的行和列都放大1.5倍( k 1 = 1.5 k_1=1.5 k1=1.5 k 2 = 1.5 k_2=1.5 k2=1.5)。则放大后的图像(下面称为g)的 widht=6= 4 × 1.5 4\times1.5 4×1.5,height=6= 4 × 1.5 4\times1.5 4×1.5。接着我们要计算g中的坐标在f中映射。如下图所示:
在这里插入图片描述
我们计算出g中的(0,1)点应该对应f中的(0,0.67)点,但是f中无此坐标,所有我们使用线性插值法来计算(0,0.67)处的像素值。
线性插值法如果你没有了解过,建议先去了解一下。
线性插值法的通用公式如下:
f ( x 2 ) − f ( x 1 ) x 2 − x 1 = f ( x ) − f ( x 1 ) x − x 1 \frac{f(x_2)-f(x_1)}{x_2-x_1}=\frac{f(x)-f(x_1)}{x-x_1} x2x1f(x2)f(x1)=xx1f(x)f(x1)
f ( x ) = x 2 − x x 2 − x 1 f ( x 1 ) + x − x 1 x 2 − x 1 f ( x 2 ) f(x)=\frac{x_2-x}{x_2-x_1}f(x_1)+\frac{x-x_1}{x_2-x_1}f(x_2) f(x)=x2x1x2xf(x1)+x2x1xx1f(x2)
上面的第二个公式就是权重公式。
所以 f ( 0 , 0.67 ) = 1 − 0.67 1 − 0 f ( 0 , 0 ) + 0.67 − 0 1 − 0 f ( 0 , 1 ) = 1 f(0,0.67)=\frac{1-0.67}{1-0}f(0,0)+\frac{0.67-0}{1-0}f(0,1)=1 f(0,0.67)=1010.67f(0,0)+100.670f(0,1)=1
我们再举一个列子:
在这里插入图片描述
注意到此时计算出的x为1.3,在1和2之间;y为2.67,在2和3之间。所以我们要通过双线性插值来计算(1.3,2.67)坐标处的像素值。双线性插值算法如下图。
在这里插入图片描述
具体就是先对 Q 11 Q_{11} Q11 Q 21 Q_{21} Q21进行线性插值,计算出 R 1 R_1 R1,然后对 Q 12 Q_{12} Q12 Q 22 Q_{22} Q22进行线性插值,计算出 R 2 R_2 R2,最后对 R 2 R_2 R2 R 1 R_1 R1进行插值,计算出我们想要的坐标 P P P的值。
f ( 1.3 , 2 ) = ( 2 − 1.3 ) f ( 2 , 2 ) + ( 1.3 − 1 ) f ( 1 , 2 ) = 1 f(1.3,2)=(2-1.3)f(2,2)+(1.3-1)f(1,2)=1 f(1.3,2)=(21.3)f(2,2)+(1.31)f(1,2)=1
f ( 1.3 , 3 ) = ( 2 − 1.3 ) f ( 2 , 3 ) + ( 1.3 − 1 ) f ( 1 , 3 ) = 1 f(1.3,3)=(2-1.3)f(2,3)+(1.3-1)f(1,3)=1 f(1.3,3)=(21.3)f(2,3)+(1.31)f(1,3)=1
我们对(1.3,2)和(1.3,3)进行线性插值。
f ( 1.3 , 2.67 ) = ( 3 − 2.67 ) f ( 1.3 , 2 ) + ( 2.67 − 2 ) f ( 1.3 , 3 ) = 1 f(1.3,2.67)=(3-2.67)f(1.3,2)+(2.67-2)f(1.3,3)=1 f(1.3,2.67)=(32.67)f(1.3,2)+(2.672)f(1.3,3)=1
所以根据双线性插值计算出来的(1.3,2.37)处的像素值为1。

总而言之,如果计算出的点在f中是边缘点,则使用单线性插值,如果不是边缘点,则使用双线性插值。
C++代码如下:

int main()
{
    
    
    cv::Mat image = cv::imread("LenaRGB.bmp");
    //Using gray image for easy calculations
    cv::Mat grayImage(image.size(), CV_8UC1);
    cv::cvtColor(image, grayImage, CV_BGR2GRAY);
    
    int width = grayImage.cols;
    int height = grayImage.rows;

    // Scale factor
    double k1 = 1.4;
    double k2 = 1.7;
    //Create zero mat to save the outputs
    cv::Mat outImage = cv::Mat::zeros(round(height*k1), round(width*k2), CV_64FC1);

    for (int row = 0; row < outImage.rows; row++)
    {
    
    
        for (int col = 0; col < outImage.cols; col++)
        {
    
    
        	//得到放大图坐标在原图中对应的坐标
            double srcX = (1 / 1.4)*row;
            double srcY = (1 / 1.7)*col;

            if (srcX > (grayImage.rows - 1))
            {
    
    
                srcX = grayImage.rows - 1;
            }
            if (srcY > (grayImage.cols - 1))
            {
    
    
                srcY = grayImage.cols - 1;
            }

            // Get x0,x1,y0,y1
            int x0 = floor(srcX);
            int x1 = ceil(srcX);
            int y0 = floor(srcY);
            int y1 = ceil(srcY);

            if ((x0 == x1) && (y0 == y1))
            {
    
    
                outImage.at<double>(row, col) = (double)grayImage.at<uchar>(x0, y0);
                continue;
            }
            // 如果是边缘点,则使用单线性插值
            if (x0 == x1)
            {
    
    
                double temp = (y1 - srcY)*grayImage.at<uchar>(x0, y0)+
                               (srcY-y0)*grayImage.at<uchar>(x0,y1);
                outImage.at<double>(row, col) = temp;
                continue;
            }
            if (y0 == y1)
            {
    
    
                double temp = (x1 - srcX)*grayImage.at<uchar>(x0, y0) +
                    (srcX - x0)*grayImage.at<uchar>(x1, y1);
                outImage.at<double>(row, col) = temp;
                continue;
            }
            // 不是边缘点则使用双线性插值
            double temp1 = (y1 - srcY)*grayImage.at<uchar>(x0, y0) +
                (srcY - y0)*grayImage.at<uchar>(x0, y1);
            double temp2 = (y1 - srcY)*grayImage.at<uchar>(x1, y0) +
                (srcY - y0)*grayImage.at<uchar>(x1, y1);
            double temp = (x1 - srcX)*temp1 + (srcX - x0)*temp2;
            outImage.at<double>(row, col) = temp;
        }
    }
    // Convert CV_64FC1 to CV_8UC1 
    outImage.convertTo(outImage, CV_8UC1);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_41596730/article/details/127498792