图像处理-放大和缩小

  在计算机图像处理和计算机图形学中,图像缩放(image scaling)是指对数字图像的大小进行调整的过程。图像缩放是一种非平凡的过程,需要在处理效率以及结果的平滑度(smoothness)和清晰度(sharpness)上做一个权衡。当一个图像的大小增加之后,组成图像的像素的可见度将会变得更高,从而使得图像表现得“软”。相反地,缩小一个图像将会增强它的平滑度和清晰度。

 

图像缩小

  图像缩小是通过减少像素个数来实现的,因此要根据缩小的尺寸,从原图像中选择合适的像素点,使图像缩小后可以保持原有图像的特征。图像缩小的算法有很多,这里主要介绍和实现两种算法:等间隔采样、局部均值。

 

基于等间隔采样的图像缩小算法

  等间隔采样其实就是在原图中每间隔一定的距离取像素点放到输出图像上。设原图的大小为W*H,宽度和长度的缩小因子分别为看k1和k2,那么采样间隔为:W/k1,W/k2.也就是说在原图的水平方向每隔W/k1,在垂直方向每隔W/k2取一个像素。长和宽的缩小因子k1和k2相等时,图像时等比例缩小,不等时是不等比例缩小,缩小图像的长和宽的比例会发生变化。

算法的实现步骤:

1)计算采样间隔

    设原图的大小为W*H,将其放大(缩小)为(k1*W)*(K2*H),则采样区间为

    ii=1/k1;      

    jj=1/k2;

    当k1==k2时为等比例缩小;当k1!=k2时为不等比例放大(缩小);当k1<1 && k2<1时为图片缩小,k1>1 && k2>1时图片放大。

2)求出放大(缩小)后的图像

    设原图为F(x,y)(i=1,2, ……W; j=1,2,……H),放大(缩小)的图像为G(x,y)(x=1,2, ……M; y=1,2,……N,其中M=W*k1,N=H*k2),则有 G(x,y) = f(ii*x, jj*y)

 

代码实现:

//图像缩小—等间隔采样 参数:原图 x轴缩放比例 y轴缩放比例
Mat ZoomOutEqualInterval(Mat& src, double x_k,double y_k)
{
 
    Mat dst(src.rows*y_k, src.cols*x_k, src.type(), Scalar(0));
    x_k = 1 / x_k;//将缩小率转换为采样间隔
    y_k = 1 / y_k;
    cout << "dst x y:" << dst.cols << " " << dst.rows << endl;
    cout << "src x y:" <<src.cols << " " << src.rows << endl;
    if (src.channels() == 3)
    {
        for (int i = 0; i < dst.rows; i++)
        {
            for (int j = 0; j < dst.cols; j++)
            {
 
                int x = j * x_k + 0.5;
                int y = i * y_k + 0.5;
                if (x >= src.cols)x = src.cols - 1;
                if (y >= src.rows)y = src.rows - 1;
                dst.at<Vec3b>(i, j) = src.at<Vec3b>(y, x);
            }
        }
    }
    return dst;
}

缩小一倍的效果如下:

1_20190110230138_288.png

 

基于局部均值的图像缩小算法

算法描述

  等间隔采样的缩小方法实现简单,但是原图像中未被选中的像素信息会在缩小后的图像中丢失。局部均值的图像缩小方法对其进行了改进。在求缩小图像的像素时,不仅仅单纯的取在原图像中的采样点像素,而是以相邻的两个采样点为分割,将原图像分成一个个的子块。缩小图像的像素取相应子块像素的均值。

tt_20190110230350_946.png

根据局部均值缩小的原理:g11 = (f11 + f12 + f21 + f22 ) / 4

代码实现: 

