双线性插值(Bilinear Interpol)原理及应用

双线性插值(Bilinear Interpol)原理及应用在很多神经网络上采样过程中会用到双线性插值,其为基础的图像resize操作。以前一直没时间仔细研究,今天探究并记录一下原理和自己的理解。一、什么是插值插值指两个方面:一是在数学上,在离散数据的基础上补插连续函数,使得这条连续曲线通过全部给定的离散数据点;二是在图像处理上面,是利用已知邻近像素点的灰度值或RGB中的三色值产生未知像素点的灰度值或RGB三色值,目的是由原始图像再生出具有更高分辨率的图像。通俗一点理解就是已知推导未知,从而强化图像,具体效果如图1所示。…

大家好,又见面了,我是你们的朋友全栈君。

在很多神经网络上采样过程中会用到双线性插值,其为基础的图像resize操作。以前一直没时间仔细研究,今天探究并记录一下原理和自己的理解。

一、什么是插值

插值指两个方面:

一是在数学上,在离散数据的基础上补插连续函数,使得这条连续曲线通过全部给定的离散数据点;

二是在图像处理上面,是利用已知邻近像素点的灰度值或RGB中的三色值产生未知像素点的灰度值或RGB三色值,目的是由原始图像再生出具有更高分辨率的图像。通俗一点理解就是已知推导未知,从而强化图像,具体效果如图1所示。

双线性插值(Bilinear Interpol)原理及应用

                                                                                           图1:插值示意图

当然,插值只是一个笼统的概念,利用怎样的规则进行插值才是重点。例如有最临近插值,双线性插值,高阶插值等等,今天主要解析一下双线性插值。

二、什么是线性插值

在研究双线性插值之前,首先看一下什么叫做线性插值。

线性插值是指插值函数为一次多项式的插值方式,其在插值节点上的插值误差为零。也就是连接两个已知量的直线来确定在这两个已知量之间的一个未知量的值的办法。

线性插值法是认为现象的变化发展是线性的、均匀的,所以可利用两点式的直线方程式进行线性插值。其几何意义可以示意为利用图中过A点和B点的直线来推断未知C点的坐标。

双线性插值(Bilinear Interpol)原理及应用

                                               图2:线性插值示意图:其中A坐标(x0,y0),B坐标(x1,y1)已知,求C(x,y)的y值

转换为公式计算(小写变大写,公式比较简单,懒得手打了)

双线性插值(Bilinear Interpol)原理及应用

单个维度的线性插值只利用两点的对应值推算,两点本身的偶然性会造成结果的误差较大,因而在图像处理中多采用双线性插值。

三、什么是双线性插值数学理解

既然单个维度的线性插值误差较大,那么很自然的会想到从多维度的角度去减小误差,这就是双线性插值,其核心思想是在两个方向分别进行一次线性插值。

双线性插值(Bilinear Interpol)原理及应用

                                                                                  图3:双线性插值示意图

如图3中所示,我们的目标是得到未知函数f在绿色点P(x,y)的像素值,已知Q11(x1,y1)Q12(x1,y2)Q21(x2,y1)Q22(x2,y2)的坐标以及对应的像素值。首先在x方向进行插值,得到R1R2,然后在y方向进行插值,得到P,这就是整个的数学计算思路。

其中R1R2的计算公式如下

双线性插值(Bilinear Interpol)原理及应用

通过在y方向插值,得到

双线性插值(Bilinear Interpol)原理及应用

代入双线性插值(Bilinear Interpol)原理及应用双线性插值(Bilinear Interpol)原理及应用,得到最终像素值结果,最后式子比较复杂,但过程比较简单,可以自己推到一下,找找手感。

四、什么是双线性插值图像处理

假设我们有一张大小为n*m的源图像,目标图像大小为a*b。那么两幅图像的边长比分别为:n/am/b

注意,通常这个比例不是整数,编程存储的时候要用浮点型。

目标图像的第(i,j)个像素点(ij列)可以通过边长比对应回源图像。其对应坐标为(i*n/a,j*m/b)。
显然,这个对应坐标一般来说不是整数,而非整数的坐标是无法在图像这种离散数据上使用的。

