(14-3)自动驾驶车辆的高速超车系统:生成最优控制轨迹

6.5  生成最优控制轨迹

文件cftoc_trajectory_generator.py用于生成约束有限时间最优控制(CFTOC)轨迹,通过有限时间优化生成一个光滑且可行的“超车”轨迹,以绕过障碍(自我)车辆。后续的 mpc_vehicle_controller.ipynb 脚本将使用模型预测控制(MPC)来沿着这个参考轨迹控制车辆。

(1)下面代码的功能是设置一个环境,以便在 Google Colab 中安装所需的库和工具,并导入必要的模块,用于生成直道路段的超车轨迹。它通过安装 Pyomo、GLPK 和 IPOPT 等工具,准备后续的轨迹优化和可视化。

import sys
IN_COLAB = 'google.colab' in sys.modules
if IN_COLAB:
  # 安装所需的库和工具
  !pip install -q pyomo
  !apt-get install -y -qq glpk-utils
  !apt-get install -y -qq coinor-cbc
  !wget -N -q "https://ampl.com/dl/open/ipopt/ipopt-linux64.zip"
  !unzip -o -q ipopt-linux64
  !pip install ttictoc

# 超车轨迹生成器,用于直道路段

import matplotlib.pyplot as plt
import numpy as np 
import pyomo.environ as pyo
from mpl_toolkits import mplot3d

(2)下面代码的功能是定义超车轨迹生成模拟的参数和初始状态,包括时间步长、仿真步数、障碍物状态和控制输入。它初始化了障碍物的状态,并通过循环计算每个时间步的障碍物位置,存储在数组中。

# 模拟参数:
Ts = 0.1  # 时间步长
N = 150   # 仿真步数
Nz = 2    # 状态维度
Nu = 2    # 控制输入维度

# 初始状态:
initEgoState = [0, 15]           # 自车初始状态
initEgoControl = [20, 0]         # 自车初始控制输入
initObsState = [100, 15]         # 障碍物初始状态
initObsControl = [10, 0]         # 障碍物初始控制输入

# 定义障碍物状态:
obsState = np.zeros((2, N+1))    # 障碍物状态数组
obsControl = np.zeros((2, N+1))   # 障碍物控制输入数组

for i in range(N + 1):
  obsControl[0, i] = initObsControl[0]  # 设置障碍物的控制输入
  obsControl[1, i] = initObsControl[1]
  obsState[0, i] = initObsState[0] + obsControl[0, i] * Ts * i  # 计算障碍物位置
  obsState[1, i] = initObsState[1]

(3)下面代码的功能是使用Pyomo库构建和求解一个超车轨迹生成模型,包括定义模型参数、目标函数、约束条件,并求解优化问题。模型考虑了车辆的状态、控制输入、速度、加速度、障碍物位置以及车道限制,最后输出车辆在每个时间步的轨迹和控制数据。

# 模型初始化:
model = pyo.ConcreteModel()
model.tidx = pyo.Set(initialize=range(0, N + 1)) 
model.zidx = pyo.Set(initialize=range(0, Nz))
model.uidx = pyo.Set(initialize=range(0, Nu))

# 创建状态和输入变量轨迹:
model.z = pyo.Var(model.zidx, model.tidx)  # 状态变量
model.u = pyo.Var(model.uidx, model.tidx)  # 控制输入变量
model.o = pyo.Var(model.tidx, initialize=0)  # 超车状态变量

# 目标函数:
model.cost = pyo.Objective(expr = 5. * (model.z[1, N] - initEgoState[1])**2 + 
                            5. * (model.u[0, N-1] - initEgoControl[0])**2 + 
                            10. * (model.z[1, N] - model.z[1, N-1])**2 +
                            0.1 * sum((model.u[1, t+1] - model.u[1, t])**2 for t in model.tidx if t < N-1) + 
                            - 10.0 * sum((model.z[0, t] - obsState[0, t]) for t in model.tidx if t < N) +
                            # - model.mb, sense=pyo.minimize)  # 注释掉的部分
                            + 0.2 * sum((model.z[1, t] - obsState[1, t])**2 * (model.o[t])  for t in model.tidx if t < N) +
                            + 0.05 * sum((model.z[1, t] - obsState[1, t] - 10)**2 * (1 - model.o[t])  for t in model.tidx if t < N), sense=pyo.minimize)

# 初始约束条件:
model.c1 = pyo.Constraint(model.zidx, rule=lambda model, i: model.z[i, 0] == initEgoState[i])  # 自车初始状态
model.c2 = pyo.Constraint(model.uidx, rule=lambda model, j: model.u[j, 0] == initEgoControl[j])  # 自车初始控制输入