//图像缩小—局部均值采样 参数:原图 x轴缩放比例 y轴缩放比例
Mat ZoomOutLocalMean(Mat& src, double x_k, double y_k)
{
 
    Mat dst(src.rows*y_k, src.cols*x_k, src.type(), Scalar(0));
    x_k = 1 / x_k;//将缩小率转换为采样间隔
    y_k = 1 / y_k;
    cout << "dst x y:" << dst.cols << " " << dst.rows << endl;
    cout << "src x y:" << src.cols << " " << src.rows << endl;
    if (src.channels() == 3)
    {
        for (int i = 0; i < dst.rows; i++)
        {
            for (int j = 0; j < dst.cols; j++)
            {
                int j_start = (j-1) * x_k+1;
                if (j_start < 0)j_start = 0;
                int j_end = j* x_k;
                if (j_end >=src.cols )j_end = src.cols;
 
                int i_start= (i-1) * y_k +1;
                if (i_start < 0)i_start = 0;
                int i_end = i * y_k;
                if (i_end >= src.rows)i_end = src.rows;
 
                int pix[3] = { 0,0,0 };
                
                int count = (j_end - j_start)*(i_end - i_start);
                for (int n = i_start; n < i_end; n++)
                    for (int m = j_start; m < j_end; m++) {
                        pix[0] = src.at<Vec3b>(n, m)[0];
                        pix[1] = src.at<Vec3b>(n, m)[1];
                        pix[2] = src.at<Vec3b>(n, m)[2];
                    }
                if (count != 0) {
                    Vec3b v(pix[0] / count, pix[1] / count, pix[2] / count);
                    dst.at<Vec3b>(i, j) = v;
 
                }
                else
                    dst.at<Vec3b>(i, j) = src.at<Vec3b>(i,j);
            }
        }
    }
    return dst;
}

效果图:

2_20190110230200_741.png

 

 

图像放大

  要将该图像放大两倍,可以有很多种算法,最简单的方法为邻域插值,即将每一个原像素原封不动地复制映射到扩展后对应四个像素中:

      这种方法在放大图像的同时保留了所有的原图像的所有信息,但是会产生锯齿现象。

双线性插值的效果对于放大的图像而言较领域插值来得平滑,但是却使得图像变得模糊而且仍然会有一部分锯齿现象。双三次插值更好比双线性插值更好。这里只实现最近邻插值和双线性插值。

 

最邻近插值

原理

    原始图像:I(x,y), 输出图像:F(x,y),则放大过程可转换为I(x,y)=F(int(c1*i),int(c2*j)),其中c1=1/y轴放大倍数,c2=1/x轴放大倍数系数乘以原坐标值得到的坐标值可能含有小数,所以,我们必须采取一定方法(如:四舍五入,我们这里直接去掉小数部分)来舍弃小数部分,从而取得整数表示的像素点坐标, 该过程即为最邻近插值方法。

 

优缺点

  最邻近插值简单且直观,但得到的图像质量不高,特别在图像放大后可能产生明显锯齿。

代码实现://图像放大-最近邻插值法

Mat ZoomInNearestNeighborInterpolation(Mat& src, double x_k, double y_k)
{
         Mat dst(src.rows*y_k, src.cols*x_k, src.type(), Scalar(0));
         x_k = 1 / x_k;
         y_k = 1 / y_k;
         if (src.channels() == 3)
         {
                   for (int i = 0; i < dst.rows; i++)
                   {
                            for (int j = 0; j < dst.cols; j++)
                                     dst.at<Vec3b>(i, j) = src.at<Vec3b>(y_k*i, x_k*j);
                   }
         }
         return dst;
}

 

x轴和y轴的放大系数均为2时,运行的效果为:

3_20190110230222_120.png

  

双线性插值

原理

      如图所示,最邻近插值是当求得p0后,直接找其邻近的点p1, p2, p3, p4中的一个的像素值作为目标点的像素;而双线性插值,则是根据p0点与周围4(p1, p2, p3, p4)距离关系计算目标点的像素值。

4_20190110230651_777.png

通过计算得到的原始点为p0(x0, y0),则其4周的点分别为:

x0的可能取值为:sx1 = (int)x0, sx2 = sx1 + 1

y0的可能取值为:sy1 = (int)y0, sy2 = sy1 + 1

设:

s1 = y0 – sy1

s2 = sx2 – x0

s3 = 1.0 – s1

s4 = 1.0 – s2

假设p1, p2, p3, p4的像素值分别为v1, v2, v3, v4,

则双线性插值计算p0点像素值v0公式为:

v0 = v1*s1*s4 + v2*s1*s2 + v3*s2*s3 + v4*s3*s4


优缺点

  双线性内插值法计算量大,但缩放后图像质量高,不会出现像素值不连续的的情况。

  双线性插值具有低通滤波器的性质,使高频分量受损,所以可能会使图像轮廓在一定程度上变得模糊。

 

