百度Apollo规划算法——轨迹拼接
百度Apollo规划算法——轨迹拼接
- 引言
- 轨迹拼接
- 1、什么是轨迹拼接?
- 2、为什么要进行轨迹拼接?
- 3、结合Apollo代码为例理解轨迹拼接的细节。
- 参考
引言
在apollo的规划算法中,在每一帧规划开始时会调用一个轨迹拼接函数,返回一段拼接轨迹点集,并告诉我们是否重规划以及重规划的原因,那大家是否深入了解并思考过什么是轨迹拼接、为什么要进行轨迹拼接以及是如何进行轨迹拼接的呢?本篇文章便是针对这几个问题,在参考了前辈的一些文章和Apollo代码的基础上进行解答。
轨迹拼接
1、什么是轨迹拼接?
在每一帧轨迹规划的开始,我们需要确定规划的起始点p0=(t,x,y,v,a,heading,kappa)p_0=(t,x,y,v,a,heading,kappa)p0=(t,x,y,v,a,heading,kappa),起始点p0p_0p0给定了规划的初始状态,然后结合当前帧的环境信息并调用规划算法规划生成当前帧轨迹。
那么问题来了,每一帧规划的起始点p0p_0p0是如何确定的呢?在Apollo规划算法中采用了两种方案:
方案1:
根据ADC(Autonomous Driving Car)实际位置状态信息或根据ADC实际位置状态信息,通过车辆运动学推导0.1s后的位置状态信息,作为规划起始点状态,在Apollo中,这一方案叫做重规划(Replan),但是它属于轨迹拼接的一种,只是拼接点集只有一个点p0p_0p0而已。
方案2:
根据当前帧时间戳以及ADC实际位置信息,在上一帧轨迹中寻找匹配点,将上一帧轨迹中匹配点往后0.1s所对应的轨迹点作为当前帧的规划起始状态点,待当前帧规划生成轨迹后,再和上一帧轨迹中所选择的规划起始点前一段距离的轨迹点进行拼接,形成一条完整的车辆运动轨迹,发送给下游控制模块。
严格来说,以上两种方案都属于轨迹拼接,区别是方案1拼接轨迹只有一个点,而方案2的拼接轨迹是上一帧的部分有序点集,但是在Apollo代码中,称第一种方案叫做重新初始化拼接点(也叫重规划)。
2、为什么要进行轨迹拼接?
在了解了什么是轨迹拼接,不知道大家有没有思考过,轨迹拼接的方法为什么有两种,是基于什么原因想法设计的呢?
在轨迹拼接中纳入第二种方法主要考虑的是连续帧之间轨迹的平滑性和连续性,因为轨迹的这两个特性会严重影响到下游控制的效果。理论上,如果控制跟踪和定位完美,不会出现任何误差,则完全可以使用方案1选择规划起始点,但是,由于定位误差和控制误差/滞后的原因,会导致每一规划时刻ADC位置大概率的偏离上一帧所规划的轨迹点,如果此时仍采用方案1,会导致当前帧所规划轨迹和上一帧规划轨迹的不平滑和不连续,从而导致控制的抖动,也可能会引起ADC行驶轨迹发散,跟踪误差越来越大,偏离设定参考线。而方案2可以保证ADC附近一段范围内前后两帧轨迹的平滑性,从而不断引导ADC沿着期望轨迹行驶。
3、结合Apollo代码为例理解轨迹拼接的细节。
结合图1和TrajectoryStitcher::ComputeStitchingTrajectory
这个函数,理解哪些情况下选择方案1、哪些情况选择方案2。
选择方案1的情况有:
a.没有使能方案2进行轨迹拼接
b.上一帧不存在轨迹(一般是起步或上一帧规划失败的情况)
c.没有处在自动驾驶模式
d.上一帧存在轨迹但是没有轨迹点
e.当前时间相对于上一帧的相对时间戳小于上一帧起点相对时间戳
f.时间匹配点超出上一帧轨迹点的终点实际范围
g.匹配点处不存在匹配的path_point
h.ADC实际位置和匹配点横纵向偏差超过给定阈值
方案1生成轨迹拼接点主要有两种情况:
情况1——起步阶段:由于起步阶段属于规划算法执行的第一帧,且速度、加速度值很小,可以直接使用当前状态点作为规划的起始状态点;
情况2——非起步阶段:则根据当前ADC状态点结合运动学模型外推TTT时间之后的状态点作为本帧规划起始点。
[xt+1yt+1ψt+1]=[xtytψt]+[cos(ψ)sin(ψ)tan(δf)l]⋅v\begin{bmatrix}x_{t+1}\\y_{t+1}\\\psi_{t+1} \end{bmatrix}= \begin{bmatrix}x_{t}\\y_{t}\\\psi_{t} \end{bmatrix}+\begin{bmatrix}cos(\psi)\\sin(\psi)\\\frac{tan(\delta_f)}{l} \end{bmatrix}\cdot vxt+1yt+1ψt+1=xtytψt+cos(ψ)sin(ψ)ltan(δf)⋅v
这两种情况都属于重新初始化轨迹拼接点。
当以上情况均不满足时,采取方案2进行轨迹拼接,方案2轨迹拼接的具体方法为:
该方案同时考虑了时间匹配和位置匹配:
对于时间匹配:
根据当前时间戳和上一帧轨迹起点的时间戳对比,计算当前时间ADC应该到达的时间匹配点(图1中t_pos
)及该匹配点在上一帧轨迹中对应的索引值t_index
,若t_pos
不存在路径点,则选择方案1。
对于位置匹配:
根据ADC当前位置点在上一帧轨迹中寻找最近的匹配点(图1中p_pos
)及该匹配点在上一帧轨迹中对应的索引值p_index
,并比较实际位置点和匹配点之间的横纵向位置偏差,若任一偏差超过给定阈值,则选择方案1。
若以上判断均通过,根据 (当前时刻的绝对时间-上一时刻轨迹的初始时间+planning_cycle_time) 得到forward_rel_time,选取时间匹配索引和位置匹配索引中的较小索引作为匹配点索引值matched_index,在上一帧的轨迹上截取出matched_index往前n个点开始,至forward_rel_time的一段轨迹,作为stitching_trajectory。最后,遍历这段轨迹,对其relative_time和s进行赋值。最终,使用stitching_trajectory的最后一个点,即图1中的p_0
,作为当前帧轨迹规划的起始点。
选择当前时间+planning_cycle_time的时间对应的上一帧轨迹的位置点作为本帧规划起点的原因是,当本帧规划需要时间planning_cycle_time,将生成轨迹送到规划模块时对应的实际时间理论上就是当前时间+planning_cycle_time。
Apollo轨迹拼接代码
/* Planning from current vehicle state if:
1. the auto-driving mode is off
(or) 2. we don't have the trajectory from last planning cycle
(or) 3. the position deviation from actual and target is too high
*/
std::vector<TrajectoryPoint> TrajectoryStitcher::ComputeStitchingTrajectory(
const VehicleState& vehicle_state, const double current_timestamp,
const double planning_cycle_time, const size_t preserved_points_num,
const bool replan_by_offset, const PublishableTrajectory* prev_trajectory,
std::string* replan_reason) {//a.是否使能轨迹拼接if (!FLAGS_enable_trajectory_stitcher) {*replan_reason = "stitch is disabled by gflag.";return ComputeReinitStitchingTrajectory(planning_cycle_time, vehicle_state);}//b.上一帧是否生成轨迹if (!prev_trajectory) {*replan_reason = "replan for no previous trajectory.";return ComputeReinitStitchingTrajectory(planning_cycle_time, vehicle_state);}//c.是否处于自动驾驶模式if (vehicle_state.driving_mode() != canbus::Chassis::COMPLETE_AUTO_DRIVE) {*replan_reason = "replan for manual mode.";return ComputeReinitStitchingTrajectory(planning_cycle_time, vehicle_state);}//d.上一帧是否存在轨迹点 size_t prev_trajectory_size = prev_trajectory->NumOfPoints();if (prev_trajectory_size == 0) {*replan_reason = "replan for empty previous trajectory.";return ComputeReinitStitchingTrajectory(planning_cycle_time, vehicle_state);}const double veh_rel_time = current_timestamp - prev_trajectory->header_time();size_t time_matched_index = prev_trajectory->QueryLowerBoundPoint(veh_rel_time);//e.判断当前时间相对于上一帧的相对时间戳是否小于上一帧起点相对时间戳if (time_matched_index == 0 &&veh_rel_time < prev_trajectory->StartPoint().relative_time()) {*replan_reason ="replan for current time smaller than the previous trajectory's first ""time.";return ComputeReinitStitchingTrajectory(planning_cycle_time, vehicle_state);}//f.判断时间匹配点是否超出上一帧轨迹点范围if (time_matched_index + 1 >= prev_trajectory_size) {*replan_reason ="replan for current time beyond the previous trajectory's last time";return ComputeReinitStitchingTrajectory(planning_cycle_time, vehicle_state);}auto time_matched_point = prev_trajectory->TrajectoryPointAt(static_cast<uint32_t>(time_matched_index));//g.判断时间匹配点处是否存在path_pointif (!time_matched_point.has_path_point()) {*replan_reason = "replan for previous trajectory missed path point";return ComputeReinitStitchingTrajectory(planning_cycle_time, vehicle_state);}size_t position_matched_index = prev_trajectory->QueryNearestPointWithBuffer({vehicle_state.x(), vehicle_state.y()}, 1.0e-6);//计算实际位置点和匹配点的横纵向偏差auto frenet_sd = ComputePositionProjection(vehicle_state.x(), vehicle_state.y(),prev_trajectory->TrajectoryPointAt(static_cast<uint32_t>(position_matched_index)));//h.判断横纵向偏差if (replan_by_offset) {auto lon_diff = time_matched_point.path_point().s() - frenet_sd.first;auto lat_diff = frenet_sd.second;//h.1:横向偏差不满足条件if (std::fabs(lat_diff) > FLAGS_replan_lateral_distance_threshold) {const std::string msg = absl::StrCat("the distance between matched point and actual position is too ""large. Replan is triggered. lat_diff = ",lat_diff);*replan_reason = msg;return ComputeReinitStitchingTrajectory(planning_cycle_time,vehicle_state);}//h.2:纵向偏差不满足条件if (std::fabs(lon_diff) > FLAGS_replan_longitudinal_distance_threshold) {const std::string msg = absl::StrCat("the distance between matched point and actual position is too ""large. Replan is triggered. lon_diff = ",lon_diff);*replan_reason = msg;return ComputeReinitStitchingTrajectory(planning_cycle_time,vehicle_state);}} else {ADEBUG << "replan according to certain amount of lat and lon offset is ""disabled";}//计算当前时刻后T时刻的时间,并计算其在上一帧轨迹中对应的索引值double forward_rel_time = veh_rel_time + planning_cycle_time;size_t forward_time_index =prev_trajectory->QueryLowerBoundPoint(forward_rel_time);ADEBUG << "Position matched index:\t" << position_matched_index;ADEBUG << "Time matched index:\t" << time_matched_index;//选择时间匹配索引和位置匹配索引中的较小索引作为匹配点索引auto matched_index = std::min(time_matched_index, position_matched_index);//构建拼接轨迹,<匹配索引点前n个点,当前时刻后的T时刻所对应的匹配点>std::vector<TrajectoryPoint> stitching_trajectory(prev_trajectory->begin() +std::max(0, static_cast<int>(matched_index - preserved_points_num)),prev_trajectory->begin() + forward_time_index + 1);const double zero_s = stitching_trajectory.back().path_point().s();for (auto& tp : stitching_trajectory) {if (!tp.has_path_point()) {*replan_reason = "replan for previous trajectory missed path point";return ComputeReinitStitchingTrajectory(planning_cycle_time,vehicle_state);}//适配时间和s值tp.set_relative_time(tp.relative_time() + prev_trajectory->header_time() -current_timestamp);tp.mutable_path_point()->set_s(tp.path_point().s() - zero_s);}return stitching_trajectory;
}
参考
【1】Apollo 6.0 轨迹拼接算法解析
【2】规划控制之轨迹拼接
【3】第二章第三节(中) 控制接口,轨迹拼接
【4】轨迹拼接(Trajectory Stitching)
【5】Apollo轨迹拼接模块(Trajectory Stitching)研读
【6】Apollo6.0
相关文章:

百度Apollo规划算法——轨迹拼接
百度Apollo规划算法——轨迹拼接引言轨迹拼接1、什么是轨迹拼接?2、为什么要进行轨迹拼接?3、结合Apollo代码为例理解轨迹拼接的细节。参考引言 在apollo的规划算法中,在每一帧规划开始时会调用一个轨迹拼接函数,返回一段拼接轨迹…...

6. unity之脚本
1. 说明 当整个游戏运行起来之后,我们无法再借助鼠标来控制物体,此时可以使用脚本来更改物体的各种姿态,驱动游戏的整体运动逻辑。 2. 脚本添加 首先在Assets目录中,新创建一个Scripts文件夹,在该文件内右键鼠标选择…...

flink-note笔记:flink-state模块中broadcast state(广播状态)解析
github开源项目flink-note的笔记。本博客的实现代码都写在项目的flink-state/src/main/java/state/operator/BroadcastStateDemo.java文件中。 项目github地址: github 1. 广播状态是什么 网上关于flink广播变量、广播状态的讲解很杂。我翻了flink官网发现,实际上在1.15里面…...

vue——预览PDF
下载插件 npm install --save vue-pdf创建组件 <template><div class"ins-submit-docs-content ins-submit-docs-pdf"><div v-if"loading" style"position: absolute; top: 40%; width: 100%;text-align: center;"><el-l…...

数据库复习
什么是数据库系统 数据库系统是指在计算机系统中引入数据库后构成的系统,一般由数据库、数据库管理系统(及其开发工具)、应用系统、数据库管理员和用户构成 数据库系统的特点是什么? 数据结构化数据的共享性高,冗余度低且易扩充数据独立性高数…...

vscode插件推荐
文章目录前言一、vscode插件推荐?1、 Chinese (Simplified) (简体中文) Language Pack for Visual Studio Code2、Auto Close Tag3、Auto Import3、Error Lens4、vscode-icons5、ES7 React/Redux/React-Native snippets6、GitLens — Git supercharged7、JavaScript…...

THUPC2023初赛总结
今天参加了THUPC2023初赛,感觉还行。 比赛本来是11:00-16:00,但出了点问题,比赛延迟了十分钟。 刚开始,我从第一题往后看,寻找简单的题。 过了一会儿,一看排行榜,怎么最后一题全是绿的&#…...

unity知识点小结02
虚拟轴 虚拟轴就是一个数值在-11内的轴,这个数轴上重要的数值就是-1,0和1。当使用按键模拟一个完整的虚拟轴时需要用到两个按键,即将按键1设置为负轴按键,按键2设置为正轴按键。在没有按下任何按键的时候,虚拟轴的数值为0…...

