1. 从“开飞机”到“画曲线”:为什么我们需要微分平坦性?

想象一下,你正在玩一个无人机模拟器,你的任务是让无人机从A点飞到B点,中途还要优雅地绕过几个障碍物。最直接的想法是什么?你可能会想:“我需要控制它的油门、俯仰角、滚转角、偏航角……” 没错,这就像在开一架真正的飞机,你需要同时关注十几个仪表盘,手忙脚乱。对于无人机来说,它的完整状态确实是一个12维的向量:三维空间位置(X, Y, Z)、三维姿态角(滚转φ、俯仰θ、偏航ψ),以及它们各自对应的速度(Ẋ, Ẏ, Ż)和角速度(p, q, r)。要实时、精确地规划和控制这12个量,计算量巨大,而且容易出错,尤其是在需要快速避障和生成光滑轨迹的场景下。

这就引出了我们今天要聊的核心概念:微分平坦性。它本质上是一种“降维打击”的智慧。简单来说,对于一个复杂的控制系统,如果我们能找到一组数量更少的“平坦输出”,使得系统的所有状态变量(位置、姿态、速度、角速度)以及控制输入(电机推力、力矩)都能用这组平坦输出及其有限阶导数的代数组合来表示,那么这个系统就是微分平坦的。对于常见的多旋翼无人机模型,经过数学上的严格推导,我们可以惊喜地发现,它的平坦输出就是三维位置(X, Y, Z)和偏航角(ψ)。也就是说,我们只需要规划好无人机要飞行的空间路径(X,Y,Z)和机头的朝向(ψ),剩下的所有事情——它应该以什么角度倾斜(φ, θ)、每个电机的转速应该是多少——都可以通过一套确定的公式计算出来,而无需再进行复杂的优化求解。

我第一次在实际项目中应用这个理论时,感觉就像突然拿到了一张“作弊码”。原本需要处理12个耦合变量的轨迹优化问题,瞬间简化成了只处理4个独立变量的曲线拟合问题。计算复杂度直线下降,实时性得到了质的提升。这不仅仅是数学上的优雅,更是工程实践中的利器。接下来,我们就一起拆解这个神奇的“降维”过程,看看数学是如何为我们赋能的。

2. 庖丁解牛:一步步推导无人机的微分平坦性

很多资料一上来就扔出一堆公式,让人望而生畏。我这里尝试用更直观的方式,把推导的脉络理清楚。我们的目标很明确:把12个状态变量,用4个平坦输出(X, Y, Z, ψ)及其导数表示出来。

2.1 起点:无人机的动力学模型

我们通常将无人机简化为一个刚性质点,其非线性动力学方程由牛顿-欧拉方程描述。简单理解,有两个核心方程:

  1. 平动方程:决定了无人机质心如何运动。总推力减去重力,产生加速度。公式是 m * [ẍ, ӱ, z̈]ᵀ = [0, 0, -mg]ᵀ + R * [0, 0, T]ᵀ。这里 R 是机体系到世界坐标系的旋转矩阵,T 是总推力。这个方程告诉我们,推力 T 在机体Z轴方向,经过旋转 R 后,提供了抵消重力和产生加速度的力。
  2. 转动方程:决定了无人机姿态如何变化。电机产生的力矩会导致角加速度,这涉及到转动惯量和角速度的叉乘,相对复杂。

但微分平坦性的妙处在于,我们不需要直接去解这个复杂的耦合系统。我们可以从结果反推。

2.2 关键洞察:推力方向决定了什么?

从平动方程出发,我们可以把加速度项和重力合并考虑。定义一个新的向量 t = [ẍ, ӱ, z̈ + g]ᵀ。这个向量实际上代表了“比力”,即单位质量所受到的除重力外的合力方向。根据方程,这个力 t 的方向,正好就是机体坐标系Z轴(Z_B)在世界坐标系中的指向!因为推力是沿机体Z轴的。所以,我们得到了第一个重要关系: Z_B = t / ||t||。 这意味着,只要我们规划出了轨迹(即知道了位置 X, Y, Z),我们就能计算出加速度(ẍ, ӱ, z̈),进而立刻得到机体Z轴的方向。这已经消去了两个姿态角(滚转φ和俯仰θ)的隐式信息。

