在一些改变图像大小的图像处理操作中,常用到图像插值算法,比如图像放大和缩小。

在图像放缩过程中,需要计算出目标图像位置对应的原图像位置,再将原图像图像像素赋值给它,但是出现对应的原图像位置是小数的时候就需要进行插值。

常用的插值算法有这几种:

1.最近邻插值

介绍:最近邻插值直接选择距离最近的像素点,作为插值的结果,将像素值赋给目标图像,该方法容易出现锯齿的现象。

代码实现:

public bool resize_near(Mat img, out Mat img_resize, int w, int h)
{
            //初始化目标图像
            double imgWidth = img.Width;
            double imgHeight = img.Height;
            img_resize = new Mat(h,w,MatType.CV_8UC1,new Scalar(0));

            //计算放大倍数
            double sx =  imgWidth/(w*1.0);
            double sy =  imgHeight/(h*1.0);

            //循环给目标图像的每个像素赋值
            for(int i=0;i<h;i++)
            {
                for(int j=0;j<w;j++)
                {
                    //得到对应的原图像中的坐标
                    double srcx =j*sx;
                    double srcy =i*sy;
                    //四舍五入选择最近的点,赋值给目标图像
                    byte value = img.At<byte>((int)(srcy+0.5),(int)(srcx+0.5));
                    img_resize.Set<byte>(i,j,value);
                }
            }
            return true;
}

2.双线性插值

介绍:双线性插值方法采用线性加权的方式,用周围的四个像素值加权得到结果像素值。

双线性插值,先分别对上下进行x方向两次线性插值,再对插值结果进行一次y方向的线性插值,得到结果。

线性插值公式:y=(x2-x)/(x2-x1) *y1 +(x-x1)/(x2-x1) *y2
 

代码实现:

public bool resize_bili(Mat img, out Mat img_resize, int w, int h)
{
            img_resize = new Mat();

            //初始化目标图像
            double imgWidth = img.Width;
            double imgHeight = img.Height;
            img_resize = new Mat(h, w, MatType.CV_8UC1, new Scalar(0));

            //计算放大倍数,此处进行了减1,和下标保持一致
            double sx = (imgWidth-1) / ((w-1 )* 1.0);
            double sy = (imgHeight-1) / ((h-1) * 1.0);

            //循环给目标图像的每个像素赋值
            for (int i = 0; i < h; i++)
            {
                for (int j = 0; j < w; j++)
                {
                    //得到对应的原图像中的坐标
                    double srcy = i * sy;
                    double srcx = j * sx;                    
                    //获取周围
                    //左上元素
                    byte lu= img.At<byte>((int)(srcy ), (int)(srcx ));
                    //右上元素
                    byte ru= img.At<byte>((int)(srcy ), (int)(srcx +1));
                    //左下元素
                    byte ld= img.At<byte>((int)(srcy + 1), (int)(srcx ));
                    //右下元素
                    byte rd= img.At<byte>((int)(srcy +1), (int)(srcx +1));
                    //以距离作为权重进行插值
                    //计算权重
                    double w1=0, w2=0;
                    double w3=0, w4=0;
                    double w5=0, w6=0;

                    w1 = ((int)(srcx+1)-srcx) / (1);
                    w2 = (srcx - (int)srcx) / (1);
                    w3 = ((int)(srcx + 1) - srcx) / (1);
                    w4 = (srcx - (int)srcx) / (1);
                    w5 = ((int)(srcy + 1) - srcy) / (1);
                    w6 = (srcy - (int)srcy) / (1);

                    //先在x方向插值两次,
                    double u =lu*w1+ru*w2;
                    double d =ld*w3+rd*w4;
                    //以x方向插值的结果进行y方向插值,得到结果值
                    //赋给目标图像的像素
                    double value =u*w5+d*w6;
                    img_resize.Set<byte>(i, j, (byte)value);
                    byte t=img_resize.At<byte>(i, j);
                }
            }

            return true;

}

3.双三次插值

介绍:

双三次插值也是采用周围像素值进行加权运算,得到目标像素值。但是和双线性插值不同的是,双三次插值法是对周围16个像素进行加权运算。

①对于每一个位置,将权重分为横纵方向,以以横纵方向权重的乘积作为该位置的权重。

②权重的计算:分别针对横纵方向,与原图对应点的距离作为输入,输出得到权重。采用的bicubic函数进行权重的计算。

③所以对于16个点,只需要计算8个权重,横向4个,纵向也是4个。

④按照权重进行加权计算

函数曲线如下

代码实现:

 //bicubic基函数