代码实现:

//图像放大-双线性插值法
Mat ZoomInBilinearInterpolation(Mat& src, double x_k, double y_k)
{
    Mat dst(src.rows*y_k, src.cols*x_k, src.type(), Scalar(0));
    x_k = 1 / x_k;
    y_k = 1 / y_k;
    if (src.channels() == 3)
    {
        for (int i = 0; i < dst.rows; i++){
            for (int j = 0; j < dst.cols; j++)
            {
                double x0 = x_k * j;
                double y0 = y_k * i;
                int x1 = int(x0);
                int y1 = int(y0);
                
                
                double s1 = y0 - y1;
                double s4 = x0 - x1;
                double s2 = 1 - s4;
                double s3 = 1 - s1;
                if (x1 >= src.cols - 1)x1 = src.cols - 2;
                if (y1 >= src.rows - 1)y1 = src.rows - 2;
                dst.at<Vec3b>(i, j) = src.at<Vec3b>(y1, x1)*s1*s4 + src.at<Vec3b>(y1, x1 + 1)*s1*s2 + src.at<Vec3b>(y1 + 1, x1 + 1)*s2*s3 + src.at<Vec3b>(y1 + 1, x1)*s3*s4;
            }
        }
    }
    return dst;
}


效果图:

5_20190110230729_462.png 

 

三次卷积法

原理

  双立方插值算法与双线性插值算法类似,对于放大后未知的像素点P,将对其影响的范围扩大到邻近的16个像素点,依据对P点的远近影响进行插值计算,因P点的像素值信息来自16个邻近点,所以可得到较细致的影像,不过速度比较慢。

6_20190110230750_486.png

  不过双立方插值算法与双线性插值算法的本质区别不仅在于扩大了影响点的范围,还采用高级的插值算法,如图所示:

7_20190110230812_167.png

     要求A,B两点之间e点的值,需要利用A,B周围A-1,A,B,B 1四个点的像素值,通过某种非线性的计算,得到光滑的曲线,从而算出e点的值来。

    所谓“双”或者叫“二次”的意思就是在计算了横向插值影响的基础上,把上述运算拓展到二维空间,再计算纵向插值影响的意思。

      双立方插值算法能够得到相对清晰的画面质量,不过计算量也变大。该算法在现在的众多图像处理软件中最为常用,比如Photoshop,After Effects,Avid,Final Cut Pro等。

      为了得到更好的图像质量,在以上的基础上,许多新的算法不断涌现,它们使用了更加复杂的改进的插值方式。譬如B样条(B-SPline), 米切尔(Mitchell)等插值算法,它们的目的是使插值的曲线显得更平滑,图像边缘的表现更加完美。


自适应样条插值极其增强技术 S-Spline & S-Spline XL

  与上述经典的插值方法最大的区别在于, S-Spline 采用了一种自适应技术,那些传统的方法总是依据周围的像素点来求未知点的色彩值,也就是说需要求解的色彩值仅仅依靠该像素点在图像中的位置,而非实际的图像的像素信息,而自适应样条算法还会考虑实际图像的像素信息。 实验表明,经过 S-Spline 算法得到的图像效果要优于双立方插值算法。

      现在 S-Spline 算法又出现了增强版 S-Spline XL,新版本的 S-Spline XL 算法较 S-Spline 而言画面的锐度得到进一步增强,物体的轮廓更加清晰,边缘的锯齿现象大大减弱,图像感受更加自然。

 

 

参考文献

[1]博客园:淑月尘缘图像缩放算法 .

https://www.cnblogs.com/sycy/p/4743620.html . 2015-08-19

[2]CSDN博客:rainbowbirds_aes.图像放大算法.

https://blog.csdn.net/rainbowbirds_aes/article/details/83114266. 2018.10.17

[3]博客园:Brook_icv. OpenCV2:等间隔采样和局部均值的图像缩小.

http://www.cnblogs.com/wangguchangqing/p/4011892.html. 2014.10.08

[3]韩九强,杨磊.数据图像处理-基于XAVIS组态软件.西安交通大学出版社.2018.8


首页 所有文章 机器人 计算机视觉 自然语言处理 机器学习 编程随笔 关于