1. 从“想去哪”到“怎么去”:聊聊自动驾驶的“大脑”运动规划

想象一下,你开车去一个陌生的地方。打开导航,输入目的地,它会立刻给你规划出一条最优路线,告诉你“前方500米右转”。这个过程,就是全局路径规划,它解决了“从A到B走哪条路”的大问题。

但当你真正手握方向盘上路,事情就复杂多了。那条规划好的“最优路线”在你眼前,只是一条虚拟的参考线。你需要时刻观察周围:前车突然减速了,你得踩刹车;旁边有电动车窜出来,你得打方向避让;快到路口了,得提前变道。这个在宏观路线指导下,实时处理动态环境、决定“下一秒方向盘转多少、油门踩多深”的微观决策过程,就是运动规划。它是自动驾驶系统从“知道去哪”到“安全平稳抵达”的关键一步,是车辆真正的“即时反应大脑”。

传统的机器人运动规划算法,比如大家可能听过的RRT*(快速探索随机树)或者DWA(动态窗口法),在结构化的城市道路场景里,常常会“水土不服”。为什么?因为它们大多是为空旷、规则较少的空间(比如仓库、野外)设计的。它们能很好地避开一个突然出现的箱子,但却很难理解“应该靠车道中心行驶”、“在十字路口要按信号灯和标线通行”、“前方有公交站要小心行人”这些复杂的交通规则和驾驶常识。

这时候,像 OpenPlanner 这样的算法就登场了。它最初是日本名古屋大学为开源自动驾驶框架 Autoware 开发的“王牌”局部规划器。我最早接触Autoware做项目时,就被OpenPlanner的设计思路吸引了。它不像一些算法那样“蛮干”,试图用复杂的数学模型一次性解决所有问题,而是采用了一种更聪明、也更像人类驾驶员思维的策略:“先广撒网,再优中选优”

简单来说,OpenPlanner的工作流分两步走,这也是它最核心的两个模块:

  1. Roll-outs Generator(候选轨迹生成器):以全局路径为参考,像扇子一样,在车辆前方生成一簇(比如几十条)平滑的、可能的行驶轨迹。这就像是老司机在脑子里快速预演了“直行一点”、“向左微调”、“向右避让”等多种可能。
  2. Roll-outs Evaluator(候选轨迹评估器):给这簇轨迹“打分”。结合实时感知到的障碍物、车道线信息,以及“尽量走中间”、“别晃来晃去”、“绝对别撞上”这些原则,给每条轨迹算一个“代价”。分数最低(代价最小)的那条,就是当前的最优选择。

这种方法的妙处在于,它把复杂的连续决策问题,转化为了一个离散的采样和评估问题,计算效率很高,非常符合车载计算单元实时性的要求。而且,因为它紧密依赖矢量地图(一种包含车道线、交通标志、红绿灯位置等语义信息的高精度地图),所以天生就“懂交规”,能让自动驾驶行为更拟人、更合规。下面,我们就来深入看看这两个核心模块到底是怎么工作的。

2. 核心一:Roll-outs Generator —— 如何“广撒网”生成候选轨迹

Roll-outs Generator 是运动规划的“创意发散”阶段。它的任务是在极短的时间内(通常要求小于0.1秒,否则车辆都跑出去好几米了,规划还没出来),基于当前车辆状态和全局参考路径,生成一簇可行的、平滑的局部轨迹。这听起来简单,但里面有不少讲究。

2.1 输入与输出:给算法“喂”什么,它“吐”什么

首先,我们得明确这个模块的输入输出。输入主要有四样东西:

  • 车辆的当前位置和姿态:包括坐标(x, y)和航向角(yaw)。这是所有计算的起点。
  • 规划距离:我们打算为未来多长的一段路程做规划?比如5秒或60米。规划太短,车辆会显得“短视”;规划太长,不确定性太大,计算量也猛增。
  • 候选路径数量:准备生成多少条备选轨迹?这个数需要权衡。太少,可能漏掉最优解;太多,计算负担重。通常根据车道宽度和规划密度来设置。
  • 全局路径:从导航模块来的那条“理想宏观路线”,作为所有局部轨迹的参考基准。

输出就是 N条平滑的轨迹点序列,每条轨迹都从车辆中心开始,一直延伸到最大规划距离。你可以把它想象成从车头向前辐射出去的一把“扇骨”。

2.2 三段式采样:像拉弓射箭一样的轨迹构造