双线性插值通过寻找距离这个对应坐标最近的四个像素点,来计算该点的值(灰度值或者RGB值)。如果你的对应坐标是(2.5,4.5),那么最近的四个像素是(2,4)、(2,5)、(3,4)、(3,5)若图像为灰度图像,那么(i,j)点的灰度值可以通过一下公式计算:
f(i,j)=w1*p1+w2*p2+w3*p3+w4*p4.
其中,pi(i=1,2,3,4)为最近的四个像素点,wi(i=1,2,3,4)为各点相应权值。

权值的计算公式如下

双线性插值(Bilinear Interpol)原理及应用

其实在上面已经出现过了。

五、遇到的问题(这部分直接引用现成的,文末有链接)

这部分的前提是,你已经明白什么是双线性插值并且在给定源图像和目标图像尺寸的情况下,可以用笔计算出目标图像某个像素点的值。当然,最好的情况是你已经用某种语言实现了网上一大堆博客上原创或转载的双线性插值算法,然后发现计算出来的结果和matlabopenCV对应的resize()函数得到的结果完全不一样。

那这个究竟是怎么回事呢?

其实答案很简单,就是坐标系的选择问题,或者说源图像和目标图像之间的对应问题。

按照网上一些博客上写的,源图像和目标图像的原点(0,0)均选择左上角,然后根据插值公式计算目标图像每点像素,假设你需要将一幅5×5的图像缩小成3×3,那么源图像和目标图像各个像素之间的对应关系如下:

双线性插值(Bilinear Interpol)原理及应用

                                                                              图4:原点选择左上角示意图

只画了一行,用做示意,从图中可以很明显的看到,如果选择右上角为原点(00),那么最右边和最下边的像素实际上并没有参与计算,而且目标图像的每个像素点计算出的灰度值也相对于源图像偏左偏上。

那么,让坐标加1或者选择右下角为原点怎么样呢?很不幸,还是一样的效果,不过这次得到的图像将偏右偏下。

最好的方法就是,两个图像的几何中心重合,并且目标图像的每个像素之间都是等间隔的,并且都和两边有一定的边距,这也是matlabopenCV的做法。如下图:

双线性插值(Bilinear Interpol)原理及应用

                                                                                      图5:更改后示意图

如果你不懂我上面说的什么,没关系,只要在计算对应坐标的时候改为以下公式即可,

                                                                                      int x=(i+0.5)*n/a-0.5

                                                                                      int y=(j+0.5)*m/b-0.5

代替

                                                                                              int x=i*n/a

                                                                                              int y=j*m/b

利用上述公式,将得到正确的双线性插值结果

 

六、最后附上两端代码(同为引用,仅供参考)

for n in range(3): # 对channel循环
        for dst_y in range(dst_h): # 对height循环
            for dst_x in range(dst_w): # 对width循环
                # 目标在源上的坐标
                src_x = (dst_x + 0.5) * scale_x - 0.5
                src_y = (dst_y + 0.5) * scale_y - 0.5
                # 计算在源图上四个近邻点的位置
                src_x_0 = int(np.floor(src_x))
                src_y_0 = int(np.floor(src_y))
                src_x_1 = min(src_x_0 + 1, src_w - 1)
                src_y_1 = min(src_y_0 + 1, src_h - 1)

                # 双线性插值
                value0 = (src_x_1 - src_x) * src[src_y_0, src_x_0, n] + (src_x - src_x_0) * src[src_y_0, src_x_1, n]
                value1 = (src_x_1 - src_x) * src[src_y_1, src_x_0, n] + (src_x - src_x_0) * src[src_y_1, src_x_1, n]
                dst[dst_y, dst_x, n] = int((src_y_1 - src_y) * value0 + (src_y - src_y_0) * value1)
    return dst
