最优化理论与自动驾驶(十一):基于iLQR的自动驾驶轨迹跟踪算法(c++和python版本)

news2024/11/15 16:54:27

最优化理论与自动驾驶(四):iLQR原理、公式及代码演示

之前的章节我们介绍过,iLQR(迭代线性二次调节器)是一种用于求解非线性系统最优控制最优控制最优控制和规划问题的算法。本章节介绍采用iLQR算法对设定的自动驾驶轨迹进行跟踪,与第十章节纯跟踪算法采用同样跟踪轨迹,同时,我们仅对控制量的上下边界进行约束,使用简单的投影法进行约束(更详细的约束参考第九章 CILQR约束条件下的ILQR求解)。其实,iLQR可以直接进行轨迹规划,主要做法是将障碍物或者边界约束通过增广拉格朗日法将原始问题的约束条件通过拉格朗日乘子和惩罚项结合到代价函数中,逐步逼近最优解,这个将在后续章节进行讨论。

1. 问题定义

假设给定一个目标轨迹(x_{ref}, u_{ref}),其中x_{ref}是状态轨迹,u_{ref}是控制输入,任务是找到一组控制输入u_k使得车辆从初始状态出发,最小化实际轨迹与目标轨迹之间的偏差。

(1) 状态方程

车辆的运动可以通过车辆动力学模型表示,通常有以下几种模型:

  • 简化运动学模型

    x_{k+1} = f(x_k, u_k)

    其中x_k是车辆的状态,u_k是控制输入。

根据参考轨迹,将非线性系统模型在当前状态和控制输入附近进行线性化:

f(x_k, u_k) \approx A_k \Delta x_k + B_k \Delta u_k + c_k

其中A_k = \frac{\partial f}{\partial x}B_k = \frac{\partial f}{\partial u}是对状态和控制的线性化,\Delta x_k = x_k - x_{ref}\Delta u_k = u_k - u_{ref}​。

(2) 代价函数

定义一个二次型代价函数,用于衡量实际轨迹与目标轨迹的偏差:

J = \sum_{k=0}^{N-1} \left( (x_k - x_{ref})^T Q (x_k - x_{ref}) + (u_k - u_{ref})^T R (u_k - u_{ref}) \right) + (x_N - x_{ref})^T Q_f (x_N - x_{ref})

其中QRQ_f分别是状态、控制输入和最终状态的权重矩阵。

(3) 反向递推

利用动态规划方法,从最终时刻 N开始,向前递推计算值函数的梯度和Hessian矩阵:

V_x = \frac{\partial J}{\partial x}, \quad V_{xx} = \frac{\partial^2 J}{\partial x^2}

同时计算控制增量的最优更新策略:

\Delta u_k = K_k \Delta x_k + d_k

其中K_k是控制增量的反馈增益,d_k是前馈控制增量。

(4) 前向传播

基于反向递推得到的最优控制策略,在给定初始状态下,通过前向传播计算新的状态和控制轨迹,更新参考轨迹:

x_{k+1} = f(x_k, u_k)

重复步骤(1)至(4)直到收敛,得到最优控制序列。

2. 示例代码

import numpy as np
import math
import matplotlib.pyplot as plt
import imageio

# 车辆模型
class Vehicle:
    def __init__(self, x=0.0, y=0.0, theta=0.0, v=0.0):
        self.x = x
        self.y = y
        self.theta = theta  # 航向角
        self.v = v          # 速度

    def update(self, a, omega, dt):
        """
        更新车辆状态
        a: 加速度
        omega: 角速度
        dt: 时间步长
        """
        self.x += self.v * np.cos(self.theta) * dt
        self.y += self.v * np.sin(self.theta) * dt
        self.theta += omega * dt
        self.v += a * dt

# 轨迹
class Trajectory:
    def __init__(self):
        self.cx = np.linspace(0, 50, 500)
        self.cy = [np.sin(ix / 5.0) * ix / 2.0 for ix in self.cx]
        self.theta = [np.arctan2(self.cy[i+1] - self.cy[i], self.cx[i+1] - self.cx[i]) if i < len(self.cx)-1 else 0.0 for i in range(len(self.cx))]
        self.v = np.full_like(self.cx, 3.0)  # 目标速度为3 m/s

    def get_reference(self, index):
        """
        获取参考轨迹点
        """
        return np.array([self.cx[index], self.cy[index], self.theta[index], self.v[index]])

