ego-planner的初始轨迹生成

发布于:2025-09-01 ⋅ 阅读:(20) ⋅ 点赞:(0)

ego-planner中生成初始轨迹的核心思路

ego中由于是不依赖建图,因此其在生成初始轨迹时,就采用的是另外的思路:位置采用五阶多项式的方式,然后再求导得到速度,加速度的方程,这样就可以保证初始轨迹的加速度,速度,位置是连续的。

位置:x(t) = c₅t⁵ + c₄t⁴ + c₃t³ + c₂t² + c₁t + c₀

速度:vₓ(t) = 5c₅t⁴ + 4c₄t³ + 3c₃t² + 2c₂t + c₁

加速度:aₓ(t) = 20c₅t³ + 12c₄t² + 6c₃t + 2c₂

以上的公式也比较容易求解。(PS:文章只是解析其原理,并不会苛求与代码完全对应)

列举线性方程

列出线性方程:C·coeff = B

以上的 c0 - c5 参数都是 coeff 矩阵的系数,那么之后的核心问题就转化为了以上的 coeff 矩阵各个系数的求解,通俗一点说,就是求解出一套 coeff 矩阵的系数,满足全时域的约束条件

其中 B 矩阵就是约束向量,表示轨迹起点坐标,终点坐标,起点速度,终点速度,起点加速度,终点加速度。即:

B = [start_pt.x, start_vel.x, start_acc.x, end_pt.x, end_vel.x, end_acc.x]ᵀ

C矩阵就是上面求导方程中除去系数的部分,如下所示:

行号 对应约束条件 矩阵行向量(按[c₅, c₄, c₃, c₂, c₁, c₀]顺序) 数学依据(代入多项式公式)
0 t=0 时,x (0)=start_pt.x [0, 0, 0, 0, 0, 1] x(0) = c₅·0⁵ + ... + c₀ = c₀ → 1·c₀ = start_pt.x
1 t=0 时,vₓ(0)=start_vel.x [0, 0, 0, 0, 1, 0] vₓ(0) = 5c₅·0⁴ + ... + c₁ = c₁ → 1·c₁ = start_vel.x
2 t=0 时,aₓ(0)=start_acc.x [0, 0, 0, 2, 0, 0] aₓ(0) = 20c₅·0³ + ... + 2c₂ = 2c₂ → 2·c₂ = start_acc.x
3 t=T 时,x (T)=end_pt.x [T⁵, T⁴, T³, T², T, 1] x(T) = c₅T⁵ + ... + c₀ → T⁵·c₅ + ... + 1·c₀ = end_pt.x
4 t=T 时,vₓ(T)=end_vel.x [5T⁴, 4T³, 3T², 2T, 1, 0] vₓ(T) = 5c₅T⁴ + ... + c₁ → 5T⁴·c₅ + ... + 1·c₁ = end_vel.x
5 t=T 时,aₓ(T)=end_acc.x [20T³, 12T², 6T, 2, 0, 0] aₓ(T) = 20c₅T³ + ... + 2c₂ → 20T³·c₅ + ... + 2·c₂ = end_acc.x

由于C是满秩矩阵(6 个约束彼此线性无关,没有一行能由其他行推导得出),方程组有且仅有唯一解 —— 这意味着:存在且仅存在一组系数,能满足所有运动学约束。

求解方程组 C·coeff = B,就是找到唯一的coeff,使得所有约束条件同时成立。

传入的时间 t 就是轨迹的总时间。

之后求解这个方程组就可以得到多项式的系数,ego 使用的是 QR 分解的方法。对应代码:

