B-splines

B-spline

Cox-de Boor recursion formula

定义集合 $U$ 由 $m+1$ 个不降的实数 $u_0\le u_1\le\cdots\le u_m$ 构成,我们称 $u_i$ 为结点(knot),$U$ 为结点向量(knot vector)。

给定结点向量 $U$,则其对应的 $k$ 次B-spline $N_{i,k}(u),\ u\in[u_0,u_m]$ 就是由以下递推公式定义的多项式:

$$
\begin{aligned}
&N_{i,0}(u) = \begin{cases}
1 & u_i\le u\lt u_{i+1}\\
0 & \text{otherwise}
\end{cases}\\
&N_{i,k}(u) = \frac{u-u_i}{u_{i+k}-u_i}N_{i,k-1}(u) + \frac{u_{i+k+1}-u}{u_{i+k+1}-u_{i+1}}N_{i+1,k-1}(u)
\end{aligned}
$$

这被称为是Cox-de Boor recursion formula。

Sample

B-spline的定义式看起来很复杂,但是仔细分析就会发现它实际上就是一个关于自变量 $u$ 的函数,只不过用了递归式来定义。递归的边界是 $N_{i,0}(u)$,这是一个只能为0或1的分段函数。

比如 $u_0=0,u_1=1,u_2=2,u_3=3$,则

$$
\begin{aligned}
N_{0,0}(u) &= \begin{cases}
1 & 0\le u\lt 1\\
0 & \text{otherwise}
\end{cases}\\
N_{1,0}(u) &= \begin{cases}
1 & 1\le u\lt 2\\
0 & \text{otherwise}
\end{cases}\\
N_{2,0}(u) &= \begin{cases}
1 & 2\le u\lt 3\\
0 & \text{otherwise}
\end{cases}\\
\end{aligned}
$$

下图为 $N_{0,0},N_{1,0},N_{2,0}$ 的图像:

为了更直观地理解 $N_{i,k}(u)$ 的递推,我们画出递归的树状图:

为了计算 $N_{i,1}$,需要先计算出 $N_{i,0},N_{i+1,0}$,然后就能再计算 $N_{i,2}$……

还是以 $u_0=0,u_1=1,u_2=2,u_3=3$ 为例,我们计算 $N_{i,1}(u)$:

$$
\begin{aligned}
N_{0,1}(u) &= \frac{u-u_0}{u_{1}-u_{0}}N_{0,0}(u) + \frac{u_{2}-u}{u_{2}-u_{1}}N_{1,0}(u)\\
&= uN_{0,0}(u) + (2-u)N_{1,0}(u)\\
&= \begin{cases}
u & 0\le u\lt 1\\
2 – u & 1\le u\lt 2
\end{cases}\\
N_{1,1}(u) &= \frac{u-u_1}{u_2-u_1}N_{1,0}(u) + \frac{u_3-u}{u_3-u_1}N_{2,0}(u)\\
&= uN_{1,0}(u) + (3-u)N_{2,0}(u)\\
&= \begin{cases}
u & 1\le u\lt 2\\
3-u & 2\le u\lt 3
\end{cases}
\end{aligned}
$$

下图为 $N_{0,1},N_{1,1}$ 的图像:

再计算 $N_{0,2}$:

$$
\begin{aligned}
N_{0,2}(u) &= \frac{u-u_0}{u_2-u_0}N_{0,1}(u) + \frac{u_3-u}{u_3-u_1}N_{1,1}(u)\\
&= 0.5u N_{0,1}(u) + 0.5(3-u)N_{1,1}(u)\\
&= \begin{cases}
0.5u^2 & 0\le u\lt 1\\
0.5u(2-u) + 0.5u(3-u) & 1\le u\lt 2\\
0.5(3-u)^2 & 2\le u\lt 3
\end{cases}
\end{aligned}
$$

下图为 $N_{0,2}$ 的图像:

Two Important Observations

Observation1

观察树状图,$N_{i,k}(u)$ 的递归边界必然是 $N_{i,0}(u),N_{i+1,0}(u),\cdots,N_{i+k,0}(u)$,也就是说 $N_{i,k}(u)$ 只在 $[u_i,u_{i+k+1})$ 上非零;或者更进一步地, $N_{i,k}(u)$ 是 $N_{i,0}(u),N_{i+1,0}(u),\cdots,N_{i+k,0}(u)$ 的线性组合。

Observation2