# 模型约束条件:
model.c3 = pyo.Constraint(model.tidx, rule=lambda model, t: model.z[0, t+1] == model.z[0, t] + model.u[0, t] * pyo.cos(model.u[1, t]) * Ts if t < N else pyo.Constraint.Skip)
model.c4 = pyo.Constraint(model.tidx, rule=lambda model, t: model.z[1, t+1] == model.z[1, t] + model.u[0, t] * pyo.sin(model.u[1, t]) * Ts if t < N else pyo.Constraint.Skip) 

# 速度和加速度约束:
model.c5 = pyo.Constraint(model.tidx, rule=lambda model, t: model.u[0, t] <= 50 if t < N else pyo.Constraint.Skip)  # 速度上限
model.c6 = pyo.Constraint(model.tidx, rule=lambda model, t: model.u[0, t] >= initEgoControl[0] if t < N else pyo.Constraint.Skip)  # 速度下限
model.c7 = pyo.Constraint(model.tidx, rule=lambda model, t: model.u[0, t+1] - model.u[0, t] >= -5 * Ts if t < N-1 else pyo.Constraint.Skip)  # 加速限制
model.c8 = pyo.Constraint(model.tidx, rule=lambda model, t: model.u[0, t+1] - model.u[0, t] <= 5 * Ts if t < N-1 else pyo.Constraint.Skip)

# 角度和角速度约束:
model.c9 = pyo.Constraint(model.tidx, rule=lambda model, t: model.u[1, t] >= -0.5 if t < N else pyo.Constraint.Skip)  # 角度下限
model.c10 = pyo.Constraint(model.tidx, rule=lambda model, t: model.u[1, t] <= 0.5 if t < N else pyo.Constraint.Skip)  # 角度上限
model.c11 = pyo.Constraint(model.tidx, rule=lambda model, t: model.u[1, t+1] - model.u[1, t] >= -0.175 * Ts if t < N-1 else pyo.Constraint.Skip)  # 角速度限制
model.c12 = pyo.Constraint(model.tidx, rule=lambda model, t: model.u[1, t+1] - model.u[1, t] <= 0.175 * Ts if t < N-1 else pyo.Constraint.Skip)

# 障碍物约束:
model.c13

(4)下面代码的功能是绘制自车在轨迹生成模型中运行的结果,包括自车的X坐标、Y坐标、速度和角度,并展示自车轨迹与障碍物的相对位置。最后,代码输出自车在最后时间步的位置以及障碍物的位置,并打印超车状态。

# 绘制结果:
plt.figure()
plt.subplot(4,1,1)  # 创建4行1列的子图,选择第1个子图
plt.plot(egoXData)  # 绘制自车X坐标
plt.ylabel('x')  # Y轴标签

plt.subplot(4,1,2)  # 选择第2个子图
plt.plot(egoYData)  # 绘制自车Y坐标
plt.ylabel('y')  # Y轴标签

plt.subplot(4,1,3)  # 选择第3个子图
plt.plot(egoVData)  # 绘制自车速度
plt.ylabel('speed')  # Y轴标签

plt.subplot(4,1,4)  # 选择第4个子图
plt.plot(egoPData)  # 绘制自车角度
plt.ylabel('psi')  # Y轴标签
plt.show()  # 显示绘图

# 绘制自车轨迹与障碍物位置:
plt.plot(egoXData, egoYData)  # 绘制自车轨迹
plt.plot(obsState[0,:], obsState[1,:], 'r-')  # 绘制障碍物轨迹,红色线
plt.axis('scaled')  # 设置坐标轴比例相同
plt.show()  # 显示绘图

# 输出最后时刻自车位置和障碍物位置:
print(egoXData[N], obsState[0, N])  # 输出自车X坐标和障碍物X坐标
print(egoYData[N], obsState[1, N])  # 输出自车Y坐标和障碍物Y坐标
print(pyo.value(model.o[:]))  # 输出超车状态

(5)下面代码的功能是为超车轨迹生成动画准备工作,包括设置图形、定义车道位置以及存储自车和障碍物的位置。为后续的动画创建准备了必要的环境和数据结构,设置了车道的Y坐标,并为自车和障碍物的动画展示做了准备。

# 超车轨迹动画

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.animation import FuncAnimation
from matplotlib.patches import Rectangle
from random import randint
from matplotlib import rc
rc('animation', html='jshtml')  # 设置动画显示格式为HTML

# 车辆尺寸
width = 2.5
length = 10

# 创建图形和坐标轴
fig, ax = plt.subplots()

# 初始化自车和障碍物的坐标列表
egoX = []
egoY = []

obsX = []
obsY = []