总线(四)Modbus总线 协议
文章目录Modbus技术背景Modbus OSI分布Moudbus分类通讯过程Moudbus协议通信过程以及报文解析RTU 与 ASCII 收发数据区别Modbus技术背景 Modbus是一种串行通信协议。 1971年,Modicon公司首次退出Modbus协议,ModbusRTU和Modbus ASCII诞生于此。 后来施耐德…...

Cadence Allegro 导出Component Report详解
⏪《上一篇》 🏡《总目录》 ⏩《下一篇》 目录 1,概述2,Component Report作用3,Component Report示例4,Component Report导出方法4.1,方法14,2,方法2B站关注“硬小二”浏览更多演示视频 1,...

程序猿成长之路之密码学篇-DES算法详解
DES的算法实现原理详情请见 https://blog.csdn.net/qq_31236027/article/details/128209185 DES算法密钥获取详情请见 https://blog.csdn.net/qq_31236027/article/details/129224730 编码工具类获取详见 https://blog.csdn.net/qq_31236027/article/details/128579451 DES算法…...

maven生命周期、阶段与默认绑定插件梳理
maven生命周期、阶段与默认绑定插件梳理 CSDN博客 码云源码 1.maven生命周期、阶段与默认绑定插件 序号生命周期lifecycle阶段phase默认绑定插件(链接官网)默认绑定插件(链接maven库)说明1cleancleanmaven-clean-pluginmaven-clean-plugin清理2.1buildvalidate——验证2.2b…...

【数学基础】
文章目录『 第1讲 高等数学预备知识 』1.1 函数的概念与特性函数的四种特性【 重要结论 】1.2 函数的图像直角坐标系下的图像极坐标系下的图像参数方程1.3 常用基础知识【 情报#1 】『 第2讲 数列极限 』2.1 引言2.2 求数列极限【 情报#2 】『 第1讲 高等数学预备知识 』 1.1 …...

网上电子商城的设计与实现
技术:Java、JSP等摘要:21 世纪以来,人类经济高速发展,人们的生活发生了日新月异的变化,特别是计算机的应用及普及到经济和社会生活的各个领域。在消费领域,网上购物已经成为大众所接受的一种新型的消费方式…...