uchar* dataDst = matDst1.data;
    int stepDst = matDst1.step;
    uchar* dataSrc = matSrc.data;
    int stepSrc = matSrc.step;
    int iWidthSrc = matSrc.cols;
    int iHiehgtSrc = matSrc.rows;

    for (int j = 0; j < matDst1.rows; ++j)
    {
        float fy = (float)((j + 0.5) * scale_y - 0.5);
        int sy = cvFloor(fy);
        fy -= sy;
        sy = std::min(sy, iHiehgtSrc - 2);
        sy = std::max(0, sy);

        short cbufy[2];
        cbufy[0] = cv::saturate_cast<short>((1.f - fy) * 2048);
        cbufy[1] = 2048 - cbufy[0];

        for (int i = 0; i < matDst1.cols; ++i)
        {
            float fx = (float)((i + 0.5) * scale_x - 0.5);
            int sx = cvFloor(fx);
            fx -= sx;

            if (sx < 0) {
                fx = 0, sx = 0;
            }
            if (sx >= iWidthSrc - 1) {
                fx = 0, sx = iWidthSrc - 2;
            }

            short cbufx[2];
            cbufx[0] = cv::saturate_cast<short>((1.f - fx) * 2048);
            cbufx[1] = 2048 - cbufx[0];

            for (int k = 0; k < matSrc.channels(); ++k)
            {
                *(dataDst+ j*stepDst + 3*i + k) = (*(dataSrc + sy*stepSrc + 3*sx + k) * cbufx[0] * cbufy[0] + 
                    *(dataSrc + (sy+1)*stepSrc + 3*sx + k) * cbufx[0] * cbufy[1] + 
                    *(dataSrc + sy*stepSrc + 3*(sx+1) + k) * cbufx[1] * cbufy[0] + 
                    *(dataSrc + (sy+1)*stepSrc + 3*(sx+1) + k) * cbufx[1] * cbufy[1]) >> 22;
            }
        }
    }
    cv::imwrite("linear_1.jpg", matDst1);

    cv::resize(matSrc, matDst2, matDst1.size(), 0, 0, 1);
    cv::imwrite("linear_2.jpg", matDst2);

参考链接:

https://www.cnblogs.com/yibeimingyue/p/11485732.html

https://blog.csdn.net/sinat_33718563/article/details/78825971

https://zhuanlan.zhihu.com/p/49832888

https://blog.csdn.net/qq_37577735/article/details/80041586

https://blog.csdn.net/xbinworld/article/details/65660665

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/137879.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • Stopwatch类的使用

    Stopwatch类的使用Stopwatch类提供了一种方便的机制来测量运行时间。Stopwatch使用了操作系统和硬件提供的最高分辨率机制,通常少于1毫秒(相比之下DateTime.Now和Environment.TickCount的分辨率在15毫秒左右)。要使用Stopwatch,可以调用StartNew方法。这将会实例化一个Stopwatch对象并开始计时(此外,也可以先实例化,再手动调用Start方法)。Elap…

  • Android 代码混淆 混淆方案

    Android 代码混淆 混淆方案本篇文章:自己在混淆的时候整理出比较全面的混淆方法,比较实用,自己走过的坑,淌出来的路。请大家不要再走回头路,可能只要我们代码加混淆,一点不对就会导致项目运行崩溃等后果,有许多人发现没有打包运行好好地,打包完成以后而又不不可以了,导致了许多困惑,本片文章来问大家解决困惑,希望对大家有帮助。

  • 对象转map(object转map)

    对象转map(object转map)importjava.lang.reflect.Field;importjava.util.LinkedHashMap;importjava.util.Map;publicclassObjectToMap{//对象转MappublicstaticMap<String,Object>getObjectToMap(Objectobj)t…

  • 函数WSAStartup[通俗易懂]

    函数WSAStartup一、WSAStartup函数               intWSAStartup                      (                         WORDwVersionRequested,                         LPWSADATAlpWSAData                      

  • matlab中plot函数用法颜色_matlab数学符号大全

    matlab中plot函数用法颜色_matlab数学符号大全线条、颜色等参数:1.简单的2维直线图:plot(x,y)同一坐标显示n条线:plot(x,y1,x,y2,…)x=0:pi/10:2*pi;y=sin(x);figure;holdon;plot(x,y)2.plot(X):X是矩阵,表示矩阵的每一行都画一条线,将显示n条线。X=rand(3,3);%随机生成3*3矩阵figure;holdon;p…

    2022年10月16日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号