# iLQR控制器
class iLQRController:
    def __init__(self, N=50, max_iter=10, dt=0.1):
        self.N = N  # 控制时域长度
        self.max_iter = max_iter  # 最大迭代次数
        self.dt = dt  # 时间步长
        self.Q = np.diag([1.0, 1.0, 0.5, 0.1])  # 状态权重矩阵
        self.R = np.diag([0.1, 0.1])  # 控制权重矩阵
        self.Qf = self.Q * 10  # 终端状态权重矩阵

    def ilqr(self, vehicle, trajectory, index):
        """
        使用iLQR计算最优控制序列
        """
        # 初始化状态和控制序列
        x_dim = 4  # 状态维度 [x, y, theta, v]
        u_dim = 2  # 控制维度 [a, omega]
        xs = np.zeros((self.N + 1, x_dim))
        us = np.zeros((self.N, u_dim))
        
        # 初始状态
        xs[0] = np.array([vehicle.x, vehicle.y, vehicle.theta, vehicle.v])
        
        # 初始猜测控制序列(全零)
        us = np.zeros((self.N, u_dim))
        
        for iteration in range(self.max_iter):
            # 前向传播,计算状态轨迹
            for k in range(self.N):
                xs[k+1] = self.dynamics(xs[k], us[k], self.dt)
            
            # 计算代价函数梯度和Hessian矩阵
            Vx = self.Qf @ (xs[-1] - trajectory.get_reference(index + self.N))
            Vxx = self.Qf
            k_list = []
            K_list = []
            for k in reversed(range(self.N)):
                xk = xs[k]
                uk = us[k]
                x_ref = trajectory.get_reference(index + k)
                # 计算状态和控制的偏导数
                fx, fu = self.linearize_dynamics(xk, uk, self.dt)
                lx = self.Q @ (xk - x_ref)
                lu = self.R @ uk
                lxx = self.Q
                luu = self.R
                lux = np.zeros((u_dim, x_dim))
                # 计算Q函数的二次近似
                Qx = lx + fx.T @ Vx
                Qu = lu + fu.T @ Vx
                Qxx = lxx + fx.T @ Vxx @ fx
                Quu = luu + fu.T @ Vxx @ fu
                Qux = lux + fu.T @ Vxx @ fx
                # 计算控制增量
                Quu_inv = np.linalg.inv(Quu + np.eye(u_dim) * 1e-6)  # 加小值防止矩阵奇异
                k = -Quu_inv @ Qu
                K = -Quu_inv @ Qux
                # 更新V函数
                Vx = Qx + K.T @ Quu @ k + K.T @ Qu + Qux.T @ k
                Vxx = Qxx + K.T @ Quu @ K + K.T @ Qux + Qux.T @ K
                # 存储k和K
                k_list.insert(0, k)
                K_list.insert(0, K)
            # 更新控制序列并前向模拟
            x_new = np.copy(xs[0])
            xs_new = [x_new]
            us_new = []
            for k in range(self.N):
                du = k_list[k] + K_list[k] @ (x_new - xs[k])
                us_new.append(us[k] + du)
                x_new = self.dynamics(x_new, us_new[-1], self.dt)
                xs_new.append(x_new)
            xs = np.array(xs_new)
            us = np.array(us_new)
            # 判断收敛性
            cost = self.compute_total_cost(xs, us, trajectory, index)
            print(f"Iteration {iteration}, Cost: {cost}")
            if cost < 1e-6:
                break
        return us[0]  # 返回当前时刻的最优控制

    def dynamics(self, x, u, dt):
        """
        动力学模型
        """
        x_next = np.zeros_like(x)
        x_next[0] = x[0] + x[3] * np.cos(x[2]) * dt  # x
        x_next[1] = x[1] + x[3] * np.sin(x[2]) * dt  # y
        x_next[2] = x[2] + u[1] * dt                 # theta
        x_next[3] = x[3] + u[0] * dt                 # v
        return x_next

    def linearize_dynamics(self, x, u, dt):
        """
        线性化动力学模型,返回状态和控制的雅可比矩阵
        """
        fx = np.eye(4)
        fx[0, 2] = -x[3] * np.sin(x[2]) * dt
        fx[0, 3] = np.cos(x[2]) * dt
        fx[1, 2] = x[3] * np.cos(x[2]) * dt
        fx[1, 3] = np.sin(x[2]) * dt
        fx[2, 2] = 1.0
        fx[3, 3] = 1.0

        fu = np.zeros((4, 2))
        fu[2, 1] = dt  # theta 对 omega 的偏导
        fu[3, 0] = dt  # v 对 a 的偏导

        return fx, fu

    def compute_total_cost(self, xs, us, trajectory, index):
        """
        计算总的代价函数
        """
        cost = 0.0
        for k in range(self.N):
            xk = xs[k]
            uk = us[k]
            x_ref = trajectory.get_reference(index + k)
            dx = xk - x_ref
            cost += dx.T @ self.Q @ dx + uk.T @ self.R @ uk
        # 终端代价
        dx = xs[-1] - trajectory.get_reference(index + self.N)
        cost += dx.T @ self.Qf @ dx
        return cost

