如何得到贝塞尔曲线的曲线长度和 t 的近似关系?

三次贝塞尔曲线方程中的 t 和走过的总曲线长度关系不是线性的,然后我搜索到下面的某位前辈的一段话: --- For each bezier curve…
关注者
145
被浏览
97,702

9 个回答

贝塞尔参数曲线的弧长积分为:

s=\int_0^T\sqrt{{x}'^2+{y}'^2}dt, T\in[0,1]

对于贝塞尔曲线的x,y参数方程,分别对t求导,然后带入以上公式,会发现其弧长积分表达式很不容易直接求得积分原函数。二阶贝塞尔的积分结果已经涉及到对数/反双曲函数,而三阶贝塞尔属于椭圆积分! 通常,椭圆积分不能用基本函数表达,意味着解析法无解。

对于无解析解的数学计算,可以使用近似求值。数值分析就是研究近似求解的学科。

梯形法则,也即直线插值。是大多数人想到的,用折线长度来近似曲线长度的方法。但是这种方法的精度很低,收敛速度也很慢。

定义:若有一种数值积分方法用于所有k次或小于k次的多项式的积分都是精确的,那么k就是这种数值积分方法的精度。这个精度也在某种程度上代表收敛速度。

梯形法则、Simpson法则、Weddle法则等使用插值多项式逼近曲线的近似积分法,统称为Newton-Cotes积分法。n阶的Newton-Cotes积分法有精度n(n是奇数)及n+1(n是偶数)。于是可知,梯形法则的精度为1。 Simpson法则,精度为3。

在最小二乘意义下对函数拟合多项式,能达到更高的效率,这就是Gauss积分法。Gauss积分法使用Legendre多项式集合,这些多项式集合是互相正交的,可以用最小的分割次数达到最高的精度。 Gauss积分法有精度2n-1,约为Newton-Cotes积分法的两倍。

Gauss积分法的缺点是误差估计较难,并且不具有递推性,当节点数增加时,无法直接利用之前计算过的数值。 Kronrod积分法,使得Gauss积分法能够递推,并给出了可靠地误差估计,但牺牲了精度,Kronrod积分法的精度约为3n/2。

Python语言的SciPy科学计算库就直接支持Gauss-Kronrod积分法,其底层是Fortran77编写的老牌数学库,速度相当快。默认算法精确到小数点后8位。

参考书籍:

  1. 数值分析,Timothy Sauer著,吴兆金、王国英、范红军译,人民邮电出版社,2010.1
  2. 数值分析导论(第3版),Kendall Atkinson、韩渭敏著,王国荣、徐兆亮、孙劼译,人民邮电出版社,2009.10
  3. 科学计算导论(第2版),Michael T.Heath著,张威、贺华、冷爱萍译,清华大学出版社,2005.10

最近在倒腾插值的问题

假设屏幕上有两点P0(起点),P1(终点),有一个函数P(t),当t属于[0,1]时P_{t}=(x_{t} ,y_{t})

运动轨迹如图


这个是最简单的线性插值,很容易知道P_{t}=(1-t ) \times P_{0}+t\times P_{1}

基础知识铺垫好了,就可以讲贝塞尔公式了

仍然知道P_{0}P_{1},可是Beizer是弯的,要控制它往哪边弯,还需要一个控制点C_{p}


他是往上弯的,上图出现的是quadratic Beizer,贝塞尔曲线氛围数据点和控制点,图像会过数据点,而不会过控制点,控制点的存在是为了控制曲线的形状。

为了获得上图曲线的公式P_{t},链接两条辅助线


可以看出P_{0} \rightarrow CP是直线,CP\rightarrow P_{1}也是直线,他们的函数为

P_{c0}(t)=(1-t)*P_{0}+t*C_{p}P_{c1}(t)=t*P_{1}+(1-t)*C_{p}

举个例子说明t的含义,假设小明要从p0走到p1,P_{t}为t时小明所在的位置,当t=0时位于起点,t=1时位于终点,P(0)=P_{0},P(1)=P_{1}

对于某个t,函数得到的值是小明在t时刻的位置,也就是点(x_{t},y_{t})

如果小明一根筋,,那么小明的路径就会是下图绿点所在直线


现在的问题是小明不走直线了,他要走曲线,所以要求出二次贝塞尔曲线的函数Q(t)

我们对于两点的插值函数:P_{t}=(1-t ) \times P_{0}+t\times P_{1},可以解释成P0和P1在t时刻对于位置P_{t}影响的大小,t靠近0,P_{0}对于P_{t}影响越大

假设两条线M(t),N(t),如果他们同时对某点Q_{t}的位置有影响,当t越靠近0,M(t)影响越大,t越靠近1,N(t)影响越大,按照两点线性插值的方法对两条线进行插值,得到函数

Q(t)=(1-t)*M(t)+t*N(t)

如果把M(t)和N(t)替换成上图的直线P_{0}\rightarrow C_{p},C_{p}\rightarrow P_{1}就可以得到公式

Q(t)=(1-t)^{2} *P_{0}+2t(1-t)*C_{P}+t^{2}*P_{1}

这个就是二次贝塞尔曲线的公式,同时叫它二次贝塞尔就是因为t的最高次项是二次

可以见得Q(0)=P_{0},Q(1)=P_{1}

然后说说三次贝塞尔,三次贝塞尔需要两个控制点

刚才做二次贝塞尔插值的时候,每一段需要3个点,我们刚好可以把这四个点拆成两组[P_{0},C_{0},C_{1}][C_{0},C_{1},P_{1}],获得两个二次贝塞尔曲线的方程Q_{0}(t),Q_{1}(t),按照同样的思路,t越靠近0,Q_{0}(t)的影响越大,t靠近1,Q_{1}(t)的影响越大

C(t)=(1-t)*Q_{0}(t)+t*Q_{1}(t)

整理一下方程

C(t)=(1-t)^{3}P_{0}+3t(1-t)^{2}C_{0}+3t^{2}(1-t)C_{1}+t^{3}P_{1}

这个就是三次赛贝尔的方程,注意观察t为0和1时的值

如果我们一直按照这样的思路插值,可以得到更高次的贝塞尔曲线

嗯,回到题主的问题,要求出贝塞尔曲线的长度,我们对于函数P(t)的定义,这个函数是指时间t时的位置,也就是说如果对于P(t)求导就是得到的函数P_{d}(t)就是t时刻的速度

如果我们对于二次赛贝尔的曲线的公式进行求导,可得函数

P_{d}(t)=2tP_{1}-2C_{p}-2(1-t)P_{0}

按照微积分的思想,把如果把t分得很小,就可以用S=\Sigma (P_{d}(t)*\Delta t)求得路程


数学问题解决了,不过我觉得求曲线长的实际应用意义不大

因为最近在倒腾插值,所以写了一个小编辑器 makecurve.duapp.com/

里面赛贝尔的插值,还有其他插值方法


最后放出参考链接 book.douban.com/subject 向@Milo Yip大神致敬