2.3 构建完整的机体坐标系:引入偏航角ψ

现在我们知道了 Z_B,但机体坐标系还有 X_B 和 Y_B 轴未确定。这里就需要引入我们的第四个平坦输出——偏航角 ψ。偏航角定义了无人机机头围绕世界坐标系Z轴的旋转。我们构造一个中间坐标系C:它的 Z_C 与世界坐标系 Z_W 相同(垂直向上),而它的 X_C 轴则由偏航角 ψ 决定,即 X_C = [cosψ, sinψ, 0]ᵀ。你可以把它想象成无人机“偏航对齐”后的一个参考方向。

由于我们希望无人机的 X_B-Z_B 平面(即机头方向和推力方向构成的平面)能够包含这个参考方向 X_C(这样机头才能指向我们期望的偏航方向),一个自然的几何约束是:X_C 应该落在由 X_B 和 Z_B 张成的平面内。这个约束导致了一个非常漂亮的结果:机体坐标系的 Y_B 轴,必须同时垂直于 Z_B 和 X_C。因此,我们可以通过叉乘得到: Y_B = (Z_B × X_C) / ||Z_B × X_C||。 一旦得到了 Y_B,X_B 就由右手定则唯一确定了: X_B = Y_B × Z_B。 至此,整个旋转矩阵 R_B = [X_B, Y_B, Z_B] 都被 (X, Y, Z, ψ) 及其导数(因为 Z_B 依赖于加速度)表示了。而旋转矩阵 R_B 等价于 (φ, θ, ψ) 三个欧拉角,这意味着滚转 φ 和俯仰 θ 已经被成功消除,它们不再是独立变量,而是由轨迹和偏航角计算得出的派生量。

2.4 最后的堡垒:角速度的表示

位置、速度、姿态都搞定了,还剩下角速度 ω = [p, q, r]ᵀ。这是最需要技巧的一步。思路是利用旋转矩阵的导数性质。对旋转矩阵 R 求导,满足关系:Ṙ = R * [ω]×,其中 [ω]× 是角速度向量的叉乘矩阵。

我们对已知的 Z_B 向量求导:Ż_B = d(Z_B)/dt。另一方面,从 Ṙ 的表达式出发,Ż_B 也可以表示为 Ż_B = ω × Z_B(这里 ω 是机体角速度在世界坐标系下的表示,但叉乘关系在旋转下保持一致)。于是我们得到关系:ω × Z_B = Ż_B。

我们令 h_ω = Ż_B,这是一个可以由平坦输出(计算到三阶导:加加速度Jerk)直接求得的已知向量。方程 ω × Z_B = h_ω 是一个向量方程。由于叉乘的特性,ω 中平行于 Z_B 的分量在这个方程中不起作用(因为 ω_z * Z_B × Z_B = 0)。我们可以通过点乘巧妙地解出 ω 在 X_B 和 Y_B 方向上的分量:

  • 将 ω × Z_B = h_ω 两边同时点乘 Y_B,利用向量混合积性质,可以得到 ω_x = -h_ω · Y_B。
  • 同理,两边点乘 X_B,得到 ω_y = h_ω · X_B。 至于 ω_z(绕机体Z轴的角速度),它主要来源于偏航角速度 ψ̇。可以证明,ω_z = ω · Z_B ≈ ψ̇ * (Z_W · Z_B)。在实际中,对于大多数平稳飞行,Z_B 接近 Z_W,所以 ω_z ≈ ψ̇。

至此,我们完成了所有12个状态变量的“平坦化”表示。它们全部是 (X, Y, Z, ψ) 及其一到三阶导数的函数。这个推导过程虽然涉及一些向量运算,但每一步都有清晰的物理或几何意义。掌握它,你就掌握了简化无人机高级控制问题的钥匙。

