贝塞尔曲线

贝塞尔曲线(Bézier curve),又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。一般的矢量图形软件通过它来精确画出曲线,贝兹曲线由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋

贝塞尔曲线于1962,由法国工程师皮埃尔·贝塞尔(Pierre Bézier)所广泛发表,他运用贝塞尔曲线来为汽车的主体进行设计。贝塞尔曲线最初由Paul de Casteljau于1959年运用de Casteljau演算法开发,以稳定数值的方法求出贝兹曲线。

贝塞尔曲线原理

贝塞尔曲线由n个点来决定,其曲线轨迹可以由一个公式来得出:
\[ B(t) = \sum_{i=0}^{n}\binom{n}{i}P_i(1-t)^{n-i}t^i=\binom{n}{0}P_0(1-t)^nt^0+\binom{n}{1}P_1(1-t)^{n-1}t^1+...+\binom{n}{n-1}P_{n-1}(1-t)^1t^{n-1}+\binom{n}{n}P_n(1-t)^0t^n,t\in [0,1] \]

其中n就代表了贝塞尔曲线是几阶曲线,该公式描述了曲线运动的路径

以下我们来讨论一下,贝塞尔公式如何推导。

一阶贝塞尔曲线

贝塞尔曲线

设定图中运动的点为\(P_t\),\(t\)为运动时间,\(t∈(0,1)\),可得如下公式
\[ P_t=P_0+(P_1-P_0)t=(1-t)P_0+tP_1 \]

二阶贝塞尔曲线

贝塞尔曲线

贝塞尔曲线

在二阶贝塞尔曲线中,已知三点恒定\((P_0,P_1,P_2)\),设定在\(P_0P_1\)中的点为\(P_a\),在\(P_1P_2\)中的点为\(P_b\),\(P_t\)在\(P_aP_b\)上的点,这三点都在相同时间t内做匀速运动。

由公式(2)可知:
\[ P_a=P_0+(P_1-P_0)t=(1-t)P_0+tP_1 \]

\[ P_b=P_1+(P_2-P_1)t=(1-t)P_1+tP_2 \]

\[ P_t=P_a+(P_b-P_a)t=(1-t)P_a+tP_b \]

将公式(3)(4)代入公式(5)中,可得:
\[ \begin{aligned} P_t&=(1-t)P_a+tP_b\\ &=(1-t)[(1-t)P_0+tP_1]+t[(1-t)P_1+tP_2]\\ &= (1-t)^2P_0+2(1-t)tP_1+t^2P_2\qquad(6) \end{aligned} \]

三阶贝塞尔曲线

贝塞尔曲线

贝塞尔曲线

同理,根据以上的推导过程可得
\[ P_a=P_0+(P_1-P_0)t=(1-t)P_0+tP_1\\ P_b=P_1+(P_2-P_1)t=(1-t)P_1+tP_2\\ P_c=P_0+(P_2-P_3)t=(1-t)P_2+tP_3\\ P_d=P_a+(P_b-P_a)t=(1-t)P_a+tP_b\\ P_e=P_b+(P_c-P_b)t=(1-t)P_b+tP_c\\ \]
由此可以推导
\[ P_t=P_d+(P_c-P_d)t=...=(1-t)^3P_0+3t(1-t)^2tP_1+3t^2(1-t)P_2+t^3P_3\quad t\in(0,1) \]

n阶贝塞尔曲线

贝塞尔曲线

贝塞尔曲线

设\(n+1\)个控制点\(P_0,P_1…P_n\),其中\(P_k=(X_k,Y_k,Z_k), 0≤k≤n\)

n次Bezier曲线为:
\[ P(t) = \sum P_iB_{i,n}(t), 0\le t \le 1 \]
其中, \(B_in(t)\)是Bernstern基函数,即
\[ B_{i,n}(t)=C_n^it^i(1-t)^{n-i}\\ \]

\[ C_n^i=\frac{n!}{i!(n-i)!}\quad i=0,1,2,...,n \]

曲线在两个端点处的边界条件

\(P(0)=P_0,P(1)=P_n\)

证明

\[ \begin{align*} &P(0)= \sum P_iB_{i,n}(t)= \sum P_iB_{i,n}(0)\\ &B_{i,n}(0)=C_n^it^i(1-t)^{n-i}=C_n^i0^i\\ &i=0时0^0=1\\ &B_{0,n}(0)=C_n^0=1\\ &B_{i,n}(0)=0\qquad(i\neq 0{时})\\ &\therefore P(0)=P_0 \end{align*} \]

