4. 轨迹规划基础:关节空间规划、笛卡尔空间规划、多项式插值

各位同学,欢迎来到轨迹规划这一章。

说实话,轨迹规划是整个人形机器人运动控制的「灵魂」所在。你想想看,就算你的动力学模型再准,控制算法再牛,如果规划出来的轨迹本身就不合理——比如加速度突变、路径穿模——那机器人跑起来肯定是一顿一顿的,甚至直接摔倒。

我个人习惯把轨迹规划分成两大流派:关节空间规划和笛卡尔空间规划。它们各有各的脾气,也各有各的坑。今天咱们就把这两条路都走一遍,顺便把最常用的多项式插值讲透。

4.1 关节空间 vs 笛卡尔空间:两条路,怎么选?

先问个问题:你希望机器人末端(比如手)走一条直线,还是希望每个关节转得平滑?

这两个目标,其实对应了两种不同的规划思路。

  • 关节空间规划:直接在关节角度、角速度、角加速度上做规划。说白了,就是告诉每个电机「你从角度A转到角度B,中间怎么走」。优点是计算量小,不会出现奇异性问题。缺点是末端轨迹不可控——你没法保证手是走直线的。
  • 笛卡尔空间规划:在末端执行器的位姿(位置+姿态)上做规划。比如让手从点P1直线运动到点P2。优点是末端轨迹直观可控。缺点是需要实时做逆运动学求解,计算量大,而且可能遇到奇异点。

我在项目中遇到过这样一个场景:让机器人端着一杯水走。如果用关节空间规划,末端路径是弧线,水很容易洒出来。换成笛卡尔空间规划,让杯子始终保持水平直线运动,问题就解决了。所以你看,选哪种规划,完全取决于你的任务需求。

核心原则:

  • 对末端轨迹有严格要求(如焊接、涂胶)→ 笛卡尔空间规划
  • 只关心起点终点,不关心中间路径(如搬运、码垛)→ 关节空间规划

4.2 多项式插值:平滑轨迹的「万能钥匙」

不管选哪种空间,最终都要解决一个问题:如何让运动平滑?

平滑意味着位置连续、速度连续、加速度连续。如果加速度突变,机器人就会抖动,电机也会受冲击。解决这个问题的经典方法就是多项式插值。

4.2.1 三次多项式:最基础的平滑

假设你只有一个起点和一个终点,要求起点和终点的速度都为0。那么最少需要几个参数?

位置和速度,一共4个约束条件。所以用三次多项式:

θ(t) = a0 + a1*t + a2*t² + a3*t³

四个系数a0~a3,正好由四个边界条件解出。嗯,这里要注意:三次多项式只能保证速度连续,加速度是跳变的。如果对加速度有要求,就得升级到五次多项式。

4.2.2 五次多项式:加速度也连续

如果你需要加速度也连续(比如高精度轨迹跟踪),那就用五次多项式:

θ(t) = a0 + a1*t + a2*t² + a3*t³ + a4*t⁴ + a5*t⁵

六个系数,对应位置、速度、加速度各两个边界条件。我在做人形机器人步态规划时,就强制要求加速度连续,否则机器人迈步时会有明显的「顿挫感」。

我的小技巧:实际工程中,我一般用五次多项式作为默认选项。虽然计算量比三次大一点,但现在的MCU完全扛得住。省这点计算量换来更好的运动品质,值!

4.2.3 多段轨迹的衔接

如果轨迹有多个中间点(比如从A到B再到C),就不能只用一段多项式了。需要分段插值,并且在衔接点处保证位置、速度、加速度连续。

我曾经踩过一个坑:分段插值时只保证了位置连续,忘了速度连续。结果机器人每到中间点就「卡」一下,像在打嗝。后来加上速度约束,才彻底解决。

多段轨迹的数学表达稍微复杂一些,但核心思想不变:每段多项式在衔接点处的各阶导数必须相等。

4.3 知识体系总览

下面这张图,是我自己梳理的轨迹规划知识框架。你可以把它当作本章的「地图」:

轨迹规划知识体系 关节空间规划 笛卡尔空间规划 三次多项式 五次多项式 梯形速度 S形速度 直线插补 圆弧插补 姿态插值 奇异点处理 核心工具:多项式插值

4.4 代码实战:五次多项式插值

光说不练假把式。下面给出一段C++代码,实现五次多项式插值。这是我在ROS2中实际用过的版本,稍作简化:

#include <vector>
#include <cmath>

struct TrajectoryPoint {
    double pos;      // 位置
    double vel;      // 速度
    double acc;      // 加速度
    double time;     // 时间
};

// 五次多项式系数计算
// 输入:起点位置、速度、加速度,终点位置、速度、加速度,总时间T
// 输出:系数a0~a5
std::vector<double> calcQuinticCoeffs(
    double q0, double v0, double a0,
    double q1, double v1, double a1,
    double T) {
    
    std::vector<double> coeffs(6);
    
    coeffs[0] = q0;
    coeffs[1] = v0;
    coeffs[2] = a0 / 2.0;
    
    double T2 = T * T;
    double T3 = T2 * T;
    double T4 = T3 * T;
    double T5 = T4 * T;
    
    coeffs[3] = (20.0*(q1 - q0) - (8.0*v1 + 12.0*v0)*T - (3.0*a0 - a1)*T2) / (2.0*T3);
    coeffs[4] = (30.0*(q0 - q1) + (14.0*v1 + 16.0*v0)*T + (3.0*a0 - 2.0*a1)*T2) / (2.0*T4);
    coeffs[5] = (12.0*(q1 - q0) - (6.0*v1 + 6.0*v0)*T - (a0 - a1)*T2) / (2.0*T5);
    
    return coeffs;
}

// 根据系数和时间t计算轨迹点
TrajectoryPoint evaluateQuintic(const std::vector<double>& coeffs, double t) {
    TrajectoryPoint pt;
    double t2 = t * t;
    double t3 = t2 * t;
    double t4 = t3 * t;
    double t5 = t4 * t;
    
    pt.pos = coeffs[0] + coeffs[1]*t + coeffs[2]*t2 + coeffs[3]*t3 + coeffs[4]*t4 + coeffs[5]*t5;
    pt.vel = coeffs[1] + 2.0*coeffs[2]*t + 3.0*coeffs[3]*t2 + 4.0*coeffs[4]*t3 + 5.0*coeffs[5]*t4;
    pt.acc = 2.0*coeffs[2] + 6.0*coeffs[3]*t + 12.0*coeffs[4]*t2 + 20.0*coeffs[5]*t3;
    pt.time = t;
    
    return pt;
}

注意:这段代码假设时间从0开始。如果你的轨迹时间不是从0开始,记得做时间偏移。我曾经因为忘了这个,调试了整整一个下午……

4.5 两种规划的对比总结

对比项 关节空间规划 笛卡尔空间规划
计算量 小(直接对角度插值) 大(需要实时逆运动学)
末端轨迹可控性 不可控 可控(直线、圆弧等)
奇异点问题 无 有(需特殊处理)
适用场景 点到点运动、码垛 焊接、涂胶、避障
插值方法 多项式、梯形速度等 直线插补、圆弧插补

好了,这一章的内容就到这里。轨迹规划说难不难,说简单也不简单。关键是要理解每种方法的适用场景,以及背后的数学原理。多项式插值是基础中的基础,一定要亲手写一遍代码,跑几个例子感受一下。

可可精品资料库