反向思考 $N_{i,0}(u)$ 可以影响哪些 $N_{i,k}(u)$,我们可以画出上图中的三角形区域。不难发现 $N_{i,0}(u)$ 对于 $k$ 次B-spline至多可以影响 $N_{i-k,k}(u), N_{i-k+1,k}(u),\cdots,N_{i,k}(u)$。

Meaning of the Coefficients

最后,我们研究递推式 $N_{i,k}(u) = \frac{u-u_i}{u_{i+k}-u_i}N_{i,k-1}(u) + \frac{u_{i+k+1}-u}{u_{i+k+1}-u_{i+1}}N_{i+1,k-1}(u)$ 中系数的意义,根据上方的观察可知 $N_{i,k-1}(u)$ 在 $[u_i,u_{i+k})$ 上非零,$N_{i+1,k-1}$ 在 $[u_{i+1},u_{i+k+1})$ 上非零。系数 $\frac{u-u_i}{u_{i+k}-u_i}$ 表示 $u$ 到左端点 $u_i$ 的长度占 $N_{i,k-1}(u)$ 非零区间 $[u_i,u_{i+k})$ 的比例,系数 $\frac{u_{i+k+1}-u}{u_{i+k+1}-u_{i+1}}$ 表示 $u$ 到右端点 $u_{i+k+1}$ 的长度占 $N_{i+1,k-1}(u)$ 非零区间 $[u_{i+1},u_{i+k+1})$ 的比例,如下图:

*图里的 $p$ 等价于文章中的 $k$。

也就是说 $N_{i,k}(u)$ 实际上就是 $N_{i,k-1}(u)$ 和 $N_{i+1,k-1}(u)$ 的线性组合。

Properties

  1. $N_{i,k}(u)$ 是一个关于 $u$ 的 $k$ 次多项式。 这一点通过上方的树形图递推过程很容易证明。
  2. $N_{i,k}(u)\ge 0$。
  3. $N_{i,k}(u)$ 仅在区间 $[u_i,u_{i+k+1})$ 上非零(对应上方观察1)。
  4. 在区间 $[u_i,u_{i+1})$ 上,至多 $k+1$ 个 $k$ 次多项式非零:$N_{i-k,k},N_{i-k+1,k},\cdots,N_{i,k}\neq0$(对应上方观察2)。
  5. $1$ 的分割(Partition of unity):在区间 $[u_i,u_{i+1})$ 上,至多 $k+1$ 个 $k$ 次多项式非零:$N_{i-k,k},N_{i-k+1,k},\cdots,N_{i,k}\neq0$,且这些多项式之和为 $1$,即
    $$
    \sum_{j=i-k}^i N_{j,k}(u) = 1,\quad u\in[u_i,u_{i+1})
    $$
    证明如下:
    1. 首先,对于 $k=0$ 有 $N_{i,0}(u) = \begin{cases}1 & u\in[u_i,u_{i+1})\\ 0 & \text{otherwise}\end{cases}$,性质成立。
    2. 假设 $k\lt p$ 时,$\sum_{j=i-k}^i N_{j,k}(u) = 1,\quad u\in[u_i,u_{i+1})$ 均成立,于是有 $N_{i-p+1,p-1}(u) + \cdots + N_{i,p-1}(u)=1$。
    3. $k=p$ 时,
    $$
    \begin{aligned}
    N_{i-p,p}(u)+N_{i-p+1,p}(u)+\cdots+N_{i,p}(u) =&\ \frac{u-u_{i-p}}{u_{i}-u_{i-p}}N_{i-p,p-1}(u) + \frac{u_{i+1}-u}{u_{i+1}-u_{i-p+1}}N_{i-p+1,p-1}(u)+\\
    &\ \frac{u-u_{i-p+1}}{u_{i+1}-u_{i-p+1}}N_{i-p+1,p-1}(u) + \frac{u_{i+2}-u}{u_{i+2}-u_{i-p+2}}N_{i-p+2,p-1}(u)+\\
    &\ \cdots+\\
    &\ \frac{u-u_i}{u_{i+p}-u_i}N_{i,p-1}(u) + \frac{u_{i+p+1}-u}{u_{i+p+1}-u_{i+1}}N_{i+1,p-1}(u)\\
    =&\ \frac{u-u_{i-p}}{u_{i}-u_{i-p}}N_{i-p,p-1}(u) + [N_{i-p+1,p-1}(u) + \cdots + N_{i,p-1}(u)] + \frac{u_{i+p+1}-u}{u_{i+p+1}-u_{i+1}}N_{i+1,p-1}(u)\\
    =&\ \frac{u-u_{i-p}}{u_{i}-u_{i-p}}N_{i-p,p-1}(u) + \frac{u_{i+p+1}-u}{u_{i+p+1}-u_{i+1}}N_{i+1,p-1}(u) + 1\\
    =&\ 1
    \end{aligned}
    $$
  6. 对于一个 $k$ 次的B-spline,在一个重数为 $p$ 的结点(即 $u_i=u_{i+1}=\cdots=u_{i+p-1}$)处,该函数的光滑性是 $C^{k-p}$(即 $k-p$ 阶连续可导)。
    比如上文中的 $N_{0,2}(u) = \begin{cases}0.5u^2 & 0\le u\lt 1\\0.5u(2-u) + 0.5u(3-u) & 1\le u\lt 2\\0.5(3-u)^2 & 2\le u\lt 3\end{cases}$ 就是 $C^1$ 光滑(一阶连续可导)。
    证明参考论文B(asic)-Spline Basics.

