术→技巧, 研发

贝塞尔曲线与B样条曲线

钱魏Way · · 831 次浏览

什么是贝塞尔曲线

贝塞尔曲线的数学基础是早在 1912 年就广为人知的伯恩斯坦多项式。但直到 1959 年,当时就职于雪铁龙的法国数学家Paul de Casteljau才开始对它进行图形化应用的尝试,并提出了一种数值稳定的de Casteljau 算法。然而贝塞尔曲线的得名,却是由于1962年另一位就职于雷诺的法国工程师Pierre Bézier的广泛宣传。他使用这种只需要很少的控制点就能够生成复杂平滑曲线的方法,来辅助汽车车体的工业设计。

贝塞尔曲线由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋,我们在绘图工具上看到的钢笔工具就是来做这种矢量曲线的。因为控制简便却具有极强的描述能力,贝塞尔曲线在工业设计领域迅速得到了广泛的应用。不仅如此,在计算机图形学领域,尤其是矢量图形学,贝塞尔曲线也占有重要的地位。今天我们最常见的一些矢量绘图软件,如 Flash、Illustrator、CorelDraw 等,无一例外都提供了绘制贝塞尔曲线的功能。甚至像 Photoshop 这样的位图编辑软件,也把贝塞尔曲线作为仅有的矢量绘制工具(钢笔工具)包含其中。

贝塞尔曲线在 web 开发领域同样占有一席之地。CSS3 新增了transition-timing-function属性,它的取值就可以设置为一个三次贝塞尔曲线方程。在此之前,也有不少 JavaScript 动画库使用贝塞尔曲线来实现美观逼真的缓动效果。

贝塞尔曲线分类

贝塞尔曲线根据控制点的数量分为:

  • 一阶贝塞尔曲线(2 个控制点)
  • 二阶贝塞尔曲线(3 个控制点)
  • 三阶贝塞尔曲线(4 个控制点)
  • n阶贝塞尔曲线(n+1个控制点)

如何绘制贝塞尔曲线

一阶贝塞尔曲线

对于一阶贝塞尔曲线为我们可以看到是一条直线,通过几何知识,很容易根据t的值得出线段上那个点的坐标:

$$B_1(t)=P_0 + (P_1 – P_0)t$$

然后可以得出:

$$B_1(t)=(1-t)P_0 + tP_1,t\in [0,1]$$

二阶贝塞尔曲线

对于二阶贝塞尔曲线,其实你可以理解为:在$P_0P_1$上利用一阶公式求出点$P_0^{‘}$,然后在$P_1P_2$上利用一阶公式求出点$P_1^{‘}$,最后在$P_0^{‘}P_1^{‘}$上再利用一阶公式就可以求出最终贝塞尔曲线上的点$P_0{”}$。具体推导过程如下:

先求出线段上的控制点:

$$P_0^{‘} = (1 – t)P_0 + tP_1$$

$$P_1^{‘} = (1 – t)P_1 + tP_2$$

将上面的公式带入至下列公式中:

$$B_{2}(t) = (1 – t)P_0^{‘} + tP_1^{‘}$$

$$= (1 – t)((1 – t)P_0 + tP_1) + t((1 – t)P_1 + tP_2)$$

$$= (1 – t)^2P_0 + 2t(1 – t)P_1 + t^2P_2$$

得出以下公式:

$$B_{2}(t) = (1 – t)^2P_0 + 2t(1 – t)P_1 + t^2P_2 , t\in[0, 1]$$

上面的推导可能比较抽象,不太能理解。下面我们以实例来讲解:

在平面内任选 3 个不共线的点,依次用线段连接。

在第一条线段上任选一个点 D。计算该点到线段起点的距离 AD,与该线段总长 AB 的比例。

根据上一步得到的比例,从第二条线段上找出对应的点 E,使得 AD:AB = BE:BC。

连接这两点 DE。

到这里,我们就确定了贝塞尔曲线上的一个点 F。接下来,请稍微回想一下极限知识,让选取的点 D 在第一条线段上从起点 A 移动到终点 B,找出所有的贝塞尔曲线上的点 F。所有的点找出来之后,我们也得到了这条贝塞尔曲线。

回过头来看这条贝塞尔曲线,为了确定曲线上的一个点,需要进行两轮取点的操作,因此我们称得到的贝塞尔曲线为二次曲线(这样记忆很直观,但曲线的次数其实是由前面提到的伯恩斯坦多项式决定的)。

与二阶贝塞尔曲线类似,可以通过相同的方法得出以下坐标公式:

$$B_{3}(t) = (1 – t)^3P_0 + 3t(1 – t)^2P_1 + 3t^2(1 – t)P_2 + t^3P_3 , t\in[0, 1]$$

当控制点个数为 4 时,情况是怎样的?

步骤都是相同的,只不过我们每确定一个贝塞尔曲线上的点,要进行三轮取点操作。如图,AE:AB = BF:BC = CG:CD = EH:EF = FI:FG = HJ:HI,其中点 J 就是最终得到的贝塞尔曲线上的一个点。

这样我们得到的是一条三次贝塞尔曲线。

多阶贝塞尔曲线

n阶贝塞尔曲线公式:

$$B(t) = \sum_{i=0}^{n}C_n^{i}P_i(1-t)^{n-i}t^i,t\in[0,1]$$

即:

$$B(t) = \sum_{i=0}^{n}P_ib_{i,n}(t),t\in[0,1]$$

公式中$C_n^i$的值为$\frac{n!}{(n – i)!\cdot i!}$,与统计学有关。

其中$b_{i,n}(t)$的值为:

$$b_{i,n}(t)=C_n^{i}(1-t)^{n-i}t^i,其中i=0,1,…,n$$