# 主函数
def main():
    vehicle = Vehicle()
    trajectory = Trajectory()
    controller = iLQRController(N=50, max_iter=10, dt=0.1)
    dt = 0.1
    x_history = []
    y_history = []
    total_time = len(trajectory.cx) - controller.N - 1

    # 创建图形
    fig, ax = plt.subplots()
    frames = []

    for t in range(total_time):
        # 获取当前最优控制
        u_opt = controller.ilqr(vehicle, trajectory, t)
        # 更新车辆状态
        vehicle.update(u_opt[0], u_opt[1], dt)
        # 记录轨迹
        x_history.append(vehicle.x)
        y_history.append(vehicle.y)

        # 绘制
        ax.cla()
        ax.plot(trajectory.cx, trajectory.cy, "-r", label="Reference Trajectory")
        ax.plot(x_history, y_history, "-b", label="Vehicle Trajectory")
        ax.set_xlim(0, 50)
        ax.set_ylim(-20, 25)
        ax.set_title(f"iLQR Trajectory Tracking - Step {t}")
        ax.set_xlabel("x [m]")
        ax.set_ylabel("y [m]")
        ax.grid(True)

        # 渲染当前帧
        fig.canvas.draw()
        image = np.frombuffer(fig.canvas.tostring_rgb(), dtype='uint8').reshape(fig.canvas.get_width_height()[::-1] + (3,))
        frames.append(image)

        # 实时显示
        plt.pause(0.001)

    # 保存为GIF
    imageio.mimsave('ilqr_trajectory_tracking.gif', frames, fps=10)
    plt.show()

if __name__ == '__main__':
    main()

2.1. 车辆模型 (Vehicle 类)

车辆模型采用一个简单的运动学模型,其中更新车辆的状态包括:

位置 (x, y)
航向角 (theta)
速度 (v)
通过给定加速度 (a) 和角速度 (omega),每个时间步长内,车辆的状态都会更新。

class Vehicle:
    def __init__(self, x=0.0, y=0.0, theta=0.0, v=0.0):
        self.x = x
        self.y = y
        self.theta = theta  # 航向角
        self.v = v          # 速度

    def update(self, a, omega, dt):
        """
        更新车辆状态
        a: 加速度
        omega: 角速度
        dt: 时间步长
        """
        self.x += self.v * np.cos(self.theta) * dt
        self.y += self.v * np.sin(self.theta) * dt
        self.theta += omega * dt
        self.v += a * dt

2. 轨迹 (Trajectory 类) 

轨迹类生成了一条目标轨迹,cx 和 cy 分别是参考轨迹的 x 和 y 轴坐标。车辆的目标是跟随这条正弦曲线,并在参考轨迹的每个点上都有相应的目标航向角 (theta) 和速度 (v)。

# 轨迹
class Trajectory:
    def __init__(self):
        self.cx = np.linspace(0, 50, 500)
        self.cy = [np.sin(ix / 5.0) * ix / 2.0 for ix in self.cx]
        self.theta = [np.arctan2(self.cy[i+1] - self.cy[i], self.cx[i+1] - self.cx[i]) if i < len(self.cx)-1 else 0.0 for i in range(len(self.cx))]
        self.v = np.full_like(self.cx, 3.0)  # 目标速度为3 m/s

    def get_reference(self, index):
        """
        获取参考轨迹点
        """
        return np.array([self.cx[index], self.cy[index], self.theta[index], self.v[index]])

3. iLQR 控制器 (iLQRController 类) 

iLQR 控制器的主要职责是计算从当前时刻开始的最优控制序列,使得车辆的状态逼近目标轨迹。以下是关键步骤:

初始化:定义状态和控制权重矩阵 (Q, R, Qf) 以惩罚偏离目标状态和过大的控制输入。
前向传播:给定初始控制输入,模拟车辆状态随时间的演化。
反向递推:通过动态规划的方法,逐步计算代价函数的梯度和 Hessian,并优化控制策略。
线性化动力学:在当前状态下线性化系统的动力学方程,计算状态和控制的雅可比矩阵。
更新控制序列:通过更新控制增量,生成新的控制序列并前向传播,直到算法收敛。

