双线性插值和双三次插值

线性插值

先讲一下线性插值:已知数据 (x0, y0) 与 (x1, y1),要计算 [x0, x1] 区间内某一位置 x 在直线上的y值(反过来也是一样,略):

yy0xx0=y1y0x1x0
y=x1xx1x0y0+xx0x1x0y1

上面比较好理解吧,仔细看就是用x和x0,x1的距离作为一个权重,用于y0和y1的加权。双线性插值本质上就是在两个方向上做线性插值。

双线性插值

在数学上,双线性插值是有两个变量的插值函数的线性插值扩展,其核心思想是在两个方向分别进行一次线性插值[1]。见下图:

这里写图片描述

假如我们想得到未知函数 f 在点 P = (x, y) 的值,假设我们已知函数 f 在 Q11 = (x1, y1)、Q12 = (x1, y2), Q21 = (x2, y1) 以及 Q22 = (x2, y2) 四个点的值。最常见的情况,f就是一个像素点的像素值。首先在 x 方向进行线性插值,得到

这里写图片描述
这里写图片描述

然后在 y 方向进行线性插值,得到

这里写图片描述

综合起来就是双线性插值最后的结果:

这里写图片描述
这里写图片描述

由于图像双线性插值只会用相邻的4个点,因此上述公式的分母都是1。opencv中的源码如下,用了一些优化手段,比如用整数计算代替float(下面代码中的*2048就是变11位小数为整数,最后有两个连乘,因此>>22位),以及源图像和目标图像几何中心的对齐
SrcX=(dstX+0.5)* (srcWidth/dstWidth) -0.5
SrcY=(dstY+0.5) * (srcHeight/dstHeight)-0.5

这个要重点说一下,源图像和目标图像的原点(0,0)均选择左上角,然后根据插值公式计算目标图像每点像素,假设你需要将一幅5x5的图像缩小成3x3,那么源图像和目标图像各个像素之间的对应关系如下。如果没有这个中心对齐,根据基本公式去算,就会得到左边这样的结果;而用了对齐,就会得到右边的结果:

这里写图片描述这里写图片描述

cv::Mat matSrc, matDst1, matDst2;  

matSrc = cv::imread("lena.jpg", 2 | 4);  
matDst1 = cv::Mat(cv::Size(800, 1000), matSrc.type(), cv::Scalar::all(0));  
matDst2 = cv::Mat(matDst1.size(), matSrc.type(), cv::Scalar::all(0));  

double scale_x = (double)matSrc.cols / matDst1.cols;  
double scale_y = (double)matSrc.rows / matDst1.rows;  

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);  

好了,本篇到这里,欢迎大家分享转载,注明出处即可。

参考资料

[1] 双线性插值(Bilinear Interpolation)
[2] OpenCV ——双线性插值(Bilinear interpolation)
[3] 双线性插值算法及需要注意事项

[4] OpenCV中resize函数五种插值算法的实现过程

=================================双三次线性插值==================================

双三次插值是一种更加复杂的插值方式,它能创造出比双线性插值更平滑的图像边缘。双三次插值方法通常运用在一部分图像处理软件打印机驱动程序和数码相机中,对原图像或原图像的某些区域进行放大。Adobe Photoshop CS 更为用户提供了两种不同的双三次插值方法:双三次插值平滑化和双三次插值锐化。

数值分析这个 数学分支中, 双三次插值(英语:Bicubic interpolation)是 二维空间中最常用的 插值方法。在这种方法中, 函数 f在点 ( x, y) 的值可以通过矩形网格中最近的十六个采样点的 加权平均得到,在这里需要使用两个多项式插值三次函数,每个方向使用一个。
双三次插值又叫双立方插值,用于在图像中“插值”(Interpolating)或增加“像素”(Pixel)数量/密度的一种方法。通常利用插值技术增加图形数据,以便在它打印或其他形式输出的时候,能够增大打印面积以及(或者)分辨率。
目前有不同的插值技术可供选用。双立方插值通常能产生效果最好,最精确的插补图形,但它速度也几乎是最慢的。“ 双线性插值”(Bilinear interpolation)的速度则要快一些,但没有前者精确。在商业性图像编辑软件中,经常采用的是速度最快,但也是最不准确的“最近相邻”(Nearest Neighbor)插值。其他一些插值技术通常只在高档或单独应用的程序中出现。
显然,无论技术多么高级,插补过的数据肯定没有原始数据准确。这意味着对一个图形文件进行插值处理后,虽然文件长度增加了(数据量增大),但不会有原先那幅图锐利,可能会在图形质量上打折扣。