OpenPlanner 把一条轨迹的生成过程,巧妙地分成了三个连续的区域,我把它比喻成“拉弓-瞄准-放箭”的过程。如下图所示(想象一张图:车辆后方一个短区域“Car Tip”,连接一个斜向过渡区“Roll in”,最后是平行的“Roll out”区):

[车辆] ---[Car Tip]--->[Roll in]=====[Roll out]=====> (规划终点)
  • Car Tip(车辆尖端区):这是紧挨着车辆后方的一小段。为什么从“后方”开始?因为我们要规划的是车辆未来要走的路径,而车辆本身有长度,轨迹的起点应该是后轴中心(或车辆中心),但平滑过渡要考虑整车姿态。这个区域的长度直接影响车辆从当前轨迹切换到新轨迹的平滑度。太短,切换会生硬;太长,反应会迟钝。它就像一个缓冲带。

  • Roll in(滚入区):这是从Car Tip末端到与全局路径平行的那一点之间的过渡区域。它的长度与车辆当前速度成正比。这是一个非常符合驾驶直觉的设计:车开得越快,你需要越早、越平缓地开始调整方向,为变道或转弯做准备。想象一下高速公路上,你要从最左道变到最右道,肯定会提前几百米就开始慢慢靠过去,而不是到了出口才猛打方向盘。Roll in区就负责生成这段平滑的过渡曲线。

  • Roll out(滚出区):这是从过渡完成到最大规划距离的主体部分。在这个区域,算法以全局路径为基准,向两侧进行等间距的横向采样。比如,每隔0.5米(这个间隔称为Roll out密度)取一个点,这些点连成的线就是一条候选轨迹。有多少个横向偏移量,就能生成多少条平行的候选轨迹。

2.3 平滑优化:让轨迹“丝般顺滑”

通过上面三步采样得到的轨迹点,还只是一些离散的“路点”,直接连起来可能像折线一样生硬,车辆如果跟着这种轨迹走,方向盘会不停地剧烈抖动,乘客会晕车。所以,必须进行平滑处理。

OpenPlanner 使用了一种叫做共轭梯度法的非线性迭代优化技术来做平滑。你可以把它理解为一个“轨迹美容师”。它的优化目标通常包括:

  1. 贴近原始采样点:平滑后的轨迹不能偏离采样点太远。
  2. 曲率最小化:让轨迹尽可能平直,转弯处也尽量圆滑,这样方向盘转动更平顺,乘坐舒适性高,也更节能。
  3. 曲率变化率最小化:避免曲率突变,也就是方向盘转角速度不要变化太猛。

经过这一套“组合拳”——三段采样加共轭梯度平滑——Roll-outs Generator 最终输出一簇既覆盖了多种横向可能(左偏、居中、右偏),又满足车辆运动学约束(不会让车“飘移”起来)的平滑候选轨迹,为下一步的“优中选优”做好了准备。

3. 核心二:Roll-outs Evaluator —— 如何“优中选优”打分评估

生成了几十条看上去都不错的轨迹后,接下来就是最关键的决策环节:到底选哪条?这就是 Roll-outs Evaluator 的职责。它就像一个严格的考官,给每条候选轨迹打分,最后录取总分最高的那位(在OpenPlanner里是代价最小)。它的评分标准非常综合,主要看三个方面,我把它总结为 “守规矩、开得稳、保安全”

3.1 优先级代价:鼓励“守规矩”,走在车道中央

这是最基础的一项代价。其核心思想是:在没有其他干扰的情况下,车辆应该稳稳地行驶在车道中心线上。 所以,在所有生成的候选轨迹中,那条与全局参考路径(通常就是车道中心线)重合度最高的轨迹,优先级代价被设为最低(比如0)。越向车道两侧偏离的轨迹,优先级代价就线性或非线性地增加。

这很好理解,就像我们开车,只要路况允许,都会下意识地把车保持在车道中间,这样左右都有安全余量,心里最踏实。优先级代价就是把这个驾驶常识数学化了。在算法内部,它会为每条轨迹分配一个基础分数,中间路径是“优等生”,两边是“良”或“中”,确保自动驾驶车的“默认状态”是规规矩矩的。

3.2 过渡代价:追求“开得稳”,避免“蛇形走位”

你有没有遇到过那种在车道里不停左右微调、画龙的车?坐起来非常不舒服。过渡代价就是为了防止我们的自动驾驶车出现这种“蛇形走位”的情况。它的计算会考虑当前时刻选择的轨迹,与上一时刻最终执行的轨迹之间的横向差异