3. 从理论到代码:Mini Snap轨迹优化实战

知道了只需要规划4个平坦输出,接下来的问题就是:如何生成一条“好”的轨迹?所谓“好”,通常意味着平滑(减少对电机和机架的冲击)、节能、并且能通过所有预设的路径点(Waypoints)。这就是轨迹优化问题。在众多方法中,Mini Snap(最小加加速度变化率) 因其在平滑性和计算效率间的出色平衡,成为了无人机轨迹规划的事实标准。我最早在实现自主无人机编队时采用了这个方法,效果非常稳定。

3.1 什么是Mini Snap?为什么不是Min Jerk?

Snap是加加速度(Jerk)的导数,也就是位置的四阶导数。物理上,它对应着推力变化率。对于多旋翼无人机,其推力直接与电机转速的平方相关,而Snap实际上反映了电机转速变化的“急促”程度。最小化Snap的积分(即Mini Snap准则),意味着让电机转速的变化尽可能平滑,这能带来多重好处:

  1. 能量最优:平滑的推力变化通常意味着更低的能量消耗。
  2. 执行器友好:减少电机和电调的瞬时负荷,延长硬件寿命。
  3. 乘坐体验:对于载人无人机或运送精密货物,更平滑的加速度变化意味着更舒适的体验。 相比之下,Min Jerk(最小加加速度)准则在机械臂规划中更常见,因为它与扭矩变化率相关。对于无人机,直接控制推力,所以Mini Snap是更自然的选择。

3.2 问题建模:分段多项式轨迹

我们通常将整条轨迹分成多段,每段轨迹用一个多项式函数来表示。例如,对于平坦输出中的X坐标,从时间 t0 到 t1 的这一段,我们用如下多项式描述: P(t) = c0 + c1*t + c2*t² + c3*t³ + c4*t⁴ + c5*t⁵ + c6*t⁶ + c7*t⁷。 为什么常用7阶多项式?因为我们要最小化Snap的平方积分,这是一个关于多项式系数的最小二乘问题。要保证解的唯一性和连续性,我们需要对每一段轨迹的起点和终点的位置、速度、加速度、加加速度(Jerk)进行约束。4个端点条件 * 2个端点 = 8个约束,恰好可以唯一确定一段7阶多项式(8个系数)。

假设我们有 M 个路径点,就会产生 M-1 段轨迹。我们的优化目标是所有段轨迹的Snap平方积分之和最小: min ∫ (d⁴P/dt⁴)² dt。

3.3 构建与求解优化问题

这是一个标准的二次规划(QP)问题。我们需要构建以下约束:

  1. 路径点约束:每段轨迹的起点和终点位置必须等于指定的路径点坐标。
  2. 连续性约束:相邻两段轨迹在连接点处,位置、速度、加速度、加加速度必须连续(甚至Snap也可以约束连续)。这是保证轨迹整体光滑的关键。
  3. 动力学可行性约束(可选但重要):根据无人机的物理极限,约束每一时刻的最大速度、最大加速度、最大推力(对应于 Z_B 向量的大小)等。这些约束是非线性的,通常可以在生成初始轨迹后进行检查和迭代调整,或者采用更高级的规划器(如Bernstein多项式)来直接处理。

将目标函数和约束全部写成关于多项式系数向量的二次型和线性方程组形式后,就可以调用高效的QP求解器(例如OSQP, qpOASES)进行求解。计算速度非常快,通常在毫秒级就能完成一条复杂轨迹的生成。

下面是一个高度简化的Python代码框架,展示了如何设置一个Mini Snap优化问题(省略了可行性约束和求解器接口部分):

