• 图像处理中常见的几种插值方法:最近邻插值、双线性插值、双三次插值(附Pytorch测试代码)


    零、前言

    在学习可变形卷积时,因为学习到的位移量Δpn可能是小数,因此作者采用双线性插值算法确定卷积操作最终采样的位置。通过插值算法我们可以根据现有已知的数据估计未知位置的数据,并且可以利用这种方法对图像进行缩放、旋转以及几何校正等任务。此处我通过这篇文章学习总结常见的三种插值方法,包括最近邻插值、双线性插值、双三次插值算法,其中双线性插值方法属于使用频率较高的方法,比如在Pytorch以及Tensorflow框架中默认的插值方法就是双线性插值方法。
    我们知道在数字图像处理中,图像用下图表示,其中每个像素点的位置都是整数:
    在这里插入图片描述
    在进行插值前,首先会通过下面公式计算出目标图像中像素点位置映射回原图的位置是什么,此处分别使用dst、src表示目标图像和原图像,(x,y)为像素点坐标位置,这个公式尤为重要哦:
    在这里插入图片描述

    一、最近邻插值(Nearest Neighbor Interpolation

    1.相关介绍

    最近邻插值,也称为零阶插值,这是最简单的插值方法,计算量较小,对于未知位置,直接采用与它最邻近的像素点的值为其赋值,这种方法通常会造成插值图像中像素点灰度值不连续,在图像边缘产生明显的锯齿状,所以现实中很少使用这种方法。
    此处分别使用Src和Dst表示原图像和目标图像,他们的大小分别为(Src_H,Src_W)和(Dst_H,Dst_W),我们的目的是利用原始图像对目标图像中的像素值进行填充,
    对于每个元素,填充值的计算分为两步:
    1)通过上面的公式计算Dst中坐标(dst_x,dst_y)对应到Src中坐标(src_x,src_y)为多少;
    2)对所求(src_x,src_y)坐标进行floor操作(我看很多人说是采用四舍五入round操作,但我通过结果看明显是floor啊),即向下取整得到(dst_x,dst_y)映射在原图中的最近邻点(floor(src_x),floor(src_y))

    在这里插入图片描述
    举个例子,假设原图像Src和目标图像Dst大小分别为(2,2)和(4,4),我们的目的是利用原始图像对目标图像中的像素值进行填充,利用Pytorch得到结果如上图,以目标图像中第一行数据7,7,2,2为例,分别使用f(x,y)、g(x,y)表示目标图和原图中像素点的灰度值,我对“最近邻”的插值方法实现过程进行展示:
    在这里插入图片描述
    最近邻插值方法不咋使用,所以大家不必纠结,继续往下看!

    2.代码实现

    import torch
    import torch.nn as nn
    from torchvision import transforms
    
    
    img=torch.randint(10,size=(1,2,2),dtype=torch.float32)
    print(img)
    print('---'*5)
    nearest_neighbor_interpolation=transforms.Resize(size=(4,4),
                                           interpolation=transforms.InterpolationMode.NEAREST)
    resize_img=nearest_neighbor_interpolation(img)
    print(resize_img)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    二、双线性插值(Bilinear Interpolation)

    1.线性插值(Linear Interpolation)

    在看双线性插值之前,我们先了解一下线性插值:线性插值应用于一维数据的插值,根据需要插值的点左右的两个已知邻近点,进行插值计算。插值过程为已知两个点,采用两点法表示一条直线,然后已知x求y或者已知y求x的过程,具体看下面公式推导以及图示说明:
    在这里插入图片描述
    图中目的是求插值点(x,y),而该点左右已知两点(x0,y0)和(x1,y1),利用“两点法”表示这条直线:
    在这里插入图片描述
    公式(1)用于已知y求x;公式(2)用于已知x求y;

    2.双线性插值(Bilinear Interpolation)

    双线性插值方法对图像起平滑作用,实际是对二维数据的x和y方向都做一维线性插值,共进行3次一维线性插值
    这种方法比较常用,采用未知位置邻近的4个像素点计算他的像素值,具体赋值过程通过下面公式计算(这里的f(Qxy)等可以理解为一维插值公式中的yx):
    在这里插入图片描述
    在这里插入图片描述
    假设目标图中未知点(dst_x,dst_y)对应原图中的点为P(x,y)=P(src_x,src_y),Q11、Q12、Q21、Q22为P在原图中邻近的四个点,对P点插值过程分为以下两步:
    1)沿着X方向,分别对Q11、Q21和Q12、Q22进行一维线性插值(共两次)得到R1(x,y1)、R2(x,y2);
    2)利用R1、R2沿着Y方向对其进行一维线性插值(共一次)得到最终的二维线性插值结果P;

    在这里插入图片描述
    为了更好的清晰双线性插值中的4个邻近点的选取,大家需要知道图像处理中所说的对角四邻域,如下图所示,对于P点,在他的对角线方向有四个邻近点。
    在这里插入图片描述
    填充步骤解析:
    1)通过文章开头的公式计算Dst中坐标(dst_x,dst_y)对应到Src中坐标P(src_x,src_y)为多少;
    2)对所得点P(src_x,src_y)在原图中根据对角四邻域的坐标关系得到其邻近点:Q11、Q12、Q21、Q22;
    3)利用得到的邻近点进行分别沿着X、Y方向进行一维线性插值完成双线性插值过程;

    举个例子,假设原图像Src和目标图像Dst大小分别为(4,4)和(8,8),我们的目的是利用原始图像对目标图像中的像素值进行填充,利用Pytorch得到结果如下图,以目标图像坐标为(5,5)的点为例,分别使用f(x,y)、g(x,y)表示原图和目标图中像素点的灰度值,我对“双线性”的插值方法实现过程进行展示:
    在这里插入图片描述
    该点需填充的值计算过程如下:
    在这里插入图片描述
    手动计算的值和Pytorch内部实现存在一点偏差,应该是Pytorch内部对该算法进行了优化:
    在这里插入图片描述

    3.代码实现

    import torch
    import torch.nn as nn
    from torchvision import transforms
    
    
    img=torch.randint(10,size=(1,4,4),dtype=torch.float32)
    print(img)
    print('---'*5)
    bilinear_interpolation=transforms.Resize(size=(8,8),
                                           interpolation=transforms.InterpolationMode.BILINEAR)
    resize_img=bilinear_interpolation(img)
    print(resize_img)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    三、双三次插值(Bicubic Interpolation)

    1.相关介绍

    双三次插值又称为双立方插值,可比双线性插值方法产生边缘更平滑的插值结果,可产生效果更好、更精确的插值结果,但是速度较慢
    插值过程中,图中点(x,y)=(i+u,j+v)的值通过矩形网格中该点邻近的16个点加权平均得到,相对该点而言16个点的坐标依次为{(i-1,j-1),(i-1,j),(i-1,j+1),(i-1,j+2),…,(i,j),…,(i+2,j-1),(i+2,j),(i+2,j+1),(i+2,j+2)},此时需要分别沿着X、Y方向使用一个多项式插值三次函数计算对应结果。
    插值公式为:
    在这里插入图片描述
    我们使用aij表示每个邻近点,fij表示原图中点aij的像素值,wi、wj分别表示该点在对应方向的权重。比如,a00表示矩形网格中左上角的第一个点,以此类推,我们第一步工作就是通过Bicubic函数计算所有的权重,然后进行所需的插值计算。
    构造的Bicubic函数如下:
    在这里插入图片描述
    其中a=-0.5时,函数形状如下:
    在这里插入图片描述
    基本知识已经了解了,接下来我们看看插值过程的具体步骤怎么实现,分别使用Dst、Src表示目标图像和原图,需要知道Dst中的点A(dst_x,dst_y)像素值应为多少,A(dst_x,dst_y)对应原图中的点为P(src_x,src_y)=P(x,y):
    在这里插入图片描述

    1)使用文章开头的公式,通过dst_x,dst_y计算出src_x,src_y,此时一般src_x,src_y是小数,可记为src_x=i+u,src_y=j+v,以(i,j)及周围的16个邻近点构成矩形网格;
    2)因为Bicubic函数是一维函数,所以我们需要分别沿着X、Y方向计算每个点对应的权重,每个点权重记为wij(w_x,w_y),计算过程为:

    • 明确该点X、Y方向与P点之间的距离lij(l_x,l_y),如a00(i-1,j-1)与P(i+u,j+v)之间距离为l00(i+u-i+1,j+v-j+1)=l00(u+1,v+1),a33(i+2,j+2)与P(i+u,j+v)之间距离为l33(i+2-i-u,j+2-j-v)=l33(2-u,2-v);
    • 所以每个邻近点对应的权重为w(w_x,w_y)=w(W(l_x),W(l_y)),此处W指Bicubic函数;

    3)得到每个点对应的权重后,通过插值公式即可得到插值结果B(dst_x,dst_y);

    很容易计算从a00到a33共16个点到P点的距离l(l_x,l_y)为:
    在这里插入图片描述
    比如a00与P之间的距离为:l00(l_0,l_0)=l00(u+1,v+1),a33与P之间的距离为:l33(l_3,l_3)=l33(2-u,2-v),到这里大家应该理解双三次插值的过程了吧!
    --------------------------------------------------------------------------------------------------------------------------
    其实在插值计算时还可以使用矩阵表示,如下:
    在这里插入图片描述

    其中f(i+u,j+v)即为要计算的值,而A、C分别表示权重也就是第一种方式中的w_x,w_y,B为原图中16个邻近点构成的矩阵,计算权重的函数使用S(x)去近似。

    2.举个例子

    输入为Src:5×5,输出为Dst:10×10,我们去求Dst中点(5,5)的值:
    在这里插入图片描述
    我手动实现上述矩阵运算方式的计算过程(计算结果总是和Pytorch内置函数计算出的结果存在一定偏差,我也是很纳闷):

    import numpy as np
    import math
    
    src_w=src_h=5
    dst_w=dst_h=10
    
    dst_x=dst_y=5
    src_x=dst_x*(src_h/dst_h)
    src_y=dst_y*(src_w/dst_w)
    
    i=math.floor(src_x)
    j=math.floor(src_y)
    
    u=src_x-i
    v=src_y-j
    # print(i,j,u,v)
    # l_x=np.array([[1.5,0.5,0.5,1.5]])
    # l_y=np.array([[1.5,0.5,0.5,1.5]])
    base=[1,0,-1,-2]
    
    
    def l_(r):
        a=np.zeros((1,4))
        for j in range(4):
            a[0,j]=r+base[j]
        return a
    
    
    l_x,l_y=l_(u),l_(v)
    # print(l_x)
    # print(l_y)
    
    # # print(l_x.shape)
    #
    #
    def S_x(l_):
        s=np.zeros(shape=(1,4))
        for j in range(4):
            x_abs=math.fabs(l_[0,j])
            if x_abs <= 1:
                s[0,j] = 1-2* math.pow(x_abs, 2)+math.pow(x_abs, 3)
            elif x_abs < 2 and x_abs > 1:
                s[0,j] = 4-8*x_abs+5*math.pow(x_abs, 2) - math.pow(x_abs, 3)
        return s
    
    
    A,C=S_x(l_x),S_x(l_y)
    
    B=np.array([
             [9., 0., 7., 3.],
             [7., 0., 1., 8.],
             [1., 8., 1., 3.],
             [5., 5., 1., 1.]])
    b=np.matmul(np.matmul(A,B),np.transpose(C))
    print(b)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    3.代码实现

    import torch
    import torch.nn as nn
    from torchvision import transforms
    
    
    img=torch.randint(10,size=(1,4,4),dtype=torch.float32)
    print(img)
    print('---'*5)
    bicubic_interpolation=transforms.Resize(size=(8,8),
                                           interpolation=transforms.InterpolationMode.BICUBIC)
    resize_img=bicubic_interpolation(img)
    print(resize_img)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    四、Pytorch实现

    大家注意在Pytorch中不同插值方法是通过对transforms.Resize方法中的interpolation参数指定,上面这三种方法只需要依次采用下面三种模式即可:

    插值方法指定方式
    最近邻插值transforms.InterpolationMode.NEAREST
    双线性插值transforms.InterpolationMode.BILINEAR
    双三次插值transforms.InterpolationMode.BICUBIC

    这里我使用周杰伦的一张照片展示一下三种方法对应的插值结果:
    原图如下:
    在这里插入图片描述

    import torch
    from torchvision import transforms
    from PIL import Image
    from torchvision.utils import  save_image
    
    
    img=Image.open('./Jay.png',mode='r')
    img_to_tensor=transforms.ToTensor()(img)
    # print(img_to_tensor.shape)
    
    nearest_neighbor_interpolation=transforms.Resize(size=(1024,1024),
                                           interpolation=transforms.InterpolationMode.NEAREST)
    nearest_resize_img=nearest_neighbor_interpolation(img_to_tensor)
    
    bilinear_interpolation=transforms.Resize(size=(1024,10248),
                                           interpolation=transforms.InterpolationMode.BILINEAR)
    bilinear_resize_img=bilinear_interpolation(img_to_tensor)
    #
    bicubic_interpolation=transforms.Resize(size=(1024,1024),
                                           interpolation=transforms.InterpolationMode.BICUBIC)
    bicubic_resize_img=bicubic_interpolation(img_to_tensor)
    
    save_image(nearest_resize_img,'./nearest.png')
    save_image(bilinear_resize_img,'./bilinear.png')
    save_image(bicubic_resize_img,'./bicubic.png')
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    插值结果:
    1)最近邻插值
    在这里插入图片描述

    2)双线性插值
    在这里插入图片描述

    3)双三次插值
    在这里插入图片描述

    参考:

    1)https://blog.csdn.net/JNingWei/article/details/78822026
    2)https://blog.csdn.net/qq_30815237/article/details/90605132
    3)https://blog.csdn.net/weixin_43135178/article/details/117262348
    4)《百度百科》
    七月没注意这个活动,今天刚好遇上,参加一下嘿嘿!
    文章内容欢迎大家的指点,大家共同学习。

  • 相关阅读:
    【Python & turtle】绘制一个有趣的的Emoticons
    玩以太坊链上项目的必备技能(初识智能合约语言-Solidity之旅一)
    最优二叉搜索树问题(Java)
    C# 给List编个序号
    【VSCode 插件商城无法搜索到插件的解决方法】
    赋安消防主机FS5050,波特率20k,单模单芯SC接头,9-36V直流供电的光纤联网方式之一
    如何搭建mysql的主从关系
    安全文件传输如何进行管控,从而促进业务的有序发展?
    DOM系列之触屏事件
    解决can‘t open camera by index
  • 原文地址:https://blog.csdn.net/qq_43665602/article/details/126853751