# iLQR控制器
class iLQRController:
    def __init__(self, N=50, max_iter=10, dt=0.1):
        self.N = N  # 控制时域长度
        self.max_iter = max_iter  # 最大迭代次数
        self.dt = dt  # 时间步长
        self.Q = np.diag([1.0, 1.0, 0.5, 0.1])  # 状态权重矩阵
        self.R = np.diag([0.1, 0.1])  # 控制权重矩阵
        self.Qf = self.Q * 10  # 终端状态权重矩阵

    def ilqr(self, vehicle, trajectory, index):
        """
        使用iLQR计算最优控制序列
        """
        # 初始化状态和控制序列
        x_dim = 4  # 状态维度 [x, y, theta, v]
        u_dim = 2  # 控制维度 [a, omega]
        xs = np.zeros((self.N + 1, x_dim))
        us = np.zeros((self.N, u_dim))
        
        # 初始状态
        xs[0] = np.array([vehicle.x, vehicle.y, vehicle.theta, vehicle.v])
        
        # 初始猜测控制序列(全零)
        us = np.zeros((self.N, u_dim))
        
        for iteration in range(self.max_iter):
            # 前向传播,计算状态轨迹
            for k in range(self.N):
                xs[k+1] = self.dynamics(xs[k], us[k], self.dt)
            
            # 计算代价函数梯度和Hessian矩阵
            Vx = self.Qf @ (xs[-1] - trajectory.get_reference(index + self.N))
            Vxx = self.Qf
            k_list = []
            K_list = []
            for k in reversed(range(self.N)):
                xk = xs[k]
                uk = us[k]
                x_ref = trajectory.get_reference(index + k)
                # 计算状态和控制的偏导数
                fx, fu = self.linearize_dynamics(xk, uk, self.dt)
                lx = self.Q @ (xk - x_ref)
                lu = self.R @ uk
                lxx = self.Q
                luu = self.R
                lux = np.zeros((u_dim, x_dim))
                # 计算Q函数的二次近似
                Qx = lx + fx.T @ Vx
                Qu = lu + fu.T @ Vx
                Qxx = lxx + fx.T @ Vxx @ fx
                Quu = luu + fu.T @ Vxx @ fu
                Qux = lux + fu.T @ Vxx @ fx
                # 计算控制增量
                Quu_inv = np.linalg.inv(Quu + np.eye(u_dim) * 1e-6)  # 加小值防止矩阵奇异
                k = -Quu_inv @ Qu
                K = -Quu_inv @ Qux
                # 更新V函数
                Vx = Qx + K.T @ Quu @ k + K.T @ Qu + Qux.T @ k
                Vxx = Qxx + K.T @ Quu @ K + K.T @ Qux + Qux.T @ K
                # 存储k和K
                k_list.insert(0, k)
                K_list.insert(0, K)
            # 更新控制序列并前向模拟
            x_new = np.copy(xs[0])
            xs_new = [x_new]
            us_new = []
            for k in range(self.N):
                du = k_list[k] + K_list[k] @ (x_new - xs[k])
                us_new.append(us[k] + du)
                x_new = self.dynamics(x_new, us_new[-1], self.dt)
                xs_new.append(x_new)
            xs = np.array(xs_new)
            us = np.array(us_new)
            # 判断收敛性
            cost = self.compute_total_cost(xs, us, trajectory, index)
            print(f"Iteration {iteration}, Cost: {cost}")
            if cost < 1e-6:
                break
        return us[0]  # 返回当前时刻的最优控制

    def dynamics(self, x, u, dt):
        """
        动力学模型
        """
        x_next = np.zeros_like(x)
        x_next[0] = x[0] + x[3] * np.cos(x[2]) * dt  # x
        x_next[1] = x[1] + x[3] * np.sin(x[2]) * dt  # y
        x_next[2] = x[2] + u[1] * dt                 # theta
        x_next[3] = x[3] + u[0] * dt                 # v
        return x_next

    def linearize_dynamics(self, x, u, dt):
        """
        线性化动力学模型,返回状态和控制的雅可比矩阵
        """
        fx = np.eye(4)
        fx[0, 2] = -x[3] * np.sin(x[2]) * dt
        fx[0, 3] = np.cos(x[2]) * dt
        fx[1, 2] = x[3] * np.cos(x[2]) * dt
        fx[1, 3] = np.sin(x[2]) * dt
        fx[2, 2] = 1.0
        fx[3, 3] = 1.0

        fu = np.zeros((4, 2))
        fu[2, 1] = dt  # theta 对 omega 的偏导
        fu[3, 0] = dt  # v 对 a 的偏导

        return fx, fu

    def compute_total_cost(self, xs, us, trajectory, index):
        """
        计算总的代价函数
        """
        cost = 0.0
        for k in range(self.N):
            xk = xs[k]
            uk = us[k]
            x_ref = trajectory.get_reference(index + k)
            dx = xk - x_ref
            cost += dx.T @ self.Q @ dx + uk.T @ self.R @ uk
        # 终端代价
        dx = xs[-1] - trajectory.get_reference(index + self.N)
        cost += dx.T @ self.Qf @ dx
        return cost

