天没亮,人已经坐在试验车副驾上盯屏幕了。泊车逻辑跑了半夜,后轴还是一顿一顿的,方向盘在接近车位的时候来回修正,像在画心电图。我把Apollo的规划日志导出来看了一眼,轨迹曲线在纸面上是平滑的,但输入给控制的时间戳上,曲率跳变集中在最后十米——这基本就是泊车轨迹优化环节的问题。那天晚上之后,我把相关模块的代码从头到尾翻了一遍,发现核心的"作案工具"其实就三个模块,它们不是单兵作战,而是既有各算各账的冲突,又有互相兜底的配合。这篇文章就把我扒到的这些机制原原本本讲清楚。
先给还不熟Apollo的朋友一个定位:泊车轨迹优化是自动驾驶规划模块里专门处理低速、近距离、高精度停靠行为的一段管线。它接收感知给出的障碍物、车位检测结果,输出一条带速度信息的轨迹给控制模块执行。整个过程不是某个单独算法一把梭,而是几个模块按阶段接力。典型的处理链路大概是:场景判断与状态机 → 参考线平滑 → 轨迹边界生成 → 路径优化 → 速度决策与优化 → 最终轨迹合并。看到这串名词不用慌,核心角色可以浓缩成三块,也就是这篇文章的骨架:负责"圈地"的边界决策、负责"画线"的路径优化、负责"踩油门节奏"的速度规划。
1. 先看清三个核心模块的分工:谁定方向、谁画路径、谁管油门
自动泊车看着简单——不就是把车从A点挪到B点吗?但把这句话翻译成规划代码里的需求,就变得非常具体:车任何时候不能碰障碍物,姿态角不能突变,加速度不能超出物理极限,而且必须在可达范围内找到一条可行曲线。这些需求落到Apollo的规划任务配置里,是由多个task串联完成的。
1.1 边界决策模块:先给车画出一个"可钻的走廊"
边界决策模块干的事,是在车辆当前所在的Frenet坐标系下,把左侧、右侧的可行驶范围都算出来。很多人第一次看轨迹优化只看平滑后的曲线,忽略了它前面的"束缚条件"。没有边界,后面所有优化器都不知道自己在哪个空间里搜索。
这个模块的实际输出是一连串在道路纵向坐标s上的左右边界数组。它会综合车道线、路沿、静态障碍物、动态障碍物的预测轨迹、甚至车位的朝向约束,最终生成一个类似"走廊"的约束空间。泊车场景里,边界约束通常比普通巡航严苛得多,因为车位本身就把可行区域压得很窄,车位的左右边界经常和车身只有十几厘米的余量。边界模块还需要考虑车辆尺寸与当前航向角,否则画出来的走廊看着够宽,但车头一摆就出界。
1.2 路径优化模块:在走廊里找一条不拧巴的线
边界给完之后,路径优化开始干活。Apollo里路径优化通常不是一步到位,而是先粗后细。粗阶段用DP(动态规划)在一个离散化网格上搜索一条大致贴近走廊中心的路径,代价函数里包含横向偏移、障碍物距离、路径变化等。DP的优点是不会被局部最优困住,缺点是它输出的是折线,没法直接控。
所以之后还有细化步骤,典型是分段加加速度路径优化——这类优化器在DP给的粗糙路径基础上,用二次规划把曲线的曲率连续性、曲率变化率、横向加速度等项目全部拉回平滑区间。它的目标函数通俗讲就是"线尽量不要扭,扭得也不要太突然,贴着走廊中心走”。但这里的"尽量"就成了后面所有冲突的导火索,因为不同模块对"尽量"的理解不一样。
1.3 速度规划模块:在时间轴上安排松紧
路径定好了,只是解决了"往哪走"的问题,车还必须在每个时刻精确知道"走多快"。速度规划模块接收路径、障碍物ST关系、限速和车辆动力学参数,输出一条速度曲线。它同样有粗优化的DP步骤和细化的二次规划步骤,会处理停车精度、停车过程中的冲击度(jerk)限制等。
速度规划的难点在于低速泊车阶段:速度越低,对时间分辨率越敏感,同一个位置停留时间的微小变化,反映到控制端就是油门刹车的反复切换。这也是泊车轨迹优化里最让人头疼的抖动来源之一——并不是路径不对,而是速度曲线在末端的时间分配不合理。
这三个模块在职责上互不重叠,但在同一根轨迹上运行,最终合并出来的结果是否可用,取决于三者之间能不能在边界条件上达成一致。接下来聊的就是它们最容易"打架"的几个位置。
2. 所谓"打架",往往出在这三处:重叠约束、分辨率冲突、目标函数互拆台
大多数深夜调试翻车,不是某个模块彻底出错,而是模块衔接处出现了隐性的互相拆台。我归纳了一下,Apollo的泊车轨迹优化中最常见的三处冲突,背后机制都挺有代表性。
2.1 边界想"安全",优化器想"顺滑",两者天生拧着
边界决策模块为保证安全,倾向于把可行驶走廊缩得很紧。只要感知模块给了一个障碍物的轮廓,边界就贴着障碍物外扩一个固定安全距离。但路径优化器计算顺滑度时,可不管边界当初是怎么来的,它只看到边界数组在某个s点突然收窄。为了满足平滑目标,优化器会尝试把路径向走廊中心靠拢,表现为在障碍物位置之前早早开始打方向。
在泊车场景里,这种冲突会被放大成"路径躲障碍躲得太早",导致车辆在空旷区域也会画一个很大的弧线,影响后续进车位的姿态。你在日志里看到的典型特征就是曲率从零点几直接跳到接近极限值,然后又跳回来,这个跳跃正好对着车位入口的边界变化位置。
解决思路不是说谁对谁错,而是要理解边界是"绝对约束",优化的平滑是"软目标"。如果边界条件本身允许一定程度的过渡,比如在障碍物前后加一个渐变带,就能给优化器更多空间,两条曲线也能重新对齐。很多团队调参只盯着优化器的权重,却忽略了边界的渐变处理,调来调去还是抖,就是这么回事。
2.2 DP网格的"粗枝大叶"遇上了QP的"斤斤计较"
路径优化内部,DP和QP的分辨率也是不匹配的。DP阶段为了控制计算量,网格往往横向40厘米到100厘米一跳,纵向2米到5米一跳。在这个分辨率下,DP能感知到的障碍物边界是"台阶状"的。它选出来的粗路径,可能存在方向上的突变。
QP阶段拿到这条粗糙路径作为参考,开始追求曲率连续。它对DP丢出来的每个拐点都会尝试做平滑,但前提是路径不能超出边界。矛盾就来了:DP在网格上绕过一个障碍物时,它选择的横向偏移量可能正好卡在边界内一点点,而QP要平滑这个绕行动作时,需要的横向空间比DP以为的要多。结果QP要么在边界附近反复试探,要么生成一个大转弯来补偿,表现出来就是轨迹在某个区间出现明显的"S形"扭动。
这个问题的本质是两套优化体系对同一段几何的描述粒度不同。经验做法是给DP的采样网格和QP的平滑范围之间做一个插值缓冲区,或者在QP的约束里增加对参考路径偏差的惩罚项,但不要把它设死,否则平滑效果就没了。
2.3 路径与速度在时间维度上的"错位"
第三个冲突是最隐蔽的。路径优化器输出的是一条与时间无关的空间曲线,它认为车在任何时刻都能精确沿着这条线走。但速度规划器在分配时间时,会考虑障碍物的动态变化和停车目标点。当速度曲线在某一小段上分配了过多时间,车辆实际行驶速度就慢了。
低速时,车辆横向控制对速度变化非常敏感。同一个前轮转角,在时速5公里和时速2公里下产生的横摆角速度完全不一样。控制模块本身有反馈补偿,但如果速度曲线在短时间内的变化超过了控制器的响应带宽,车就开始"画龙"。日志里的现象往往是路径平滑没问题、曲率也在限值内,但横摆角速度波动很大,方向盘修正频率很高。
这部分的排查方向不在路径优化,而在速度曲线的时间分辨率。特别是泊车末端,速度规划器如果只关注"在目标点停下来",容易在最后一两米生成一段极其缓慢但仍在变化的参考速度,控制模块就会持续修正。
3. 深夜调试实录:一个真实泊车抖动问题的完整排查链路
讲完机制,分享一段我实际处理过的调试经历,大家能更直观地看到问题是如何一步一步定位出来的。
现象是这样的:在某园区封闭停车场里,车辆执行垂直车位泊入。整个过程前半段都很正常,车速均匀,方向稳定。但接近车位边界线大约3米处开始,方向盘出现高频小幅修正,车身横向摇摆,控制日志里横向误差在正负5厘米内震荡。到车辆完全停稳后,误差才归零。
我第一步先抓规划层输出的轨迹,看路径曲率和速度曲线是否存在异常。打开DreamView回放之后,路径曲线在前半段非常圆滑,曲率连续无跳变,说明路径优化模块输出的几何没问题。速度曲线在最后2米有一个轻微的小台阶——不是急刹,只是从1.5米/秒缓慢降到0.5米/秒左右,这也算正常。
第二步,我把控制模块的反馈日志单独拉出来对比。发现纵向速度跟踪误差很小,但横向偏差在时间序列上呈现出和方向盘转角同频率的振荡。这个现象基本排除速度规划单独出问题的可能,因为控制端在纵向精度上没出现大的滞后,振荡是横向上被扰动之后一直无法收敛。
第三步,我回头翻边界决策模块的可视化结果。这一翻就发现问题了:车位入口附近,边界不是渐变收缩的,而是直接从一个较宽的值跳到一个很小的值,形成了台阶。原因在于感知模块对车位角点的检测置信度在某一帧发生变化,导致车位轮廓微调,边界决策没有对时序变化做滤波,直接把新轮廓映射成了新的左右边界。
这个台阶被路径优化器看到之后,QP为了不超边界,被迫在台阶附近生成一个紧贴边界的过渡曲线。DP粗路径没感知到这个台阶的严重性,QP平滑时又无法突破边界,只能在边界允许的区间内反复调整,于是产生了一个很隐蔽的小S弯。这个小S弯在几何上看着不严重,但进入控制端后,恰好处于低速段,横向控制增益被放大,就成了肉眼可见的抖动。
定位到根因后,修复方案分两步。第一步,在边界决策模块增加时序平滑,让边界值的变化受到速率限制,不允许在相邻两帧内突变超过设定阈值。第二步,在路径优化器里增加一个针对边界突变处的约束缓冲,即检测到相邻边界宽度变化过大时,自动在突变点前后扩展一段过渡区间,让QP有足够的余量平滑过渡。
改完参数再跑同一场景,方向盘修正频率明显下降,横向误差稳定在正负1厘米内。整个排查过程耗时一晚上,但最终确认问题不在某个算法的单一环节,而是边界决策与路径优化在衔接处的信息不对称造成的。
4. 参数没调对才叫惨:三个模块的关键配置与经验值
这部分直接给干货。Apollo的规划配置主要通过配置文件管理,路径优化、速度规划、边界决策分别有各自的关键参数。我不把完整配置文件贴出来,只挑调试中最常踩坑的几项,并给出我实测下来比较稳的经验范围。
4.1 边界决策模块的平滑参数
边界决策没有太多直接的平滑参数,但有一个很容易被忽略的字段:边界变化速率限制。默认情况下这个值可能设得比较大,导致障碍物轮廓变化能直接反映到边界上。泊车场景建议把它降低到原来的一半左右。
另一个重要的参数是边界扩展的安全距离。普通巡航场景可能只需要让路径离障碍物保持30到50厘米,但泊车入位时,由于车辆需要摆姿态,路径可能需要在更近的距离内通过。这个值设太大,路径优化器会以为空间不够,始终无法生成一条能顺利进车位的曲线;设太小,又可能撞到感知误差范围内的障碍物。我个人的经验是,低速泊车时将安全距离压缩到15到25厘米,并同时依赖控制模块的精度来兜底。
4.2 路径优化器的权重与限值
路径优化器里最影响手感的是曲率变化率权重(对应方向盘转动平顺性)和横向加速度限值(对应身体侧倾感)。权重太大,轨迹会变得过度平滑,在窄车位里可能无法满足边界约束;权重太小,轨迹会显得很"硬",方向突变明显。
调试顺序建议是先看路径是否满足边界,再看曲率限值,最后才调权重。具体数值因车型而异,但有一个通用的经验:如果日志里出现"path optimizer failed"或者频繁进入fallback状态,优先检查边界生成的宽度是否满足最小转弯半径需求,其次是横向采样分辨率是否过粗。
另外一个小经验:不要同时调多个权重。一次只改一个参数,跑同一场景对比,否则你根本不知道是哪个改动起了作用。我见过很多同事一口气改了五个参数,结果轨迹变了但不知道因为哪一项,后面根本没法定量分析。
4.3 速度规划的时间分辨率与停车精度
速度规划模块的调试重点在末端的减速策略。Apollo的速度规划支持配置停车阶段的结束速度、减速度和冲击度上限。泊车场景往往把停车精度要求设在正负10厘米以内,这意味着末段时间不能排得太密,否则控制端的执行精度跟不上。
实测中,我会把减速阶段的冲击度限制设置得比常规巡航更柔和一些,同时把末段的最小参考速度抬高一点,避免规划器生成"极慢但尚未停稳"的轨迹让控制端反复试探。一个微小的调整就能明显改善车内乘员的体感。
下面是我调了一段时间后沉淀下来的一套初始参数表,具体项目可以在此基础上微调。
| 模块 | 关键参数 | 我的经验值(低速泊车场景) | 备注 |
|---|---|---|---|
| 边界决策 | 安全距离 | 15cm-25cm | 过大会导致端到端姿态无法生成 |
| 边界决策 | 边界变化率限制 | 默认值的一半 | 防止感知抖动直接传导到路径 |
| 路径优化 | 曲率变化率权重 | 略高于默认值 | 关键看末段S弯是否明显 |
| 路径优化 | 横向采样分辨率 | 20cm-50cm | 采样太粗会丢失边界细节 |
| 速度规划 | 末端冲击度限制 | 1.0-1.5 m/s³ | 冲击度太高会让体感突然顿挫 |
| 速度规划 | 停车目标精度 | ±5cm-±10cm | 精度过高会加重控制器负担 |
提示:参数表里的数值来自我实际测试的某个车型配置,不同车的轴距、转向比、控制带宽都不一样。换车前先把这只当作初始值,不要当作标准答案直接上线。
5. 一些更隐蔽的"高级雷区":多场景切换与回退机制
调试过程中我还发现,有些问题的根源其实不在轨迹优化本身,而在于上游场景状态切换时,规划模块的行为发生了改变。泊车场景不是一个从头到尾都相同的状态机,它会根据车辆位置、档位、障碍物情况在多个子状态之间切换,比如"巡航接近车位"、"调整姿态"、"直接倒车入库"等。
每次状态切换都可能触发不同的任务配置,这导致两个相邻状态对同一段轨迹的优化目标不一致。举个真实例子:车辆在“巡航接近”状态时,路径优化器的参数是偏保守的,速度规划也按常规巡航速度执行;但当状态切换到"泊车调整"后,优化器更激进地追求贴合边界,速度规划开始大幅度降速。
如果切换发生得过早或过晚,前后两段轨迹之间就会出现一个明显的"断点"——不是说路径不连续,而是路径的曲率特性和速度的分布规律在切换点附近发生突变。控制端在切换点附近的反馈会变得异常敏感,产生可感知的闯动。
处理这种问题的思路是检查各状态的切换条件里是否包含了足够的状态保持时间,或者增加一个过渡区间的轨迹平滑逻辑,让两个状态之间的轨迹特征渐变衔接。单纯在参数上修往往是无力的,因为问题出在上层状态机。
还有一个容易踩的雷区是规划器的回退机制。Apollo的规划器在优化失败时会回退到更简单的策略,以保证车辆不会停在原地。这种回退机制是安全兜底,但它的输出轨迹质量往往比较粗糙,如果在上游因素偶尔触发回退,就会看到一段与前后轨迹风格完全不同、抖动明显的曲线。
排查时建议把回退触发条件打开日志,定位到具体是哪个约束导致优化失败,而不是看到一个结果就直接去调下游参数。很多时候,回退不是因为参数不合理,而是因为边界条件里出现了极端值——比如障碍物紧贴车身,或者车位几何形状超出了当前算法能处理的范围。
6. 怎么把三个模块的关系理顺:我的长期调参经验
如果你问我在大量实车测试之后最大的感悟是什么,我会说:泊车轨迹优化的三个模块不是串联在一起的流水线,而是一个互相反馈的闭环。
边界决策如果粗糙,路径优化再精细也是白搭;路径优化如果只追求几何平滑,速度规划就不得不扛下所有不适;速度规划如果过于激进,控制模块就会在末端不停修正。真正好用的配置,是三个模块在边界条件上互相尊重,在目标函数上主次分明。
我现在的做法是,每次拿到一个新车或者说新场景,先花半天时间跑一遍默认配置,把三个模块的可视化结果同时打开,盯着边界有没有突变、优化器有没有频繁失败、速度曲线在末端是否平滑。定位问题不急着改参数,先跟代码确认这个参数的物理意义,想清楚它影响的是约束还是目标,再一次性做最小改动。
这些经验不一定能直接解决你手里的具体问题,但它能帮你建立一个排查顺序:先查边界,再查路径,最后查速度。很多时候你以为是优化器的问题,结果往上翻一眼就发现,边界决策就已经把方向带偏了。
泊车轨迹优化这行没有银弹,所有"骚操作"背后都是模块之间无数次的妥协与配合。把这三个模块想成三个合作方,你的调试思路会清晰很多。