PolynomialTraj PolynomialTraj::one_segment_traj_gen(const Eigen::Vector3d &start_pt, const Eigen::Vector3d &start_vel, const Eigen::Vector3d &start_acc,
                                                    const Eigen::Vector3d &end_pt, const Eigen::Vector3d &end_vel, const Eigen::Vector3d &end_acc,
                                                    double t)
{
  Eigen::MatrixXd C = Eigen::MatrixXd::Zero(6, 6), Crow(1, 6);
  Eigen::VectorXd Bx(6), By(6), Bz(6);
  C(0, 5) = 1;
  C(1, 4) = 1;
  C(2, 3) = 2;
  Crow << pow(t, 5), pow(t, 4), pow(t, 3), pow(t, 2), t, 1;
  C.row(3) = Crow;
  Crow << 5 * pow(t, 4), 4 * pow(t, 3), 3 * pow(t, 2), 2 * t, 1, 0;
  C.row(4) = Crow;
  Crow << 20 * pow(t, 3), 12 * pow(t, 2), 6 * t, 2, 0, 0;
  C.row(5) = Crow;
  Bx << start_pt(0), start_vel(0), start_acc(0), end_pt(0), end_vel(0), end_acc(0);
  By << start_pt(1), start_vel(1), start_acc(1), end_pt(1), end_vel(1), end_acc(1);
  Bz << start_pt(2), start_vel(2), start_acc(2), end_pt(2), end_vel(2), end_acc(2);
  Eigen::VectorXd Cofx = C.colPivHouseholderQr().solve(Bx);
  Eigen::VectorXd Cofy = C.colPivHouseholderQr().solve(By);
  Eigen::VectorXd Cofz = C.colPivHouseholderQr().solve(Bz);
  vector<double> cx(6), cy(6), cz(6);
  for (int i = 0; i < 6; i++)
  {
    cx[i] = Cofx(i);
    cy[i] = Cofy(i);
    cz[i] = Cofz(i);
  }
  PolynomialTraj poly_traj;
  poly_traj.addSegment(cx, cy, cz, t);
  return poly_traj;
}

轨迹总时间的计算

在 ego 中,轨迹总时间的计算分为两个方案,是根据距离与最大速度,最大加速度约束来计算的,分界点就是临界距离的计算,这里就写作 d_critical 吧,当然 ego 的代码里没有这样取名字,它直接用了问号表达式,文章只是解析原理,不用太深究完全对应代码。

根据匀加速运动的规律,当无人机起点与终点的距离小于临界距离时,无人机是不需要进入匀速阶段就能到达位置的,当距离大于临界距离时,才会需要进入匀速阶段,最终呈现为:匀加速->匀速->匀减速 三阶段来达到目标位置。

因此可以看到其核心是是否会需要进入匀速阶段,这里将其分为两部分,短距离与长距离。如下图所示,当然,ego在代码中没有考虑先加速不匀速后减速的情况,这里先按照他的代码逻辑来画了

临界距离满足:v_max² = 2 * a_max * d_critical

可以得到:d_critical = v_max² / a_max

临界距离也可以理解为不需要经过匀速阶段能达到的最大距离。

对应的 ego 代码为:

// 根据距离计算轨迹总时间
double dist = (start_pt - local_target_pt).norm();
// 如果是短距离,则时间计算就根据匀加速运动(从静止开始以最大加速度 a_max 加速到最大速度 v_max)公式: d = vmax^2 / a_max
// 如果求解出来的加速到最大能达到的距离大于当前距离,说明不需要经历匀速阶段,此时距离公式就为 d = 0.5 * a_max * T^2, 这里代码求解时间简化为 T = sqrt(d / a_max)
// 如果距离比较远时,需要经历匀加速,匀速,匀减速三个阶段,因此就求解三个阶段的时间,相加得到最终时间
double time = pow(pp_.max_vel_, 2) / pp_.max_acc_ > dist ? sqrt(dist / pp_.max_acc_) : (dist - pow(pp_.max_vel_, 2) / pp_.max_acc_) / pp_.max_vel_ + 2 * pp_.max_vel_ / pp_.max_acc_;

短距离时间计算

短距离就是临界距离以内,则看上图就可以得到时间公式:

d = 0.5 * (a_max * T) * T

正常求解出来的 T = sqrt(2*d / a_max)

但需要注意的是,ego 代码中是 T = sqrt(d / a_max) ,不知道是不是它做的简化操作。后续的学习中如果发现原因了再更新。

长距离时间计算

长距离计算就分为三个阶段来计算。

匀加速:T1 = v_max / a_max

匀减速:T3 = T1 (因为对称)

匀速:d2 = d - 2 * d1,

d2 = v_max * T2,可得 T2 = d2 / v_max = (d - v_max² / a_max) / v_max。

总距离就 T1 + T2 + T3 即可,对应 ego 的代码。

python 代码验证