4. 主循环 (main 函数)

主函数执行轨迹跟踪的模拟过程。每一步:

调用 iLQR 控制器生成最优控制输入。
根据控制输入更新车辆状态。
绘制当前的车辆轨迹与参考轨迹,并生成一帧图像。
最终,所有帧被保存为 GIF 文件,展示整个轨迹跟踪过程。

5. 执行结果

6. c++版本

在实际的工程开发中,一般采用c++代码进行开发。为了方便应用,将上述代码转为C++,涉及的矩阵运算主要采用eigen库(参考 C++教程(一):超详细的C++矩阵操作和运算(附实例代码,与python对比))。同时为了方便展示动态运行过程,采用了python的matplotlib的c++库matplotlibcpp,也非常容易使用,直接include头文件即可。C++代码如下:

#include <iostream>
#include <vector>
#include <cmath>
#include <Eigen/Dense>
#include "matplotlibcpp.h"

namespace plt = matplotlibcpp;
using Eigen::MatrixXd;
using Eigen::VectorXd;

// 车辆模型类
class Vehicle {
public:
    double x, y, theta, v;

    // 车辆构造函数,初始化车辆的状态
    Vehicle(double x_init = 0.0, double y_init = 0.0, double theta_init = 0.0, double v_init = 0.0)
        : x(x_init), y(y_init), theta(theta_init), v(v_init) {}

    // 更新车辆状态,a为加速度,omega为角速度,dt为时间步长
    void update(double a, double omega, double dt) {
        x += v * cos(theta) * dt;    // 更新x坐标
        y += v * sin(theta) * dt;    // 更新y坐标
        theta += omega * dt;         // 更新航向角theta
        v += a * dt;                 // 更新速度v
    }
};

// 轨迹类,用于存储参考轨迹信息
class Trajectory {
public:
    std::vector<double> cx, cy, theta, v;

    // 轨迹构造函数,初始化参考轨迹
    Trajectory() {
        // 生成参考轨迹的x和y坐标
        for (double i = 0; i < 50; i += 0.1) {
            cx.push_back(i);
            cy.push_back(sin(i / 5.0) * i / 2.0);
        }
        // 计算每个轨迹点的航向角theta
        for (size_t i = 0; i < cx.size() - 1; ++i) {
            theta.push_back(atan2(cy[i+1] - cy[i], cx[i+1] - cx[i]));
        }
        theta.push_back(0.0);  // 最后一个点的theta设置为0
        v.assign(cx.size(), 3.0);  // 设置所有点的目标速度为3 m/s
    }

    // 获取指定索引处的参考状态向量[x, y, theta, v]
    VectorXd get_reference(size_t index) const {
        VectorXd ref(4);
        ref << cx[index], cy[index], theta[index], v[index];
        return ref;
    }
};

// iLQR(迭代线性二次调节器)控制器类
class iLQRController {
public:
    int N;             // 控制步数
    int max_iter;      // 最大迭代次数
    double dt;         // 时间步长
    MatrixXd Q, R, Qf; // 代价函数的权重矩阵

    // 控制器构造函数
    iLQRController(int N_input = 50, int max_iter_input = 10, double dt_input = 0.1)
        : N(N_input), max_iter(max_iter_input), dt(dt_input) {
        // 初始化状态代价矩阵Q,控制代价矩阵R,终端状态代价矩阵Qf
        Q = MatrixXd::Zero(4, 4);
        Q(0, 0) = 1.0; Q(1, 1) = 1.0; Q(2, 2) = 0.5; Q(3, 3) = 0.1;
        R = MatrixXd::Identity(2, 2) * 0.1;
        Qf = Q * 10.0;
    }