如果这次选中的轨迹,相比上次的位置偏移很大,那么即使它其他方面很好,也会因为“过渡代价”高而被扣分。这个设计强迫算法倾向于选择与上一周期轨迹连续的、变化平缓的选项,从而保证了车辆横向控制的平滑性,提升了乘坐舒适度。在实际调试中,这个代价的权重系数很重要,调得太小,车可能会频繁晃动;调得太大,车又会显得过于“固执”,遇到障碍时反应迟钝。

3.3 碰撞代价:死守“保安全”的底线

这是三项代价中最重要、权重通常也最高的一项。安全是自动驾驶的绝对红线。碰撞代价负责评估每条候选轨迹与感知模块检测到的所有动态、静态障碍物发生碰撞的风险。它的计算又细分为两个维度,非常精细:

  • 横向碰撞代价:计算轨迹上的每个点,到最近障碍物的横向距离。距离越近,代价越高。这主要用于评估“擦碰”风险。比如,旁边车道有一辆大货车贴得很近,即使纵向还有距离,横向代价也会飙升,促使车辆向另一侧微调,拉开横向安全距离。
  • 纵向碰撞代价:计算沿着轨迹方向,车辆与障碍物的相对距离和时间。这评估的是“追尾”或“对撞”风险。算法会预测如果沿着这条轨迹行驶,在多少秒后会与障碍物相遇(Time to Collision, TTC)。TTC越小,代价呈指数级增长。这能让车辆提前很远就发现风险并开始柔和制动。

在实际计算时,算法会为每个障碍物生成一个“风险场”,轨迹穿过风险越高的区域,积分得到的碰撞代价就越大。这种基于距离和时间的代价函数,比简单的“有/无”碰撞检测要精细得多,能实现更早、更柔和的避障。

3.4 归一化与加权求和:做出最终决策

三项代价算出来之后,它们的数值范围和量纲可能完全不同。比如碰撞代价可能动辄几千,而过渡代价只有个位数。如果不处理,碰撞代价就会“一言堂”,完全主导结果。因此,必须进行归一化处理。通常是将所有候选轨迹的同一项代价,分别除以该项代价中的最大值(或经过某种缩放),将它们的值都映射到[0, 1]或一个固定的区间内。

最后,就是加权求和了: 总代价 = w1 * 归一化优先级代价 + w2 * 归一化过渡代价 + w3 * 归一化碰撞代价 这里的 w1, w2, w3 就是调参工程师的“魔法旋钮”了。在不同的场景下(高速巡航、城市跟车、拥堵蠕行),可以调整这些权重来改变车辆的“性格”:是更激进地贴线行驶,还是更保守地远离风险,或是更注重乘坐的平稳感。OpenPlanner的灵活性很大程度上就体现在这里。

评估器遍历所有候选轨迹,计算每条的总代价,然后选出总代价最小的那条,输出给底层的转向和速度控制器去执行。至此,一个完整的规划周期就结束了,然后立刻开始下一个周期的感知-规划-控制循环。

4. 为什么是OpenPlanner?对比传统方案的独特优势

理解了原理,我们再来看看OpenPlanner在实战中到底强在哪里。我参与过一些基于ROS的机器人导航项目,也折腾过传统的Navigation Stack,后来转到自动驾驶领域用Autoware和OpenPlanner,感受非常明显。它的优势不是某一项技术特别黑科技,而是一套组合拳打在了自动驾驶城市导航的痛点上。

第一,对矢量地图的深度集成,让它“天生懂规则”。 这是OpenPlanner相比很多传统机器人规划器(如OMPL)的降维打击。传统的规划器通常在一张“代价地图”上工作,这张地图上只有“可通行”和“不可通行”(障碍物)的区别,顶多加上“膨胀层”表示安全距离。但OpenPlanner直接使用矢量地图,这种地图里,车道线、停止线、交通灯、斑马线、甚至限速标志都是带有精确坐标和语义的物体。这意味着:

  • 全局规划器可以直接在道路网络(哪条车道连哪条车道)上搜索路径,速度快,路径天然符合道路拓扑。
  • 局部规划器(就是我们讲的Roll-outs Generator)在生成候选轨迹时,其参考线本身就是车道中心线,保证了生成的轨迹天然符合车道几何。
  • 行为状态机(比如遇到红灯该减速停车在停止线前)可以非常方便地与地图元素关联,实现基于规则的精确驾驶行为。