# 定义车道位置
lowerLaneX = np.linspace(start=egoXData[0]-5, stop=egoXData[N]+5, num=100)  # 下车道X坐标
lowerLaneY = 10 * np.ones(100)  # 下车道Y坐标

middleLaneX = np.linspace(start=egoXData[0]-5, stop=egoXData[N]+5, num=100)  # 中车道X坐标
middleLaneY = 20 * np.ones(100)  # 中车道Y坐标

upperLaneX = np.linspace(start=egoXData[0]-5, stop=egoXData[N]+5, num=100)  # 上车道X坐标
upperLaneY = 30 * np.ones(100)  # 上车道Y坐标

(6)下面代码的功能是创建一个超车轨迹的动画,显示自车和障碍物的运动,并绘制不同车道的线路。它还将自车的状态(位置、速度和角度)整合为一个数组,以便进行后续分析。

def animate(i):
    # 更新自车坐标
    egoX.append(egoXData[i])
    egoY.append(egoYData[i])

    # 更新障碍物坐标
    obsX.append(obsState[0, i])
    obsY.append(obsState[1, i])

    # 清除当前图形
    ax.clear()
    
    # 绘制障碍物的路径
    ax.plot(obsX, obsY, c='r')
    
    # 绘制自车的矩形
    ax.add_patch(Rectangle((egoX[-1] - length/2, egoY[-1] - width/2), length, width, angle=egoPData[i]*180/np.pi))
    
    # 绘制自车的路径
    ax.plot(egoX, egoY, c='b')
    
    # 绘制障碍物的矩形
    ax.add_patch(Rectangle((obsX[-1] - length/2, obsY[-1] - width/2), length, width))
    
    # 绘制车道线
    ax.plot(lowerLaneX, lowerLaneY, c='g')
    ax.plot(middleLaneX, middleLaneY, c='g')
    ax.plot(upperLaneX, upperLaneY, c='g')
    
    # 设置坐标轴比例
    plt.gca().set_aspect(2)

# 创建动画
ani = FuncAnimation(fig, animate, frames=N, interval=100, repeat=False)

# 整合自车状态数据
egoXdata = np.array(egoXData)
egoXdata = egoXdata.reshape((1, 151))
egoYdata = np.array(egoYData)
egoYdata = egoYdata.reshape((1, 151))
egoVdata = np.array(egoVData)
egoVdata = np.append(egoVdata, egoVData[-1])
egoVdata = egoVdata.reshape((1, 151))
egoPdata = np.array(egoPData)
egoPdata = np.append(egoPdata, egoPData[-1])
egoPdata = egoPdata.reshape((1, 151))

# 合并状态数据
combined_state_data1 = np.concatenate((egoXdata, egoYdata))
combined_state_data2 = np.concatenate((egoVdata, egoPdata))
combined_state_data = np.concatenate((combined_state_data1, combined_state_data2))

# 绘制合并后的状态数据
plt.plot(combined_state_data[0, :], combined_state_data[1, :])

nz = 4  # 状态数量
nu = 2  # 输入数量

l_r = 1.738  # 车辆后轴到重心的距离  

        

上述代码通过动画展示自车的超车过程,同时记录和整合了自车的状态数据,以便进一步分析。

(7)下面代码的功能是利用优化模型解决一个控制问题,生成自车在给定状态下的轨迹,比较开环和闭环的轨迹,并将生成的轨迹进行可视化。它使用了Pyomo库来构建并求解线性规划模型,同时支持对自车状态和输入约束的定义。