    // iLQR算法的实现,返回当前时刻的最优控制输入
    VectorXd ilqr(Vehicle &vehicle, const Trajectory &trajectory, size_t index) {
        int x_dim = 4;  // 状态维度 [x, y, theta, v]
        int u_dim = 2;  // 控制维度 [a, omega]
        std::vector<VectorXd> xs(N + 1, VectorXd::Zero(x_dim));  // 状态序列
        std::vector<VectorXd> us(N, VectorXd::Zero(u_dim));      // 控制序列

        // 初始化状态
        xs[0] << vehicle.x, vehicle.y, vehicle.theta, vehicle.v;

        // 迭代更新控制输入和状态
        for (int iter = 0; iter < max_iter; ++iter) {
            // 前向传播,基于当前控制序列预测状态序列
            for (int i = 0; i < N; ++i) {
                xs[i + 1] = dynamics(xs[i], us[i], dt);
            }

            // 反向传播,更新控制增量和反馈矩阵
            VectorXd Vx = Qf * (xs[N] - trajectory.get_reference(index + N));
            MatrixXd Vxx = Qf;

            std::vector<VectorXd> k_control_list(N, VectorXd::Zero(u_dim));  // 控制增量
            std::vector<MatrixXd> K_feedback_list(N, MatrixXd::Zero(u_dim, x_dim));  // 反馈矩阵

            for (int i = N - 1; i >= 0; --i) {
                VectorXd x_ref = trajectory.get_reference(index + i);
                std::pair<MatrixXd, MatrixXd> linearized = linearize_dynamics(xs[i], us[i], dt);
                MatrixXd fx = linearized.first;
                MatrixXd fu = linearized.second;

                // 计算代价梯度和Hessian矩阵
                VectorXd lx = Q * (xs[i] - x_ref);
                VectorXd lu = R * us[i];
                MatrixXd lxx = Q;
                MatrixXd luu = R;
                MatrixXd lux = MatrixXd::Zero(u_dim, x_dim);

                // 计算Q函数的二次近似
                VectorXd Qx = lx + fx.transpose() * Vx;
                VectorXd Qu = lu + fu.transpose() * Vx;
                MatrixXd Qxx = lxx + fx.transpose() * Vxx * fx;
                MatrixXd Quu = luu + fu.transpose() * Vxx * fu;
                MatrixXd Qux = lux + fu.transpose() * Vxx * fx;

                // 计算控制增量和反馈矩阵
                MatrixXd Quu_inv = Quu.inverse();
                VectorXd k_control = -Quu_inv * Qu;
                MatrixXd K_feedback = -Quu_inv * Qux;

                // 更新价值函数
                Vx = Qx + K_feedback.transpose() * Quu * k_control + K_feedback.transpose() * Qu + Qux.transpose() * k_control;
                Vxx = Qxx + K_feedback.transpose() * Quu * K_feedback + K_feedback.transpose() * Qux + Qux.transpose() * K_feedback;

                k_control_list[i] = k_control;
                K_feedback_list[i] = K_feedback;
            }

            // 更新控制序列并进行前向模拟
            std::vector<VectorXd> xs_new(N + 1, xs[0]);
            std::vector<VectorXd> us_new;

            for (int i = 0; i < N; ++i) {
                VectorXd du = k_control_list[i] + K_feedback_list[i] * (xs_new[i] - xs[i]);
                us_new.push_back(us[i] + du);
                xs_new[i + 1] = dynamics(xs_new[i], us_new.back(), dt);
            }
            xs = xs_new;
            us = us_new;

            // 判断是否收敛
            double cost = compute_total_cost(xs, us, trajectory, index);
            std::cout << "Iteration " << iter << ", Cost: " << cost << std::endl;
            if (cost < 1e-6) {
                break;
            }
        }
        return us[0];  // 返回当前时刻的最优控制输入
    }

    // 车辆动力学模型
    VectorXd dynamics(const VectorXd &x, const VectorXd &u, double dt) {
        VectorXd x_next = x;
        x_next[0] += x[3] * cos(x[2]) * dt;  // 更新x坐标
        x_next[1] += x[3] * sin(x[2]) * dt;  // 更新y坐标
        x_next[2] += u[1] * dt;              // 更新theta
        x_next[3] += u[0] * dt;              // 更新速度v
        return x_next;
    }

    // 线性化车辆动力学模型
    std::pair<MatrixXd, MatrixXd> linearize_dynamics(const VectorXd &x, const VectorXd &u, double dt) {
        MatrixXd fx = MatrixXd::Identity(4, 4);
        fx(0, 2) = -x[3] * sin(x[2]) * dt;
        fx(0, 3) = cos(x[2]) * dt;
        fx(1, 2) = x[3] * cos(x[2]) * dt;
        fx(1, 3) = sin(x[2]) * dt;

        MatrixXd fu = MatrixXd::Zero(4, 2);
        fu(2, 1) = dt;
        fu(3, 0) = dt;

        return {fx, fu};
    }

    // 计算总成本
    double compute_total_cost(const std::vector<VectorXd> &xs, const std::vector<VectorXd> &us, const Trajectory &trajectory, size_t index) {
        double cost = 0.0;
        for (int i = 0; i < N; ++i) {
            VectorXd dx = xs[i] - trajectory.get_reference(index + i);
            cost += (dx.transpose() * Q * dx)(0, 0) + (us[i].transpose() * R * us[i])(0, 0);
        }
        VectorXd dx_terminal = xs[N] - trajectory.get_reference(index + N);
        cost += (dx_terminal.transpose() * Qf * dx_terminal)(0, 0);
        return cost;
    }
};