第二,与非完整约束车辆的完美适配。 很多移动机器人(包括汽车)都是“非完整约束”系统,简单说就是不能像螃蟹一样横向移动,它的运动方向受前轮转向角限制。传统的Navigation Stack里的局部规划器(如DWA、TEB)虽然也考虑运动学,但OpenPlanner从采样开始(Roll in区与速度相关)到平滑优化(共轭梯度法考虑曲率),整个设计都紧密贴合了阿克曼转向车辆的模型。用它来规划车的轨迹,感觉就是“对味儿”,轨迹更自然,控制器也更容易跟踪。

第三,模块化与实时性的平衡。 OpenPlanner把复杂的规划问题分解成“生成-评估”两个相对独立的模块。这种架构清晰,易于理解和调试。Roll-outs Generator负责几何可行性,Roll-outs Evaluator负责综合决策。虽然采样评估的方法在理论上不是绝对最优(可能采样漏了真正的最优解),但在工程实践中,只要采样密度足够,它就能以可承受的计算成本,在几十毫秒内找到一个非常优的解,这对于需要每秒决策10-20次(100ms周期)的实时系统来说,是至关重要的权衡。我实测下来,在主流车载计算平台(如英伟达Jetson AGX)上,跑满整个Autoware的感知、定位、规划模块,OpenPlanner的局部规划部分依然能稳定保持在50ms以内,非常“稳”。

第四,行为逻辑与运动规划的分离。 在OpenPlanner的完整架构中,在局部规划器之上还有一个行为状态生成器。这个模块负责更高层的决策,比如“现在是跟车状态还是换道状态?”“前方有停车标志,应该进入停车等待状态”。它根据矢量地图、交通规则和感知结果,输出一个“行为状态”给局部规划器。局部规划器再根据这个状态,调整代价函数的权重或采样策略。比如在“换道”状态,优先级代价的权重可能会降低,允许车辆更偏向车道一侧。这种分离让系统结构更清晰,维护和升级行为逻辑变得更容易,而不需要去改动底层复杂的轨迹生成与评估代码。

当然,OpenPlanner也不是万能的。在完全无结构化、没有先验地图的野外环境(比如越野),它的优势就不明显了,这时候可能更需要Hybrid A*这类自由空间规划器。但在有高精地图、交通规则复杂的城市道路和高速场景下,它确实提供了一套经过大量实践验证的、高效可靠的解决方案。这也是为什么它能在Autoware这样一个工业级开源框架中,长期作为主流的局部规划算法存在。

5. 动手实践:在Autoware中运行与调试OpenPlanner

光说不练假把式。如果你对OpenPlanner感兴趣,最好的方式就是把它跑起来,看看实际效果。这里我以Autoware Universe版本为例,分享一下基本的操作和调试经验。注意:以下操作假设你已经有一个可以运行的基础Autoware环境,包括ROS 2、地图和必要的感知定位模块。

5.1 核心参数配置:调整车辆的“性格”

OpenPlanner的行为很大程度上由参数文件控制。主要配置文件通常位于 op_planner 包的 config 目录下,比如 scenario_planning 中的 lane_driving 相关yaml文件。这里有几个我经常调整的关键参数,它们直接影响了规划器的“性格”:

  • 规划相关参数

    planning:
      # 最大规划距离,单位米。高速场景需要调大,城区可调小。
      max_local_plan_distance: 100.0
      # 横向采样滚动密度,单位米。值越小,生成的候选轨迹越多越密,规划更精细,但计算量增大。
      roll_out_density: 0.5
      # 横向采样总宽度,单位米。通常是车道宽度的倍数,决定了轨迹的搜索范围。
      roll_out_width: 6.0
      # 候选轨迹总数。通常由宽度和密度决定,但也可以直接设定上限。
      roll_outs_number: 40
    

    调整这些参数就像调整相机的焦距和视野。规划距离是你的“前瞻距离”,密度和宽度决定了你“考虑选项的精细度和范围”。

  • 代价函数权重

    evaluation:
      # 优先级代价权重。调高会让车更“强迫症”地保持在车道中心。
      priority_weight: 0.5
      # 过渡代价权重。调高会让车更“平滑”,但可能对突发障碍反应变慢。
      transition_weight: 0.3
      # 碰撞代价权重。这是最重要的,通常设得最高。调高会让车更“胆小”。
      longitudinal_weight: 5.0
      lateral_weight: 2.0
    

    这是我调试时花时间最多的地方。比如发现车辆在空旷路上左右轻微摆动,可能是过渡代价权重太低;发现车辆贴障碍物太近,可能是横向碰撞代价权重不够高。没有一套通用的“黄金参数”,必须根据你的车辆动力学特性和应用场景反复路测调整。

  • 安全距离参数

    avoidance:
      # 与障碍物的最小纵向安全距离(米)
      min_distance_to_obstacle: 3.0
      # 与障碍物的最小横向安全距离(米)
      lateral_safety_distance: 0.8
    

    这些是安全底线。即使代价函数算出来某条轨迹最优,但如果它离障碍物距离小于这些值,也会被直接否决。

