百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

图像的三次B样条插值原理与C++实现

cac55 2024-10-12 02:29 30 浏览 0 评论

01 前言

前文我们讲过图像中最常用的三种插值算法:最邻近插值、双线性插值、双三次插值

常见图像插值算法的原理与C++实现

双三次插值算法的C++实现与SSE指令优化

插值的本质,就是使用周围点的值来计算插值点的值,如下图所示,红点的值已知,黑点的值未知,那么通过一定算法,使用黑点周围红点的值来计算黑点的值,就是插值。

在图像中也是类似的,整型坐标点的像素值已知,浮点型坐标点的像素值未知,所以如果想求浮点型坐标点的像素值,则需要使用其周围整型坐标点的像素值来计算,如下图所示:



02 三次B样条插值原理

图像处理中几种常见的插值算法基本都是取浮点型坐标点周围的n*n个整型坐标点的像素值进行加权和,从而得到该浮点型坐标点的像素值,不同插值算法的主要区别在于权重计算方法,如下式(其中W为权重):

三次B样条插值与双三次插值的原理几乎一样,区别仅在于插值的基函数不一样,所以我们先来复习一下双三次插值原理。

如下图,假设想求图像中浮点型坐标点(x', y')的像素值。

双三次插值算法使用点(x', y')周围4*4个整型点的像素值来计算点(x', y')的像素值。如下式,其中I表示像素值,W表示权重。

权重W(i,j)的计算如下式,其中a取值范围-1~0之间,一般取固定值-0.5,p(i,j).x和p(i,j).y分别表示点p(i,j)的x坐标、y坐标。

上式中函数w(d)通常被称为双三次插值的基函数,三次B样条插值除了基函数与双三次插值不一样,其它都相同,其基函数如下:



03 几种常见插值算法的总结与比较


  • 最邻近插值算法

最邻近插值取离浮点型坐标点的最近点像素值作为其像素值,也可以看成使用浮点型坐标点周围2*2个整型点的像素值来计算其像素值,不过只有最靠近的那个点权重为1,其余3个点权重系数都为0。

  • 双线性插值算法

双线性插值与最邻近插值类似,同样使用浮点型坐标点周围2*2个整型点的像素值来计算其像素值,不过其周围每个整型点的权重都不为0。

  • 双三次插值算法

双三次插值使用浮点型坐标点周围4*4个整型点的像素值来计算其像素值

  • 三次B样条插值算法

三次B样条插值除了基函数与双三次插值不一样,其它都相同。

下面我们取d从-2.5到2.5,数据点间隔0.01,分别画出以上几种插值算法的基函数曲线。从曲线可以知道,d越小,也即周围整型点越靠近插值点,那么该整型点的权重越大。从曲线平滑度来看:三次B样条插值>双三次插值>双线性插值>最邻近插值



04 几种常见插值算法的代码实现

  • 最近邻插值算法的代码实现

最邻近插值取离浮点型坐标点的最近点像素值作为其像素值,也即相当于对浮点型x、y坐标分别四舍五入,从而得到最近点的整型坐标。

//src--uchar型图像数据
//x_float--浮点型的x坐标
//y_float--浮点型的y坐标
uchar nearst_inner(Mat src, float x_float, float y_float)
{
    int x = (int)(x_float + 0.5);   //四舍五入
    int y = (int)(y_float + 0.5);   //四舍五入


    return src.ptr<uchar>(y)[x];
}
  • 双线性插值算法的代码实现
//基函数
float line_w_f(float x)
{
    return (abs(x) <= 1) ? (1 - abs(x)) : 0; 
}


//src--uchar型图像数据
//x_float--浮点型的x坐标
//y_float--浮点型的y坐标
uchar line_inner(Mat src, float x_float, float y_float)
{
    int x = floor(x_float);
    int y = floor(y_float);
   
    float a0_0 = line_w_f(x - x_float) * line_w_f(y - y_float);
    float a0_1 = line_w_f(x + 1 - x_float) * line_w_f(y - y_float);
    float a1_0 = line_w_f(x - x_float) * line_w_f(y + 1 - y_float);
    float a1_1 = line_w_f(x + 1 - x_float) * line_w_f(y + 1 - y_float);
    
    float sum = src.ptr<uchar>(y)[x] * a0_0 + src.ptr<uchar>(y)[x + 1] * a0_1 + src.ptr<uchar>(y + 1)[x] * a1_0 + src.ptr<uchar>(y + 1)[x + 1] * a1_1;


    return ((uchar)sum);
}
  • 双三次插值算法的代码实现