什么是样条?

样条从英文spline翻译过来的,由于没有实际参照对象,非常让人费解。B样条的数学定义更是让人匪夷所思。实际应用中,样条是一根富有弹性的细木条或塑料条。在应用CAD/CAM技术以前,航空、船舶和汽车制造业普遍采用手工绘制自由曲线。绘制员用压铁压住样条,使其通过所有给定的型值点,再适当地调整压铁,改变样条形态,直到符合设计要求。所以在绘图术语中,样条是通过一组指定点集而生成平滑曲线的柔性带。样条曲线(spline curve)原指用这种方式绘制的曲线。

  • 从力学角度考虑,样条可看作一弹性细梁,压铁是作用在梁上的集中载荷。由此,设计样条曲线的过程可抽象为:求弹性细梁在外加集中载荷作用下产生的弯曲变形。
  • 在数学上使用分段的三次多项式函数来描绘这种曲线,其中各曲线段的连接处有连续的一次和二次导数。
  • 在计算机图形学中,样条曲线指由多项式曲线段连接而成的曲线,在每段的边界处满足特定的连续性条件。

什么是B样条?

在学习B样条的知识之前,一定要首先了解Bezier曲线在外形设计的应用中存在一些具体的不足之处:

  • 确定了多边形的顶点数(m个),也就决定了所定义的Bezier曲线的阶次(m-1次),这样很不灵活。
  • 当顶点数(m)较大时,曲线的阶次将比较高。此时,多边形对曲线形状的控制将明显减弱。
  • Bezier的调和函数的值,在开区间(0,1)内均不为0。因此,所定义的曲线在(0<t<1)的区间内的任何一点均要受到全部顶点的影响,即改变其中任一个顶点的位置,都将对整条曲线产生影响,因此对曲线进行局部修改是不可能的。

B样条曲线,简单来说,它是对贝塞尔曲线的一个补充。为什么这样说呢?是因为贝塞尔曲线某些情况下不实用:曲线上每个点受所有控制点影响,这会给调整曲线工作带来麻烦。可以想到的第一个优化是,把整个贝塞尔曲线变成多段贝塞尔子曲线的拼接。然而,这个方案也不好用,因为拼接工作很难做好,因为要拼接曲线显得“光滑”前提是保证相邻曲线之间的连续性。

为了克服以上提到的在Bezier曲线中存在的问题,Gordon、Riesenfeld和Forrest等人拓展了Bezier曲线,用n次B样条基函数替换了伯恩斯坦基函数,构造了B样条曲线。B样条曲线除了保持Bezier曲线所具有的优点外,还增加了可以对曲线进行局部修改这一突出的优点。除此之外,它还具有对特征多边形更逼近以及多项式阶次较低等优点。

B样条是对贝塞尔曲线的一般化。因而,Bezier曲线能表达的,B样条也同样可以。而且,因为B样条引入了控制点个数与曲线阶次无关的特性,使得我们可以对低阶曲线有了更好的控制能力。而且,通过对节点序列的控制,使得我们对曲线有自由的调节能力。因此,B样条曲线在外形设计中得到了更广泛的重视和应用。

与Bezier曲线的定义相似,B样条曲线的定义是:给定n+1个控制点$\{P_0,P_1,…,P_n\}$,m+1个节点$\{u_0,u_1,…,u_m\}$,p阶B样条曲线$C(u)=\sum_{i=0}^n N_{i,p}(u)P_i$。相对Bezier曲线不同之处在于:

  • B样条的控制点个数与曲线次数无关,因此B样条的自由度更大,可以定义很多控制点又不用担心曲线次数过高而计算困难。
  • B样条有多个节点区间,使得其为分段函数,而Bezier曲线只有一个区间。

B样条取消与赛贝尔曲线的关系

  • B样条上任意一个区间$[u_i,u_{i+1})$上的曲线的定义都是一个Bezier。因此可以将B样条转为分段bezier曲线。
  • Bezier是一种特殊的样条曲线。

样条及B样条再Python中的使用

样条示例代码:(已废弃)

import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import spline

x = range(50)
y = [137, 17, 110, 383, 477, 623, 858, 1165, 1353, 1516, 2039, 1353, 2453, 1245, 2136, 1149, 1148, 894, 277, 414, 399, 297, 103, 102, 103, 57, 54, 63, 52, 27,38, 102, 14, 13, 31, 38, 11, 17, 51, 11, 19, 14, 22, 18, 29, 3, 7, 6, 11, 142]

x = np.array(x)
y= np.array(y)

x_smooth = np.linspace(x.min(),x.max(),1000)
y_smooth = spline(x, y, x_smooth)

plt.plot(x_smooth, y_smooth)
plt.show()

B样条示例代码:

import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import BSpline, splrep

x = range(50)
y = [137, 17, 110, 383, 477, 623, 858, 1165, 1353, 1516, 2039, 1353, 2453, 1245, 2136, 1149, 1148, 894, 277, 414, 399,
     297, 103, 102, 103, 57, 54, 63, 52, 27, 38, 102, 14, 13, 31, 38, 11, 17, 51, 11, 19, 14, 22, 18, 29, 3, 7, 6, 11,
     142]

x = np.array(x)
y = np.array(y)

t, c, k = splrep(x, y, s=0, k=3)

x_smooth = np.linspace(x.min(), x.max(), 1000)
y_smooth = BSpline(t, c, k, extrapolate=False)


plt.plot(x, y, 'ko', label='Original points')
plt.plot(x_smooth, y_smooth(x_smooth), 'r', label='BSpline')
plt.legend(loc='best')
plt.show()

参考链接:

发表评论

邮箱地址不会被公开。 必填项已用*标注