自动驾驶面经总结(一)
前言
做一点总结学习
一、Dijstra算法,算法流程
Dijkstra算法是一种基于贪心策略的最短路径算法,它的核心思想是按照路径长度逐点增长的方法构造一棵路径树,从而得出从该树的根节点(即指定节点)到其他所有节点的最短路径。
个人的理解就是逐渐把新的节点加入到已知的节点,看看路径有没有变短,变短了就把最短路径数组更新。即dis[n] > dis[n+1] +mp[n+1][m];
Dijkstra算法的流程如下:
- 创建一个集合S,用于存放已经找到了最短路径的顶点。
- 创建一个数组dis,用于保存源点到各个顶点的最短距离。
- 初始化dis数组,将源点到自己的距离赋值为0,其他顶点到源点的距离赋值为无穷大。
- 从dis数组中找到距离源点最近的顶点v,并将其加入集合S中。
- 遍历v的邻居w,如果w不在集合S中,则更新dis数组中w的距离值。具体来说,如果源点到v的距离加上v到w的距离小于源点到w的距离,则更新dis[w]为这个更小的值。
- 重复步骤4和5,直到所有顶点都被加入集合S中。
Dijkstra算法可以应用于路径规划问题
可以但是要把地图离散为顶点和边。
例如,在地图上找到两个地点之间的最短路径。在这种情况下,每个地点可以看作图中的一个顶点,而地点之间的道路可以看作图中的一条边。通过使用Dijkstra算法,我们可以找到从一个地点到另一个地点的最短路径。
Dijkstra算法是动态规划的思想吗?
Dijkstra算法是一种基于贪心、广度优先搜索和动态规划的算法,用于求解一个图中一个点到其他所有点的最短路径 ²。Dijkstra算法建立在带open标记的动态规划算法之上,但在选取推进点时只选取valu值已达终态的点v(称为"极元")¹。
因此,Dijkstra算法和动态规划有一定的关系,但是它们的框架是相同的,不同点仅在于“推进点的选取”和“算法终止条件”
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int INF = 0x3f3f3f3f;
const int MAXN = 1005;
int n, m, s, t;
int dis[MAXN], vis[MAXN];
int mp[MAXN][MAXN];
void Dijkstra() {
memset(dis, INF, sizeof(dis));
memset(vis, 0, sizeof(vis));
dis[s] = 0;
for (int i = 1; i <= n; i++) {
int k = -1;
for (int j = 1; j <= n; j++) {
if (!vis[j] && (k == -1 || dis[k] > dis[j])) {
k = j;
}
}
vis[k] = 1;
for (int j = 1; j <= n; j++) {
if (!vis[j] && mp[k][j] != INF) {
dis[j] = min(dis[j], dis[k] + mp[k][j]);
}
}
}
}
int main() {
cin >> n >> m >> s >> t;
memset(mp, INF, sizeof(mp));
for (int i = 1; i <= m; i++) {
int u, v, w;
cin >> u >> v >> w;
mp[u][v] = min(mp[u][v], w);
}
Dijkstra();
cout << dis[t] << endl;
return 0;
}
二、A*算法
A算法,算法流程,编程实现的过程中使用了哪些数据结构,项目中在原有方法基础上有哪些改进,启发函数的设计,A与Dijstra的区别;
这个A*算法讲的最好
A*算法的基本算法原理就是尽量每一步都做最好的选择
f =g+h
f: 最终的代价
g:从起点到当前位置已经付出的代价,也就是沉没成本
h: 当前点到终点的预计代价(只计算直线距离,并且无视障碍)
量化评估:通过数字来评估每走一步的代价,总是选择最小代价
A*算法
A*算法是一种启发式搜索算法,用于在图形中找到最短路径。它是一种基于Dijkstra算法的改进算法,通过引入启发式函数来加速搜索过程 ¹。
A*算法的流程如下:
- 将起点加入open列表中。
- 重复以下步骤:
- 从open列表中选择f值最小的节点作为当前节点。
- 将当前节点从open列表中删除,并将其加入close列表中。
- 对当前节点的所有邻居进行如下操作:
- 如果邻居不可通行或者已经在close列表中,则忽略它。
- 如果邻居不在open列表中,则将其加入open列表,并将当前节点作为邻居的父节点。
- 如果邻居已经在open列表中,则计算从起点到该邻居的g值。如果新的g值比原来的g值更小,则将邻居的父节点设置为当前节点,并更新该邻居的g值和f值。
- 如果目标节点被加入到了close列表中,或者open列表为空,则搜索结束。
编程实现的过程中使用了哪些数据结构
在A*算法编程实现的过程中,需要维护两个数据结构:OPEN集和CLOSED集。OPEN集是一个优先队列,用于存储待扩展的节点;CLOSED集是一个哈希表,用于存储已经扩展过的节点 ¹³。
此外,A*算法在运算过程中,每次从优先队列中选取f(n)值最小(优先级最高)的节点作为下一个待遍历的节点 ³。
启发函数的设计
A算法的启发函数是一种估价函数,用于评估从当前节点到目标节点的距离。启发函数应该是一种低估计,即它不应该超过实际距离。如果启发函数是准确的,则A算法将找到最短路径 ¹。
启发函数的设计可以根据问题的特点进行调整。例如,在地图上寻找路径时,可以使用曼哈顿距离或欧几里得距离作为启发函数
曼哈顿距离和欧几里得距离的例子。
*当我们在二维平面上计算两个点之间的曼哈顿距离时,可以将其看作是从一个点到另一个点的“街区距离”,即只能沿着水平或垂直方向移动,不能斜着走。例如,假设有两个点
A
(
1
,
2
)
A(1, 2)
A(1,2) 和
B
(
4
,
5
)
B(4, 5)
B(4,5),则它们之间的曼哈顿距离为:
d
1
(
A
,
B
)
=
∣
1
−
4
∣
+
∣
2
−
5
∣
=
6
d_{1}(A,B)=|1-4|+|2-5|=6
d1(A,B)=∣1−4∣+∣2−5∣=6
当我们在二维平面上计算两个点之间的欧几里得距离时,可以将其看作是从一个点到另一个点的直线距离。例如,假设有两个点
A
(
1
,
2
)
A(1, 2)
A(1,2) 和
B
(
4
,
5
)
B(4, 5)
B(4,5),则它们之间的欧几里得距离为:
d
2
(
A
,
B
)
=
(
1
−
4
)
2
+
(
2
−
5
)
2
≈
4.24
d_{2}(A,B)=\sqrt{(1-4)^{2}+(2-5)^{2}}\approx 4.24
d2(A,B)=(1−4)2+(2−5)2≈4.24
A*与Dijstra的区别
Dijkstra算法和 A算法都是 最短路径问题的常用算法,下面就对这两种算法的特点进行一下比较。
1.Dijkstra算法计算源点到其他所有点的最短路径长度,A关注点到点的最短路径(包括具体路径)
2.Dijkstra算法建立在较为抽象的图论层面,A算法可以更轻松地用在诸如游戏地图寻路中。
3.Dijkstra算法的实质是广度优先搜索,是一种发散式的搜索,所以空间复杂度和时间复杂度都比较高。对路径上的当前点,A算法不但记录其到源点的代价,还计算当前点到目标点的期望代价,是一种启发式算法,也可以认为是一种深度优先的算法。
4.由第一点,当目标点很多时,A算法会带入大量重复数据和复杂的估价函数,所以如果不要求获得具体路径而只比较路径长度时,Dijkstra算法会成为更好的选择。
个人的理解,dijstra
+算法需要搜索更多节点,而A算法因为加入了启发函数需要搜索的节点更少,因而效率更高。
三、Lattice
Lattice是自动驾驶中的一种路径规划方法。Lattice路径规划方法将车辆的运动状态表示为一个状态空间,然后将状态空间分解为一组离散的状态集合,称为状态格。这些状态格可以被视为车辆的轨迹候选集合。Lattice路径规划方法可以在不同的场景下生成不同的轨迹,例如在高速公路上行驶时,车辆可以选择直线轨迹,而在城市道路上行驶时,车辆可以选择弯曲轨迹
百度Apollo Lattice Planner算法
Lattice算法是规划模块的一部分,以预测模块、routing模块、高精地图和定位的结果作为输入,通过算法,输出一条平稳、舒适、安全的轨迹,交给控制模块去执行。Lattice规划算法的工作流程包括采样过程、计算每一条轨迹计算的cost和循环检测的过程。在这个过程中,我们每次会先挑选出cost最低的轨迹,对其进行物理限制检测和碰撞检测。如果挑出来的轨迹不能同时通过这两个检测,就将其筛除,考察下一条cost最低的轨迹。最终将符合汽车物理性状且不会有碰撞风险的轨迹作为规划轨迹输出。
Lattice规划算法的cost包括轨迹的可行性、安全性等因素。 该算法会采样足够多的轨迹,提供尽可能多的选择,计算每一条轨迹计算的cost,然后通过循环检测的过程,挑选出cost最低的轨迹。该算法会考虑到以下几个方面:能否使自动驾驶汽车到达目的地、是否符合交规、能否避免碰撞、是否能保证一定的舒适性。
以百度apollo为例,lattice算法是规划模块的一部分,输入来自预测,routing,定位,高精地图,输出是一条平稳舒适安全的轨迹,交给控制模块去执行。Lattice规划算法是先采样,生成备选轨迹,然后计算每条轨迹的cost。Cost是安全性,交规,平滑性,车道成本的线性组合。
四、RRT算法
RRT算法是一种单查询(single-query)算法,目标是尽可能快地找到一条从起点到终点的可行路径。它是一种在完全已知的环境中通过采样扩展搜索的算法,相比较于基于图的搜索算法,最主要的优点就是快,因此在多自由度机器人的规划问题中发挥着较大的作用,比如机械臂的规划算法基本都是以RRT为基础的 。
RRT算法是一种基于树形结构的路径规划算法,它的流程如下:
- 初始化随机树tree,以空的随机树开始添加节点,最开始只有Qinit。
- 执行sample函数,在地图中获得一个随机点Qrand。
- 遍历tree中所有节点,找出与Qrand之间代价最小的点Qnearest。
- 执行extend函数,获得Qnearest向Qrand方向上的指定长度的扩展点Qnew。
- 如果Qnew不在障碍物中,则将其添加到tree中,并将Qnearest到Qnew之间的边添加到tree中。
这样,我们就可以在地图中生成一棵树,从而实现路径规划。
五、frenet坐标系
Frenet坐标系是一种描述曲线上点的坐标系,它是由法国数学家Jean Frenet在1852年提出的。Frenet坐标系的优点是可以描述汽车相对于道路的位置,因此在自动驾驶领域中得到了广泛应用。在Frenet坐标系中,s代表沿道路的距离称为纵坐标,d表示与纵向线的位移称为横坐标 。
Frenet坐标系的建立基于一个参考线,这个参考线可以是任意曲线,但在自动驾驶运动规划中一般定义为道路的中心线,这条中心线是地图模块输入过来的一系列离散点
Frenet坐标系有什么好处:
Frenet坐标系是一种常用于描述曲线运动的坐标系,它有以下几个优点:
参数化描述:Frenet坐标系使用曲线的弧长作为参数,可以直接描述曲线上的点位置和曲率,避免了使用笛卡尔坐标系中的x和y坐标来描述曲线的复杂性。
适用于自动驾驶弯道的路线规划。
将地图数据降维处理,计算量减小,提高效率。
Frenet坐标系明显地简化了问题,因为在车辆行驶中,我们总是能够简单的找到道路的参考线(即道路的中心线),那么基于参考线的位置的表示就可以简单的使用纵向距离(即沿着道路方向的距离)和横向距离(即偏离参考线的距离)来描述。 同样的,两个方向的速度(s˙、s和d˙、d)的计算也相对简单。 同时纵向和横向的速度、加速度、加加速度等信息也更便于计算。
为什么车辆动力学控制要用frenet坐标系?
答(chatGPT): 车辆动力学控制使用Frenet坐标系的原因是因为Frenet坐标系可以提供一种方便而自然的方式来描述车辆在曲线路径上的运动。
Frenet坐标系是一种基于曲线的局部坐标系,它由曲线的切线向量、法向量和副法向量组成。切线向量表示曲线的方向,法向量表示曲线的曲率方向,而副法向量则是切线向量和法向量的叉积,垂直于曲线平面。
在车辆动力学控制中,Frenet坐标系可以被用来描述车辆相对于道路曲线的位置和方向。切线向量可以表示车辆的前进方向,而法向量可以表示车辆相对于曲线的横向位置。此外,曲率和转向率等重要的道路几何信息也可以方便地从Frenet坐标系中推导出来。
因此,Frenet坐标系为车辆动力学控制提供了一种自然、方便且有效的方式,使得控制器可以更加准确地预测车辆在曲线上的运动,并采取相应的控制策略来实现所需的运动。
六:十字路口左转算法
十字路口左转算法的路径规划和决策是自动驾驶领域中的一个重要问题。路径规划是指如何规划车辆的行驶路线,而决策是指如何根据当前的路况和车辆状态来决定车辆的行驶策略。
在十字路口左转算法中,路径规划和决策是密不可分的。一般来说,路径规划可以分为全局路径规划和局部路径规划两个部分。全局路径规划是指在整个行驶过程中,如何规划车辆的行驶路线,而局部路径规划则是指在当前位置附近,如何规划车辆的行驶路线。
决策部分则需要考虑当前的路况和车辆状态,根据这些信息来决定车辆的行驶策略。例如,在十字路口左转时,需要考虑左转的安全性和效率,以及其他车辆和行人的位置和速度等信息。
目前,有很多研究机构和公司都在研究十字路口左转算法。例如,滑铁卢大学Cogdrive实验室提出了基于关键左转点(CTP)和部分可观察马尔可夫决策过程(POMDP)的分层规划框架来应对无人车左转决策规划的挑战 ¹。