这里编写了一个python代码来验证:

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
class PolynomialTraj:
    """实现EGO-Planner中描述的五次多项式轨迹生成"""
    @staticmethod
    def one_segment_traj_gen(start_pt, start_vel, start_acc,
                             end_pt, end_vel, end_acc, total_time):
        """
        生成单段五次多项式轨迹,满足论文中6个运动学约束条件
        参数:
            start_pt: 起点位置 [x, y, z]
            start_vel: 起点速度 [vx, vy, vz]
            start_acc: 起点加速度 [ax, ay, az]
            end_pt: 终点位置 [x, y, z]
            end_vel: 终点速度 [vx, vy, vz]
            end_acc: 终点加速度 [ax, ay, az]
            total_time: 轨迹总时间
        返回:
            多项式系数 (3x6矩阵),每行对应x, y, z方向的系数 [c5, c4, c3, c2, c1, c0]
        """
        # 构造约束矩阵C,如论文中描述的五次多项式约束形式
        t = total_time
        C = np.zeros((6, 6))
        # t=0时刻的约束
        C[0, 5] = 1  # 位置: x(0) = c0
        C[1, 4] = 1  # 速度: v(0) = c1
        C[2, 3] = 2  # 加速度: a(0) = 2*c2
        # t=T时刻的约束
        C[3] = [t ** 5, t ** 4, t ** 3, t ** 2, t, 1]  # 位置
        C[4] = [5 * t ** 4, 4 * t ** 3, 3 * t ** 2, 2 * t, 1, 0]  # 速度
        C[5] = [20 * t ** 3, 12 * t ** 2, 6 * t, 2, 0, 0]  # 加速度
        # 求解三个方向的多项式系数
        coeffs = np.zeros((3, 6))
        for i in range(3):
            # 构造约束向量B,对应论文中的边界条件
            B = np.array([
                start_pt[i],  # 起点位置
                start_vel[i],  # 起点速度
                start_acc[i],  # 起点加速度
                end_pt[i],  # 终点位置
                end_vel[i],  # 终点速度
                end_acc[i]  # 终点加速度
            ])
            # 求解线性方程组 C·coeff = B,得到系数
            coeffs[i] = np.linalg.solve(C, B)
        return coeffs
    @staticmethod
    def evaluate(coeffs, time):
        """计算指定时刻的位置、速度和加速度"""
        pos = np.zeros(3)
        vel = np.zeros(3)
        acc = np.zeros(3)
        t = time
        # 计算位置 (x(t) = c5*t^5 + c4*t^4 + c3*t^3 + c2*t^2 + c1*t + c0)
        pos[0] = np.dot(coeffs[0], [t ** 5, t ** 4, t ** 3, t ** 2, t, 1])
        pos[1] = np.dot(coeffs[1], [t ** 5, t ** 4, t ** 3, t ** 2, t, 1])
        pos[2] = np.dot(coeffs[2], [t ** 5, t ** 4, t ** 3, t ** 2, t, 1])
        # 计算速度 (v(t) = 5c5*t^4 + 4c4*t^3 + 3c3*t^2 + 2c2*t + c1)
        vel[0] = np.dot(coeffs[0], [5 * t ** 4, 4 * t ** 3, 3 * t ** 2, 2 * t, 1, 0])
        vel[1] = np.dot(coeffs[1], [5 * t ** 4, 4 * t ** 3, 3 * t ** 2, 2 * t, 1, 0])
        vel[2] = np.dot(coeffs[2], [5 * t ** 4, 4 * t ** 3, 3 * t ** 2, 2 * t, 1, 0])
        # 计算加速度 (a(t) = 20c5*t^3 + 12c4*t^2 + 6c3*t + 2c2)
        acc[0] = np.dot(coeffs[0], [20 * t ** 3, 12 * t ** 2, 6 * t, 2, 0, 0])
        acc[1] = np.dot(coeffs[1], [20 * t ** 3, 12 * t ** 2, 6 * t, 2, 0, 0])
        acc[2] = np.dot(coeffs[2], [20 * t ** 3, 12 * t ** 2, 6 * t, 2, 0, 0])
        return pos, vel, acc
def calculate_trajectory_time(distance, max_vel, max_acc):
    """
    根据论文中的方法计算轨迹总时间
    短距离: 仅加速阶段
    长距离: 加速→匀速→减速阶段
    """
    # 计算临界距离:达到最大速度所需的距离
    critical_distance = (max_vel ** 2) / max_acc
    if distance < critical_distance:
        # 短距离:仅加速即可到达目标
        return np.sqrt(distance / max_acc)
    else:
        # 长距离:加速→匀速→减速
        time_acc_dec = 2 * max_vel / max_acc  # 加速和减速时间之和
        time_constant = (distance - critical_distance) / max_vel  # 匀速时间
        return time_acc_dec + time_constant