B-spline Curves

B-spline Curves Definition

给定 $n+1$ 个控制点(control points) $P_0,P_1,\cdots,P_{n}$ 和 $n+k+1$ 个结点构成的结点向量 $U = (u_0,u_1,\cdots,u_{n+k})$,就能定义一条 $k$ 次B-spline曲线:

$$
S(u) = \sum_{i=0}^n N_{i,k}(u) P_i
$$

在B-spline curve中,结点向量的选取一般有以下几种特殊形式:

  1. uniform:$u_{i+1}-u_i$ 是常数,比如 $U=(0,1,2,3,4)$。
  2. open uniform:前 $k+1$ 和后 $k+1$ 个 $u_i$ 分别相等,其余 $u_i$ 则满足 $u_{i+1}-u_i$ 是常数,比如 $U=(0,0,0,0,1,2,3,4,4,4,4)$。这个trick也被称为是”clamped”,这也将是我们后文中主要讨论的B-spline curve的构造形式。


Properties

性质1

B-spline curve $S(u)$ 是由 $n+1$ 条 $k$ 次分段曲线构成的,这就是 $N_{i,k}(u)$ 的性质。

性质2

用open uniform形式结点向量构造的B-spline curve的起点、终点分别是 $P_0$ 和 $P_n$。 注意到 $N_{0,k}(u)$ 是控制点 $P_0$ 的系数,并且在 $[u_0,u_{k+1})$ 上非零,因为 $u_0=u_1=\cdots=u_k$,所以 $N_{0,0}(u),N_{1,0}(u),\cdots,N_{k-1,0}(u)=0$,只有 $N_{k,0}\neq 0$,因此 $N_{k,0}=1$,即 $S(u_0)=P_0$,同理有 $S(u_{n+k})=P_1$。(这也是open uniform的核心优势)

性质3

可以用低阶曲线去近似大量的控制点

B-spline Curve
Bezier Curve

上图中的B-spline curve由11个控制点,14个结点构成,由定义可知该曲线为3次曲线,而同样情况下的Bezier curve因为次数过高所以近似效果劣于B-spline curve。

性质4

修改控制点 $P_i$ 只会影响区间 $[u_i,u_{i+p+1})$ 上的曲线 $S(u)$。

这是因为控制点 $P_i$ 的系数是 $N_{i,k}(u)$,而 $N_{i,k}(u)$ 仅在区间 $[u_i,u_{i+p+1})$ 上非零。

如上图,移动 $P_2$ 只会影响局部的曲线。

性质5

假设 $u\in [u_{i},u_{i+1})$,则 $S(u)$ 位于控制点构成的凸包 $P_{i-k},P_{i-k+1},\cdots,P_{i}$ 内部。

因为 $u\in [u_{i},u_{i+1})$,该区间对应的B-spline $N_{i,0}$ 只能影响到 $N_{i,k}(u),\cdots,N_{i-p+1}(u),N_{i-p}(u)$,它们分别是控制点 $P_i,\cdots,P_{i-k+1},P_{i-k}$ 的系数,这些系数全部非零且和为 $1$,因此 $S(u)$ 必然位于凸包 $P_{i-k},P_{i-k+1},\cdots,P_{i}$ 内部(线性组合的性质)。

性质6

对于一个 $k$ 次的B-spline curve,如果其结点向量中没有重复点,那么该曲线的光滑性至少是 $C^{k-1}$。

性质7

对于一个 $k$ 次的B-spline curve,在一个重数为 $p$ 的结点(即 $u_i=u_{i+1}=\cdots=u_{i+p-1}$)处,该曲线的光滑性是 $C^{k-p}$(当结点向量中没有重复点时,$p=1$,该性质等价于性质6)。

