PythonRobotics B-Spline 路径规划:插值、近似与曲率可视化完整指南
【免费下载链接】PythonRoboticsPython sample codes and textbook for robotics algorithms.项目地址: https://gitcode.com/GitHub_Trending/py/PythonRobotics
导读
本文以 PythonRobotics 仓库中 PathPlanning.BSplinePath 模块为主线,系统讲解 B-Spline(基样条)曲线在机器人路径规划中的两种典型用法:插值(Interpolation)——生成严格经过全部航点(waypoints)的光滑路径;近似(Approximation)——生成逼近航点但不一定穿过航点的光滑路径。读完本文,你将掌握 B-Spline 的数学原理(基函数与 De Boor 递推)、两个核心函数的完整参数语义(含平滑参数s的权衡逻辑)、曲率与航向的输出计算方式,以及如何运行示例与单元测试进行验证。
1. B-Spline 路径规划概述
在 bspline_path_main.rst 中,B-Spline 规划被定义为这样一类例程:输入一串航点(waypoints),输出一条由 B-Spline 曲线生成的光滑路径。仓库中的实现(bspline_path.py)同时提供两种生成方式:
- 插值(Interpolation):生成的曲线经过全部航点,适合要求严格命中关键点的场景;
- 近似(Approximation):生成的曲线逼近航点但不一定穿过,通过平滑参数
s在"平滑度"与"贴合度"之间权衡。
下图是两种方式的直观对比(红色为近似路径,蓝色为插值路径,绿色点为输入航点):
该文档位于仓库文档体系 5_path_planning 章节中,与 cubic_spline、catmull_rom_spline、clothoid_path、eta3_spline、bezier_path 等曲线类路径规划方案并列,共同构成平滑曲线生成工具集。
2. B-Spline 数学基础
2.1 定义与符号
BSpline(Basis-Spline,基样条)是一种分段多项式样条曲线。文档中给出的核心表达式为:
S(x) = Σ (i = k−p → k) cᵢ · Bᵢ,ₚ(x)
其中各符号含义如下:
| 符号 | 含义 |
|---|---|
| S(x) | 样条在参数 x 处的曲线点 |
| cᵢ | 生成样条的控制点(control point) |
| p + 1 | B-Spline 的维度(p为样条次数/degree) |
| k | 节点(knots)的数量 |
| Bᵢ,ₚ(x) | 基函数(Basis Function) |
2.2 基函数与 De Boor 递推
基函数由De Boor 递推公式(De Boor recursion formula)递归计算:
- 0 阶基函数:
Bᵢ,₀(x) := 1 (当 tᵢ ≤ x < tᵢ₊₁),否则为 0
- p 阶基函数:
Bᵢ,ₚ(x) := (x − tᵢ)/(tᵢ₊ₚ − tᵢ) · Bᵢ,ₚ₋₁(x) + (tᵢ₊ₚ₊₁ − x)/(tᵢ₊ₚ₊₁ − tᵢ₊₁) · Bᵢ₊₁,ₚ₋₁(x)
其中tᵢ是节点向量(knot vector)的每个元素。注意公式中当分母为零(即t[i+k] == t[i])时需要特殊处理,源码示例中通过将该项系数置 0 规避除零。
下图展示了 k=3(三次样条)时各 i 对应的基函数曲线,横轴 x∈[0,5],纵轴为基函数值:
2.3 关键性质:基函数归一性
图中隐含一个核心性质:在任意 x 处,所有基函数求和恒为 1.0。正因如此,控制点被基函数加权叠加后,得到的曲线是光滑的(权重总和恒定,不会出现突变)。这也是 B-Spline 相比直接折线连接航点的本质优势。
2.4 基函数生成的参考实现
文档提供了一段基于scipy.interpolate.BSpline生成基函数图像的参考代码(也用于生成上图),其核心是一个递归版本的基函数计算与一个调用 scipy 的封装:
from scipy.interpolate import BSpline def B_orig(x, k, i, t): if k == 0: return 1.0 if t[i] <= x < t[i + 1] else 0.0 if t[i + k] == t[i]: c1 = 0.0 else: c1 = (x - t[i]) / (t[i + k] - t[i]) * B(x, k - 1, i, t) if t[i + k + 1] == t[i + 1]: c2 = 0.0 else: c2 = (t[i + k + 1] - x) / (t[i + k + 1] - t[i + 1]) * B(x, k - 1, i + 1, t) return c1 + c2 def B(x, k, i, t): c = np.zeros_like(t) c[i] = 1 return BSpline(t, c, k)(x) def main(): k = 3 # degree of the spline t = [0, 1, 2, 3, 4, 5] # knots vector x = np.linspace(0, 5, 1000, endpoint=False) t = np.r_[[np.min(t)]*k, t, [np.max(t)]*k] n = len(t) - k - 1 for i in range(n): y = np.array([B(ix, k, i, t) for ix in x]) plt.plot(x, y, label=f'i = {i}') plt.title(f'Basis functions (k = {k}, knots = {t})') plt.show()要点说明:
- 代码先构造内部节点向量
[0,1,2,3,4,5],再通过np.r_在两端各重复k次边界节点,形成夹紧(clamped)节点向量[0,0,0,0,1,2,3,4,5,5,5,5],这正是图标题中显示的结果; - 基函数个数
n = len(t) - k - 1,本例为 8,对应图中 i=0 至 i=7 的 8 条曲线; - 递归实现
B_orig与 scipy 封装B并存,前者展示 De Boor 递推的原始逻辑,后者利用scipy.interpolate.BSpline提供数值验证。
3. B-Spline 插值规划(Interpolation)
3.1 功能与接口
interpolate_b_spline_path生成穿过全部航点的曲线。其函数签名为:
def interpolate_b_spline_path(x, y, n_path_points: int, degree: int = 3) -> tuple:参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
x/y | array_like | 待插值航点的 x/y 坐标列表 |
n_path_points | int | 输出路径的采样点数 |
degree | int | B-Spline 次数,必须满足 2 ≤ k ≤ 5,默认 3 |
返回值为四元组(x, y, heading, curvature):x/y 为结果路径坐标,heading为各点航向角,curvature为各点曲率。
下图是插值结果示例(蓝色曲线严格经过绿色航点):
3.2 源码实现原理
从 bspline_path.py 的实现看,插值本质上是近似函数在s=0时的特例:
def interpolate_b_spline_path(x, y, n_path_points: int, degree: int = 3) -> tuple: return approximate_b_spline_path(x, y, n_path_points, degree, s=0.0)即插值 = 平滑参数s=0的近似。因为s=0时UnivariateSpline表现为严格插值,所以曲线必然穿过全部航点。
3.3 曲率可视化
文档中插值示例还叠加了曲率可视化,由 utils/plot.py 的plot_curvature实现。其原理是:以路径上每点为起点,沿航向法线方向(yaw - π/2)偏移d * k(d为该点曲率,k为缩放因子,默认 0.01),偏移量的大小即反映曲率大小——直行段曲率为 0,曲线贴合原路径;转弯处向一侧凸出,左转/右转分别凸向不同侧。
默认样条次数为 3(三次样条),因此曲率沿路径平滑变化,这为后续路径跟踪控制器提供了利于执行的几何条件。
4. B-Spline 近似规划(Approximation)
4.1 功能与接口
approximate_b_spline_path生成逼近航点的曲线,不保证穿过所有航点。函数签名为:
def approximate_b_spline_path( x: list, y: list, n_path_points: int, degree: int = 3, s=None, ) -> tuple:参数说明:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
x/y | list | — | 被逼近航点的 x/y 坐标列表 |
n_path_points | int | — | 输出路径采样点数 |
degree | int | 3 | B-Spline 次数,必须 2 ≤ k ≤ 5 |
s | int/float | None | 平滑参数。越大路径越平滑但越不贴合航点;越小越贴合但越不光滑。s=0时等价于插值;None时内部取len(x) |
返回四元组(x, y, heading, curvature),语义与插值函数一致。
下图是近似结果示例(红色曲线逼近但不穿过绿色航点):
4.2 平滑参数 s 的权衡
文档与源码 docstring 一致强调s的核心权衡逻辑:
s越大→ 路径越平滑,但对航点的贴合度越差(误差增大);s越小→ 路径越贴合航点,但光滑度下降;s = 0→ 退化为严格插值;s = None(默认)→ 内部使用len(x)作为平滑量,即随航点数量自适应。
4.3 源码实现原理
近似实现的内部流程(bspline_path.py)如下:
distances = _calc_distance_vector(x, y) spl_i_x = interpolate.UnivariateSpline(distances, x, k=degree, s=s) spl_i_y = interpolate.UnivariateSpline(distances, y, k=degree, s=s) sampled = np.linspace(0.0, distances[-1], n_path_points) return _evaluate_spline(sampled, spl_i_x, spl_i_y)- 参数化:
_calc_distance_vector先按相邻航点间的欧氏距离做累积和并归一化,将航点映射到 [0,1] 参数区间,作为UnivariateSpline的自变量; - 两轴独立拟合:对 x 与 y 分别用
scipy.interpolate.UnivariateSpline拟合(次数k=degree、平滑s); - 重采样:在参数区间内均匀采样
n_path_points个点,交给_evaluate_spline求值。
4.4 航向与曲率的解析计算
_evaluate_spline(bspline_path.py)利用样条的可微性,通过解析导数而非数值差分计算航向与曲率:
dx = spl_i_x.derivative(1)(sampled) dy = spl_i_y.derivative(1)(sampled) heading = np.arctan2(dy, dx) ddx = spl_i_x.derivative(2)(sampled) ddy = spl_i_y.derivative(2)(sampled) curvature = (ddy * dx - ddx * dy) / np.power(dx * dx + dy * dy, 2.0 / 3.0)- heading:由一阶导
(dx, dy)经arctan2得到,即路径切线方向角; - curvature:由一阶导与二阶导组合得到,分母中的
2.0/3.0次幂对应曲率公式(x'y'' − y'x'') / (x'² + y'²)^(3/2)的等价写法。
由于默认degree=3,一阶、二阶导数连续,曲率曲线平滑无突变。
5. 运行示例与单元测试
5.1 运行演示程序
模块自带main()(bspline_path.py),直接运行即可同时看到近似与插值两张图:
python PathPlanning/BSplinePath/bspline_path.py示例默认航点:
way_point_x = [-1.0, 3.0, 4.0, 2.0, 1.0] way_point_y = [0.0, -3.0, 1.0, 1.0, 3.0] n_course_point = 50 # sampling number演示中近似使用s=0.5,插值使用默认参数(即s=0),并分别以红色、蓝色绘制,绿色-og标记航点,同时调用plot_curvature叠加曲率可视化,最后plt.axis("equal")保持横纵比例一致。
5.2 运行单元测试
仓库为 B-Spline 模块提供了专项测试 test_bspline_path.py,覆盖三类场景:
test_list_input:Python list 输入下,近似(s=0.5)与插值的四个返回数组长度一致;test_array_input:numpy.ndarray输入同样成立;test_degree_change:degree=2与degree=4均可正常输出;degree=1时近似与插值均抛出ValueError(印证了文档"必须 2 ≤ k ≤ 5"的约束)。
运行方式(从仓库根目录):
python -m pytest tests/test_bspline_path.py5.3 依赖环境
本模块依赖numpy、scipy、matplotlib,具体版本可参考 requirements/requirements.txt(仓库锁定的 scipy 1.18.1 提供了interpolate.UnivariateSpline与interpolate.BSpline)。测试还需pytest。
6. 小结
PythonRobotics 的 B-Spline 路径规划模块以 scipy 的UnivariateSpline/BSpline为数值内核,将"航点 → 光滑路径"这一常见规划需求封装为两个高复用接口:
interpolate_b_spline_path:s=0的近似特例,曲线严格经过航点;approximate_b_spline_path:通过平滑参数s在"平滑度"与"贴合度"间自由权衡。
两者均返回(x, y, heading, curvature),其中航向与曲率由样条解析导数直接求得,天然平滑;再配合 plot_curvature 即可直观审视路径弯曲分布,为下游的路径跟踪控制(如纯追踪、Stanley、LQR 控制等模块)提供几何质量保障。文档原文位于 docs/modules/5_path_planning/bspline_path/bspline_path_main.rst,实现与测试分别见 PathPlanning/BSplinePath/bspline_path.py 与 tests/test_bspline_path.py,可对照阅读。
【免费下载链接】PythonRoboticsPython sample codes and textbook for robotics algorithms.项目地址: https://gitcode.com/GitHub_Trending/py/PythonRobotics
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考