import numpy as np
from scipy.optimize import minimize
# 假设我们有3个路径点,生成2段轨迹,每段使用7阶多项式
waypoints = np.array([[0,0,0], [2,1,1], [3,3,2]]) # X,Y,Z坐标
num_segments = len(waypoints) - 1
order = 7 # 多项式阶数
# 每段轨迹的持续时间(可以优化,这里预先给定)
segment_times = [2.0, 2.0]
# 设计变量:所有段的多项式系数(每段 (order+1)个系数,共3维)
# 这里以X维度为例,Y和Z维度独立但同构处理
total_coeffs = num_segments * (order + 1)
# 目标函数:最小化Snap平方的积分
# 对于多项式 P(t)=Σc_i t^i,其四阶导数是 Σ i*(i-1)*(i-2)*(i-3)*c_i t^{i-4}
# 积分 ∫ (d⁴P/dt⁴)² dt 是关于系数c的二次型:c^T Q c
# 我们需要为每一段构造其对应的Q矩阵
def compute_Q_matrix(T, order):
    Q = np.zeros((order+1, order+1))
    for i in range(4, order+1):
        for j in range(4, order+1):
            # 积分 ∫_0^T (i*(i-1)*(i-2)*(i-3) * t^{i-4}) * (j*(j-1)*(j-2)*(j-3) * t^{j-4}) dt
            coef = (i*(i-1)*(i-2)*(i-3)) * (j*(j-1)*(j-2)*(j-3))
            power = i + j - 7
            Q[i, j] = coef * (T**power) / power
    return Q
# 构建总目标函数矩阵(块对角矩阵,因为各段独立)
Q_total = np.zeros((total_coeffs, total_coeffs))
offset = 0
for seg in range(num_segments):
    Q_seg = compute_Q_matrix(segment_times[seg], order)
    n_coeff = order + 1
    Q_total[offset:offset+n_coeff, offset:offset+n_coeff] = Q_seg
    offset += n_coeff
# 约束:起点/终点的位置、速度、加速度、加加速度;连接点的连续性
# 这部分需要构建一个巨大的等式约束矩阵 A_eq * x = b_eq
# 具体构建过程略,是工程实现中最繁琐但最核心的部分。
# ...
# 调用求解器求解二次规划问题:min (1/2) x^T Q_total x, s.t. A_eq x = b_eq
# result = solve_qp(Q_total, None, None, None, A_eq, b_eq) # 伪代码

在实际项目中,我推荐使用成熟的开源库,如**mav_trajectory_generation(ROS环境)或polytraj**,它们已经高效地实现了上述所有步骤,并考虑了时间分配优化和动力学约束。

4. 闭环:将规划好的轨迹送入控制器

生成了光滑的 (X, Y, Z, ψ) 轨迹及其高阶导数后,我们如何让无人机飞起来呢?这就需要用上一节推导的微分平坦性变换,形成一个完整的规划-控制闭环。

控制器通常分为两层:外环位置控制和内环姿态控制。微分平坦性在这里起到了桥梁作用:

  1. 外环(位置控制):根据期望的轨迹,我们可以计算出当前时刻期望的位置 p_d、速度 v_d、加速度 a_d。一个简单的PID或比例控制器可以根据当前位置误差计算出所需的总推力向量 t_desired(即我们之前推导中的 t)。
  2. 微分平坦变换:利用 t_desired 和期望的偏航角 ψ_d,我们立刻可以计算出:
    • 期望的机体Z轴方向:Z_B,desired = t_desired / ||t_desired||
    • 期望的旋转矩阵 R_desired(通过 X_C, Y_B, Z_B 构造)
    • 期望的机体角速度 ω_desired(通过对 R_desired 或 Z_B 求导得到)
  3. 内环(姿态控制):内环控制器(通常是角速度或扭矩控制器)的输入就是上一步计算出的 R_desired 和 ω_desired。控制器会努力驱动无人机,使其实际的姿态和角速度跟踪这些期望值。
  4. 推力映射:总推力大小 T = m * ||t_desired||,其中 m 是无人机质量。再根据无人机的具体模型(如四旋翼的“+”型或“X”型布局),将总推力 T 和期望的机体力矩(由内环控制器产生)分配到各个电机的转速指令上。