// 主函数
int main() {
    Vehicle vehicle;  // 初始化车辆
    Trajectory trajectory;  // 初始化轨迹
    iLQRController controller(10, 10, 0.1);  // 初始化iLQR控制器
    double dt = 0.1;
    std::vector<double> x_history, y_history;  // 记录车辆轨迹

    for (size_t t = 0; t < trajectory.cx.size() - controller.N - 1; ++t) {
        VectorXd u_opt = controller.ilqr(vehicle, trajectory, t);  // 获取最优控制输入
        vehicle.update(u_opt[0], u_opt[1], dt);  // 更新车辆状态
        x_history.push_back(vehicle.x);  // 记录x坐标
        y_history.push_back(vehicle.y);  // 记录y坐标

        // 绘制车辆轨迹和参考轨迹
        plt::clf();
        plt::named_plot("Reference Trajectory", trajectory.cx, trajectory.cy, "-r");
        plt::named_plot("Vehicle Trajectory", x_history, y_history, "-b");
        plt::legend();
        plt::xlim(0, 50);
        plt::ylim(-20, 25);
        plt::title("iLQR Trajectory Tracking");
        plt::xlabel("x [m]");
        plt::ylabel("y [m]");
        plt::grid(true);
        plt::pause(0.001);  // 短暂暂停以动态展示
    }

    plt::show();  // 展示最终图形
    return 0;
}

 6. 后续优化

加入障碍物回避:在代价函数中加入障碍物相关的约束项,直接进行规控一体优化;

采用全DDP算法:考虑系统动态中的二阶项,通过全DDP算法提升优化精度;

噪声处理:考虑车辆运动中的过程噪声和观测噪声,用iLQG使得算法更加鲁棒。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2155498.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Cpp类和对象(中)(4)

文章目录 前言一、类的六个默认成员函数二、构造函数构造函数的概念构造函数的特性构造函数的两种分类编译器默认生成构造函数意义及相关问题C11打的补丁 三、析构函数析构函数的概念析构函数的特性验证是否会自动调用析构函数验证析构函数对于内置与自定义类型处理验证先定义后…

【学习笔记】数据结构(六 ②)

树和二叉树&#xff08;二&#xff09; 文章目录 树和二叉树&#xff08;二&#xff09;6.3.2 线索二叉树 6.4 树和森林6.4.1 树的存储结构6.4.2 森林与二叉树的转换6.4.3 树和森林的遍历 6.5 树与等价问题6.5.1 等价定义6.5.2 划分等价类的方法6.5.3 划分等价类的具体操作 - 并…

【LeetCode热题100】位运算

这篇博客先介绍了常见位运算操作&#xff0c;然后记录了关于位运算的几道题&#xff0c;包括判定字符是否唯一、丢失的数字、两整数之和、只出现一次的数字2、消失的两个数字。 在这一部分&#xff0c;我们不妨先来总结一下常见位运算操作&#xff1a; 1.基础位运算 >>…

vite 使用飞行器仪表示例

这里写自定义目录标题 环境vue代码效果图 环境 jquery npm install -S jqueryjQuery-Flight-Indicators 将img、css、js拷贝到vite工程目录中 打开 jquery.flightindicators.js&#xff0c;在文件开头加上import jQuery from "jquery"; vue代码 <template>&…

C#(.NET FrameWork库)逆向基础流程(纯小白教程)

一&#xff0c;例题链接 限时题目&#xff0c;只能用网盘来分享了&#xff0c;侵权联系删->百度网盘 请输入提取码 二&#xff0c;文件特征 使用工具查看文件信息&#xff0c; 能看到分析出文件编写语言为C#&#xff0c;使用了.NET库 三&#xff0c;做题流程 &#xff08…

浙版传媒思迈特软件大数据分析管理平台建设项目正式启动

近日&#xff0c;思迈特软件与出版发行及电商书城领域的领军企业——浙江出版传媒股份有限公司&#xff0c;正式启动大近日&#xff0c;思迈特软件与出版发行及电商书城领域的领军企业——浙江出版传媒股份有限公司&#xff0c;正式启动大数据分析管理平台建设项目。浙版传媒相…

Java之继承1

1. 继承 1.1 为什么要继承 在Java中我们定义猫类和狗类&#xff0c;如下 public class Cat {public String name;public int age;public String color;public void eat(){System.out.println(name "正在吃饭");}public void sleep(){System.out.println(name &qu…

基于pytorch本地部署微调bert模型(yelp文本分类数据集)

项目介绍 本项目使用hugging face上提供的Bert模型API&#xff0c;基于yelp数据集&#xff0c;在本地部署微调Bert模型&#xff0c;官方的文档链接为https://huggingface.co/docs/transformers/quicktour&#xff0c;但是在官方介绍中出现了太多的API调用接口&#xff0c;无法…

