Lattice Planner从入门到放弃

news2024/9/21 18:55:29

Lattice Planner相关背景和更正式的公式推导可以直接参考其原始论文《Optimal Trajectory Generation for Dynamic Street Scenarios in a Frenét Frame》(ICRA 2010),本文侧重于Lattic planner理论和代码的结合。

1. Lattice Planner基本流程

Lattice Planner算法(含轨迹跟踪算法)的基本流程如下所示:
在这里插入图片描述

  1. 在笛卡尔坐标系中获取车辆的全局规划路径点(包括起始点和终点)的坐标,如有必要可以按照固定距离重新采样,并进一步计算其角度和曲率的信息;
  2. 将上述全局路径作为在Frenet坐标系中进行局部规划的参考线(即坐标轴),转化至Frenet坐标系后获取自车当前在Frenet坐标系下的坐标;
  3. 基于上述信息,进行局部轨迹采样,其中构成轨迹需要横纵向解耦,即需要Frenet坐标系中横向( d , d ˙ , d ¨ d, \dot{d}, \ddot{d} d,d˙,d¨等,也可以用符号 l l l表示横向),纵向( s , s ˙ , s ¨ s,\dot{s}, \ddot{s} s,s˙,s¨等)和时间尺度( t t t)的信息。横向上基于状态空间(即离散化采点两倍最大道路宽度),局部轨迹的预测时间(例如后续2s-3s的轨迹并进行离散化采点)进行采样,纵向上基于速度和时间序列进行采样,并根据区间起始点终止点的边界条件分别构造横向(五次多项式)和纵向(四次多项式)轨迹;
  4. 将上述轨迹重新转化为笛卡尔坐标系,并重新计算角度和曲率等信息,利用预先设定的损失函数、车辆约束条件(最大速度,最大加速度等)、避障所需障碍物信息筛选出最优的局部轨迹;
  5. 控制部分:横向根据LQR控制器进行位姿跟踪,纵向根据PID控制器进行速度跟踪;

2. 重要模块所涉及的相关理论知识

2.1. Frenet与笛卡尔坐标系相互转化

显式转化:利用公式

显示的Frenet与笛卡尔坐标系的相互转化公式推导很复杂,这里可以参考up老王的视频进一步学习(其主要思路是利用向量法进行推导),本文只贴出视频中二者的转换公式结论:

直角坐标系转化为Frenet坐标系
在这里插入图片描述
在这里插入图片描述

Frenet坐标系转化为直角坐标系(仅包括转化所需要的输入输出)
在这里插入图片描述

利用三次样条曲线(Cubic Spline)进行隐式求解