def ego_planner_init_traj_main():
    # 1. 设置轨迹参数
    start_pt = np.array([0.0, 0.0, 0.0])  # 起点位置
    start_vel = np.array([0.5, 0.0, 0.2])  # 起点速度
    start_acc = np.array([0.1, 0.1, 0.0])  # 起点加速度
    end_pt = np.array([8.0, 4.0, 2.0])  # 终点位置
    end_vel = np.array([0.5, 0.3, 0.0])  # 终点速度
    end_acc = np.array([0.0, 0.1, -0.1])  # 终点加速度
    max_vel = 2.0  # 最大速度,符合论文中的动力学约束
    max_acc = 1.0  # 最大加速度,符合论文中的动力学约束
    # 2. 计算距离和轨迹总时间
    distance = np.linalg.norm(end_pt - start_pt)
    total_time = calculate_trajectory_time(distance, max_vel, max_acc)
    print(f"轨迹距离: {distance:.2f}m")
    print(f"计算得到的轨迹总时间: {total_time:.2f}s")
    # 3. 生成五次多项式轨迹
    coeffs = PolynomialTraj.one_segment_traj_gen(
        start_pt, start_vel, start_acc,
        end_pt, end_vel, end_acc,
        total_time
    )
    # 4. 采样轨迹并验证
    time_points = np.linspace(0, total_time, 1000)
    pos_list = []
    vel_list = []
    acc_list = []
    for t in time_points:
        pos, vel, acc = PolynomialTraj.evaluate(coeffs, t)
        pos_list.append(pos)
        vel_list.append(vel)
        acc_list.append(acc)
    pos_array = np.array(pos_list)
    vel_array = np.array(vel_list)
    acc_array = np.array(acc_list)
    # 5. 验证边界条件是否满足(检查误差)
    print("\n边界条件验证:")
    print(f"起点位置误差: {np.linalg.norm(pos_array[0] - start_pt):.6f}m")
    print(f"终点位置误差: {np.linalg.norm(pos_array[-1] - end_pt):.6f}m")
    print(f"起点速度误差: {np.linalg.norm(vel_array[0] - start_vel):.6f}m/s")
    print(f"终点速度误差: {np.linalg.norm(vel_array[-1] - end_vel):.6f}m/s")
    print(f"起点加速度误差: {np.linalg.norm(acc_array[0] - start_acc):.6f}m/s²")
    print(f"终点加速度误差: {np.linalg.norm(acc_array[-1] - end_acc):.6f}m/s²")
    # 6. 可视化轨迹(3D路径和各方向的位置、速度、加速度曲线)
    fig = plt.figure(figsize=(15, 12))
    # 3D轨迹图
    ax1 = fig.add_subplot(221, projection='3d')
    ax1.plot(pos_array[:, 0], pos_array[:, 1], pos_array[:, 2], 'b-')
    ax1.scatter(start_pt[0], start_pt[1], start_pt[2], c='g', s=100, label='start')
    ax1.scatter(end_pt[0], end_pt[1], end_pt[2], c='r', s=100, label='end')
    ax1.set_xlabel('X (m)')
    ax1.set_ylabel('Y (m)')
    ax1.set_zlabel('Z (m)')
    ax1.set_title('3Dtraj')
    ax1.legend()
    # 位置曲线
    ax2 = fig.add_subplot(222)
    ax2.plot(time_points, pos_array[:, 0], 'r-', label='X')
    ax2.plot(time_points, pos_array[:, 1], 'g-', label='Y')
    ax2.plot(time_points, pos_array[:, 2], 'b-', label='Z')
    ax2.set_xlabel('time (s)')
    ax2.set_ylabel('x (m)')
    ax2.set_title('x-time')
    ax2.legend()
    # 速度曲线
    ax3 = fig.add_subplot(223)
    ax3.plot(time_points, vel_array[:, 0], 'r-', label='X')
    ax3.plot(time_points, vel_array[:, 1], 'g-', label='Y')
    ax3.plot(time_points, vel_array[:, 2], 'b-', label='Z')
    ax3.axhline(y=max_vel, color='k', linestyle='--', label='v_max')
    ax3.axhline(y=-max_vel, color='k', linestyle='--')
    ax3.set_xlabel('time (s)')
    ax3.set_ylabel('vel (m/s)')
    ax3.set_title('vel-time')
    ax3.legend()
    # 加速度曲线
    ax4 = fig.add_subplot(224)
    ax4.plot(time_points, acc_array[:, 0], 'r-', label='X')
    ax4.plot(time_points, acc_array[:, 1], 'g-', label='Y')
    ax4.plot(time_points, acc_array[:, 2], 'b-', label='Z')
    ax4.axhline(y=max_acc, color='k', linestyle='--', label='a_max')
    ax4.axhline(y=-max_acc, color='k', linestyle='--')
    ax4.set_xlabel('time (s)')
    ax4.set_ylabel('acc (m/s²)')
    ax4.set_title('acc-time')
    ax4.legend()
    plt.tight_layout()
    plt.show()