上述公式是系统的状态空间方程,控制的目的就是设计控制器使得控制输入u能够使得误差e趋于0。
1、LQR
对这个得到的误差微分方程,因为不是标准的LQR中的线性约束形式,所以先忽略这一项,对这个LQR线性约束系统进行代价函数最小值的求解,如下图就是系统(这里的x就是)。
LQR原理分为连续的LQR和离散的LQR。在实际应用上用的都是离散的LQR,因为处理的数据大多都是离散的;连续的LQR原理涉及泛函数与变分法。
1.1、离散LQR
离散LQR问题就是将离散化为,然后求出在约束下取极小值的。
对于,利用积分中值定理得出 ,
向前欧拉法:令,则
向后欧拉法:令,则
中点欧拉法:令,则
先对利用积分定理得出,然后对采用中点欧拉法,对采用向前欧拉法,得出离散化的方程:
,其中,
1.2、求解离散LQR
对于在约束下的极小值的问题,可以先求在约束下的极小值然后将n趋于无穷。
在约束下的极小值可以用拉格朗日乘子法写为:
化简得到,其中,或。
J分别对、、求导为0,解出:
,
,
,
由这四个式子可以推出。
设,,则,
得出递推式,这就是黎卡提方程。
那么就可以算出的表达式:
这里的就是误差微分方程的。
对于黎卡提方程来说迭代几十次就会收敛,何况是无穷次,因此对这个方程求解,就可以得出收敛的P的值,那么收敛的控制量u:
。
一般利用矩阵求逆引理得到黎卡提方程的另一个表达式(计算量小):
总结就是对于误差微分方程的前两项,首先离散化为,然后求解黎卡提方程得出P,代入得出控制量u。
2、前馈控制
对这个误差微分方程,由LQR得出的代入误差微分方程得出误差不可能一直为0,所以不能只用LQR得出u,因此要加入一个前馈控制(前馈控制不受x的影响)处理这一项,使得稳态误差为0。
控制量u包含两部分,LQR算出的k和前馈控制:
控制量u代入,稳定时=0,此时的,则目标就是选取合适的前馈控制使得接近0。
(1)、求
可以写为向量:
是向量k的第三个分量。当时,的第一个分量。而第三个分量不受LQR的k和前馈控制控制,并且要让航向误差为0的话,那么就应该为。
车在自然坐标系下的投影的,可以根据角度的关系写为,近似为。由曲率的定义式得出:
又公式和公式,假设无漂移(可以舍去),得出:
由以上两个公式代入,则:
将车按质心不变等效为前后两部分,再根据侧向力的公式得出:
进一步的,如上图所示,。在三角形中,,得出,最终化简出:
稳态时就为,则说明稳态时的航向误差就是0。
总结:总的目标就是通过控制u使得中的、、为0,为(即满足航向误差为0)。过程是先使用LQR算出反馈控制的k值,然后利用k去算出前馈控制,此时施加控制就满足中的、、为0,为(即满足航向误差为0)。
3、结合自动驾驶问题的基于LQR控制代码实现
LQR用于自动驾驶控制的横向控制。
1、导入库文件
import numpy as np
import math
import carla
import cvxopt
from collections import deque
2、初始化信息(车的位置信息、车的参数信息、主车变量、主车、规划出的信息、预测区间、控制区间、矩阵A、矩阵B、误差等)
class Lateral_LQR_controller(object): #横向控制
def __init__(self, ego_vehicle, vehicle_para, pathway_xy_theta_kappa):
"""
self.vehicle_para = (a, b, Cf, Cr, m, Iz)
a,b:前后轮中心距离车质心的距离
CF, Cr:前后轮的侧偏刚度(按负的处理,apollo按正的处理)
m:车的质量
Iz:车的转动惯量
"""
self.vehicle_para = vehicle_para
self.vehicle_state = None # self.vehicle_state = (x, y, fai, vy, fai_dao)存储车的位置信息
self.vehicle = ego_vehicle # ego_vehicle是carla中的主车
self.vehicle_vx = 0 # ego_vehicle的车辆速度在车轴纵向方向的分量
self.target_path = pathway_xy_theta_kappa # 规划出的信息集(x_m, y_m, theta_m, k_m)
self.m = 4 # 状态变量x有四个分量
self.p = 1 # 控制输入u有一个分量
# 根据误差微分方程来写A、B
self.A = np.zeros(shape=(self.m, self.m), dtype="float64")
self.B = np.zeros(shape=(self.m, self.p), dtype="float64")
self.A_bar = None # 离散化的A矩阵
self.B_bar = None # 离散化的B矩阵
self.K = None # 反馈控制的K
self.k_r = None # 投影点曲率
self.err = None # 车的信息和规划的信息的误差即状态变量
self.delta_f = None # 前馈控制δf
self.min_index = 0
self.x_pre = 0 # 预测点x
self.y_pre = 0 # 预测点y
self.fai_pre = 0 # 预测点fai
self.fai_dao_pre = 0 # 预测点fai_dao
self.vx_pre = 0 # 预测点vx
self.vy_pre = 0 # 预测点vy
self.x_r = 0 # 投影点x
self.y_r = 0 # 投影点y
self.theta_r = 0 # 投影点θ
3、获取车辆的位置和状态信息(位置(x,y)、车身横摆角φ、速度向量、航向角、质心侧偏角β、角速度、vx以及vy)
def vehicle_info(self):
"""
函数:获取车辆的位置和状态信息
return: None
"""
vehicle_location = self.vehicle.get_location() # self.vehicle.get_location()的格式:Location(x=70.000000, y=200.000000, z=1.942856)
x, y = vehicle_location.x, vehicle_location.y # 70.0 200.0
fai = self.vehicle.get_transform().rotation.yaw * (math.pi / 180) # 车身横摆角φ即车轴纵向和x轴的夹角,结果转成弧度制:79*π/180
v = self.vehicle.get_velocity() # self.vehicle.get_velocity()的格式:Vector3D(x=0.000000, y=0.000000, z=-0.194462),航向角是车速v的方向与x轴夹角(=质心侧偏角β+车身横摆角φ)即arctan(v.y/v.x)
v_length = math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z) # 速度大小
beta = (math.atan2(v.y, v.x) - fai) # 质心侧偏角β,车速和车轴纵向之间的夹角
vx = v_length * math.cos(beta) # 车速在车身坐标系下x轴(即纵向)的分量
vy = v_length * math.sin(beta) # 车速在车身坐标系下y轴(即横向)的分量
if abs(vx) < 0.005 and vx >= 0:
vx = 0.005
if abs(vx) < 0.005 and vx < 0:
vx = -0.005
fai_dao = self.vehicle.get_angular_velocity().z * (math.pi / 180) # 角速度 self.vehicle.get_angular_velocity()的格式:Vector3D(x=0.000000, y=0.000000, z=0.000000)
self.vehicle_state = (x, y, fai, vy, fai_dao) # 得到车的位置和状态信息
self.vehicle_vx = vx # 得到ego_vehicle的车辆速度在纵向方向的分量
4、计算k时刻横向控制的误差err(即状态空间方程的)以及曲率
def cal_err_k_r(self, ts=0.1):
"""
函数:计算预测点和规划点的误差err
ts:预测的时间
self.target_path:规划模块输出的轨迹
[(x_m1, y_m1, theta_m1, k_m1),
(x_m2, y_m2, theta_m2, k_m2),
...
(x_mn, y_mn, theta_mn, k_mn)]
x_r, y_r:直角坐标系下位置
theta_r:速度方向与x轴夹角
k_r:规划点的曲率
self.vehicle_state: 车辆当前位置(x, y, fai, vy, fai_dao)
x,y:车辆当前的的实际位置
fai:航向角即车轴和x轴的夹角
fai_dao:fai对时间的导数即角速度
vx:车的质心速度在车轴(纵向)方向的分量
vy:车的质心速度在垂直车轴(横向)方向的分量
return: None
"""
vx = self.vehicle_vx
x, y, fai, vy, fai_dao = self.vehicle_state
# 预测模块计算预测点信息(因为算法具有滞后性)
self.x_pre = x + vx * ts * math.cos(fai) - vy * ts * math.sin(fai)
self.y_pre = y + vy * ts * math.cos(fai) + vx * ts * math.sin(fai)
self.fai_pre = fai + fai_dao * ts
self.fai_dao_pre = fai_dao
self.vx_pre = vx
self.vy_pre = vy
# 1、找匹配点
path_length = len(self.target_path)
min_d = 1000
for i in range(self.min_index, min(self.min_index + 50, path_length)): # 向前搜索50个点
d = (self.target_path[i][0] - x) ** 2 + (self.target_path[i][1] - y) ** 2
if d < min_d:
min_d = d
self.min_index = i
min_index = self.min_index
# 2、计算自然坐标系下规划点的轴向向量tor和法向量n
tor = np.array([math.cos(self.target_path[min_index][2]), math.sin(self.target_path[min_index][2])])
n = np.array([-math.sin(self.target_path[min_index][2]), math.cos(self.target_path[min_index][2])])
# 3、计算匹配点指向车位置的向量d_err
d_err = np.array([x - self.target_path[min_index][0], y - self.target_path[min_index][1]])
# 4、计算横向距离误差ed, 纵向距离误差es
ed = np.dot(n, d_err)
es = np.dot(tor, d_err)
# 5、获取投影点坐标(x_r,y_r)
self.x_r, self.y_r = np.array([self.target_path[min_index][0], self.target_path[min_index][1]]) + es * tor
# 6、计算theta_r
self.theta_r = self.target_path[min_index][2] + self.target_path[min_index][3] * es # (按投影点的theta_m)
# self.theta_r = self.target_path[min_index][2] # apollo的方案(按匹配点的theta_m)
# 7、计算投影点的曲率k_r,近似等于匹配点的曲率k_m
self.k_r = self.target_path[min_index][3]
# 8、计算ed的导数ed_dao
ed_dao = self.vy_pre * math.cos(fai - self.theta_r) + vx * math.sin(fai - self.theta_r)
# 9、计算e_fai
e_fai = math.sin(fai - self.theta_r)
# e_fai = fai - theta_r
# 10、计算投影点速度(s的导数)s_dao
s_dao = (vx * math.cos(fai - self.theta_r) - vy * math.sin(fai - self.theta_r)) / (1 - self.k_r * ed)
# 11、计算e_fai的导数e_fai_dao
e_fai_dao = fai_dao - self.k_r * s_dao
self.err = (ed, ed_dao, e_fai, e_fai_dao)
5、计算矩阵A、B(根据状态空间方程代入车辆参数、vx),并计算离散化矩阵、
系统的状态空间方程:
计算、、、矩阵:
def cal_A_B_and_discretion(self):
"""
函数:根据整车参数self.vehicle_para和vx,计算矩阵A,B,并离散化状态空间方程(不带常数项Cθr_dao)
return: None
"""
vx = self.vehicle_vx
(a, b, Cf, Cr, m, Iz) = self.vehicle_para
# 1、A
self.A[0][1] = 1
self.A[1][1] = (Cf + Cr) / (m * vx)
self.A[1][2] = -(Cf + Cr) / m
self.A[1][3] = (a * Cf - b * Cr) / (m * vx)
self.A[2][3] = 1
self.A[3][1] = (a * Cf - b * Cr) / (Iz * vx)
self.A[3][2] = -(a * Cf - b * Cr) / Iz
self.A[3][3] = (a ** 2 * Cf + b ** 2 * Cr) / (Iz * vx)
# 2、B
self.B[1][0] = -Cf / m
self.B[3][0] = -a * Cf / Iz
# 3、A_bar、B_bar
dt = 0.1 # 状态空间方程离散化的时间间隔dt
e = np.linalg.inv(np.eye(4) - (dt * self.A) / 2) # np.linalg.inv()是矩阵求逆
self.A_bar = e @ (np.eye(4) + (dt * self.A) / 2)
self.B_bar = e @ self.B * dt
6、求解黎卡提方程得出K
黎卡提方程:
K:
def cal_LQR_K(self, Q, R):
"""
函数:根据Q、R、self.A_bar、self.B_bar,通过迭代黎卡提方程P = Q + A.TPA - A.TPB(R+B.TPB)'B.TPA('是求逆)求出向量K(这里的A和B均是离散过的self.A_bar和self.B_bar)
Q: 每一时刻误差代价的权重对应的对角矩阵,矩阵大小为self.m * self.m,对角线的数值越大算法的性能越好,但是会牺牲算法稳定性,而且最终控制量u很大。
R: 每一时刻控制代价的权重对应的对角矩阵,矩阵大小为self.p * self.p,对角线的数值越大越平稳,变化越小,控制效果越好,但是误差会很大。
self.A_bar: 状态空间方程系数矩阵,大小(self.m, self.m)
self.B_bar: 状态空间方程系数矩阵,大小(self.m, self.p)
return: None
"""
P = Q
P_pre = Q
max_iterate = 5000
eps = 0.1
A = self.A_bar
B = self.B_bar
for i in range(max_iterate):
P = Q + A.T @ P @ A - (A.T @ P @ B) @ np.linalg.inv(R + B.T @ P @ B) @ (B.T @ P @ A)
if abs(P - P_pre).max() < eps:
print("黎卡提方程迭代次数:", i) # 输出迭代的次数
break
P_pre = P
self.K = np.linalg.inv(R + B.T @ P @ B) @ (B.T @ P @ A) # K的大小为(1×self.m)
7、代入公式求解前馈控制
def forward_control_delta_f(self):
"""
函数:计算前馈控制量delta_f
self.vehicle_para = (a, b, Cf, Cr, m, Iz)
K: LQR的输出结果
self.k_r: 投影点曲率
vx:车的质心速度在车轴(纵向)方向的分量
return: 前馈控制量delta_f
"""
a, b, Cf, Cr, m, Iz = self.vehicle_para
K_3 = self.K[0][2]
vx = self.vehicle_vx
self.delta_f = self.k_r * (a + b - b * K_3 - (m * vx * vx) / (a + b) * (b / Cf + a * K_3 / Cr - a / Cr))
self.delta_f = self.delta_f * np.pi / 180 # 将前馈控制量转化为弧度形式
8、设置Q、R,LQR控制输出转角
def LQR_control(self):
"""
函数:LQR控制算法
K: LQR输出
e_rr: 误差输出
delta_f: 前馈输出
return: steer_r
"""
Q = np.eye(4)
Q[0][0] = 200
Q[1][1] = 1
Q[2][2] = 50
Q[3][3] = 1
b = 1
R = b
self.vehicle_info()
self.cal_err_k_r(ts=0.1)
self.cal_A_B_and_discretion()
self.cal_LQR_K(Q, R)
self.forward_control_delta_f()
steer_r = -np.dot(self.K, np.array(self.err)) + self.delta_f
steer_r = steer_r[0]
return steer_r
Lateral_LQR_control = Lateral_LQR_controller(ego_vehicle, vehicle_para, pathway)
steer = Lateral_LQR_control.LQR_control()
print("steer:", steer)
讲解了LQR的求解过程,介绍了前馈控制的作用以及求解,最终完成了结合自动驾驶问题的基于LQR横向控制的代码实现。