public bool baseFunction(double x,out double res)
{
            double a = -0.5;
            
            if(Math.Abs(x)<=1)
            {
                res =(a+2)*Math.Pow(Math.Abs(x),3)-(a+3)*Math.Pow(Math.Abs(x),2)+1;
            }
            else if(Math.Abs(x)<2 && Math.Abs(x)>1)
            {
                res =a*Math.Pow(Math.Abs(x),3)-5*a*Math.Pow(Math.Abs(x),2)+8*a*Math.Abs(x)-4*a;
            }
            else 
            {
                res = 0;
            }
            return true;
}
public bool  resize_cubic(Mat img,out Mat  img_resize,int w,int h)
{
            img_resize = new Mat();
            //初始化目标图像
            double imgWidth = img.Width;
            double imgHeight = img.Height;
            img_resize = new Mat(h, w, MatType.CV_8UC1, new Scalar(0));
            //计算放大倍数,此处进行了减1,和下标保持一致
            double sx = (imgWidth - 1) / ((w - 1) * 1.0);
            double sy = (imgHeight - 1) / ((h - 1) * 1.0);
            //循环给目标图像的每个像素赋值
            for (int i = 0; i < h; i++)
            {
                for (int j = 0; j < w; j++)
                {
                    //得到对应的原图像中的坐标
                    double srcy = i * sy;
                    double srcx = j * sx;

                    //获取周围16个像素的值
                    if(srcx-1<0)
                    {
                        srcy = 1;
                    }
                    if(srcy-1<0)
                    {
                        srcy = 1;
                    }
                    //可以采用for循环减小代码精简;
                    byte a00 = img.At<byte>((int)(srcy-1), (int)(srcx-1));
                    byte a01 = img.At<byte>((int)(srcy-1), (int)(srcx ));
                    byte a02 = img.At<byte>((int)(srcy -1), (int)(srcx+1));
                    byte a03 = img.At<byte>((int)(srcy -1), (int)(srcx + 2));

                    byte a10 = img.At<byte>((int)(srcy), (int)(srcx-1));
                    byte a11 = img.At<byte>((int)(srcy), (int)(srcx ));
                    byte a12 = img.At<byte>((int)(srcy ), (int)(srcx+1));
                    byte a13 = img.At<byte>((int)(srcy), (int)(srcx + 2));

                    byte a20 = img.At<byte>((int)(srcy+1), (int)(srcx-1));
                    byte a21 = img.At<byte>((int)(srcy+1), (int)(srcx ));
                    byte a22 = img.At<byte>((int)(srcy + 1), (int)(srcx+1));
                    byte a23 = img.At<byte>((int)(srcy + 1), (int)(srcx + 2));

                    byte a30 = img.At<byte>((int)(srcy+2), (int)(srcx-1));
                    byte a31 = img.At<byte>((int)(srcy+2), (int)(srcx ));
                    byte a32 = img.At<byte>((int)(srcy + 2), (int)(srcx+ 1));
                    byte a33 = img.At<byte>((int)(srcy + 2), (int)(srcx + 2));

                    //求横向和纵向的权重
                    double w_r0, w_r1, w_r2, w_r3, w_c0, w_c1, w_c2, w_c3;
                    //有可能是负数??????
                    //以横纵坐标的距离作为x变量,带入bicubic基函数,进行计算权重
                    baseFunction((int)(srcy - 1)-srcy, out w_r0);
                    baseFunction((int)(srcy) - srcy, out w_r1);
                    baseFunction((int)(srcy +1) - srcy, out w_r2);
                    baseFunction((int)(srcy+2) - srcy, out w_r3);

                    baseFunction((int)(srcx - 1)-srcx, out w_c0);
                    baseFunction((int)(srcx ) - srcx, out w_c1);
                    baseFunction((int)(srcx + 1) - srcx, out w_c2);
                    baseFunction((int)(srcx + 2) - srcx, out w_c3);
                    //16个元素乘上对应的权值,求和得到目标值
                   // double value = 0;
                   double value =  a00 * w_r0 * w_c0 + a01 * w_r0 * w_c1 + a02 * w_r0 * w_c2 + a03 * w_r0 * w_c3
                           + a10 * w_r1 * w_c0 + a11 * w_r1 * w_c1 + a12 * w_r1 * w_c2 + a13 * w_r1 * w_c3
                           + a20 * w_r2 * w_c0 + a21 * w_r2 * w_c1 + a22 * w_r2 * w_c2 + a23 * w_r2 * w_c3
                           + a30 * w_r3 * w_c0 + a31 * w_r3 * w_c1 + a32 * w_r3 * w_c2 + a33 * w_r3 * w_c3;
                    //由于会产生负数,此处直接采用最近邻代替
                    if (value<0)
                    {
                        //value = img.At<byte>((int)(srcy + 0.5), (int)(srcx + 0.5));
                        value = -value;
                    }
                    img_resize.Set<byte>(i, j, (byte)value);
                }
            }
            return true;
}

4.Opencv的插值算法

Size size = new Size(2000,2000);

Mat resizeResbyCv_cubic=new Mat(), 
    resizeResbyCv_liner=new Mat(), 
    resizeResbyCv_near =new Mat(), 
    resizeResbyCv_area =new Mat();
Cv2.Resize(test, resizeResbyCv_area,  size,(double)InterpolationFlags.Area);
Cv2.Resize(test, resizeResbyCv_cubic, size, (double)InterpolationFlags.Cubic);
Cv2.Resize(test, resizeResbyCv_liner, size, (double)InterpolationFlags.Linear);
Cv2.Resize(test, resizeResbyCv_near,  size, (double)InterpolationFlags.Nearest);

效果:

①最近邻,锯齿严重

②双线性

③双三次

参考:

传统图片超分算法——双三次插值 (Bicubic)、附C++源码-CSDN博客

https://blog.csdn.net/weixin_40647819/article/details/86600384

https://www.cnblogs.com/weijiakai/p/15495661.html

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