IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 人工智能 -> 【自动驾驶】Stanley实现轨迹跟踪 -> 正文阅读

[人工智能]【自动驾驶】Stanley实现轨迹跟踪

参考资料

1. Stanley算法

之前学习了纯追踪算法PID算法在轨迹跟踪上的应用,现在学习Stanley算法。

1.1 算法思想

前轮反馈控制(Front wheel feedback)也就是常说的Stanley方法,其核心思想是基于车辆前轴中心点的路径跟踪偏差量对方向盘转向控制量进行计算。

Stanley方法是一种基于横向跟踪误差(前轴中心到最近路径点的距离,注意,这里横向跟踪误差的定义与前文pure pursuitPID中的轨迹跟踪定义的不太一样,后两者是基于后轴中心定义的)的非线性反馈函数,并且能实现横向跟踪误差指数收敛于0。

1.2 公式推导

在这里插入图片描述

前轮转角控制变量 δ \delta δ由两部分构成:

  • 一部分是航向误差引起的转角,即当前车身方向与参考轨迹最近点的切线方向的夹角 θ e \theta_e θe?;
  • 另一部分是横向误差引起的转角,即前轮速度方向与参考轨迹最近点的切线方向所成的夹角 δ e \delta_e δe?

根据上图展示的几何关系,易得
δ = θ e + δ e (1) \tag{1} \delta =\theta_e+\delta_e δ=θe?+δe?(1)

显然, δ e \delta_e δe?满足
δ e = arctan ? k e y d = arcsin ? k e y v (2) \tag{2} \delta_e= \arctan{\frac{ke_y}{d}}= \arcsin{\frac{ke_y}{v}} δe?=arctandkey??=arcsinvkey??(2)
其中, d d d是与车速 v v v相关的。

这里值得注意的是, δ e \delta_e δe?在许多论文中都是表示成
δ e = arctan ? k e y v (3) \tag{3} \delta_e = \arctan{\frac{ke_y}{v}} δe?=arctanvkey??(3)
猜测的一个可能原因是小角度假设,当 δ e \delta_e δe?很小时公式(3)是成立的;另外一个可能原因是因为增益系数 k k k可以任意选择,当 k k k选择恰当时,公式(3)是可以成立的。后面最终表达式也选用公式(3)的形式。

另外,根据几何关系, θ e \theta_e θe?满足
θ e = ψ t ? ψ (4) \tag{4} \theta_e = \psi_t-\psi θe?=ψt??ψ(4)
其中, ψ \psi ψ表示车辆航向角, ψ t \psi_t ψt?表示离车辆前轴中心最近目标路径点处的航向角。

结合等式(1)(3)(4),得前轮转角控制量 δ \delta δ的最终表达式
δ = ψ t ? ψ + arctan ? k e y v (5) \tag{5} \delta = \psi_t-\psi+\arctan{\frac{ke_y}{v}} δ=ψt??ψ+arctanvkey??(5)

1.3 横向误差变化率

横向误差的变化率为
e ˙ y = ? v sin ? δ e (6) \tag{6} \dot e_y = -v\sin{\delta_e} e˙y?=?vsinδe?(6)

带负号是因为在公式(5)的控制下,横向误差会越来越小,因此横向偏差变化率会有负号。

其中 sin ? δ e \sin \delta_e sinδe? 根据几何关系可知:
sin ? δ e = e y d ( t ) 2 + e y 2 (7-1) \tag{7-1} \sin \delta_{e}=\frac{e_{y}}{\sqrt{d(t)^{2}+e_{y}^{2}}} sinδe?=d(t)2+ey2? ?ey??(7-1)
根据公式(3),进一步也有
sin ? δ e = k e y v 2 + ( k e y ) 2 (7-2) \tag{7-2} \sin \delta_{e}=\frac{k e_{y}}{\sqrt{v^{2}+\left(k e_{y}\right)^{2}}} sinδe?=v2+(key?)2 ?key??(7-2)

故有:
e ˙ y = ? k v e y v 2 + ( k e y ) 2 = ? k e y 1 + ( k e y v ) 2 (8) \tag{8} \dot{e}_{y}=\frac{-k v e_{y}}{\sqrt{v^{2}+\left(k e_{y}\right)^{2}}}=\frac{-k e_{y}}{\sqrt{1+\left(\frac{k e_{y}}{v}\right)^{2}}} e˙y?=v2+(key?)2 ??kvey??=1+(vkey??)2 ??key??(8)
当横向跟踪误差 e y e_{y} ey? 很小时,上式改写为:
e ˙ y ≈ ? k e y (9) \tag{9} \dot{e}_{y} \approx-k e_{y} e˙y??key?(9)
积分上式(一阶线性微分方程的求解),得:
e y ( t ) = e y ( 0 ) × e ? k t (10) \tag{10} e_{y}(t)=e_{y}(0) \times e^{-k t} ey?(t)=ey?(0)×e?kt(10)
因此,当 t → ∞ t\rightarrow\infty t时,横向误差以指数形式收敛于 0,参数 k k k 决定了收敛速度。