我在实际调试中发现,这个流程非常可靠。只要轨迹规划器生成的轨迹是动力学可行的(即所需的加速度和角速度在无人机能力范围内),整个控制系统就能稳定地跟踪。你甚至可以只给位置路径点,让偏航角 ψ 自动设置为速度的方向(即 ψ = atan2(ẏ, ẋ)),这样无人机在转弯时会自动“甩头”,看起来非常自然。

5. 避坑指南与高级技巧

理论很美好,但实际应用总会遇到坑。这里分享几个我在项目实践中总结的关键点。

5.1 时间分配的艺术

Mini Snap优化中,每段轨迹的持续时间 T_i 是预先给定的参数。分配不合理会导致大问题。如果某段时间给得太短,为了在规定时间内走完该段路径,多项式会“被迫”产生巨大的速度和加速度,超出物理极限,导致跟踪失败甚至失控。我常用的策略是:

  • 初始估计:根据路径长度和预设的最大速度、最大加速度,做一个简单的匀速或匀加速运动估算,得到每段的大致时间。
  • 迭代缩放:生成轨迹后,检查所有点的速度、加速度、推力是否超限。如果超限,就将对应段的时间 T_i 乘以一个大于1的系数(如1.2),然后重新优化。重复这个过程直到满足所有约束。
  • 时间最优规划:更高级的做法是将时间也作为优化变量,在满足动力学约束的前提下最小化总时间。这属于非线性优化问题,可以用诸如时间分配器(Time Allocation) 和贝塞尔(Bernstein)多项式相结合的方法来解决,后者能方便地将约束转换到控制顶点上,保证整个轨迹始终可行。

5.2 处理障碍物与走廊约束

原始的Mini Snap只经过路径点,两点之间的路径可能会撞上障碍物。解决方法是将路径点加密,或者在障碍物之间构造一个安全的飞行走廊。例如,我们可以用一系列重叠的立方体或球体来表示无人机可以安全飞行的空间。优化问题则变为:在每一段时间内,无人机的位置必须位于对应的立方体内。这引入了位置不等式约束,问题变成了一个带不等式约束的QP,仍然可解。开源库mav_trajectory_generation就支持基于凸多面体走廊的轨迹生成。

5.3 数值稳定性与单位

这是新手最容易出错的地方。一定要保证单位统一(国际单位制:米、秒、弧度)。在计算旋转矩阵和角速度时,涉及的叉乘、点乘运算对数值精度敏感。当无人机处于悬停或低速状态时,加速度 a ≈ [0, 0, -g],此时计算 Z_B = t / ||t|| 中的 t 范数很小,可能导致数值不稳定。在实际代码中,需要增加保护性判断,例如当 ||t|| 小于一个阈值时,直接令 Z_B = [0, 0, 1](即机体水平)。

5.4 与状态估计器的配合

规划出的轨迹是期望值,而控制器跟踪的是无人机的实际状态。实际状态来自机载传感器(IMU、视觉、GPS)通过状态估计器(如卡尔曼滤波器)融合得到。务必确保规划器使用的坐标系(通常是世界坐标系)与状态估计器输出的坐标系完全一致。任何微小的坐标系偏差(例如重力方向未对齐)都会在变换中引入持续误差,导致控制性能下降。在项目开始阶段,花时间做好传感器标定和坐标系对齐,能省去后期大量的调试时间。

微分平坦性和Mini Snap为无人机带来了优雅且强大的轨迹生成能力。从理论推导到代码实现,再到实际系统集成,每一步都充满了工程实践的智慧。当你看到自己编写的算法驱动无人机流畅地穿梭于障碍物之间时,那种成就感是对所有复杂数学推导和深夜调试的最佳回报。这条路我走过,虽然有些坑洼,但终点风景独好。希望这些分享能帮你更顺畅地开启自己的无人机智能飞行之旅。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