什么是超分

超分,全称超分辨率(super resolution),就是将分辨率低的图像通过一定的算法转化为分辨率高的图像,使图像更加清晰,细节更加明显。如下图:

超分ESRGAN_超分辨率重建

超分算法

超分算法有传统超分和深度学习超分,其中传统超分算法可以使用插值法和 SVM。对于插值法,我现在实现了单线性插值和双线性插值算法,当然还有更多的插值算法,如双线性三插值 and so on 。SVM 方法有点思路,还在尝试。

另外,由于我做的是视频超分算法,而上面提到的这些都是针对单帧图像的,也就是帧内超分。所以我有个大胆的想法:有没有可能研究出帧间超分的算法呢?(当然想想就好,以我现阶段的能力估计还做不到。)

单线性插值

这是我第一个想到的算法,插值过程如下图所示:

1.原图像(绿点表示原来的像素点,空白的表示插值的位置)

超分ESRGAN_机器学习_02


2. 第一次插值(蓝色的1)

超分ESRGAN_机器学习_03


此处先进行行插值。当然也可以选择先进行列插值。

  1. 第二次插值(红色的2)

    至此,整个插值算法结束。

当然这个插值算法是有很大的不足的,因为在插值过程中只单独参考了行或者列周围的像素值,并没有综合参考行和列,因此行列间落差会很大,且先行后列和先列后行的顺序不同也会导致处理后得到的图片不同。

代码如下:
(src: 源图像,times: 放大倍数,返回值:超分完成的图像)

cv::Mat singleLinear(const cv::Mat&src,int times=2){
    cv::Mat dst;
    dst=cv::Mat::zeros(cv::Size(src.rows*times,src.cols*times),CV_8UC1);

    //第一次插值
   for(int j=0;j<src.cols;j++){
        for(int i=1;i<src.rows;i++){
            double delta=(double)(src.at<uchar>(i-1,j)-src.at<uchar>(i,j))/times*1.0;
            double current_val=src.at<uchar>(i-1,j);
            for(int k=0;k<times;k++){
            	//防止越界溢出
                if(current_val<=0) current_val=0;
                if(current_val>=255) current_val=255;
                dst.at<uchar>((i-1)*times+k,j*times)=(unsigned char)current_val;
                current_val+=delta;
            }
        }
    }

    //第二次插值
    for(int i=0;i<dst.cols;i++){
        for(int j=0;j<src.rows;j++){
            double delta=(double)(dst.at<uchar>(i,(j+1)*times)-dst.at<uchar>(i,j*times))/times*1.0;
            double current_val=dst.at<uchar>(i,j*times);

            for(int k=1;k<times;k++){
                //防止越界溢出
                if(current_val<=0) current_val=0;
                if(current_val>=255) current_val=255;
                dst.at<uchar>(i,j*times+k)=(unsigned char)current_val;
                current_val+=delta;
                
            }
        }
    }

    return dst;
}

所使用的源图像(分辨率:25x25)

超分ESRGAN_超分ESRGAN_04


超分后的结果(放大20倍,即500x500)

超分ESRGAN_算法_05


可以看到,效果确实不尽人意,像素块非常的明显

双线性插值


双线性插值算法的优点有:每次插值用上4个点,同时考虑到行和列元素的值,使得插值效果更好。该算法也是 OpenCV 中 cv::resize 函数默认采用的算法。

程序代码:

cv::Mat bitLinear(const cv::Mat &src,int times){
    cv::Mat dst=cv::Mat::zeros(cv::Size(src.cols*times,src.rows*times),CV_8UC1);

    //先对边界进行单线性插值处理
    for(int j=0;j<dst.cols;j++){
        double lamda=(j%times)/(double)times;
        dst.at<uchar>(0,j)=(1-lamda)*src.at<uchar>(0,j/times)+lamda*src.at<uchar>(0,j/times+1);
        dst.at<uchar>(dst.rows-1,j)=(1-lamda)*src.at<uchar>(src.rows-1,j/times)+lamda*src.at<uchar>(src.rows-1,j/times+1);
    }
    for(int i=0;i<dst.rows-1;i++){
        double lamda=(i%times)/(double)times;
        dst.at<uchar>(i,0)=(1-lamda)*src.at<uchar>(i/times,0)+lamda*src.at<uchar>(i/times+1,0);
        dst.at<uchar>(i,dst.cols-1)=(1-lamda)*src.at<uchar>(i/times,dst.cols-1)+lamda*src.at<uchar>(i/times+1,dst.cols-1);
    }

    for(int i=1;i<dst.rows-1;i++){
        double lamda_rows=(i%times)/(double)times;
        for(int j=1;j<dst.cols-1;j++){

            double lamda_cols=(j%times)/(double)times;
            double top_mid=(1-lamda_cols)*src.at<uchar>(i/times,j/times)+lamda_cols*src.at<uchar>(i/times,j/times+1);
            double bot_mid=(1-lamda_cols)*src.at<uchar>(i/times+1,j/times)+lamda_cols*src.at<uchar>(i/times+1,j/times+1);

            dst.at<uchar>(i,j)=(1-lamda_rows)*top_mid+lamda_rows*bot_mid;
        }
    }
    return dst;
}

我这里是先对边界进行处理,存在一些问题,会导致边界上像素值突变。但并不影响我们的观感

采用图片:

超分ESRGAN_计算机视觉_06


运行结果(放大20倍):

超分ESRGAN_机器学习_07


跟单线性插值相比,看起来真的舒服多了,是吧 ~