\[ \begin{align*} &P(1)= \sum P_iB_{i,n}(1)\\ &B_{i,n}(1)= C_n^i1^i(1-1)^{n-i}\\ &B_{n,n}(1)=C_n^n1^n0^0=1\qquad(i=n时)\\ &B_{i,n}(1)=0\qquad (i\neq n时)\\ &\therefore P(1)=P_n \end{align*} \]

其他

Bezier曲线在端点处的一阶导数值可以由控制点坐标计算
\[ P’(0)=-nP_0+nP_1\\ P’(1)=-nP_n-1+nP_n \]
曲线起点的切线在头两个控制点连线上。

曲线终点的切线在最后两个控制点连线上。

贝塞尔曲线

Bezier曲线落在控制点的凸包内(凸多边形边界)

Bezier曲线的Casteljau算法

详细算法可看:https://pages.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/de-casteljau.html

前提:三切线定理

贝塞尔曲线

设\(P_0\)、\(P_0^2\)、\(P_2\)是一条抛物线上顺序三个不同的点。过\(P_0\)和\(P_2\)点的两切线交于\(P_1\)点,在\(P_0^2\)点的切线交\(P_0P_1\)和\(P_2P_1\)于\(P_0^1\)和\(P_1^1\),则如下比例成立:

贝塞尔曲线

这是所谓抛物线的三切线定理

证明

当\(P_0\),\(P_2\)固定,引入参数\(t\),令上述比值为\(\frac {t}{1-t}\),即有:

贝塞尔曲线

\(t\)从\(0\)变到\(1\),第一、二式就分别表示控制二边形的第一、二条边,它们是两条一次Bezier曲线。将一、.二式代入第三式得:

当\(t\)从\(0\)变到\(1\)时,它表示了由三顶点\(P_0\)、\(P_1\)、\(P_2\)三点定义的一条二次Bezier曲线。并且表明:这二次Bezier曲线\(P_0^2\)可以定义为分别由前两个顶点\((P_0,P_1)\)和后两个顶点\((P_1,P_2)\)决定的一次Bezier曲线线性组合。依次类推,由四个控制点定义的三次Bezier曲线\(P_0^3\)可被定义为分别由\((P_0,P_1,P_2)\)和\((P_1,P_2,P_3)\)确定的2条二次Bezier曲线的线性组合,由\((n+1)\)个控制点\(P_i(i=0,1,...,n)\)定义的n次Bezier曲线\(P_0^n\)可被定义为分别由前、后\(n\)个控制点定义的两条\((n-1)\)次Bezier曲线\(P_0^{n-1}\)与\(P_1^{n-1}\)的线性组合:
\[ P_0^n=(1-t)P_0^{n-1}+tP_1^{n-1}\qquad t\in [0,1] \]
由此得到Bezier曲线的递推计算公式,也就是de Casteljau算法
\[ P_i^k= \left\{\begin{matrix} P_i & k=0\\ (1-t)P_i^{k-1}+tP_{i+1}^{k-1} & k=1,2,...,n,i=0,1,...,n-k \end{matrix}\right. \]
贝塞尔曲线

代码示例

import numpy as np
import matplotlib.pyplot as plt
import bezier
b_xs = []
b_ys = []


# xs表示原始数据
# n表示阶数
# k表示索引
def one_bezier_curve(a, b, t):
    return (1 - t) * a + t * b


def n_bezier_curve(xs, n, k, t):
    if n == 1:
        return one_bezier_curve(xs[k], xs[k + 1], t)
    else:
        return (1 - t) * n_bezier_curve(xs, n - 1, k, t) + t * n_bezier_curve(xs, n - 1, k + 1, t)


def bezier_curve(xs, ys, num, b_xs, b_ys):
    n = 5  # 采用5次bezier曲线拟合
    t_step = 1.0 / (num - 1)
    # t_step = 1.0 / num
    print(t_step)
    t = np.arange(0.0, 1 + t_step, t_step)
    print(len(t))
    for each in t:
        b_xs.append(n_bezier_curve(xs, n, 0, each))
        b_ys.append(n_bezier_curve(ys, n, 0, each))


def func():
    xs = [1.0, 2.1, 3.0, 4.0, 5.0, 6.0]
    ys = [0, 1.1, 2.1, 1.0, 0.2, 0]
    num = 20

    bezier_curve(xs, ys, num, b_xs, b_ys)  # 将计算结果加入到列表中
    print(b_xs, b_ys)
    plt.figure()
    plt.plot(b_xs, b_ys, 'r')  # bezier曲线
    # plt.plot(xs, ys)  # 原曲线
    # plt.show()

func()
上一篇:自动化框架——PO设计模式自学——简单百度登录,搜索封装


下一篇:Bootstrap栅格布局