React 中的延迟加载

延迟加载是 Web 开发中的一种有效的性能优化技术&#xff0c;尤其是对于 React 等库和框架。它涉及仅在需要时加载组件或资源&#xff0c;无论是响应用户操作还是当元素即将在屏幕上显示时。这可以减少应用程序的初始加载时间&#xff0c;减少资源消耗&#xff0c;并改善用户体…

ETLCloud:新一代ETL数据抽取工具的定义与革新

数据集成、数据治理已经成为推动企业数字化转型的核心动力&#xff0c;现在的企业比任何时候都需要一个更为强大的新一代数据集成工具来处理、整合并转化多种数据源。 而ETL&#xff08;数据提取、转换、加载&#xff09;作为数据管理的关键步骤&#xff0c;已在企业数据架构中…

串口助手的qt实现思路

要求实现如下功能&#xff1a; 获取串口号&#xff1a; foreach (const QSerialPortInfo &serialPortInfo, QSerialPortInfo::availablePorts()) {qDebug() << "Port: " << serialPortInfo.portName(); // e.g. "COM1"qDebug() <<…

【JavaEE】——线程的安全问题和解决方式

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯&#xff0c;你们的点赞收藏是我前进最大的动力&#xff01;&#xff01;希望本文内容能够帮助到你&#xff01; 目录 一&#xff1a;问题引入 二&#xff1a;问题深入 1&#xff1a;举例说明 2&#xff1a;图解双线程计算…

SwiftUI 实现关键帧动画

实现一个扫描二维码的动画效果&#xff0c;然而SwiftUI中没有提供CABasicAnimation 动画方法&#xff0c;该如何实现这种效果&#xff1f;先弄清楚什么关键帧动画&#xff0c;简单的说就是指视图从起点至终点的状态变化&#xff0c;可以是形状、位置、透明度等等 本文提供了一…

(done) 声音信号处理基础知识(3) (一个TODO: modulation 和 timbre 的关联)(强度、响度、音色)

来源&#xff1a;https://www.youtube.com/watch?vJkoysm1fHUw sound power 通常可以被认为是能量传输的速率 声源往所有方向传输的每时间单位能量 用 瓦特(W) 作为单位测量 Sound intensity 声音强度&#xff0c;每单位面积的 sound power W/m^2 人类实际上能听到非常小强…

八. 实战:CUDA-BEVFusion部署分析-coordTrans Precomputation

目录 前言0. 简述1. 案例运行2. coordTrans3. Precomputation总结下载链接参考 前言 自动驾驶之心推出的 《CUDA与TensorRT部署实战课程》&#xff0c;链接。记录下个人学习笔记&#xff0c;仅供自己参考 本次课程我们来学习下课程第八章—实战&#xff1a;CUDA-BEVFusion部署分…

Python Selenium 自动化爬虫 + Charles Proxy 抓包

一、场景介绍 我们平常会遇到一些需要根据省、市、区查询信息的网站。 1、省市查询 比如这种&#xff0c;因为全国的省市比较多&#xff0c;手动查询工作量还是不小。 2、接口签名 有时候我们用python直接查询后台接口的话&#xff0c;会发现接口是加签名的。 而签名算法我…

keil5 MDK 最新版本官网下载(v5.40为例) ARM单片机环境搭建安装教程(STM32系列为例)

正所谓授之以鱼不如授之以渔。本文将细讲从官网下载keil5MDK来保证keil5为最新版本的实时性 &#xff08;注意新老版本可能出现版本兼容问题&#xff0c;若不放心&#xff0c;跟着老弟我一起下载5.40版本即可&#xff09; 目录 一、下载keil5 MDK 方法①:CSDN下载&#xff0…

计算机毕业设计 基于 Hadoop平台的岗位推荐系统 SpringBoot+Vue 前后端分离 附源码 讲解 文档

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

【深入学习Redis丨第六篇】Redis哨兵模式与操作详解

〇、前言 哨兵是一个分布式系统&#xff0c;你可以在一个架构中运行多个哨兵进程&#xff0c;这些进程使用流言协议来接收关于Master主服务器是否下线的信息&#xff0c;并使用投票协议来决定是否执行自动故障迁移&#xff0c;以及选择哪个Slave作为新的Master。 文章目录 〇、…

Django 5 学习笔记 2024版

1. 官方中文文档 Django 文档 | Django 文档 | Django (djangoproject.com) 2. 第一个应用 博客 总目录 <1>依赖安装: pip install django <2> 创建 工程 myapp django-admin startproject myapp cd myapp <3>创建 应用 app > python manage.py s…