第二章:航迹生成基础——航迹与路径的区别、数学表示与基本流程

各位同学,今天我们来聊聊航迹生成的基础。说实话,我刚入行那会儿,也分不清「路径」和「航迹」到底有啥区别。直到有一次做无人机编队项目,被老工程师一句话点醒——「路径是地图上的线,航迹是时间轴上的舞步」。嗯,这个比喻我一直记到现在。

2.1 航迹与路径:别搞混了

先问大家一个问题:你从北京坐高铁到上海,12306给你规划的那条铁路线,是路径还是航迹?

答案是路径。它只告诉你「走哪条铁轨」,不关心你几点出发、几点到站、中间停多久。而航迹呢?它得告诉你:8:00发车、8:15到济南、8:17离开济南……每个时刻你在哪里、速度多少、加速度多少。

说白了,路径是空间概念,航迹是时空概念。

我在项目中遇到过不少新手,拿着路径规划算法(比如A*、RRT)算出一条线,就以为航迹生成完成了。结果一跑仿真,无人机在拐弯处直接超速——因为路径只给了位置,没给时间约束。嗯,这个坑我踩过,希望大家别重蹈覆辙。

核心区别总结:

  • 路径(Path):空间中的一条几何曲线,只包含位置信息。例如:从点A到点B的一条直线或曲线。
  • 航迹(Trajectory):路径 + 时间信息。每个点都绑定了时刻、速度、加速度等运动状态。

你想想看,如果只给路径不给时间,机器人怎么知道什么时候该加速、什么时候该减速?所以航迹生成,本质上是在路径上「贴上时间标签」。

2.2 航迹的数学表示

航迹的数学表示,我习惯把它分成两类:离散型和连续型。两种我都用过,各有各的适用场景。

2.2.1 时间序列表示(离散型)

这是最直观的方式。把航迹表示成一系列离散的时间点上的状态:

Trajectory = { (t₀, x₀, y₀, z₀, v₀, a₀),
               (t₁, x₁, y₁, z₁, v₁, a₁),
               ...
               (tₙ, xₙ, yₙ, zₙ, vₙ, aₙ) }

每个时间步 tᵢ 对应一个状态向量,包含位置、速度、加速度等。这种表示方法简单直接,适合做数值计算和仿真。

优点:存储方便,计算效率高。
缺点:分辨率受采样率限制,插值精度有限。

我的经验:在无人机航迹规划中,时间序列采样率一般取 10-50 Hz。太低会丢失动态细节,太高又浪费算力。我曾经试过 100 Hz 采样,结果内存爆了——嗯,教训深刻。

2.2.2 样条曲线表示(连续型)

如果你需要一条光滑的航迹,样条曲线是更好的选择。我个人最常用的是三次样条和B样条。

三次样条:通过一系列控制点,生成一条二阶连续(C²连续)的光滑曲线。每个分段是三次多项式。

// 三次样条示例(伪代码)
struct CubicSpline {
    double a, b, c, d;  // 多项式系数
    double t_start, t_end;  // 时间区间
};

double evaluate(CubicSpline spline, double t) {
    double dt = t - spline.t_start;
    return spline.a + spline.b*dt + spline.c*dt² + spline.d*dt³;
}

B样条:比三次样条更灵活,支持局部修改。你改一个控制点,只影响附近一段曲线,不会牵一发动全身。

表示方法 连续性 局部控制 计算复杂度 适用场景
时间序列 离散 O(n) 数值仿真、实时控制
三次样条 C²连续 O(n) 路径平滑、轨迹插值
B样条 C²连续 O(n) 复杂航迹设计、局部调整

注意:样条曲线虽然光滑,但容易产生「过冲」现象——在控制点密集的地方,曲线可能会剧烈波动。我曾经在一条狭窄通道里用高次样条,结果航迹直接穿墙了……后来改用B样条加约束条件才解决。

2.3 航迹生成的基本流程

好了,理论讲完了,咱们来看看实际怎么做。航迹生成,我一般把它拆成四个步骤:

  1. 输入获取:拿到起点、终点、环境地图、约束条件(最大速度、加速度、障碍物位置等)。
  2. 路径规划:用A*、RRT、PRM等算法,找出一条无碰撞的几何路径。
  3. 时间参数化:给路径上的每个点分配时间,生成速度、加速度曲线。这一步是核心。
  4. 优化与验证:检查航迹是否满足所有约束,必要时做平滑或重规划。

下面这张图,是我自己画的一个流程框架,大家感受一下:

输入获取 起点/终点/地图/约束 路径规划 A*/RRT/PRM 时间参数化 速度/加速度分配 优化与验证 约束检查/平滑 不满足约束时反馈重规划 航迹生成基本流程 虚线箭头表示:如果验证不通过,返回路径规划或时间参数化步骤重新调整

这里我想重点说说第三步——时间参数化。很多教材把它一笔带过,但实际项目中,这一步才是真正的「坑王」。

时间参数化的核心问题:给定一条路径,如何分配时间,使得运动满足动力学约束(最大速度、最大加速度、 jerk 限制等)?

我常用的方法有两种:

  • 梯形速度曲线:加速段→匀速段→减速段。简单可靠,适合大多数场景。
  • S形速度曲线:加加速度(jerk)受限,运动更平滑。适合高精度任务,比如机械臂抓取。

避坑指南:我曾经在一个项目中,直接用梯形速度曲线给无人机规划航迹。结果无人机在加速段末端剧烈抖动——因为梯形曲线在加速到匀速的切换点处,加速度突变,导致 jerk 无穷大。后来换成 S 形曲线,问题就解决了。所以,如果你的系统对平滑度要求高,别偷懒,用 S 形曲线。

2.4 一个简单的航迹生成示例

说了这么多,咱们来点实际的。假设我们要生成一条从 (0,0) 到 (10,10) 的直线航迹,最大速度 2 m/s,最大加速度 1 m/s²。

# 伪代码:梯形速度曲线航迹生成
def generate_trajectory(start, end, v_max, a_max):
    # 1. 计算路径长度
    distance = norm(end - start)  # 约14.14米
    
    # 2. 计算加速段和减速段长度
    acc_distance = v_max² / (2 * a_max)  # 2米
    
    # 3. 如果距离太短,无法达到最大速度
    if distance < 2 * acc_distance:
        # 三角形速度曲线(只有加速和减速)
        v_peak = sqrt(a_max * distance)
        t_acc = v_peak / a_max
        t_total = 2 * t_acc
    else:
        # 梯形速度曲线
        t_acc = v_max / a_max
        t_cruise = (distance - 2 * acc_distance) / v_max
        t_total = 2 * t_acc + t_cruise
    
    # 4. 生成时间序列
    trajectory = []
    for t in range(0, int(t_total * 100), 1):  # 10ms采样
        t_sec = t / 100.0
        # 根据时间计算位置、速度、加速度
        # ...(省略具体插值代码)
        trajectory.append((t_sec, x, y, v, a))
    
    return trajectory

这个例子虽然简单,但包含了航迹生成的核心思想:在路径上叠加时间信息,同时满足运动学约束

我的建议:刚开始做航迹生成时,别一上来就搞复杂的样条曲线。先用梯形速度曲线跑通流程,再逐步优化。我见过太多人一开始就追求「完美」,结果连基本流程都没跑通——嗯,欲速则不达。

好了,这一章的内容就到这里。航迹生成说难不难,说简单也不简单。关键是把「路径」和「航迹」这两个概念分清楚,然后掌握时间参数化的基本方法。下一章我们会深入讲样条曲线的具体实现,到时候再聊。


公众号:蓝海资料掘金营,微信deep3321