//基函数
float cubic_w_f(float x, float a)
{
    if (x <= 1)
    {
        return 1 - (a + 3) * x * x + (a + 2) * x * x * x;
    }
    else if (x < 2)
    {
        return -4 * a + 8 * a * x - 5 * a * x * x + a * x * x * x;
    }
    return 0.0;
}


//计算权重系数
void cal_cubic_coeff(float x, float y, float* coeff)
{
    float u = x - floor(x) + 1;
    float v = y - floor(y) + 1;
    float a = -0.15;


    float A[4];
    A[0] = cubic_w_f(abs(u), a);
    A[1] = cubic_w_f(abs(u - 1), a);
    A[2] = cubic_w_f(abs(u - 2), a);
    A[3] = cubic_w_f(abs(u - 3), a);


    for (int s = 0; s < 4; s++)
    {
        float C = cubic_w_f(abs(v - s), a);
        coeff[s * 4] = A[0] * C;
        coeff[s * 4 + 1] = A[1] * C;
        coeff[s * 4 + 2] = A[2] * C;
        coeff[s * 4 + 3] = A[3] * C;
    }
}


//双三次插值
uchar cubic_inner(Mat src, float x_float, float y_float, float a)
{
    float coeff[16];
    cal_cubic_coeff(x_float, y_float, coeff);  //计算权重系数




    float sum = 0.0;
    int x0 = floor(x_float) - 1;
    int y0 = floor(y_float) - 1;




    for (int i = 0; i < 4; i++)
    {
        for (int j = 0; j < 4; j++)
        {
            sum += coeff[i * 4 + j] * src.ptr<uchar>(y0 + i)[x0 + j];
        }
    }
    return ((uchar)sum);
}
  • 三次B样条插值算法的代码实现
//基函数
float bpline_w_f(float x)
{
    if (x <= 1)
    {
        return 2.0/3.0 - (1.0 - x/2.0)*x*x;
    }
    else if (x > 1 && x <= 2)
    {
        return (2.0 - x) * (2.0 - x) * (2.0 - x) / 6.0;;
    }


    return 0.0;
}


//计算权重系数
void cal_bpline_coeff(float x, float y, float* coeff)
{
    float u = x - floor(x) + 1;
    float v = y - floor(y) + 1;


    float A[4];
    A[0] = bpline_w_f(abs(u));
    A[1] = bpline_w_f(abs(u - 1));
    A[2] = bpline_w_f(abs(u - 2));
    A[3] = bpline_w_f(abs(u - 3));


    for (int s = 0; s < 4; s++)
    {
        float C = bpline_w_f(abs(v - s));
        coeff[s * 4] = A[0] * C;
        coeff[s * 4 + 1] = A[1] * C;
        coeff[s * 4 + 2] = A[2] * C;
        coeff[s * 4 + 3] = A[3] * C;  
    }
  
}


//三次B样条插值
uchar bpline_inner(Mat src, float x_float, float y_float)
{
    float coeff[16];
    cal_bpline_coeff(x_float, y_float, coeff);  //计算权重系数




    float sum = 0.0;
    int x0 = floor(x_float) - 1;
    int y0 = floor(y_float) - 1;


    for (int i = 0; i < 4; i++)
    {
        for (int j = 0; j < 4; j++)
        {         
            sum += coeff[i * 4 + j] * src.ptr<uchar>(y0 + i)[x0 + j];
        }
    }
    return ((uchar)sum);
}



05 几种常见插值算法应用于图像缩放的结果对比

将以上几种插值算法分别应用于Lena图像的缩放,测试代码如下:

//src -- 输入图像
//dst -- 输出图像
//row_m -- 行的缩放倍数
//col_m -- 列的缩放倍数
//inner_type -- 插值类型 0:最邻近,1:双线性,2:双三次,3:三次B样条
void resize_img(Mat src, Mat& dst, float row_m, float col_m, int inner_type)
{
    const int row = (int)(src.rows * row_m);
    const int col = (int)(src.cols * col_m);
    const float x_a = 1.0 / col_m;
    const float y_a = 1.0 / row_m;


    Mat dst_tmp = Mat::zeros(row, col, CV_8UC1);


    if (inner_type == 0)
    {
        for (int i = 0; i < row; i++)
        {
            uchar* p = dst_tmp.ptr<uchar>(i);
            float y = i * y_a;


            for (int j = 0; j < col; j++)
            {
                float x = j * x_a;


                p[j] = nearst_inner(src, x, y);


            }
        }
    }
    else if (inner_type == 1)
    {
        for (int i = 0; i < row; i++)
        {
            uchar* p = dst_tmp.ptr<uchar>(i);
            float y = i * y_a;


            for (int j = 0; j < col; j++)
            {
                float x = j * x_a;


                p[j] = line_inner(src, x, y);


            }
        }
    }
    else if (inner_type == 2)
    {
        for (int i = 0; i < row; i++)
        {
            uchar* p = dst_tmp.ptr<uchar>(i);
            float y = i * y_a;


            for (int j = 0; j < col; j++)
            {
                float x = j * x_a;


                p[j] = cubic_inner(src, x, y, -0.5);


            }
        }
    }
    else
    {
        for (int i = 0; i < row; i++)
        {
            uchar* p = dst_tmp.ptr<uchar>(i);
            float y = i * y_a;


            for (int j = 0; j < col; j++)
            {
                float x = j * x_a;


                p[j] = bpline_inner(src, x, y);


            }
        }
    }


    dst_tmp.copyTo(dst);
}


//测试函数,调用以上图像缩放,并显示结果
void resize_img_test(void)
{
    Mat img = imread("image/lena.png", CV_LOAD_IMAGE_GRAYSCALE);


    float mul = 3.0;  //宽和高的放大倍数都为3


    Mat img_resize_nearst, img_resize_line, img_resize_cubic, img_resize_bpline;


    resize_img(img, img_resize_nearst, mul, mul, 0);
    resize_img(img, img_resize_line, mul, mul, 1);
    resize_img(img, img_resize_cubic, mul, mul, 2);
    resize_img(img, img_resize_bpline, mul, mul, 3);
        


    imshow("img", img);
    imshow("img_resize_nearst", img_resize_nearst);  //最近邻
    imshow("img_resize_line", img_resize_line);  //双线性
    imshow("img_resize_cubic", img_resize_cubic);   //双三次
    imshow("img_resize_bpline", img_resize_bpline);  //三次B样条
    waitKey();
}

运行以上代码,分别使用几种插值算法对Lena的宽、高都放大3倍,得到结果如下:

由以上结果可知放大图像之后:

  • 马赛克现象:最近邻插值>双线性插值≥双三次插值>三次B样条插值
  • 边缘锯齿现象:最近邻插值>双线性插值≥双三次插值>三次B样条插值
  • 插值后模糊度:最近邻插值<双线性插值≤双三次插值<三次B样条插值

为了对比更明显一点,我们把Lena图像中帽子边缘区域截取出来对比:

相关推荐

14款健身APP蹿红 看看下载最多的是哪款?

Zombies,Run!($3.99,安卓,iOS)如果你的运动理念是:除非有人追,否则绝不跑起来,那么这款APP应该适合你。Zombies,Run!这款程序把单调的跑步过程变身为躲避僵尸的游戏...

微软官方彩蛋庆祝《回到未来》纪念日

2015年10月21日,是MartyMcFly和Brown博士回到未来的时间。现在,这一天真的到了,那么当时影片中展示的一些科技产品究竟有多少实现了呢?作为一家走在技术前沿的公司,日前,微软就在M...

时尚圈最潮同志情侣 帅到没朋友(同志情侣微信头像)