5.2 可视化与调试:用RViz“看见”规划过程

调试OpenPlanner,离不开ROS强大的可视化工具RViz。你需要添加一些关键的显示项来观察内部状态:

  1. 全局路径:通常以 global_path 或类似话题发布,显示为一条蓝色的线。这是你的参考基准。
  2. 候选轨迹:OpenPlanner会发布所有Roll-outs轨迹。在RViz中订阅 rolloutscandidate_trajectories 话题,你会看到一簇彩色的线(比如从绿到红表示代价从低到高)。这是最直观的,你能看到算法“想了”哪些可能。
  3. 最终轨迹:订阅 final_trajectorylocal_path 话题,通常是一条醒目的绿色或红色实线。这就是评估器选出的“最优解”。
  4. 代价信息:有些版本会发布文本信息或数组,显示每条轨迹的各项代价和总代价。这对于深度调试代价函数权重至关重要。

一个典型的调试流程是:让车辆在模拟或实车中运行,在RViz中观察候选轨迹簇。当遇到障碍物时,看哪条轨迹“胜出”,思考它为什么胜出?是因为离障碍物远(碰撞代价低),还是因为它更居中(优先级代价低)?如果选出的轨迹你觉得不合理,比如太激进或太保守,就回头去调整对应代价的权重,然后再次测试。

5.3 常见问题与“踩坑”经验

  • 问题:车辆轨迹抖动,不够平滑。

    • 可能原因1:Roll-outs Generator的平滑优化参数(如共轭梯度的迭代次数、曲率约束权重)设置不当。
    • 可能原因2transition_weight(过渡代价权重)设置过低,无法抑制轨迹间的频繁跳跃。
    • 可能原因3:底层控制器(如Pure Pursuit或MPC)的跟踪性能不佳,或者控制频率与规划频率不匹配。
    • 我的经验:先确保规划器输出的轨迹本身在RViz里看是平滑的。如果规划轨迹平滑但车抖,问题大概率在控制器;如果规划轨迹本身就跳变,那就调整规划器参数。
  • 问题:对突然出现的障碍物反应迟钝或过激。

    • 可能原因1:感知模块的检测和跟踪有延迟,或者障碍物话题的发布时间戳不同步,导致规划器用的是“过去”的信息。
    • 可能原因2:碰撞代价函数中的“安全距离”或TTC阈值设置不合理。距离设得太大,车会过早制动显得“怂”;设得太小,又可能反应不及。
    • 可能原因3:规划频率太低。如果规划周期是200ms,那意味着最坏情况下,算法对突发事件的反应会慢200ms。
    • 我的经验:一定要用 ros2 topic hz 等工具检查关键感知话题(如障碍物、定位)的发布频率和延迟。确保规划器在一个同步、新鲜的感知世界里做决策。然后通过大量场景测试,微调安全距离参数。
  • 问题:在弯道或复杂路口,轨迹偏离车道中心甚至“切弯”。

    • 可能原因:全局路径本身可能不够平滑,或者局部规划器的“跟随”权重设置有问题。也可能是在弯道处,为了优化曲率(让转弯更平缓),平滑算法牺牲了对全局参考路径的贴合度。
    • 我的经验:检查全局规划器生成的路径质量。可以尝试提高Roll-outs Generator中“路径跟踪误差”在平滑代价函数中的权重。同时,确保矢量地图中车道中心线的几何精度足够高。

调试自动驾驶算法是个细致活,尤其是像运动规划这样承上(感知、决策)启下(控制)的模块。OpenPlanner给了我们一个清晰、可调试的框架,但真正让它在一个具体的车上、具体的场景下跑得又好又稳,还需要大量的测试、数据分析和参数微调。这个过程没有捷径,就是不断地“假设-调整-验证”。每解决一个奇怪的轨迹问题,你对算法的理解就会更深一层。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