def solve_cftoc(Ts, Nmodel, d, nz, nu, x0, stateData):
    model = pyo.ConcreteModel()
    model.tidx = pyo.Set(initialize=range(0, Nmodel + 1))  # 有限优化问题的长度
    model.zidx = pyo.Set(initialize=range(0, nz))
    model.uidx = pyo.Set(initialize=range(0, nu))

    # 创建状态和输入变量轨迹:
    model.z = pyo.Var(model.zidx, model.tidx)
    model.u = pyo.Var(model.uidx, model.tidx)

    # 目标函数:
    model.cost = pyo.Objective(expr=10 * sum((model.z[i, Nmodel] - stateData[i, -1]) ** 2 for i in model.zidx) + 
                               sum((model.z[0, t] - stateData[0, t]) ** 2 + 
                                   (model.z[1, t] - stateData[1, t]) ** 2 + 
                                   (model.z[2, t] - stateData[2, t]) ** 2 for t in model.tidx), 
                               sense=pyo.minimize)

    # 约束条件:
    model.constraint1 = pyo.Constraint(model.zidx, rule=lambda model, i: model.z[i, 0] == x0[i])
    model.constraint2 = pyo.Constraint(model.tidx, rule=lambda model, t: model.z[0, t + 1] == model.z[0, t] + 
                                       Ts * (model.z[2, t] * pyo.cos(model.z[3, t] + model.u[1, t] + 0.5 * d))
                                       if t < Nmodel else pyo.Constraint.Skip)
    model.constraint3 = pyo.Constraint(model.tidx, rule=lambda model, t: model.z[1, t + 1] == model.z[1, t] + 
                                       Ts * (model.z[2, t] * pyo.sin(model.z[3, t] + model.u[1, t] + 0.5 * d))
                                       if t < Nmodel else pyo.Constraint.Skip)
    model.constraint4 = pyo.Constraint(model.tidx, rule=lambda model, t: model.z[2, t + 1] == model.z[2, t] + 
                                       Ts * model.u[0, t]
                                       if t < Nmodel else pyo.Constraint.Skip)
    model.constraint5 = pyo.Constraint(model.tidx, rule=lambda model, t: model.z[3, t + 1] == model.z[3, t] + 
                                       Ts * (model.z[2, t] / l_r * pyo.sin(model.u[1, t]))
                                       if t < Nmodel else pyo.Constraint.Skip)
    model.constraint6 = pyo.Constraint(model.tidx, rule=lambda model, t: model.u[0, t] <= 4
                                        if t < Nmodel else pyo.Constraint.Skip)
    model.constraint7 = pyo.Constraint(model.tidx, rule=lambda model, t: model.u[0, t] >= -4
                                        if t < Nmodel else pyo.Constraint.Skip)
    model.constraint8 = pyo.Constraint(model.tidx, rule=lambda model, t: model.u[1, t + 1] - model.u[1, t] <= 0.2
                                        if t < Nmodel - 1 else pyo.Constraint.Skip)
    model.constraint9 = pyo.Constraint(model.tidx, rule=lambda model, t: model.u[1, t + 1] - model.u[1, t] >= -0.2
                                        if t < Nmodel - 1 else pyo.Constraint.Skip)
    model.constraint10 = pyo.Constraint(model.tidx, rule=lambda model, t: model.u[1, t] >= -0.6
                                         if t < Nmodel else pyo.Constraint.Skip)
    model.constraint11 = pyo.Constraint(model.tidx, rule=lambda model, t: model.u[1, t] <= 0.6
                                         if t < Nmodel else pyo.Constraint.Skip)
    model.constraint12 = pyo.Constraint(model.tidx, rule=lambda model, t: model.z[2, t] <= 50
                                         if t < Nmodel else pyo.Constraint.Skip)

    # 现在可以求解:
    results = pyo.SolverFactory('ipopt').solve(model)

    xOpt = np.asarray([[model.z[i, t]() for i in model.zidx] for t in model.tidx]).T
    uOpt = np.asarray([model.u[:, t]() for t in model.tidx]).T
    return [model, xOpt, uOpt]

xOpt = combined_state_data[:, 0]
dtest = np.random.random() * 0.1
print(dtest)
[a, b, c] = solve_cftoc(Ts, N, dtest, nz, nu, xOpt, combined_state_data)
plt.plot(b[0, :], b[1, :])
plt.plot(combined_state_data[0, :], combined_state_data[1, :], 'r--')
plt.show()
plt.plot(b[2, :])
plt.plot(combined_state_data[2, :])
plt.show()

# 保存和导出生成的轨迹

M = 50  # 模拟步骤
xOpt = np.zeros((4, N + 1))
xOpt[:, 0] = combined_state_data[:, 0]
fig = plt.figure(figsize=(9, 6))
for t in range(0, M):
    print(t)
    [model, x, u] = solve_cftoc(Ts, N - t, nz, nu, xOpt[:, t], combined_state_data[:, t:])

    # 保存闭环轨迹
    # 注意x的第二列表示最优闭环状态
    xOpt[:, t + 1] = x[:, 1]
for t in range(M, 2 * M):
    print(t)
    # print(N-t)
    [model, x, u] = solve_cftoc(Ts, N - t, nz, nu, xOpt[:, t], combined_state_data[:, t:])
    # plt.plot(x[0,:],x[1,:])
    # plt.show()
    xOpt[:, t + 1] = x[:, 1]

# 绘制开环轨迹
line1 = plt.plot(combined_state_data[0, :], combined_state_data[1, :], 'r--')

# 绘制闭环轨迹
line2 = plt.plot(xOpt[0, :], xOpt[1, :], 'b-')
plt.legend(['Closed-loop', 'Open Loop'])
plt.xlabel('x')
plt.ylabel('y')
plt.axis('equal')