来源:MSN时尚综合|2015-03-0419:45:15男演员ZacharyQuinto(中)与男模MilesMcMillan(右)于纽约街头公开热吻。情人节这个拥有不同起源传说,最早可以...

IE浏览器阻止过期ActiveX控件或将影响网银的使用

IE浏览器网银IE浏览器网银如果经常使用IE浏览器浏览网页的用户,可能都有遇到过浏览器窗口提示安装ActiveX控件的情况,一般情况下用户也是会选择直接安装。ActiveX控件广义上是指微软公司的整...

如何使Microsoft Band连接到WP设备

如果你幸运地购买到了MicrosoftBand,那么恭喜你。现在我们(winbeta)推出了“帮助系列”,那些尚未买到MicrosoftBand的朋友可以了解设备的一些新功能,以及设备的其他关键特...

毕业生不得不看的五大骗局全揭秘(毕业生防骗)

目前,距离高校大学生毕业已不足100天,大部分毕业生都十分忙碌。论文定稿、答辩,参加招聘、面试等成了应届毕业生的头等大事。但随着毕业季的临近,不法分子专门针对毕业生的诈骗高发期也随之来临。360手机安...

菠萝觅生活是O2O应用流量入口最大的供应商

现在主流的传统O2O生活服务,他们其实都有一个共通点,那就是各行其道。打车有快的,滴滴,外卖有饿了么,买机票有去哪儿网…每个APP都有着自己的核心竞争力。而用户呢?既想拥有海量有趣应用,又担心占用过多...

WP8.1版MSN健康应用,现已支持锁屏计步

IT之家(www.ithome.com):WP8.1版MSN健康应用,现已支持锁屏计步@WP之家报道,微软今天已将必应系列应用品牌归为MSN,除此之外,WP8.1版MSN健康和天气应用也获得一些新的...

短信就能传播手机病毒?看完推理惊呆了!

很多人都收到过一种带网址的陌生短信,有的人会点击网址看看,有的还会在好奇心驱使下回复短信。近日《北京新发现》栏目报道了一起离奇的电信诈骗案,事主耿先生的银行卡从未离身,但是在收到一条带网址的陌生短信,...

微软OneClip:我承包了你的剪贴板(微软onedrive云空间)

不久前,Twitter用户WalkingCat曝光了微软一款名为OneClip的应用。这是一款剪贴板应用,根据描述这款应用将覆盖Windows10(包括桌面和移动)、iOS和Android平台,可以...

Windows 10手机应该是什么样?微博用户给出了概念图

随着Windows10发布的不断临近,WindowsPhone的用户对Windows10的旗舰手机的期望也越来越高,我们WP中文网也在微博上发出了同样的问题,搜集用户对Windows10的硬...

云管家出席武汉2015年支付宝O2O生态峰会

2月4日,蚂蚁金服O2O生态峰会在武汉启幕。此次峰会展现了2015年蚂蚁金服在O2O领域的开放思路和策略,以及合作伙伴对O2O的创新观念及思路分享,吸引了武汉近3000名企业大佬、众多创业者、第三方服...

微软将于下周开启Windows开发中心帐号迁移工作

自下周开始微软将启动Windows开发中心的帐号迁移工作。根据WindowsBuildingApps博客透露Windows开发中心帐号迁移工作将会分为几个阶段。首个阶段从下周开始持续到今年7月份...

如何解绑已经合并的MSN账户和Skype账户?

如果您绑定的账户已经充值,建议您把产品消耗完毕后,再进行解绑。当您需要解绑合并的账户时,可登入Skype点卡账户自助操作。输入Skype或MSN账号、密码登录账户:登录后,可在页面左下角选择语言"中文...

微博账号已显示所属MCN机构,成为目前第二个上线该功能的平台

7月25日,多位网友发现,部分微博大V的个人主页已经显示其所属的MCN机构名称,微博也成为目前第二个上线该功能的平台。【来源:中新经纬】声明:此文版权归原作者所有,若有来源错误或者侵犯您的合法权益,您...

取消回复欢迎 发表评论: