常用的几种插值算法的原理与代码实现(OpenvSharp)
在一些改变图像大小的图像处理操作中,常用到图像插值算法,比如图像放大和缩小。
在图像放缩过程中,需要计算出目标图像位置对应的原图像位置,再将原图像图像像素赋值给它,但是出现对应的原图像位置是小数的时候就需要进行插值。
常用的插值算法有这几种:
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
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)