B样条曲线介绍和实现(等值线平滑)

最近得到个任务是把之前的等值线光滑一下,思考良久决定用B样条去做平滑。虽然之前经常看到这个词条,但一直有正面接触,就趁着这次撸下来,做个总结吧。

B样条曲线

B样条是通过逼近一组控制点生成的曲线,它有如下计算表示:

其中是输入的一组n+1个控制点中第k个控制点。为B样条混合函数,是一种调和函数。中的k表示第k个混合函数,有多少个控制点就有多少个混合函数。中的d表示次数,如三次B样条曲线d为3。有的资料会用阶数表示d(阶数=次数+1)。我们这里就用次数表示d。d可以赋值为1到n之间的任意整数。虽然也可以设置d=0,但是这样就是折线本身了。B样条的混合函数由Cox-deBoor递归公式定义:

其中是节点向量的索引为k的(总共n+d+2个节点)节点,均匀节点区间通常使用[0,1]或者[0,n+d+1]。

节点向量

理解B样条曲线最重要就是理解什么是结点向量。区别于控制点,控制点表示用n+1个点去控制曲线,而节点表示将曲线划分为n+d+1段,如下图所示:

直线上的6个点投影到曲线上对应的6个点就是节点向量U。我们在实践中往往U是均匀分布的即,c为常数。下图展示了一个常见的2次B样条曲线:

折线上的点就是我们的控制点,曲线上红色的点即为我们的节点。这里会发现一个奇怪的现象,明明节点数为n+d+2,控制点为n+1,为什么图中节点数少于控制点。这时由于混合函数的性质。

混合函数性质

我们把混合函数的计算进行排列:

  • 每个混合函数都定义在为起点,取值范围为d+1的子区间上。

比如,其定义域为,因此其他情况下取值为0。这表明了在k=1,即第一个控制点影响范围为。如下图所示:

  • 每个样条曲线段(两个相邻节点间)受d+1个控制点影响

比如我们取节点范围为,根据混合函数,发现它影响,表明其只受第0到第3个控制点的影响。如下图所示。

我们发现从的线段无法满足4个影响点,同样的范围也无法取到4个点,因此这些范围被定义为无效范围。即:

  • 节点记为,所生成的B样条线曲线仅定义在节点范围上。

B样条代码实现