双三次插值通过下式进行计算: [1]  
或者用一种更加紧凑的形式,
计算系数
的过程依赖于插值数据的特性。如果已知插值函数的导数,常用的方法就是使用四个顶点的高度以及每个顶点的三个导数。一阶导数
表示 x 与 y 方向的表面斜率,二阶相互导数
表示同时在 x 与 y 方向的斜率。这些值可以通过分别连续对 x 与 y 向量取 微分得到。对于网格单元的每个顶点,将局部坐标(0,0, 1,0, 0,1 和 1,1) 带入这些方程,再解这 16 个方程。


今天学习了第三种图像缩放的方法,双三次插值法。由于理解能力比较差,看了好久的公式,还是云里雾里,但是为了督促自己学习,还是把已知的部分记录下来。

数学原理

维基百科的解释

假设源图像A大小为m*n,缩放后的目标图像B的大小为M*N。那么根据比例我们可以得到B(X,Y)在A上的的
对应坐标为A(x,y)=A(X*(m/M),Y*(n/N))。在双线性插值法中,我们选取A(x,y)的最近四个点。而在双立方
插值法中,我们选取的是最近的16个像素点作为计算目标图像B(X,Y)处像素值的参数。如图所示:

双立方插值说明图

如图所示P点就是目标图像B在(X,Y)处对应于源图像中的位置,P的坐标位置会出现小数部分,所以我们假设
P的坐标为P(x+u,y+v),其中x,y分别表示整数部分,u,v分别表示小数部分。那么我们就可以得到如图所示的
最近16个像素的位置,在这里用a(i,j)(i,j=0,1,2,3)来表示。
双立方插值的目的就是通过找到一种关系,或者说系数,可以把这16个像素对于P处像素值得影响因子找出
来,从而根据这个影响因子来获得目标图像对应点的像素值,达到图像缩放的目的。
我在这次的学习中学习的是基于BiCubic基函数的双三次插值法,BiCubic基函数形式如下:

这里写图片描述

参考这里的博客

我们要做的就是求出BiCubic函数中的参数x,从而获得上面所说的16个像素所对应的系数。在学习双线性插
值法的时候,我们是把图像的行和列分开来理解的,那么在这里,我们也用这种方法描述如何求出a(i,j)对应
的系数k_ij。假设行系数为k_i,列系数为k_j。我们以a00位置为例:
首先,我们要求出当前像素与P点的位置,比如a00距离P(x+u,y+v)的距离为(1+u,1+v)。
那么我们可以得到:k_i_0=W(1+u),k_j_0=W(1+v).
同理我们可以得到所有行和列对应的系数:

k_i_0=W(1+u), k_i_1=W(u), k__i_2=W(1-u), k_i_3=W(2-u);
k_j_0=W(1+v), k_j_1=W(v), k_j_2=W(1-v), k_j_3=W(2-v);

这样我们就分别得到了行和列方向上的系数。
k_i_j=k_i*k_j我们就可以得到每个像素a(i,j)对应的权值了。

最后通过求和公式可以得到目标图片B(X,Y)对应的像素值:
pixelB(X,Y)=pixelA(0,0)*k_0_0+pixelA(0,1)*k_0_1+…+pixelA(3,3)*k_3_3;
这里其实就是个求和公式,由于不知道怎么编辑公式,就这样表达了。

程序实现

/**********************10-9*******************************
功能:双三次插值缩放图片
数学原理:假设原图像A的大小为m*n,新图像B的大小为M*N
如果我们要求B(X,Y)处的像素值:
我们首先可以得到B(X,Y)在图像A中对应的位置(x,y)=(X*(m/M),Y*(N/n))
这个时候求得的x,y是小数值,我们可以通过这个小数值坐标找到距离最近的16个像素点,
利用所选择的基函数,求出对应的每个像素的权值,最终获得pixelB(X,Y)
**********************************************************/

#include <opencv2\opencv.hpp>
#include <iostream>
#include <math.h>
using namespace std;
using namespace cv;

float a = -0.5;//BiCubic基函数
void getW_x(float w_x[4], float x);
void getW_y(float w_y[4], float y);

int main(){
    Mat image = imread("lena.jpg");//源图像

    float Row_B = image.rows*2;
    float Col_B = image.cols*2;


    Mat biggerImage(Row_B, Col_B, CV_8UC3);

    for (int i = 2; i < Row_B-4; i++){
        for (int j = 2; j < Col_B-4; j++){
            float x = i*(image.rows / Row_B);//放大后的图像的像素位置相对于源图像的位置
            float y = j*(image.cols / Col_B);

            /*if (int(x) > 0 && int(x) < image.rows - 2 && int(y)>0 && int(y) < image.cols - 2){*/
                float w_x[4], w_y[4];//行列方向的加权系数
                getW_x(w_x, x);
                getW_y(w_y, y);

                Vec3f temp = { 0, 0, 0 };
                for (int s = 0; s <= 3; s++){
                    for (int t = 0; t <= 3; t++){
                        temp = temp + (Vec3f)(image.at<Vec3b>(int(x) + s - 1, int(y) + t - 1))*w_x[s] * w_y[t];
                    }
                }

                biggerImage.at<Vec3b>(i, j) = (Vec3b)temp;
            }
        }

    imshow("image", image);
    imshow("biggerImage", biggerImage);
    waitKey(0);

    return 0;
}
/*计算系数*/
void getW_x(float w_x[4],float x){
    int X = (int)x;//取整数部分
    float stemp_x[4];
    stemp_x[0] = 1 + (x - X);
    stemp_x[1] = x - X;
    stemp_x[2] = 1 - (x - X);
    stemp_x[3] = 2 - (x - X);

    w_x[0] = a*abs(stemp_x[0] * stemp_x[0] * stemp_x[0]) - 5 * a*stemp_x[0] * stemp_x[0] + 8 * a*abs(stemp_x[0]) - 4 * a;
    w_x[1] = (a + 2)*abs(stemp_x[1] * stemp_x[1] * stemp_x[1]) - (a + 3)*stemp_x[1] * stemp_x[1] + 1;
    w_x[2] = (a + 2)*abs(stemp_x[2] * stemp_x[2] * stemp_x[2]) - (a + 3)*stemp_x[2] * stemp_x[2] + 1;
    w_x[3] = a*abs(stemp_x[3] * stemp_x[3] * stemp_x[3]) - 5 * a*stemp_x[3] * stemp_x[3] + 8 * a*abs(stemp_x[3]) - 4 * a;
}
void getW_y(float w_y[4], float y){
    int Y = (int)y;
    float stemp_y[4];
    stemp_y[0] = 1.0 + (y - Y);
    stemp_y[1] = y - Y;
    stemp_y[2] = 1 - (y - Y);
    stemp_y[3] = 2 - (y - Y);

    w_y[0] = a*abs(stemp_y[0] * stemp_y[0] * stemp_y[0]) - 5 * a*stemp_y[0] * stemp_y[0] + 8 * a*abs(stemp_y[0]) - 4 * a;
    w_y[1] = (a + 2)*abs(stemp_y[1] * stemp_y[1] * stemp_y[1]) - (a + 3)*stemp_y[1] * stemp_y[1] + 1;
    w_y[2] = (a + 2)*abs(stemp_y[2] * stemp_y[2] * stemp_y[2]) - (a + 3)*stemp_y[2] * stemp_y[2] + 1;
    w_y[3] = a*abs(stemp_y[3] * stemp_y[3] * stemp_y[3]) - 5 * a*stemp_y[3] * stemp_y[3] + 8 * a*abs(stemp_y[3]) - 4 * a;
}

注:由于作者编程能力有限,希望有人能指正一下怎么优化这里的程序,这个程序只是实现了算法,运行
速度慢的要死不能忍受!

效果展示

源图像

放大两倍的图像


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值