本文所介绍的代码实现版本中使用cubic spline进行隐式的坐标转换。本节先对cubic spline中使用natural spline边界条件的方法进行详细推导,内容参考该博客。具体如何实现cubic spline进行Frenet与笛卡尔坐标系的转换见代码实现部分。
在这里插入图片描述用cubic spline进行Frenet到笛卡尔坐标系转化的思路是:分别将笛卡尔坐标系中的 x , y x,y x,y坐标信息当作为关于Frenet坐标系中 s s s(即每个坐标对应的行驶距离)的函数,即 x = f ( s ) , y = g ( s ) x=f(s),y=g(s) x=f(s),y=g(s) f ( ⋅ ) , g ( ⋅ ) f(\cdot), g(\cdot) f(),g()为不同参数下构造出的cubic spline,后续Frenet转化为Cartesian的过程则使用上述函数进行插值求解。上图以笛卡尔坐标系下坐标轴 x x x为例,展示了该方向的离散点。离散化后的自变量区间为 [ ( s 0 , s 1 ) , ( s 1 , s 2 ) , … , ( s n − 1 , s n ) ] [(s_0,s_1),(s_1,s_2),\dots, (s_{n-1},s_n)] [(s0,s1),(s1,s2),,(sn1,sn)],总共 n n n个区间, 4 n 4n 4n个参数, 4 n 4n 4n个边界条件。第 i i i个区间对应的cubic spline函数记为 S i ( s ) S_i(s) Si(s),其本身、一二阶导数公式如下:
{ S i ( s ) = a i + b i ( s − s i ) + c i ( s − s i ) 2 + d i ( s − s i ) 3 S i ′ ( s ) = b i + 2 c i ( s − s i ) + 3 d i ( s − s i ) 2 S i ′ ′ ( s ) = 2 c i + 6 d i ( s − s i ) \begin{cases} S_i(s)=a_i+b_i(s-s_i)+c_i(s-s_i)^2+d_i(s-s_i)^3 \\ S_i'(s)= b_i+2c_i(s-s_i)+3d_i(s-s_i)^2 \\ S_i''(s)= 2c_i+6d_i(s-s_i) \end{cases} Si(s)=ai+bi(ssi)+ci(ssi)2+di(ssi)3Si(s)=bi+2ci(ssi)+3di(ssi)2Si′′(s)=2ci+6di(ssi)
所需要的边界条件包括:

  1. 所有点满足区间的边界插值点条件,除去端点中间 n − 1 n-1 n1个点对应 2 ( n − 1 ) 2(n-1) 2(n1)个条件,加上2个端点总共 2 n 2n 2n个条件,公式为: S i ( s i ) = x i , ( i = 0 , 1 , … , n − 1 ) S_i(s_i)=x_i,(i=0,1,\dots, n-1) Si(si)=xi,(i=0,1,,n1) S i ( s i + 1 ) = x i + 1 , ( i = 0 , 1 , … , n − 1 ) S_i(s_{i+1})=x_{i+1}, (i=0,1,\dots, n-1) Si(si+1)=xi+1,(i=0,1,,n1)
  2. n − 1 n-1 n1个内部点的一阶导数应该是连续的,即 S i ′ ( s i + 1 ) = S i + 1 ′ ( s i + 1 ) , ( i = 0 , 1 , … , n − 2 ) S_i'(s_{i+1})=S_{i+1}'(s_{i+1}),(i=0,1,\dots, n-2) Si(si+1)=Si+1(si+1),(i=0,1,,n2),总共 n − 1 n-1 n1个条件;
  3. n − 1 n-1 n1个内部点的二阶导数连续,即 S i ′ ′ ( s i + 1 ) = S i + 1 ′ ′ ( s i + 1 ) , ( i = 0 , 1 , … , n − 2 ) S_i''(s_{i+1})=S_{i+1}''(s_{i+1}),(i=0,1,\dots, n-2) Si′′(si+1)=Si+1′′(si+1),(i=0,1,,n2),总共 n − 1 n-1 n1个条件;
  4. 上述总共是 4 n − 2 4n-2 4n2个条件,剩下的2个条件有三种方式,本文采用其中的natural spline方式,即 S 0 ′ ′ ( s 0 ) = 0 ,   S n − 1 ( s n ) = 0 S_0''(s_0)=0,\ S_{n-1}(s_n)=0 S0′′(s0)=0, Sn1(sn)=0

将上述边界条件带入cubic spline函数和其导数的公式中,进行求解:

  • 对于边界条件1的第一个条件,可得 S i ( s i ) = a i = x i S_i(s_i)=a_i=x_i Si(si)=ai=xi
  • 对于边界条件1的第二个条件,令 h i = s i + 1 − s i h_i=s_{i+1}-s_i hi=si+1si S i ( s i + 1 ) = a i + b i h i + c i h i 2 + d i h i 3 = x i + 1 S_i(s_{i+1})=a_i+b_ih_i+c_ih_i^2+d_ih_i^3=x_{i+1} Si(si+1)=ai+bihi+cihi2+dihi3=xi+1
  • 对于上述边界条件2,等式左边为 S i ′ ( s i + 1 ) = b i + 2 c i h i + 3 d i h i 2 S_i'(s_{i+1})=b_i+2c_ih_i+3d_ih_i^2 Si(si+1)=bi+2cihi+3dihi2,等式右边的 S i + 1 ′ ( s ) = b i + 2 c i ( s − s i + 1 ) + 3 d i ( s − s i + 1 ) 2 S_{i+1}'(s)=b_i+2c_i(s-s_{i+1})+3d_i(s-s_{i+1})^2 Si+1(s)=bi+2ci(ssi+1)+3di(ssi+1)2,因此代入 s i + 1 s_{i+1} si+1可得: S i + 1 ′ ( s i + 1 ) = b i + 1 S_{i+1}'(s_{i+1})=b_{i+1} Si+1(si+1)=bi+1,整个边界条件2构成的公式为: b i + 2 c i h i + 3 d i h i 2 = b i + 1 b_i+2c_ih_i+3d_ih_i^2=b_{i+1} bi+2cihi+3dihi2=bi+1
  • 同理,对于边界条件3可得公式: 2 c i + 6 d i h i = 2 c i + 1 2c_i+6d_ih_i=2c_{i+1} 2ci+6dihi=2ci+1
  • 根据natural spline的条件可得 c 0 = 0 , c n = 0 c_0=0, c_n=0 c0=0,cn=0

整理一下上述公式:
{ a i = x i ( 1 ) b i h i + c i h i 2 + d i h i 3 = x i + 1 − x i ( 2 ) b i + 2 c i h i + 3 d i h i 2 = b i + 1 ( 3 ) c i + 3 d i h i = c i + 1 ( 4 ) \begin{cases} a_i=x_i & (1)\\ b_ih_i+c_ih_i^2+d_ih_i^3=x_{i+1}-x_i & (2) \\ b_i+2c_ih_i+3d_ih_i^2=b_{i+1} & (3)\\ c_i+3d_ih_i=c_{i+1} & (4) \end{cases} ai=xibihi+cihi2+dihi3=xi+1xibi+2cihi+3dihi2=bi+1ci+3dihi=ci+1(1)(2)(3)(4)

接下来先将参数 b i , d i b_i,d_i bi,di转化成与 c i c_i ci相关的公式,先整理上述公式 ( 4 ) (4) (4) d i d_i di,再得到 b i , c i b_i,c_i bi,ci之间的关系,可得:
{ d i = c i + 1 − c i 3 h i b i = x i + 1 − x i h i − c i h i − h i 3 ( c i + 1 − c i ) \begin{cases} d_i=\frac{c_{i+1}-c_i}{3h_i}\\ b_i=\frac{x_{i+1}-x_{i}}{h_i}-c_ih_i-\frac{h_i}{3}(c_{i+1}-c_i) \\ \end{cases} {di=3hici+1cibi=hixi+1xicihi3hi(ci+1ci)

再将所得公式代入上述公式 ( 3 ) (3) (3)可得:
h i c i + 2 ( h i + h i + 1 ) c i + 1 + h i + 1 c i + 2 = 3 ( x i + 2 − x i + 1 h i + 1 − x i + 1 − x i h i ) , i = 0 , 1 , … , n − 2 h_ic_i+2(h_i+h_{i+1})c_{i+1}+h_{i+1}c_{i+2}=3\left( \frac{x_{i+2}-x_{i+1}}{h_{i+1}}-\frac{x_{i+1}-x_{i}}{h_{i}}\right),i=0,1,\dots,n-2 hici+2(hi+hi+1)ci+1+hi+1ci+2=3(hi+1xi+2xi+1hixi+1xi),i=0,1,,n2
此时算上natural spline的条件 c 0 = 0 , c n = 0 c_0=0, c_n=0 c0=0,cn=0可以得到所有 n n n个关于 c c c的方程:
{ c 0 = 0 , h 0 c 0 + 2 ( h 0 + h 1 ) c 1 + h 1 c 2 = 3 ( x 2 − x 1 h 1 − x 1 − x 0 h 0 ) , h 1 c 1 + 2 ( h 1 + h 2 ) c 2 + h 2 c 3 = 3 ( x 3 − x 2 h 2 − x 2 − x 1 h 1 ) , … h n − 2 c n − 2 + 2 ( h n − 2 + h n − 1 ) c n − 1 + h n − 1 c n = 3 ( x n − x n − 1 h n − 1 − x n − 1 − x n − 2 h n − 2 ) , c n = 0 \begin{cases} c_0=0,\\ h_0c_0+2(h_0+h_{1})c_{1}+h_{1}c_{2}=3\left( \frac{x_{2}-x_{1}}{h_{1}}-\frac{x_{1}-x_{0}}{h_{0}}\right), \\ h_1c_1+2(h_1+h_{2})c_{2}+h_{2}c_{3}=3\left( \frac{x_{3}-x_{2}}{h_{2}}-\frac{x_{2}-x_{1}}{h_{1}}\right), \\ \dots \\ h_{n-2}c_{n-2}+2(h_{n-2}+h_{n-1})c_{n-1}+h_{n-1}c_{n}=3\left( \frac{x_{n}-x_{n-1}}{h_{n-1}}-\frac{x_{n-1}-x_{n-2}}{h_{n-2}}\right), \\ c_n=0\\ \end{cases} c0=0,h0c0+2(h0+h1)c1+h1c2=3(h1x2x1h0x1x0),h1c1+2(h1+h2)c2+h2c3=3(h2x3x2h1x2x1),hn2cn2+2(hn2+hn1)cn1+hn1cn=3(hn1xnxn1hn2xn1xn2),cn=0
整理成矩阵的形式为:

[ 1 0 0 ⋯ 0 h 0 2 ( h 0 + h 1 ) h 1 0 0 h 1 2 ( h 1 + h 2 ) h 2 0 0 0 h 2 2 ( h 2 + h 3 ) h 3 ⋮ ⋮ ⋱ ⋱ ⋱ 0 ⋯ 0 h n − 2 2 ( h n − 2 + h n − 1 ) h n − 1 0 0 0 ⋯ ⋯ 1 ] ⋅ [ c 0 c 1 c 2 ⋮ c n − 1 c n ] = 3 [ 0 x 2 − x 1 h 1 − x 1 − x 0 h 0 x 3 − x 2 h 2 − x 2 − x 1 h 1 ⋮ x n − x n − 1 h n − 1 − x n − 1 − x n − 2 h n − 2 0 ] = 0 \begin{bmatrix} 1&0&0 & &\cdots & 0\\ h_{0}&2(h_{0}+h_{1})&h_{1} &0& \\ 0 & h_{1}&2(h_{1}+h_{2})&h_{2} &0 &\\ 0 & 0& h_{2}&2(h_{2}+h_{3})&h_{3} & \vdots\\ \vdots & & \quad\ddots&\quad\ddots &\quad\ddots& \\ \\ 0 &\cdots & 0& h_{n-2}&2(h_{n-2}+h_{n-1})&h_{n-1}\\ 0&0&0 &\cdots &\cdots &1 \end{bmatrix} \cdot\begin{bmatrix} c_0 \\ c_1\\ c_2\\ \vdots \\ c_{n-1} \\ c_n \end{bmatrix}= 3\begin{bmatrix} 0\\ \frac{x_{2}-x_{1}}{h_{1}}-\frac{x_{1}-x_{0}}{h_{0}}\\ \frac{x_{3}-x_{2}}{h_{2}}-\frac{x_{2}-x_{1}}{h_{1}}\\ \vdots\\ \frac{x_{n}-x_{n-1}}{h_{n-1}}-\frac{x_{n-1}-x_{n-2}}{h_{n-2}}\\ 0 \end{bmatrix}=0 1h0000002(h0+h1)h1000h12(h1+h2)h2000h22(h2+h3)hn20h32(hn2+hn1)0hn11 c0c1c2cn1cn =3 0h1x2x1h0x1x0h2x3x2h1x2x1hn1xnxn1hn2xn1xn20 =0

因此所有参数都可以由 c i c_i ci推导出:
{ a i = x i b i = x i + 1 − x i h i − c i h i − h i 3 ( c i + 1 − c i ) d i = c i + 1 − c i 3 h i \begin{cases} a_i=x_i \\ b_i=\frac{x_{i+1}-x_{i}}{h_i}-c_ih_i-\frac{h_i}{3}(c_{i+1}-c_i)\\ d_i=\frac{c_{i+1}-c_i}{3h_i}\\ \end{cases} ai=xibi=hixi+1xicihi3hi(ci+1ci)di=3hici+1ci

2.2 局部轨迹采样(横向与纵向)

2.2.1 横向采样

总体思路:横向采样可以在状态空间或控制空间进行采样,本文介绍在状态空间中,即利用结构化道路的最大宽度,进行横向采样的方法。采样点示意图如下所示:

在这里插入图片描述

根据已有的采样点,还需要将点与点之间连接起来构成横向轨迹,采用的方法是给定边界条件后根据五次多项式进行连接,五次多项式的公式和一二阶导数公式如下:

{ d ( t ) = a 5 t 5 + a 4 t 4 + a 3 t 3 + a 2 t 2 + a 1 t + a 0 d ˙ ( t ) = 5 a 5 t 4 + 4 a 4 t 3 + 3 a 3 t 2 + 2 a 2 t + a 1 d ¨ ( t ) = 20 a 5 t 3 + 12 a 4 t 2 + 6 a 3 t + 2 a 2 \begin{cases} d(t)=a_5t^5+a_4t^4+a_3t^3+a_2t^2+a_1t+a_0 \\ \dot{d}(t)=5a_5t^4+4a_4t^3+3a_3t^2+2a_2t+a_1\\ \ddot{d}(t)=20a_5t^3+12a_4t^2+6a_3t+2a_2 \end{cases} d(t)=a5t5+a4t4+a3t3+a2t2+a1t+a0d˙(t)=5a5t4+4a4t3+3a3t2+2a2t+a1d¨(t)=20a5t3+12a4t2+6a3t+2a2

求解五次多项式系数的思路是通过边界条件求解方程中的参数(6个边界条件组成6个方程组以求解6个参数),区间 [ 0 , T i ] [0,T_i] [0,Ti]的起始点、终止点边界条件分别有三个,包括位置、速度、加速度条件,分别记为 d ( 0 ) = x s , d ˙ ( 0 ) = v s , d ¨ ( 0 ) = a s d(0)=x_s,\dot{d}(0)=v_s,\ddot{d}(0)=a_s d(0)=xs,d˙(0)=vs,d¨(0)=as d ( T i ) = x e , d ˙ ( T i ) = v e , d ¨ ( T i ) = a e d(T_i)=x_e,\dot{d}(T_i)=v_e,\ddot{d}(T_i)=a_e d(Ti)=xe,d˙(Ti)=ve,d¨(Ti)=ae,整理可得:
{ d ( 0 ) = x s = a 0 , d ˙ ( 0 ) = v s = a 1 , d ¨ ( 0 ) = a s = 2 a 2 → a 2 = a s 2 d ( T i ) = a 5 T i 5 + a 4 T i 4 + a 3 T i 3 + a 2 T i 2 + a 1 T i + a 0 = x e , d ˙ ( T i ) = 5 a 5 T i 4 + 4 a 4 T i 3 + 3 a 3 T i 2 + 2 a 2 T i + a 1 = v e , d ¨ ( T i ) = 20 a 5 T i 3 + 12 a 4 T i 2 + 6 a 3 T i + 2 a 2 = a e \begin{cases} d(0)=x_s=a_0,\\ \dot{d}(0)=v_s=a_1,\\ \ddot{d}(0)=a_s=2a_2 \to a_2=\frac{a_s}{2}\\ d(T_i)=a_5T_i^5+a_4T_i^4+a_3T_i^3+a_2T_i^2+a_1T_i+a_0=x_e,\\ \dot{d}(T_i)=5a_5T_i^4+4a_4T_i^3+3a_3T_i^2+2a_2T_i+a_1=v_e,\\ \ddot{d}(T_i)=20a_5T_i^3+12a_4T_i^2+6a_3T_i+2a_2=a_e \end{cases} d(0)=xs=a0,d˙(0)=vs=a1,d¨(0)=as=2a2a2=2asd(Ti)=a5Ti5+a4Ti4+a3Ti3+a2Ti2+a1Ti+a0=xe,d˙(Ti)=5a5Ti4+4a4Ti3+3a3Ti2+2a2Ti+a1=ve,d¨(Ti)=20a5Ti3+12a4Ti2+6a3Ti+2a2=ae

整理成矩阵形式:
[ T i 3 T i 4 T i 5 3 T i 2 4 T i 3 5 T i 4 6 T i 12 T i 2 20 T i 3 ] ⋅ [ a 3 a 4 a 5 ] = [ x e − a 0 − a 1 T i − a 2 T i 2 v e − a 1 − 2 a 2 T i a e − 2 a 2 ] \begin{bmatrix} T_i^3 & T_i^4 & T_i^5\\ 3T_i^2 & 4T_i^3 & 5T_i^4\\ 6T_i & 12T_i^2 & 20T_i^3 \end{bmatrix} \cdot\begin{bmatrix} a_3 \\ a_4 \\a_5 \end{bmatrix} =\begin{bmatrix} x_e-a_0-a_1T_i-a_2T_i^2\\ v_e-a_1-2a_2T_i\\ a_e-2a_2 \end{bmatrix} Ti33Ti26TiTi44Ti312Ti2Ti55Ti420Ti3 a3a4a5 = xea0a1Tia2Ti2vea12a2Tiae2a2

最终用eigen库求解得到对应的参数即可。

2.2.2 纵向采样

总体思路:纵向采样与横向采样类似,只不过是依据给定的目标速度进行采样,点与点之间的连接方式则采用四次多项式。四次多项式的公式和一二阶导数公式如下:

{ s ( t ) = a 4 t 4 + a 3 t 3 + a 2 t 2 + a 1 t + a 0 s ˙ ( t ) = 4 a 4 t 3 + 3 a 3 t 2 + 2 a 2 t + a 1 s ¨ ( t ) = 12 a 4 t 2 + 6 a 3 t + 2 a 2 \begin{cases} s(t)=a_4t^4+a_3t^3+a_2t^2+a_1t+a_0 \\ \dot{s}(t)=4a_4t^3+3a_3t^2+2a_2t+a_1\\ \ddot{s}(t)=12a_4t^2+6a_3t+2a_2 \end{cases} s(t)=a4t4+a3t3+a2t2+a1t+a0s˙(t)=4a4t3+3a3t2+2a2t+a1s¨(t)=12a4t2+6a3t+2a2

求解四次多项式系数的思路与上述五次多项式相同,用5个边界条件组成的5个方程组以求解5个参数,区间 [ 0 , T i ] [0,T_i] [0,Ti]的起始点边界条件分别有三个,包括位置、速度、加速度条件,分别记为 s ( 0 ) = x s , s ˙ ( 0 ) = v s , s ¨ ( 0 ) = a s s(0)=x_s,\dot{s}(0)=v_s,\ddot{s}(0)=a_s s(0)=xs,s˙(0)=vs,s¨(0)=as,终止点的边界条件只需要两个: s ˙ ( T i ) = v e , s ¨ ( T i ) = a e \dot{s}(T_i)=v_e,\ddot{s}(T_i)=a_e s˙(Ti)=ve,s¨(Ti)=ae,整理可得:
{ s ( 0 ) = x s = a 0 , s ˙ ( 0 ) = v s = a 1 , s ¨ ( 0 ) = a s = 2 a 2 → a 2 = a s 2 s ˙ ( T i ) = 4 a 4 T i 3 + 3 a 3 T i 2 + 2 a 2 T i + a 1 = v e , s ¨ ( T i ) = 12 a 4 T i 2 + 6 a 3 T i + 2 a 2 = a e \begin{cases} s(0)=x_s=a_0,\\ \dot{s}(0)=v_s=a_1,\\ \ddot{s}(0)=a_s=2a_2 \to a_2=\frac{a_s}{2}\\ \dot{s}(T_i)=4a_4T_i^3+3a_3T_i^2+2a_2T_i+a_1=v_e,\\ \ddot{s}(T_i)=12a_4T_i^2+6a_3T_i+2a_2=a_e \end{cases} s(0)=xs=a0,s˙(0)=vs=a1,s¨(0)=as=2a2a2=2ass˙(Ti)=4a4Ti3+3a3Ti2+2a2Ti+a1=ve,s¨(Ti)=12a4Ti2+6a3Ti+2a2=ae

整理成矩阵形式:
[ 3 T i 2 4 T i 3 6 T i 12 T i 2 ] ⋅ [ a 3 a 4 ] = [ v e − a 1 − 2 a 2 T i a e − 2 a 2 ] \begin{bmatrix} 3T_i^2 & 4T_i^3\\ 6T_i & 12T_i^2 \end{bmatrix} \cdot\begin{bmatrix} a_3 \\ a_4 \end{bmatrix} =\begin{bmatrix} v_e-a_1-2a_2T_i\\ a_e-2a_2 \end{bmatrix} [3Ti26Ti4Ti312Ti2][a3a4]=[vea12a2Tiae2a2]

最终用eigen库求解得到对应的参数即可。

2.3 筛选最优局部轨迹

筛选最优局部轨迹依靠最小化损失函数,并判断轨迹是否满足约束条件(车辆的最大速度、最大加速度、最大曲率)与避障条件,其中损失函数构造包括三个部分,代码实现中的损失函数形式为:
{ Lateral : C d = k j J t ( d ( t ) ) + k t T + k d d 2 Longitudinal : C v = k j J t ( s ( t ) ) + k t T + k d s 2 Total : C f = k l a t C d + k l o n g C v \begin{cases} \textbf{Lateral}:\quad C_d=k_jJ_t(d(t))+k_tT+k_d d^2\\ \textbf{Longitudinal}:\quad C_v=k_jJ_t(s(t))+k_tT+k_d s^2\\ \textbf{Total}:\quad C_f=k_{lat}C_d+k_{long}C_v\end{cases} Lateral:Cd=kjJt(d(t))+ktT+kdd2Longitudinal:Cv=kjJt(s(t))+ktT+kds2Total:Cf=klatCd+klongCv

3. 代码实现

本部分内容主要介绍深蓝学院《自动驾驶控制与规划》课程作业中的代码;首先看一下文件结构:

在这里插入图片描述
最重要的文件是frenet_optimal_trajectory.cpppath_planning_node.cpp,包含了lattice planner算法的核心内容。接下来主要介绍两部分,一是利用cubic spline进行Frenet与笛卡尔坐标系的转化,二是局部轨迹采样的实现。

3.0 代码运行环境配置及目前运行效果

运行环境:Ubuntu 20.04, ROS1, Carla-ROS-bridge, CARLA 0.9.11, C++;
运行指令的顺序:先启动CARLA-ROS-bridge节点,再启动lattice planner;
代码运行截图如下图:
在这里插入图片描述

3.1 Cubic Spline实现Frenet到笛卡尔坐标系的转化

转化思路参考第二部分,代码实现部分就是创建一个类,在对象初始化的时候根据矩阵形式用eigen库求解对应的参数,之后若给定随意的自变量,利用二分查找的方式找到该值所在的区间,之后根据cubic spline公式求解即可。

class Spline {
 public:
  Vec_f x;
  Vec_f y;
  int nx;
  Vec_f h;
  Vec_f a;
  Vec_f b;
  Vec_f c;
  // Eigen::VectorXf c;
  Vec_f d;

  Spline(){};
  // d_i * (x-x_i)^3 + c_i * (x-x_i)^2 + b_i * (x-x_i) + a_i
  Spline(Vec_f x_, Vec_f y_)
      : x(x_), y(y_), nx(x_.size()), h(vec_diff(x_)), a(y_) {
    Eigen::MatrixXf A = calc_A();
    Eigen::VectorXf B = calc_B();
    Eigen::VectorXf c_eigen = A.colPivHouseholderQr().solve(B);
    float* c_pointer = c_eigen.data();
    // Eigen::Map<Eigen::VectorXf>(c, c_eigen.rows(), 1) = c_eigen;
    c.assign(c_pointer, c_pointer + c_eigen.rows());

    for (int i = 0; i < nx - 1; i++) {
      d.push_back((c[i + 1] - c[i]) / (3.0 * h[i]));
      b.push_back((a[i + 1] - a[i]) / h[i] -
                  h[i] * (c[i + 1] + 2 * c[i]) / 3.0);
    }
  };

  float calc(float t) {
    if (t < x.front() || t > x.back()) {
      throw std::invalid_argument(
          "received value out of the pre-defined range");
    }
    int seg_id = bisect(t, 0, nx);
    float dx = t - x[seg_id];
    return a[seg_id] + b[seg_id] * dx + c[seg_id] * dx * dx +
           d[seg_id] * dx * dx * dx;
  };

  float calc_d(float t) {
    if (t < x.front() || t > x.back()) {
      throw std::invalid_argument(
          "received value out of the pre-defined range");
    }
    int seg_id = bisect(t, 0, nx - 1);
    float dx = t - x[seg_id];
    return b[seg_id] + 2 * c[seg_id] * dx + 3 * d[seg_id] * dx * dx;
  }

  float calc_dd(float t) {
    if (t < x.front() || t > x.back()) {
      throw std::invalid_argument(
          "received value out of the pre-defined range");
    }
    int seg_id = bisect(t, 0, nx);
    float dx = t - x[seg_id];
    return 2 * c[seg_id] + 6 * d[seg_id] * dx;
  }

 private:
  Eigen::MatrixXf calc_A() {
    Eigen::MatrixXf A = Eigen::MatrixXf::Zero(nx, nx);
    A(0, 0) = 1;
    for (int i = 0; i < nx - 1; i++) {
      if (i != nx - 2) {
        A(i + 1, i + 1) = 2 * (h[i] + h[i + 1]);
      }
      A(i + 1, i) = h[i];
      A(i, i + 1) = h[i];
    }
    A(0, 1) = 0.0;
    A(nx - 1, nx - 2) = 0.0;
    A(nx - 1, nx - 1) = 1.0;
    return A;
  };
  Eigen::VectorXf calc_B() {
    Eigen::VectorXf B = Eigen::VectorXf::Zero(nx);
    for (int i = 0; i < nx - 2; i++) {
      B(i + 1) = 3.0 * (a[i + 2] - a[i + 1]) / h[i + 1] -
                 3.0 * (a[i + 1] - a[i]) / h[i];
    }
    return B;
  };

  int bisect(float t, int start, int end) {
    int mid = (start + end) / 2;
    if (t == x[mid] || end - start <= 1) {
      return mid;
    } else if (t > x[mid]) {
      return bisect(t, mid, end);
    } else {
      return bisect(t, start, mid);
    }
  }
};

二维的spline代码实现如下:

class Spline2D {
 public:
  Spline sx;
  Spline sy;
  Vec_f s;

  Spline2D(Vec_f x, Vec_f y) {
    s = calc_s(x, y);
    sx = Spline(s, x);
    sy = Spline(s, y);
  };

  Poi_f calc_postion(float s_t) {
    float x = sx.calc(s_t);
    float y = sy.calc(s_t);
    return {{x, y}};
  };

  float calc_curvature(float s_t) {
    float dx = sx.calc_d(s_t);
    float ddx = sx.calc_dd(s_t);
    float dy = sy.calc_d(s_t);
    float ddy = sy.calc_dd(s_t);
    return (ddy * dx - ddx * dy) / (dx * dx + dy * dy);
  };

  float calc_yaw(float s_t) {
    float dx = sx.calc_d(s_t);
    float dy = sy.calc_d(s_t);
    return std::atan2(dy, dx);
  };

 private:
  Vec_f calc_s(Vec_f x, Vec_f y) {
    Vec_f ds;
    Vec_f out_s{0};
    Vec_f dx = vec_diff(x);
    Vec_f dy = vec_diff(y);

    for (unsigned int i = 0; i < dx.size(); i++) {
      ds.push_back(std::sqrt(dx[i] * dx[i] + dy[i] * dy[i]));
    }

    Vec_f cum_ds = cum_sum(ds);
    out_s.insert(out_s.end(), cum_ds.begin(), cum_ds.end());
    return out_s;
  };
};

3.2 局部轨迹采样(横向与纵向) 实现

代码实现中的局部轨迹采样如下代码所示:

FrenetPath FrenetOptimalTrajectory::frenet_optimal_planning(
    Spline2D csp, const FrenetInitialConditions& frenet_init_conditions,
    Vec_Poi ob) {
  // 01 获取采样轨迹数组
    Vec_Path fp_list = calc_frenet_paths(c_speed, c_d, c_d_d, c_d_dd, s0);
  // 02 根据参考轨迹与采样的轨迹数组,计算frenet中的其他曲线参数,如航向角,曲率,ds等参数
    calc_global_paths(fp_list, csp);
  // 03 检查路径,通过限制做大速度,最大加速度,最大曲率与避障,选取可使用的轨迹数组
    Vec_Path save_paths = check_paths(fp_list, ob);
    float min_cost = numeric_limits<float>::max();
    FrenetPath final_path;
    for (auto path:save_paths){
      if (path.cf <= min_cost){
          min_cost = path.cf;
          final_path = path;
      }
    }
    return final_path;
}

其中子函数calc_frenet_paths包含了轨迹采样的核心逻辑,其代码由两部分组成,横向和纵向采样,此外,两个方向的采样都需要进行时间轴采样:

Vec_Path FrenetOptimalTrajectory::calc_frenet_paths(float c_speed, float c_d,
                                                    float c_d_d, float c_d_dd,
                                                    float s0) {
  std::vector<FrenetPath> fp_list;

  // 对横向位移 d 进行采样
  for (float di = -1 * MAX_ROAD_WIDTH; di < MAX_ROAD_WIDTH; di += D_ROAD_W) { // sample every 1.0m
    // 对纵向时间序列采样
    for (float Ti = MINT; Ti < MAXT; Ti += DT) { //[2.0, 3.0, 0.2]
      // 当 (di,Ti) 确定后,可获得一条连接当前状态与 (di, Ti) 的五次多项式轨迹曲线
      FrenetPath fp;
      QuinticPolynomial lat_qp(c_d, c_d_d, c_d_dd, di, 0.0, 0.0, Ti);

      // 记录离散时间下对应的轨迹点
      for (float t = 0; t < Ti; t += DT) {
        fp.t.push_back(t);
        fp.d.push_back(lat_qp.calc_point(t));
        fp.d_d.push_back(lat_qp.calc_first_derivative(t));
        fp.d_dd.push_back(lat_qp.calc_second_derivative(t));
        fp.d_ddd.push_back(lat_qp.calc_third_derivative(t));
      }

      // 对纵向车速进行采样
      for (float tv = TARGET_SPEED - D_T_S * N_S_SAMPLE;
           tv < TARGET_SPEED + D_T_S * N_S_SAMPLE; tv += D_T_S) {
        // 当 (vi, Ti) 确定后,可获得一条连接当前状态和 (vi, Ti) 的四次多项式轨迹曲线
        FrenetPath fp_bot = fp;
        QuarticPolynomial lon_qp(s0, c_speed, 0.0, tv, 0.0, Ti);

        // 初始化最大速度和最大加速度
        fp_bot.max_speed = std::numeric_limits<float>::min();
        fp_bot.max_accel = std::numeric_limits<float>::min();
        
        // 记录离散时间下对应的轨迹点
        for (float t_ : fp.t) {
          fp_bot.s.push_back(lon_qp.calc_point(t_));
          fp_bot.s_d.push_back(lon_qp.calc_first_derivative(t_));
          fp_bot.s_dd.push_back(lon_qp.calc_second_derivative(t_));
          fp_bot.s_ddd.push_back(lon_qp.calc_third_derivative(t_));

          // 更新最大加速度和最大速度
          if (fp_bot.s_d.back() > fp_bot.max_speed) {
            fp_bot.max_speed = fp_bot.s_d.back();
          }
          
          if (fp_bot.s_dd.back() > fp_bot.max_accel) {
            fp_bot.max_accel = fp_bot.s_dd.back();
          }
        }

        // 计算代价函数
        float Jp = sum_of_power(fp.d_ddd);      // square of jerk
        float Js = sum_of_power(fp_bot.s_ddd);  // square of jerk
        // square of diff from target speed
        float ds = (TARGET_SPEED - fp_bot.s_d.back());

        fp_bot.cd = KJ * Jp + KT * Ti + KD * std::pow(fp_bot.d.back(), 2);
        fp_bot.cv = KJ * Js + KT * Ti + KD * ds;
        fp_bot.cf = KLAT * fp_bot.cd + KLON * fp_bot.cv;

        // 将轨迹添加至候选轨迹中
        fp_list.push_back(fp_bot);
      }
    }
  }
  return fp_list;
};

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

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

相关文章

2023年6月GESP能力等级认证C++一级真题

2023-06 GESP一级真题 题数&#xff1a;27 分数&#xff1a;100 测试时长&#xff1a;60min 一、选择题(每题 2 分&#xff0c;共 30 分) 1.以下不属于计算机输入设备的有 (B ) 。&#xff08;2分&#xff09; A、键盘 B、音箱 C、鼠标 D、传感器 答案解析&#xff1…

如果你正在做AI测试,那么这十点你必须注意

AI是一个已经进入人类日常生活的新技术时代&#xff0c;例如Siri&#xff0c;Alexa语音接口等。通过大数据和数据科学实现数据存储的进步&#xff0c;使用户能够进行快速分析和数据检索。机器学习是一个新领域&#xff0c;机 AI是一个已经进入人类日常生活的新技术时代&#x…

今天我们来说说MySQL的缓存机制

原文链接&#xff1a;http://www.ibearzmblog.com/#/technology/info?id5770c555acd4302f81d86976c06e2319 前言 当我们向数据库服务器发送一条SQL的时候&#xff0c;但数据库收到后就会执行&#xff0c;但是如果在短时间内都执行同一条SQL&#xff0c;如果每次数据库都会执…

CUDA和显卡驱动以及pytorch版本的对应关系

1 支持CUDA 的GPU 支持 CUDA 的 NVIDIA Quadro 和 NVIDIA RTX CUDA GPU | NVIDIA Developer您的 GPU 计算能力 您是否正在寻找 GPU 的计算能力然后查看以下表格。您可以在这里了解更多 计算能力 。 NVIDIA GPU 为全球数百万台台式机笔记本电脑工作站和超级计算机提供动力加速…

《计算机网络——自顶向下方法》精炼——4.4.3-4.4.5

学习是终身的职业。在学习的道路上&#xff0c;谁想停下来就要落伍。 文章目录 UPnP因特网控制报文协议&#xff08;ICMP&#xff09;IPv6IPv6数据报格式IPv4到IPv6的变革 UPnP 通用即插即用&#xff08;UPnP&#xff09;提供了一种让外部网络的主机与NAT内主机交换数据的方式…

六十分之十七———低头拉车后的抬头看路

目录 前言&#xff1a;一、目标二、计划三、完成情况四、提升改进(最少3点)五、意外之喜(最少2点)六、总结 前言&#xff1a; 距离上次的月总已经过去四个多月时间了&#xff0c;这段时间里又是一次阶段性的下沉和突破&#xff0c;于是也就有了现在的总结。 此时还想将自己17…

未来10年,网络安全人才就业的黄金期

随着大数据、物联网、人工智能等新技术的发展&#xff0c;信息技术与经济社会各领域的融合也更加深入。网络攻击行为日趋复杂、黑客攻击行为组织性更强、针对手机无线终端的网络攻击日趋严重&#xff0c;近几年有关网络攻击和数据泄露的新闻层出不穷。因此&#xff0c;随着国家…

Planning-oriented Autonomous Driving 解析

abstract 现代自动驾驶系统通常是模块化的序列任务&#xff0c;这种方式很容易造成累积误差和任务协调不足&#xff0c;因此设计一个端到端架构&#xff0c;从全局的视角出发为agent的交互提供互补的特征提取。 introduction (a) Most industrial solutions deploy separate …

【QQ界面展示-获取监听到的通知的具体内容 Objective-C语言】

一、获取监听到的通知的具体内容 1.刚才这个没说啊,给大家补充一下, 我们看一下,刚才我们只说了一个通知的发布、和移除吧, 刚才我们这里,只是说了一个通知的发布、和移除吧, 以及监听, 那么,大家有没有发现, 我们一开始,给大家介绍的时候,是不是有两个对象啊,…

关于手机Camera的硬件电路知识

前阶段&#xff0c;小白教同事测了些Camere的基本功耗。正愁不知道写什么的小白&#xff0c;突然想到了素材&#xff0c;于是乎便趁着周末雷雨天宅家之际&#xff0c;写一篇关于手机Camere的硬件文章。 手机Camera 一、工作原理 关于Camera&#xff0c;景物通过镜头生成光学…

CRM软件能否代替Excel进行客户管理?

很多销售习惯使用Excel表格来管理客户&#xff0c;觉得这样既简单又方便。但随着客户的增多&#xff0c;使用Excel表格管理客户会带来很多问题。因此&#xff0c;客户管理不用Excel表格用CRM软件&#xff0c;是一种更加明智的选择。 1、统一管理数据 数据是企业的命脉。一旦客…

Python二级综合应用题:通讯录管理

Python二级考试中的最后一题一般是压轴题&#xff0c;有一定的难度&#xff0c;而且分值也高。此题一般分为1~3个问题&#xff0c;步步为赢&#xff0c;才能更好地解决。遇到这种问题&#xff0c;首先是不要怕&#xff0c;能解决几步是几步。今天我们一个通讯录管理题来说明一下…

Unbuntu下载Clion并配置Cmake工具

Clion 下载CLion安装Cmake工具 下载CLion 如果使用的是Unbuntu系统&#xff0c;一行命令就搞定了 sudo snap install clion --classic其他系统的下载方法就不在这里记载了 安装Cmake工具 刚下好Clion的时候&#xff0c;打开之后&#xff0c;如果没有Cmake工具.Clion是不会帮…

父亲节礼物:用Python编写一个小型游戏

名字&#xff1a;阿玥的小东东 学习&#xff1a;Python、C/C 主页链接&#xff1a;阿玥的小东东的博客_CSDN博客-python&&c高级知识,过年必备,C/C知识讲解领域博主 目录 安装必要的库 绘制游戏界面 添加游戏元素 为游戏添加交互性 结论 一、父亲节的来历简介 二…

taskAffinity、launchMode 与 flag

1、官方文档 public static final int FLAG_ACTIVITY_CLEAR_TASK 如果在传递给 的意向中设置&#xff0c;则 此标志将导致与 在活动开始之前要清除的活动。即活动 成为原本为空的任务和任何旧活动的新根 都完成了。这只能与 结合使用。Context.startActivity()FLAG_ACTIVITY_NE…

Kubernetes Blog 更新:DaoCloud 为数字世界寻找全局最优解

“ 近日&#xff0c;一篇名为《「DaoCloud 道客」与 Kubernetes--为数字世界寻找全局最优解》的博文&#xff0c;在 Kubernetes 的全球官网上线&#xff08;链接&#xff1a;https://kubernetes.io/case-studies/daocloud/&#xff09;&#xff0c;下面一起来了解一下具体内容…

ArcEngine二次开发0——入门(下载 部署 组件学习)

折腾一下ArcGIS Engine二次开发。 目录 1、开发环境配置2、部署一个ArcGIS Engine应用程序3、ArcObject组件学习4、报错及解决4、其他 1、开发环境配置 参考&#xff1a;https://blog.csdn.net/H48662654/article/details/113384150 &#xff08;使用ArcEngine前&#xff0c;…

时序预测 | MATLAB实现SO-ELM蛇群算法优化极限学习机时间序列预测

时序预测 | MATLAB实现SO-ELM蛇群算法优化极限学习机时间序列预测 目录 时序预测 | MATLAB实现SO-ELM蛇群算法优化极限学习机时间序列预测效果一览基本介绍程序设计学习总结参考资料 效果一览 基本介绍 Matlab实现SO-ELM蛇群算法优化极限学习机时间序列预测 1.data为单变量时间…

拿捏指针(三)--- 对指针的高级认识(高级)

函数指针 函数指针的定义 通过对 对指针的基本认识 和 对指针的进阶认识 我们知道&#xff0c;整型指针是指向整型的指针&#xff0c;数组指针是指向数组的指针&#xff0c;其实&#xff0c;函数指针就是指向函数的指针。 和学习数组指针一样&#xff0c;学习函数指针我们也需…

【WebPack】前端工程化

文章目录 前端工程化一、前端工程化概念二、前端工程化优点三、前端工程化解决方案四、webpack 的基本使用4.1 什么是 webpack4.2 创建列表隔行变色项目4.3 安装 webpack4.4 配置 webpack4.5 自定义 打包入口与出口 五、webpack 的插件使用5.1 webpack 常见插件5.2 webpack-dev…