//边界情况(闭合或者相切)
enum class B_Spline_Border{TANGENT,CLOSED};class B_SPLINE{
public:B_SPLINE(){}B_SPLINE(int d,const std::vector<float>& p);//获取P(u)void getPmiu(float u,float& px,float& py);//计算基函数递归式float Cox(float u,int i,int p);//获取B样条线序列void getUniPos(int numU,std::vector<float>& p);
private://次数(阶数-1)int m_D;//结点向量(uk)std::vector<float> m_VecKnots;//控制点std::vector<float> m_Pos;//控制段数n(控制点=n+1)int m_N;//边界情况B_Spline_Border m_Bor;
};B_SPLINE::B_SPLINE(int d,const std::vector<float>& p){m_D=d;m_N=int(p.size()/2)-1;if(p[0]==p[2*m_N]&&p[1]==p[2*m_N+1]){//闭合情况m_Bor=B_Spline_Border::CLOSED;for (int i=0; i<(m_D-1)/2; i++) {//首控制点前添加控制点m_Pos.push_back(p[2*(m_N+i-(m_D-1)/2)]);m_Pos.push_back(p[2*(m_N+i-(m_D-1)/2)+1]);}for (int i=0; i<p.size(); i++) {m_Pos.push_back(p[i]);}for (int i=0; i<m_D/2; i++) {//尾控制点后添加控制点m_Pos.push_back(p[2*(i+1)]);m_Pos.push_back(p[2*(i+1)+1]);}}else{//非闭合采用首尾结点重复度为d+1的方法生成切线m_Bor=B_Spline_Border::TANGENT;for (int i=0; i<m_D; i++) {m_Pos.push_back(p[0]);m_Pos.push_back(p[1]);}for (int i=0; i<p.size(); i++) {m_Pos.push_back(p[i]);}for (int i=0; i<m_D; i++) {m_Pos.push_back(p[2*m_N]);m_Pos.push_back(p[2*m_N+1]);}}m_N=int(m_Pos.size()/2)-1;
}void B_SPLINE::getPmiu(float u,float& px,float& py){px=0;py=0;for (int i=0; i<m_N+1; i++) {px+=m_Pos[2*i]*Cox(u, i, m_D);py+=m_Pos[2*i+1]*Cox(u, i, m_D);}}float B_SPLINE::Cox(float u,int i,int p){if(p==0){if (u>=m_VecKnots[i]&&u<m_VecKnots[i+1]) {return 1;}else{return 0;}}else{float a,b;float dev=(m_VecKnots[i+p]-m_VecKnots[i]);a=dev?(u-m_VecKnots[i])/dev:0;dev=(m_VecKnots[i+p+1]-m_VecKnots[i+1]);b=dev?(m_VecKnots[i+p+1]-u)/dev:0;return a*Cox(u,i,p-1)+b*Cox(u, i+1, p-1);}
}void B_SPLINE::getUniPos(int numU,std::vector<float>& p){//均匀样条m_VecKnots.clear();for (int i=0; i<=m_N+m_D+1; i++) {m_VecKnots.push_back(i);}float x,y;p.clear();for(int i=0;i<=numU;i++){float u=float(i)/numU*(m_VecKnots.size()-1);if (u>=m_VecKnots[m_D]&&u<m_VecKnots[m_N+1]) {getPmiu(u,x,y);p.push_back(x);p.push_back(y);}}//由于右区间是开区间(un+1这个点未定义),所以最后要闭合得手动连接首尾点(但如果细分够细,也可以选择忽略)if (m_Bor==B_Spline_Border::CLOSED) {p.push_back(p[0]);p.push_back(p[1]);}}

其中Cox是我们的混合函数递归式。这里还可以直接根据解析式写出代码,避免递归造成的时间浪费,但是根据定义来撸代码也是挺爽的。由于是用于等值线的绘制,我们这里使用了两种边界处理方法,开放均匀B样条和闭合B均匀样条。

均匀周期性B样条和开放均匀B样条

均匀周期性B样条表示我们的节点向量是均匀分布的。而周期性表示,当我们的节点向量是均匀分布时,所有的混合函数都是可以通过平移得到的。如下图所示:

开放均匀B样条(也称开放B样条)表示我们可以只改变首尾为d+1次重复,而中间的其他节点向量仍然是均匀的。比如节点向量{0,0,0,0,1,2,3,4,5,6,7,8,9,9,9,9}。我们发现该结点向量首尾各重复了4次(也可称为重复度4),这种情况生成的B样条即为开放均匀的。如下图的三次B样条所示:

上左图为均匀周期B样条曲线,右图为开放均匀B样条。我们发现右图可以使曲线和首尾两个控制点相切,这里只要保证首尾的重复度为d+1生成的B样条曲线就是开放B样条曲线,开放B样条曲线也可以称为Clamped-B样条曲线。

(PS:许多网上的资料都说开放B样条为不闭合的B样条曲线,个人还是不太赞同的。个人比较认同《计算机图形学》这本书中对开放B样条的定义,即首尾重复d+1次)

闭合B样条曲线

我们如果想获得如下闭合的样条曲线应该怎么做呢:

非常常用的办法为包裹(wrapping)控制点

包裹控制点:

(1)设计一个均匀 m+1 (m=d+n+1)个节点的节点序列:u0 = 0, u1 = 1/mu1 = 2/m, ..., um = 1。注意曲线的定义域是.

(2)包裹头d个和最后d个控制点。更准确地,设P0 = Pn-d+1, P1 = Pn-d+2, ..., Pd-2 = Pn-1 and Pd-1 = Pn.

如果我们输入的d个控制点首尾是相等的,即连接的情况下:如果d为奇数,则首控制点之前和尾控制点之后各添加int(d/2)个控制点。如果d为偶数则在首控制点之前添加int((d-1)/2)个控制点,尾控制点之后添加int(d/2)个控制点,如上述代码所示:

    if(p[0]==p[2*m_N]&&p[1]==p[2*m_N+1]){//闭合情况m_Bor=B_Spline_Border::CLOSED;for (int i=0; i<(m_D-1)/2; i++) {//首控制点前添加控制点m_Pos.push_back(p[2*(m_N+i-(m_D-1)/2)]);m_Pos.push_back(p[2*(m_N+i-(m_D-1)/2)+1]);}for (int i=0; i<p.size(); i++) {m_Pos.push_back(p[i]);}for (int i=0; i<m_D/2; i++) {//尾控制点后添加控制点m_Pos.push_back(p[2*(i+1)]);m_Pos.push_back(p[2*(i+1)+1]);}}

如下图所示

根据上述代码绘制的两种曲线效果如下:

左图为闭合效果,右图为相切效果。

B样条曲线介绍和实现(等值线平滑)相关推荐

  1. 样条曲线_Apollo规划算法基于样条曲线的平滑分析(一)

    欢迎关注微信公众号<不想做科学家的工程师不是好码农> 样条曲线的思想是把一个长线分成N段,每段用一个多项式去表示,本文为了简化公式书写,示例中均使用三次多项式表示,实际算法中阶数根据需求不 ...

  2. 指数平滑方法(一次指数平滑、二次指数平滑、三次指数平滑):理论、代码、参数 介绍(全)

    @创建于:20210324 @修改于:20210324 文章目录 特别说明 参考来源 包版本号 1.简介 2.一次指数平滑 2.1 理论介绍 2.2 代码展示 2.3 参数介绍 3. 二次指数平滑 3 ...

  3. 【时间序列】单变量时间序列平滑方法介绍

    时间序列是由按时间排序的观察单位组成的数据.可能是天气数据.股市数据.,也就是说它是由按时间排序的观察值组成的数据. 在本文中将介绍和解释时间序列的平滑方法,时间序列统计方法在另一篇文章中进行了解释. ...

  4. 灰度图像--图像增强 平滑之均值滤波、高斯滤波

     灰度图像--图像增强 平滑之均值滤波.高斯滤波         目录(?)[+] 开篇废话 均值滤波 数学 效果 代码 高斯滤波 数学 效果 代码 总结 学习DIP第30天 转载请标明本文出处: ...

  5. 系统学习NLP(四)--数据平滑

    转子:https://blog.csdn.net/fuermolei/article/details/81353746 在自然语言处理中,经常要计算单词序列(句子)出现的概率估计.但是,算法训练的时候 ...

  6. 基于指数平滑模型与ARIMA模型在苹果股价的预测应用

    一.项目背景 股票投资已经随着人们生活水平的逐步提高而变得普遍,更多的人开始逐渐关注并参与到股票投资市场中来.股票具有高收益的同时也伴随着较高的风险,我们知道,股票价格的变动受很多因素的影响,因此对于 ...

  7. 时间序列分析之指数平滑法(holt-winters及代码)

    在做时序预测时,一个显然的思路是:认为离着预测点越近的点,作用越大.比如我这个月体重100斤,去年某个月120斤,显然对于预测下个月体重而言,这个月的数据影响力更大些.假设随着时间变化权重以指数方式下 ...

  8. R语言里的非线性模型:多项式回归、局部样条、平滑样条、 广义相加模型GAM分析

    总览 在这里,我们放宽了流行的线性方法的假设.最近我们被客户要求撰写关于非线性模型的研究报告,包括一些图形和统计输出.有时线性假设只是一个很差的近似值.有许多方法可以解决此问题,其中一些方法可以通过使 ...

  9. 点云平滑之双边滤波适用性分析

    双边滤波原文"The Bilateral Filter for Point Clouds",作者为 Julie Digne. Carlo Franchis.由于双边滤波原理简单,其 ...

最新文章

  1. 50k大牛告诉你Python怎么学,10个特性带你快速了解python
  2. 《预训练周刊》第11期:全球最大智能模型“悟道2.0”重磅发布、谷歌KELM:将知识图与语言模型预训练语料库集成...
  3. CrowdStrike加入VirusTotal阵营
  4. http://wenku.baidu.com/view/63e7b8270066f5335a812142.html
  5. Windows CE 程序设计 (3rd 版)
  6. 1.6 this关键字详解(3种用法)
  7. 函数计算自动化运维实战1 -- 定时任务
  8. XML DOM 节点
  9. Python reload() 函数
  10. mysql dump工具升级_MySQL数据库升级
  11. 代码描述10911 - Forming Quiz Teams
  12. Java同步三种实现方式
  13. php结合phantomjs实现网页截屏、抓取js渲染的页面
  14. 五千字说清汽车基础软件及国产现状
  15. 小米发布会的米8探索者——很吓人的技术分析
  16. 手把手写深度学习(3)——用RNN循环神经网络自动生成歌词之理论篇
  17. 由浅入深玩转华为WLAN—12安全认证配置(5)Portal认证,外置Protal服务器TSM对接
  18. 2020年中国儿童家具行业产量、市场规模发展现状及儿童家具企业竞争格局分析[图]
  19. 免费发外链论坛有哪些?
  20. 敏捷方法 - 极限编程与工程实践

热门文章

  1. matlab取矩阵实部和虚部,MATLAB中容易忽略却经常遇到的小技巧总结
  2. servlet 接收request发送过来的多维数组_049 JAVA-Servlet
  3. ajax获取301,PHP获取301重定向页面跳转后真实URL地址
  4. 计算机专业评副高论文要求,护士晋升副高职称论文要求
  5. 大学计算机基础网络配置实验报告答案,大学计算机基础实验报告2.doc
  6. python查看数据类型type_python——获取数据类型:type()、isinstance()的使用方法:...
  7. flutter java混编_有赞 Flutter 混编方案
  8. python实现共轭梯度算法(含误差与运算次数的折线图)
  9. android git 版本管理,Android版本管理(git 和 repo)
  10. ibatis mysql_mysql +ibatis