1.4 算法伪代码

  • 输入:当前车辆位置、航向角 ψ \psi ψ、速度 v v v?,当前目标路点和离车辆前轴中心最近目标路径点处的航向角 ψ t \psi_t ψt?
  • 计算:
    • 计算横向误差 e y e_y ey?(这一步很重要)
    • 计算 δ = ψ t ? ψ + arctan ? k e y v \delta = \psi_t-\psi+\arctan{\frac{ke_y}{v}} δ=ψt??ψ+arctanvkey??
  • 输出:前轮转角控制量 δ \delta δ

2. python代码实现

2.1 车辆模型

车辆运动学模型以后轴中心为车辆中心的单车运动学模型(具体可参考笔者之前的博客),即
{ x ˙ = V cos ? ( ψ ) y ˙ = V sin ? ( ψ ) ψ ˙ = V L tan ? δ f V ˙ = a \left\{\begin{array}{l} \dot{x}=V \cos (\psi) \\ \dot{y}=V \sin (\psi) \\ \dot{\psi}=\frac{V}{L}\tan{\delta_f}\\ \dot{V}=a \end{array}\right. ????????x˙=Vcos(ψ)y˙?=Vsin(ψ)ψ˙?=LV?tanδf?V˙=a?
python实现代码如下。

import math
class KinematicModel_3:
  """假设控制量为转向角delta_f和加速度a
  """

  def __init__(self, x, y, psi, v, L, dt):
    self.x = x
    self.y = y
    self.psi = psi
    self.v = v
    self.L = L
    # 实现是离散的模型
    self.dt = dt

  def update_state(self, a, delta_f):
    self.x = self.x+self.v*math.cos(self.psi)*self.dt
    self.y = self.y+self.v*math.sin(self.psi)*self.dt
    self.psi = self.psi+self.v/self.L*math.tan(delta_f)*self.dt
    self.v = self.v+a*self.dt

  def get_state(self):
    return self.x, self.y, self.psi, self.v

2.2 相关参数设置

k=0.2 # 增益系数
dt=0.1 # 时间间隔,单位:s
L=2 # 车辆轴距,单位:m
v = 2 # 初始速度
x_0=0 # 初始x
y_0=-3 #初始y
psi_0=0 # 初始航向角

2.3 搜索目标临近点

def cal_target_index(robot_state, refer_path):
    """得到临近的路点

    Args:
        robot_state (_type_): 当前车辆位置
        refer_path (_type_): 参考轨迹(数组)

    Returns:
        _type_: 最近的路点的索引
    """
    dists = []
    for xy in refer_path:
        dis = np.linalg.norm(robot_state-xy)
        dists.append(dis)

    min_index = np.argmin(dists)
    return min_index

2.4 角度归一化

def normalize_angle(angle):
    """
    Normalize an angle to [-pi, pi].

    :param angle: (float)
    :return: (float) Angle in radian in [-pi, pi]
    copied from https://atsushisakai.github.io/PythonRobotics/modules/path_tracking/stanley_control/stanley_control.html
    """
    while angle > np.pi:
        angle -= 2.0 * np.pi

    while angle < -np.pi:
        angle += 2.0 * np.pi

    return angle

2.5 Stanley 算法实现

def stanley_control(robot_state,refer_path, refer_path_psi):
    """stanley控制

    Args:
        robot_state (_type_): 机器人位姿,包括x,y,yaw,v
        refer_path (_type_): 参考轨迹的位置
        refer_path_psi (_type_): 参考轨迹上点的切线方向的角度
        last_target_index (_type_): 上一个目标临近点

    Returns:
        _type_: _description_
    """
    current_target_index = cal_target_index(robot_state[0:2],refer_path)

    
    # 当计算出来的目标临近点索引大于等于参考轨迹上的最后一个点索引时
    if current_target_index>=len(refer_path):  
        current_target_index=len(refer_path)-1 
        current_ref_point = refer_path[-1] 
        psi_t = refer_path_psi[-1]
    else:
        current_ref_point=refer_path[current_target_index]
        psi_t = refer_path_psi[current_target_index]
    
    # 计算横向误差e_y,参考自https://blog.csdn.net/renyushuai900/article/details/98460758
    if(robot_state[0]-current_ref_point[0])*psi_t-(robot_state[1]-current_ref_point[1])>0:

        e_y=np.linalg.norm(robot_state[0:2]-current_ref_point)
    else:
        e_y = -np.linalg.norm(robot_state[0:2]-current_ref_point)


    # 通过公式(5)计算转角,符号保持一致
    psi = robot_state[2]
    v = robot_state[3]
    # psi_t的计算我看还有直接这么计算的
    # psi_t = math.atan2(current_ref_point[1]-robot_state[1],current_ref_point[0]-robot_state[0])
    theta_e = psi_t-psi
    delta_e = math.atan2(k*e_y,v)
    delta = normalize_angle(theta_e+delta_e)
    return delta,current_target_index

2.6 主函数



from celluloid import Camera # 保存动图时用,pip install celluloid
# set reference trajectory
refer_path = np.zeros((1000, 2))
refer_path[:, 0] = np.linspace(0, 100, 1000)  # 直线
refer_path[:, 1] = 2*np.sin(refer_path[:, 0]/3.0) +  2.5*np.cos(refer_path[:, 0]/2.0)  # 生成正弦轨迹
refer_path_psi = [math.atan2(refer_path[i+1,1]-refer_path[i,1],refer_path[i+1,0]-refer_path[i,0]) for i in range(len(refer_path)-1)] # 参考轨迹上点的切线方向的角度,近似计算

# 运动学模型
ugv = KinematicModel_3(x_0, y_0, psi_0, v, L, dt)

x_ = []
y_ = []
fig = plt.figure(1)
# 保存动图用
camera = Camera(fig)
# plt.ylim([-3,3])
for i in range(500):
    robot_state = np.zeros(4)
    robot_state[0] = ugv.x
    robot_state[1] = ugv.y
    robot_state[2]=ugv.psi
    robot_state[3]=ugv.v


    delta,ind = stanley_control(robot_state,refer_path,refer_path_psi)

    ugv.update_state(0, delta)  # 加速度设为0,恒速

    x_.append(ugv.x)
    y_.append(ugv.y)

    # 显示动图
    plt.cla()
    plt.plot(refer_path[:, 0], refer_path[:, 1], '-.b', linewidth=1.0)
    plt.plot(x_, y_, "-r", label="trajectory")
    plt.plot(refer_path[ind,0], refer_path[ind,1], "go", label="target")
    # plt.axis("equal")
    plt.grid(True)
    plt.pause(0.001)
#     camera.snap()
# animation = camera.animate()
# animation.save('trajectory.gif')
plt.figure(2)
plt.plot(refer_path[:, 0], refer_path[:, 1], '-.b', linewidth=1.0)
plt.plot(x_, y_, 'r')
plt.show()


跟踪正弦曲线,效果如下:
在这里插入图片描述

跟踪非正弦曲线,效果如下:
在这里插入图片描述

可见效果不是很好,猜测原因有三:一个原因可能是控制增益没有调好;第二,也可能因为我计算目标路点的切线角度(航向角 ψ t \psi_t ψt?)时用的是近似计算的;最后,有可能因为我计算横向误差时方式不太对,代码中的横向误差计算是参考别人的,目前也不是很理解那个判断是怎么来的,有知道怎么计算横向误差的朋友们可以在评论区解个惑呀。谢谢~~

跟踪直线的效果还是挺好的
在这里插入图片描述

完整python代码文件见github仓库

3. 小结:Pure pursuit与Stanley 算法简单对比

Pure pursuit 与 Stanley 两个方法都是基于对前轮转角进行控制来消除横向误差。

Pure pursuit算法的关键在于预瞄点的选取:其距离越短,控制精度越高,但可能会产生震荡;预瞄距离越长,控制效果趋于平滑,震荡减弱。实际调试只需根据上述规则以及应用需求调整预瞄系数即可。

Stanley 算法的控制效果取决于控制增益,它缺少Pure pursuit算法的规律性,调试时需要花一定精力去寻找合适的控制增益值。

  人工智能 最新文章
2022吴恩达机器学习课程——第二课(神经网
第十五章 规则学习
FixMatch: Simplifying Semi-Supervised Le
数据挖掘Java——Kmeans算法的实现
大脑皮层的分割方法
【翻译】GPT-3是如何工作的
论文笔记:TEACHTEXT: CrossModal Generaliz
python从零学(六)
详解Python 3.x 导入(import)
【答读者问27】backtrader不支持最新版本的
上一篇文章           查看所有文章
加:2022-05-25 11:36:15  更:2022-05-25 11:37:51 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 4:39:24-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码