这点通过B-spline的性质6即可推出。

性质8

Bezier curve是B-spline curve的特殊情况,假设 $n=k$(即B-spline的次数为 $n$,结点向量满足 $u_0=u_1=\cdots=u_k, u_{k+1}=u_{k+2}=\cdots=u_{n+k+1}$),(为了推导方便,我们假设 $u_0=0,u_{k+1}=1$),此时

$$
\begin{aligned}
N_{i,k}(u) &= \frac{u-u_i}{u_{i+k}-u_i}N_{i,k-1}(u) + \frac{u_{i+k+1}-u}{u_{i+k+1}-u_{i+1}}N_{i+1,k-1}(u)\\
&= uN_{i,k-1}(u) + (1-u)N_{i+1,k-1}(u)
\end{aligned}
$$

这和Bezier curve中的bernstein多项式的递推式:$B_{i,n} = tB_{i,n-1}(t) + (1-t)B_{i+1,n-1}(t)$ 相同。

实际上Bezier curve在一般情况下就是由 $n-k+1$ 条Bezier Curve拼接而成的。

Code implementation

from typing import List, Tuple
import matplotlib.pyplot as plt
import numpy as np
import math


def bezier(p: List[Tuple[float, float]]):
    n = len(p) - 1
    x, y = [], []
    for t in np.linspace(0, 1, 1000):
        xt, yt = 0, 0
        for i in range(n + 1):
            xt += math.comb(n, i) * ((1 - t) ** (n - i)) * (t ** i) * p[i][0]
            yt += math.comb(n, i) * ((1 - t) ** (n - i)) * (t ** i) * p[i][1]
        x.append(xt)
        y.append(yt)

    plt.plot(x, y, label="bezier curve")
    xx, yy = zip(*p)
    plt.scatter(xx, yy)
    plt.plot(xx, yy, linestyle='dashed')
    plt.legend()
    plt.show()


def b_splines(p: List[Tuple[float, float]], t: List[float]):
    n = len(p) - 1
    k = len(t) - n - 2
    x, y = [], []

    for v in np.linspace(t[0], t[-1], 1000):
        dp = {}

        def cox_de_boor(i, k):
            if (i, k) in dp:
                return dp[(i, k)]
            if k == 0:
                dp[(i, k)] = 1 if t[i] <= v < t[i + 1] else 0
                return dp[(i, k)]
            else:
                c1 = (v - t[i]) / (t[i + k] - t[i]) if t[i + k] > t[i] else 0
                c2 = (t[i + k + 1] - v) / (t[i + k + 1] - t[i + 1]) if t[i + k + 1] > t[i + 1] else 0
                dp[(i, k)] = c1 * cox_de_boor(i, k - 1) + c2 * cox_de_boor(i + 1, k - 1)
                return dp[(i, k)]

        xt, yt = 0, 0
        for i in range(n + 1):
            n_ik = cox_de_boor(i, k)
            xt += n_ik * p[i][0]
            yt += n_ik * p[i][1]

        x.append(xt)
        y.append(yt)

    plt.plot(x[:-1], y[:-1], label="b-spline curve")
    xx, yy = zip(*p)
    plt.scatter(xx, yy)
    plt.plot(xx, yy, linestyle='dashed')
    plt.legend()
    plt.show()


p = [(0, 0), (1, 1), (4, 2), (6, 0), (5, -2), (4, -3), (2, -4)]
t2 = [0, 0, 0, 1, 2, 3, 4, 5, 5, 5]
t3 = [0, 0, 0, 0, 1, 2, 3, 4, 4, 4, 4]
t6 = [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1]
# bezier(p)
b_splines(p, t3)

B-spline Curve的代码实现实际上就是实现Cox-de Boor recursion formula,写一个记忆化搜索即可。

open uniform形式构造的3次B-spline curve

References

  1. B-spline Basis Functions: Definition. (n.d.). https://pages.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/B-spline/bspline-basis.html
  2. B-spline Curves: Important Properties. (n.d.). https://pages.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/B-spline/bspline-curve-prop.html
  3. Coolwater (https://math.stackexchange.com/users/84917/coolwater), B-Spline basis function partition of unity, URL (version: 2019-05-31): https://math.stackexchange.com/q/3246650
  4. BoorCarl, D., & Center, W. U. M. M. R. (n.d.). B(asic)-Spline Basics. DTIC. https://apps.dtic.mil/sti/citations/ADA172773
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