1. 从一份空白的输入说起:为什么我要啃Open Planner的论文和参数
拿到这个标题的时候,项目正文、关键词、摘要描述全是空的,只有一行字——“Autoware Open Planner论文阅读 参数介绍”。这其实挺典型的,很多做自动驾驶规划控制方向的朋友,在GitHub上把Autoware的仓库clone下来,编译跑通Demo之后,下一步就卡住了:Open Planner那一堆参数到底是什么意思?论文里写的和代码里实现的是一回事吗?改哪个参数能让车在路口不那么犹豫?哪个参数又会让轨迹抖得像筛糠?
我自己最开始接触Autoware的Open Planner,也是从“能跑就行”的阶段过来的。那时候改参数全靠猜,把某个值调大一点,车的行为变了,但为什么变、边界在哪、和论文里的公式怎么对应,完全说不清楚。后来逼着自己把Open Planner相关的几篇核心论文翻出来逐段读,再对着源码里的参数定义一个个抠,才算把这条链路打通。这篇内容就是把我踩过的坑、理清的参数逻辑、以及论文和代码之间的对应关系,完整地分享出来。
Open Planner在Autoware的规划模块里扮演的是全局路径规划与行为决策之间的衔接层。它不像某些纯采样规划器那样只关心局部避障,也不像纯粹的全局搜索那样只输出一条静态路径。它要处理的是:给定一张高精地图和一条全局路线,如何生成一条符合车辆运动学约束、平滑可跟踪、并且能应对动态障碍物的局部轨迹。这个定位决定了它的参数体系会同时涉及路径搜索、轨迹优化、行为状态机三个层面。
适合读这篇内容的人,我大致分三类:第一类是在Autoware上做二次开发,需要调规划器行为但不想盲调参数的工程师;第二类是在校做自动驾驶规划方向研究,想把论文里的算法和工业级代码对应起来的学生;第三类是做其他规划器(比如Apollo的EM Planner、或者自己写的优化器),想横向对比Open Planner设计思路的从业者。不管你是哪一类,只要你对“参数背后的物理意义”这件事有执念,这篇内容应该能帮你省下不少翻源码的时间。
2. Open Planner的论文脉络:从搜索到优化的两段式设计
2.1 核心论文到底解决了什么问题
Open Planner的设计思路,如果只让我用一句话概括,就是**“用搜索算法找一条粗解,再用优化算法把粗解磨成可执行的轨迹”**。这个两段式结构在论文里有非常清晰的描述,也是理解所有参数的前提。
第一段是基于状态的路径搜索。论文里通常把它建模成一个在Frenet坐标系下的动态规划问题。为什么用Frenet坐标系?因为在高精地图里,道路的参考线(reference line)天然给出了一个纵向s和横向d的坐标框架。在这个框架下,车辆沿道路前进的方向是s轴,垂直于道路的方向是d轴。这样做的好处是,横向的偏移量和纵向的进度可以解耦处理,搜索空间从二维笛卡尔空间变成了一个相对规整的网格,计算量大幅下降。
第二段是轨迹优化。搜索出来的粗解在离散网格上,点与点之间可能不满足车辆的运动学约束(比如曲率突变、加速度不连续),所以需要用一个优化器去平滑它。论文里常见的是基于二次规划(QP)或者非线性优化(NLP)的方法,目标函数通常包含平滑度代价、与参考线的偏离代价、障碍物距离代价这几项。每一项前面都有一个权重系数,这些权重就是你在配置文件里看到的那些weight_*参数。
2.2 搜索层和优化层的参数分界
很多刚上手的人会把所有参数混在一起看,结果越看越乱。我的建议是先把参数按搜索层和优化层分开。
搜索层的参数主要控制“粗解长什么样”。比如搜索的网格分辨率、纵向采样间隔、横向采样范围、动态规划的状态转移代价。这些参数决定了搜索出来的路径是不是足够贴近参考线、能不能绕过障碍物、计算耗时能不能接受。
优化层的参数主要控制“磨出来的轨迹有多顺”。比如平滑项权重、横向偏移权重、障碍物距离权重、曲率约束的上限、加速度和加加速度(jerk)的边界。这些参数决定了最终轨迹的舒适性和可跟踪性。
提示:如果你发现车在直道上画龙,先别急着调优化层的平滑权重,去看搜索层的横向采样间隔是不是太大了。粗解本身就在抖,优化器再厉害也救不回来。
2.3 论文里的公式和代码参数的对应关系
论文里通常会写一个代价函数,形式大概是:
J = w_smooth * J_smooth + w_ref * J_ref + w_obs * J_obs其中J_smooth是平滑代价,J_ref是参考线偏离代价,J_obs是障碍物代价。这三个w_*在代码里就是对应的权重参数。但论文不会告诉你的是,代码里往往还会加一些工程化的修正项,比如对曲率变化的惩罚、对速度波动的惩罚、甚至对方向盘转角速率的惩罚。这些在论文里可能一笔带过,但在实际调参时影响很大。
我自己的做法是,先把论文里的核心公式和代码里的参数名做一个映射表,然后每调一个参数,都问自己:这个参数对应公式里的哪一项?调大它会压制什么、放大什么?这样调起来才有方向。
3. 搜索层参数逐个拆:网格、采样与代价函数
3.1 网格分辨率与采样间隔的取舍
搜索层最基础的参数是网格分辨率。在Frenet坐标系下,纵向和横向的采样间隔决定了搜索空间的粒度。纵向间隔通常和速度有关,速度越高,纵向采样可以越稀疏,因为高速下车辆对前方距离的敏感度相对低一些;横向间隔则和道路宽度、障碍物密度有关,窄路或者障碍物多的时候需要更密的采样。
这里有一个很实际的坑:采样间隔太小,计算量爆炸;采样间隔太大,粗解精度不够,优化层压力大。我试过在一条双向四车道的城市道路上,把横向采样间隔从0.5米调到0.2米,搜索时间从十几毫秒涨到接近一百毫秒,但最终轨迹的平滑度提升并不明显,因为优化层本来就能处理一定的粗解误差。后来我把横向间隔稳定在0.3到0.5米之间,纵向间隔根据速度动态调整,整体表现比较均衡。
还有一个容易被忽略的参数是搜索范围。横向搜索范围决定了车辆在遇到障碍物时能往旁边偏多少。如果这个范围设得太小,遇到路边停车的场景,规划器会直接报“无解”;设得太大,又可能搜出一些贴着对向车道边缘的危险路径。我的经验是,横向搜索范围不要超过当前道路的物理边界,同时要留出至少一个车宽的余量给优化层去微调。
3.2 动态规划的状态转移代价
Open Planner的搜索层用的是动态规划(DP),那就必然有状态转移代价。简单说,从当前状态转移到下一个状态,要付出多少代价。这个代价通常由几部分组成:偏离参考线的代价、与障碍物碰撞的代价、状态变化的代价。
偏离参考线的代价很好理解,离参考线越远,代价越高。这个代价的权重决定了搜索出来的路径是“尽量贴着参考线走”还是“可以灵活绕行”。在城市道路里,我一般会把这个权重设得中等偏高,让车尽量保持在车道中心附近;但在一些狭窄的胡同或者非结构化道路里,会适当降低,给绕行留出空间。
碰撞代价是硬约束,通常用一个很大的值来表示“不可通行”。但这里有个细节:障碍物的膨胀半径。论文里可能只写了“障碍物代价”,但代码里会有一个参数控制障碍物向外膨胀多少。这个膨胀半径要覆盖车辆的宽度和一定的安全余量。设小了,规划出来的路径会贴着障碍物擦过去;设大了,本来能过的窄缝就过不去了。我一般会根据车辆的实际宽度加上0.2到0.3米的安全余量来设。
状态变化的代价,指的是车辆从当前状态(位置、速度、航向)转移到下一个状态时,如果变化太剧烈,就要付出代价。这个参数直接影响搜索出来的路径是不是“激进”。调大它,路径会更保守、更平缓;调小它,路径会更灵活,但也可能更突兀。
3.3 搜索层参数的实际调试顺序
我自己的调试顺序是这样的:
- 先固定优化层参数,把搜索层的横向采样间隔和搜索范围调到一个能跑通基本场景的值。
- 然后调障碍物膨胀半径,确保在测试场景里不会出现“无解”或者“擦边”的情况。
- 再调偏离参考线权重和状态变化代价,观察路径是偏保守还是偏激进,根据实际道路宽度和交通流调整。
- 最后微调纵向采样间隔,在计算耗时和轨迹质量之间找平衡。
这个顺序的好处是,每一步只动一个变量,出了问题容易定位。我见过有人一次性改五六个参数,结果车的行为完全不可预测,最后只能全部回滚重来。
4. 优化层参数逐个拆:平滑、约束与权重平衡
4.1 平滑项权重与曲率约束
优化层的核心目标是让轨迹可跟踪。什么叫可跟踪?简单说就是方向盘打得过来、加速度不超限、乘客不晕车。平滑项权重就是控制这个的。
平滑项通常包含两部分:一阶导数的平滑(速度、航向角变化率)和二阶导数的平滑(加速度、曲率变化率)。在代码里,你可能看到的是weight_smoothness或者weight_curvature这样的参数。调大它,轨迹会更顺,但可能偏离参考线更多;调小它,轨迹更贴参考线,但可能曲率突变。
曲率约束是一个硬边界。车辆的最小转弯半径决定了最大曲率。这个参数如果设得比车辆实际能力还大,规划出来的轨迹物理上就不可跟踪,控制器会一直报错。我一般会留10%到20%的余量,比如车辆最小转弯半径是5米,那最大曲率就按1/5.5来设。
注意:曲率约束和平滑权重是相互作用的。如果平滑权重很大但曲率约束很松,优化器可能会找到一条曲率不大但横向偏移很大的轨迹;反过来,如果曲率约束很紧但平滑权重很小,轨迹可能会在约束边界上来回震荡。
4.2 障碍物距离代价的软硬之分
优化层里的障碍物代价和搜索层不一样。搜索层是“碰到就死”,优化层通常是软约束,也就是离障碍物越近代价越高,但不是绝对不可通行。这样做的好处是,当搜索层给出的粗解已经绕开了障碍物,优化层只需要微调,不需要重新处理硬约束。
障碍物距离代价的参数通常有两个:安全距离和代价衰减系数。安全距离是代价开始上升的阈值,衰减系数决定代价上升的快慢。安全距离设得太小,轨迹会贴障碍物太近;设得太大,车辆会过度避让,甚至跑到隔壁车道去。我一般把安全距离设成车辆宽度的一半加上0.5米左右,衰减系数根据障碍物类型调整——静态障碍物可以衰减快一点,动态障碍物(比如行人、自行车)衰减慢一点,留出更多余量。
4.3 权重平衡的实操经验
优化层最难的其实是权重平衡。平滑、贴参考线、避障这三个目标天然是矛盾的。你不可能同时做到“完全贴参考线”“绝对平滑”“离障碍物很远”。调参的本质就是在这三个目标之间找平衡。
我的经验是,先确定优先级。在城市道路里,避障优先级最高,贴参考线次之,平滑最后。所以障碍物代价的权重会设得比较高,平滑权重中等,参考线权重相对低一点。但在高速公路上,贴参考线和舒适性更重要,避障反而没那么频繁,权重分配就要反过来。
还有一个技巧是动态调权重。有些实现里会根据车速或者道路曲率动态调整权重。比如车速高的时候,平滑权重加大,避免急打方向;曲率大的时候,参考线权重降低,给车辆更多空间走外内外。这个在论文里可能没写,但在实际工程里很常见。
5. 论文与代码的Gap:那些论文不会告诉你的工程细节
5.1 离散化带来的数值问题
论文里的公式都是连续的,但代码里是离散的。离散化会带来两个问题:数值精度和计算稳定性。
数值精度方面,比如曲率计算,连续公式是k = y'' / (1 + y'^2)^(3/2),但在离散点上用差分近似,如果采样间隔太大,曲率估计会非常不准。我遇到过在直道上曲率估计出0.01的情况,导致规划器以为在转弯,轨迹莫名其妙地偏了一下。后来把采样间隔调小,问题就消失了。
计算稳定性方面,优化问题在离散化之后可能变成病态的。比如二次规划的Hessian矩阵条件数很大,求解器会报数值错误。这时候需要加正则项,或者调整权重让矩阵更良态。这些在论文里通常不会提,但工程实现里必须处理。
5.2 实时性约束下的参数降级策略
Open Planner是要跑在车上的,实时性是硬约束。论文里可以慢慢优化,但车上不行。所以代码里往往会有降级策略:当计算时间超过阈值时,自动降低搜索分辨率、减少优化迭代次数、或者直接输出搜索层的粗解。
这些降级策略对应的参数,论文里基本不会写。但你在调参的时候必须知道它们的存在。比如你发现规划轨迹偶尔会“跳”一下,可能就是触发了降级策略,从优化解切回了粗解。这时候要么放宽时间阈值,要么优化搜索层参数让粗解本身更平滑。
5.3 坐标系转换与参考线生成
Open Planner依赖Frenet坐标系,那就必然涉及笛卡尔坐标和Frenet坐标的转换。这个转换本身是有误差的,尤其是在参考线曲率大的地方。论文里通常假设转换是完美的,但实际代码里会有各种边界情况。
参考线的生成也是一个坑。如果参考线本身不平滑,搜索层和优化层都会受影响。我见过有人用高精地图的原始车道中心线做参考线,结果因为地图采集误差,参考线本身就在抖,规划出来的轨迹自然好不了。后来加了一个参考线平滑的预处理步骤,问题才解决。
6. 调参实战:从直道画龙到路口犹豫的排查链路
6.1 直道画龙:先查搜索层还是优化层
直道画龙是最常见的规划问题。车在直道上应该走直线,但实际轨迹左右摆。遇到这个问题,我的排查顺序是:
第一步,看搜索层的粗解。把搜索层的输出可视化出来,如果粗解本身就在左右跳,那问题在搜索层。常见原因是横向采样间隔太大,或者状态转移代价里对横向变化的惩罚太小。
第二步,看优化层的权重。如果粗解是直的,但优化后的轨迹在摆,那问题在优化层。常见原因是平滑权重太小,或者参考线权重太大导致优化器在参考线附近来回修正。
第三步,看参考线。如果参考线本身就在抖,那前面两步都是白搭。先把参考线平滑了再说。
我自己的经验是,直道画龙十有八九是搜索层的问题,尤其是横向采样间隔。把间隔从0.5米降到0.3米,大部分画龙问题都能缓解。
6.2 路口犹豫:行为状态机与规划器的交互
路口犹豫是另一个高频问题。车到了路口,一会儿想走一会儿想停,轨迹断断续续。这个问题往往不在规划器本身,而在行为状态机和规划器的交互。
Open Planner通常会订阅一个行为状态(比如“跟车”“左转”“右转”“停车”),然后根据状态生成对应的轨迹。如果状态机切换太频繁,规划器就会不停地重新规划,轨迹自然不连续。
排查的时候,先把状态机的输出打出来,看状态是不是在短时间内反复切换。如果是,去调状态机的迟滞参数或者确认阈值。如果状态机是稳定的,但规划轨迹还是断,那可能是规划器的重规划触发条件太敏感,比如障碍物距离阈值设得太小,稍微有点风吹草动就重规划。
6.3 避障过激:障碍物代价与膨胀半径的联合调试
避障过激的表现是,车离障碍物还有很远就开始大幅避让,甚至压到隔壁车道。这个问题通常是障碍物代价权重太高或者膨胀半径太大。
我的调试方法是:先把膨胀半径调到刚好覆盖车辆宽度加安全余量,然后逐步降低障碍物代价权重,观察避让行为。如果降到某个值之后,车开始贴障碍物太近,就说明找到边界了,往回加一点。
这里有个细节:动态障碍物和静态障碍物的参数应该分开。行人、自行车这类动态障碍物,膨胀半径要大一些,代价衰减要慢一些;路边停的车、锥桶这类静态障碍物,可以适当收紧。很多实现里只有一个统一的障碍物参数,调起来就很别扭。
7. 参数配置文件的结构与版本管理
7.1 配置文件的分层组织
Open Planner的参数通常放在YAML文件里。我建议按搜索层、优化层、行为层分成三个文件,或者至少在一个文件里用清晰的注释分块。这样做的好处是,调参的时候目标明确,不会改着改着忘了哪个参数是干嘛的。
一个典型的组织方式是这样的:
# search_layer.yaml search: longitudinal_resolution: 1.0 lateral_resolution: 0.3 lateral_range: 3.0 obstacle_inflation: 0.5 weight_ref: 10.0 weight_state_change: 5.0 # optimization_layer.yaml optimization: weight_smoothness: 100.0 weight_ref: 50.0 weight_obstacle: 200.0 max_curvature: 0.18 max_acceleration: 2.0 max_jerk: 5.0 # behavior_layer.yaml behavior: stop_distance: 2.0 follow_distance: 5.0 lane_change_threshold: 1.5这样分的好处是,当你发现轨迹不平滑时,直接去优化层找平滑权重,不用在几百行参数里翻。
7.2 参数版本管理与回滚
调参最怕的就是改乱了回不去。我的做法是每次调参前先commit一次,把当前能跑的版本存下来。然后每改一组参数,记录改了什么、为什么改、效果如何。如果效果不好,直接回滚到上一个commit。
如果不用git,至少也要把配置文件复制一份,加上日期和备注。我见过有人调了一下午,最后发现还是最初的版本最好,但已经改得面目全非,只能凭记忆往回改,非常痛苦。
7.3 不同场景的参数集切换
城市道路、高速公路、停车场,这三个场景对规划器的要求完全不同。城市道路要频繁避障和路口处理,高速公路要高速稳定和舒适,停车场要低速精确和窄缝通过。用一套参数打天下是不现实的。
我的做法是按场景维护多套参数,通过一个场景标识来切换。比如:
| 场景 | 横向采样间隔 | 平滑权重 | 障碍物权重 | 最大曲率 |
|---|---|---|---|---|
| 城市道路 | 0.3m | 100 | 200 | 0.18 |
| 高速公路 | 0.5m | 200 | 100 | 0.05 |
| 停车场 | 0.2m | 50 | 300 | 0.25 |
切换的时候要注意,参数切换的瞬间可能会引起轨迹跳变,最好在状态机里做一个平滑过渡,或者等当前规划周期结束再切。
8. 一些让我少走弯路的工具与习惯
8.1 可视化工具的选择
调参离不开可视化。Autoware本身有rviz插件,可以把搜索层的粗解、优化层的轨迹、障碍物、参考线都显示出来。我建议至少开三个视图:俯视图看轨迹形状,侧视图看速度曲线,还有一个专门看曲率和加速度的曲线图。
如果rviz不够用,可以把关键数据录成bag,用Python脚本离线分析。我写过一个简单的脚本,把轨迹的曲率、加速度、jerk都画出来,一眼就能看出哪段不平滑。这个脚本后来成了我调参的标配。
8.2 日志与回放
每次路测或者仿真,都要录bag。调参的时候,同一个bag反复回放,改一组参数看一次效果,比重新跑一遍场景快得多。我一般会把典型场景(直道、弯道、路口、避障)各录一个bag,调参的时候轮着回放。
日志方面,规划器通常会输出一些调试信息,比如搜索耗时、优化迭代次数、最终代价。把这些日志收集起来,可以分析参数变化对计算性能的影响。比如你把平滑权重调大之后,优化迭代次数是不是增加了?计算时间是不是超了?这些都要看日志才知道。
8.3 仿真环境下的快速迭代
实车调参成本太高,大部分时间应该在仿真里完成。Autoware有官方的仿真环境,也可以自己搭一个基于ROS的简单仿真。我的做法是,先在仿真里把参数调到一个“能跑”的状态,然后再上车微调。上车之后主要调的是和车辆动力学相关的参数,比如曲率约束、加速度边界,这些在仿真里很难完全模拟。
仿真里还有一个好处是可以批量测试。写一个脚本,自动跑几十个场景,记录每个场景的成功率、平均耗时、最大曲率。这样调参就不是凭感觉,而是有数据支撑。
9. 关于Open Planner参数调优的个人体会
调了这么久Open Planner的参数,我最大的体会是:参数不是孤立的,它们之间是相互耦合的。你改一个搜索层的采样间隔,优化层的权重可能就要跟着调;你改一个障碍物膨胀半径,行为状态机的阈值可能也要动。所以调参一定要有全局观,不能头痛医头。
另一个体会是,论文要读,但不能全信。论文给的是理想情况下的模型,代码里充满了工程妥协。理解论文能让你知道“应该是什么样”,读代码能让你知道“实际是什么样”,两者结合才能调好参数。
最后,记录比调参本身更重要。我现在的习惯是,每调一组参数,都在一个表格里记下来:改了哪些参数、从多少改到多少、测试场景是什么、效果如何、有没有副作用。这个表格后来成了团队里的调参手册,新人上手的时候直接照着走,省了很多重复劳动。
如果你也在调Open Planner的参数,建议从搜索层开始,先把粗解调稳,再调优化层。不要一上来就动优化权重,那样很容易陷入“改了这里坏了那里”的循环。先把搜索层的网格、采样、膨胀半径这些基础参数搞扎实,优化层的工作会轻松很多。