2023thupc总结
A 大富翁 很有意思的题 ∑x∈A∑y∈B[x支配y]−∑x∈A∑y∈B[y支配x]−∑x∈Awx\sum_{x\in A}\sum_{y\in B}[x支配y]-\sum_{x\in A}\sum_{y\in B}[y支配x]-\sum_{x\in A}w_x∑x∈A∑y∈B[x支配y]−∑x∈A∑y∈B[y支配x]−∑x∈Awx ∑x∈A∑y[x支配y]−∑x∈A∑y[y支…...

【数据库】MySQL数据库基础
目录 1.数据库: 2.数据库基本操作 2.1 MySQL的运行原理 2.2显示数据库: 2.3创建数据库 2.4使用数据库 2.5删除数据库 3.常见的数据类型 3.1数值类型: 3.2字符型类型 3.3日期类型 4.表的操作 4.1创建表 4.2查看表 4.3删除表 5.汇总…...

grid了解
结构 <div class"grid"><div>1</div><div>2</div><div>3</div><div>4</div><div>5</div><div>6</div><div>7</div><div>8</div><div>9</div>&l…...

2023年全国最新工会考试精选真题及答案13
百分百题库提供工会考试试题、工会考试预测题、工会考试真题、工会证考试题库等,提供在线做题刷题,在线模拟考试,助你考试轻松过关。 81.女职工委员会在()下开展工作。 A.企业工会委员会领导 B.企业工会委员会指导 …...

初识HTML技术
文章目录一、为什么学习前端?二、第一个HTML文件VSCode三. HTML元素四. HTML页面一、为什么学习前端? 我们作为一个后端程序员,为什么还要学习前端,因为我们的终极目的是实现web开发,搭建网站,网站 前端 后端 比如我们随便…...

我们为什么要用消息队列?
消息队列是系统设计中存在时间最长的中间件之一,从系统有通信需求开始,就产生了消息队列。 消息队列的使用场景 在日常系统设计与实现的过程中,下面3种场景会涉及到消息队列: 异步处理流量控制服务解耦 异步处理 典型的应用场…...

Linux进程控制
进程控制fork函数进程终止退出码常见的退出方式进程等待什么是进程等待,为什么要进程等待阻塞与非阻塞进程替换替换原理替换函数执行系统命令执行自己写的程序模拟实现简易的shellfork函数 fork函数是创建一个子进程,之前用过。 #include <unistd.h…...

PMP项目管理引论介绍
目录1. 指南概述和目的1.1 项目管理标准1.2 道德与专业行为规范2 基本要素2.1 项目2.2 项目管理的重要性2.3 项目、项目集、项目组合以及运营管理之间的关系2.3.1 概述2.3.2. 项目组合与项目集管理2.3.3. 运营管理2.3.4. 组织级项目管理和战略2.3.5. 项目管理2.3.6. 运营管理与…...

计算机视觉废钢堆提取问题
计算机视觉废钢堆提取问题 背景介绍 在钢铁炼制中,废钢是非常重要的原料,不同等级废钢对于钢成品影响很大,因此需要对废钢进行正确分类。某废钢料场中,卸料区域布置了多个摄像头,用于拍摄卸料场中废钢堆,…...

判断水仙花数-课后程序(Python程序开发案例教程-黑马程序员编著-第二章-课后作业)
实例5:判断水仙花数 水仙花数是一个3位数,它的每位数字的3次幂之和等于它本身,例如13 53 33 153,153就是一个水仙花数。 本实例要求编写程序,实现判断用户输入的3位数是否为水仙花数的功能。 实例目标 掌握Pytho…...

目标检测: 数据增强代码详解
1. 常见的数据增强 1.1 翻转图像 左右水平翻转 假设图片的宽高为w,h,bdbox左上角A坐标为(x1,y1), 右下角B为(x2,y2)。经过左右水平翻转后,bdbox的左上角A1坐标(w-x2,y1) ,右下角B1坐标为(w-x1,y2)左右水平翻转的代码实现如下:from PIL import Image image = Image.open(i…...

第二讲:ambari编译复盘,如何实现一次性成功编译ambari
上节课我们已经讲解了如何成功编译ambari源码,安装ambari-server rpm包以及成功部署ambari。本节课我们来复盘一下上节课的编译过程,以及思考如何实现一次性成功编译ambari。 要想一次性成功编译ambari,那么就需要将预置工作做好,比如: maven镜像源配置,node_moudle模块…...

Windows下jdk安装与卸载-超详细的图文教程
jdk安装 下载jdk 由于现在主流就是jdk1.8,所以这里就下载jdk1.8进行演示。官方下载地址:https://www.oracle.com/java/technologies/downloads/#java8-windows。 官方下载需要注册oracle账号,国内下载有可能速度慢,若不想注册账…...

Jackson CVE-2018-5968 反序列化漏洞
0x00 前言 同CVE-2017-15095一样,是CVE-2017-7525黑名单绕过的漏洞,主要还是看一下绕过的调用链利用方式。 可以先看: Jackson 反序列化漏洞原理 或者直接看总结也可以: Jackson总结 影响版本:至2.8.11和2.9.x至…...

解析MySQL 8.0 OCP(1Z0-908)考试中一道大部分同学都会做错的题目
一个用户有下面的权限: mysql>SHOW GRANTS FOR jsmith;---------------------------------------------------------------------- | Grants for jsmith% | ----------------------------------------------------------…...

Java死锁
什么是死锁? 多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。 死锁的必要条件: 1、互斥条件:该资源任意一个时刻只由一个线程占用。 2、请求与…...