Apollo 6.0 planning模块protobuf定义详解与避坑指南

发布时间:2026/10/21 9:11:24
Apollo 6.0 planning模块protobuf定义详解与避坑指南 1. 从一条报错说起为什么要啃 Apollo 6.0 的 protobuf 定义如果你在自动驾驶规划模块的开发或调试中遇到过类似Cannot find field planning.TrajectoryPoint.v或者AttributeError: ADCTrajectory object has no attribute debug这样的报错那你大概率已经踩到了同一个坑Apollo 6.0 的 planning 模块 protobuf 定义和你手头那份“祖传”的 proto 文件对不上。Apollo 6.0 是百度 Apollo 开源自动驾驶平台一个相当关键的版本节点。相比 3.5、5.0它在 planning 模块做了大量重构尤其是 protobuf 消息结构的变化直接影响了规划算法的输入输出、调试信息、场景判定等核心链路。很多同学拿到一份 planning 模块的代码编译能过但一跑仿真就崩或者 debug 信息死活读不出来根子往往就在 proto 定义没对齐。这篇文章就是围绕Apollo 6.0 planning module 的 protobuf 定义展开的。我会把 planning 模块涉及的核心 proto 文件、消息结构、字段含义、版本差异、以及实际开发中怎么用、怎么改、怎么避坑全部拆开讲清楚。不管你是刚接触 Apollo 的在校学生还是正在做规划算法落地的工程师只要你的工作涉及 planning 模块的数据流这篇内容都能直接拿来参考。需要提前说明的是Apollo 的 proto 文件分布在多个目录planning 模块相关的定义主要集中在modules/planning/proto/下同时还会依赖modules/common/proto/、modules/canbus/proto/、modules/perception/proto/、modules/routing/proto/等模块的 proto。理解这些文件之间的依赖关系比单纯背字段名重要得多。2. Apollo 6.0 planning 模块 proto 文件全景梳理2.1 核心 proto 文件清单与职责划分先上一张表把 planning 模块最核心的 proto 文件列出来。这张表是我自己整理代码时反复核对过的建议你直接收藏。proto 文件核心消息主要职责planning.protoADCTrajectory、TrajectoryPoint、EStop规划模块最终输出轨迹、急停信息planning_config.protoPlanningConfig、TaskConfig、ScenarioConfig规划模块全局配置、任务配置、场景配置decision.protoDecisionResult、MainDecision、ObjectDecision决策结果包含对障碍物的行为决策pad_msg.protoPadMessage人工接管、调试指令输入scenario.protoScenario、ScenarioType场景类型定义与场景状态traffic_rule_config.protoTrafficRuleConfig交通规则配置learning_model.protoLearningModelConfig学习型规划模型配置reference_line.protoReferenceLine、ReferencePoint参考线定义st_graph.protoSTGraphData、STBoundary速度规划用的 ST 图数据sl_boundary.protoSLBoundary障碍物在 SL 坐标系下的边界path.protoPathPoint、Path路径点与路径定义这些文件不是孤立的。planning.proto里的ADCTrajectory会引用common/proto/pnc_point.proto中的TrajectoryPoint而decision.proto又会引用perception/proto/perception_obstacle.proto中的障碍物定义。搞清楚引用链才能避免“改了 A 文件B 文件编译不过”的尴尬。2.2 proto 文件之间的依赖关系Apollo 的 proto 依赖是通过import语句建立的。举个例子planning.proto的开头通常长这样syntax proto2; package apollo.planning; import modules/common/proto/header.proto; import modules/common/proto/pnc_point.proto; import modules/common/proto/geometry.proto; import modules/planning/proto/decision.proto; import modules/planning/proto/planning_config.proto; import modules/canbus/proto/chassis.proto;这里有几个关键点值得注意。第一Apollo 6.0 的 proto 用的是proto2 语法不是 proto3。这意味着字段默认是optional的而且required字段一旦缺失会导致解析失败。第二package声明为apollo.planning所以在 C 里访问时要用apollo::planning::ADCTrajectoryPython 里则是apollo.planning.ADCTrajectory_pb2。第三import 路径是相对于 Apollo 工作空间根目录的编译时需要在BUILD文件里正确声明依赖。提示如果你在自定义模块里引用 planning 的 proto务必在BUILD文件的deps里加上对应的proto_library目标否则会出现“找不到符号”的链接错误。2.3 为什么 Apollo 6.0 的 proto 改动这么大Apollo 6.0 相比 5.0planning 模块的 proto 改动主要集中在三个方面。一是场景管理器的引入scenario.proto新增了更细粒度的场景类型和状态机定义支撑ScenarioManager的运作。二是决策信息的结构化decision.proto里的ObjectDecision增加了更多行为标签方便调试和可视化。三是配置文件的模块化planning_config.proto把原本臃肿的PlanningConfig拆成了多个子配置每个 task 和 scenario 都有独立的 config。这些改动的背后逻辑其实很清晰Apollo 6.0 想让 planning 模块从“一个大流程”变成“可插拔的任务编排”。proto 作为数据契约必须先把结构定清楚后面的插件化才有基础。理解了这一点你再看那些新增字段就不会觉得是“为了改而改”了。3. 核心消息结构深度拆解3.1 ADCTrajectory规划模块的最终答卷ADCTrajectory是 planning 模块最重要的输出消息没有之一。它定义了自车在未来一段时间内的完整轨迹下游的控制模块就是靠它来驱动车辆的。在 Apollo 6.0 里它的结构大致如下message ADCTrajectory { optional apollo.common.Header header 1; optional double total_path_length 2; optional double total_path_time 3; repeated apollo.common.TrajectoryPoint trajectory_point 4; optional EStop estop 5; optional apollo.planning.DecisionResult decision 6; optional apollo.planning.PlanningDebug debug 7; optional apollo.canbus.Chassis.GearPosition gear 8; optional apollo.planning.TrajectoryPoint debug_point 9; optional apollo.common.VehicleSignal vehicle_signal 10; optional bool is_replan 11; optional apollo.planning.Scenario scenario 12; optional double replan_reason 13; optional apollo.planning.LatencyStats latency_stats 14; optional apollo.planning.TrajectoryPoint right_of_way_status 15; }这里我重点讲几个容易踩坑的字段。trajectory_point是repeated的每个点包含位置、速度、加速度、时间戳、相对时间等信息。注意 Apollo 6.0 里TrajectoryPoint的定义在common/proto/pnc_point.proto不是 planning 自己的文件很多人第一次找会找错地方。estop字段是急停信息当规划模块判定需要紧急停车时会在这里填充原因。debug字段是调试信息的入口里面包含了每一个 task 的执行结果是排查问题的金矿。is_replan和replan_reason这两个字段在 6.0 里被强化了。当规划器决定重新规划时is_replan置为 truereplan_reason会给出原因码。实际调试中如果你发现轨迹频繁跳变先看这两个字段往往能快速定位是哪个场景触发了重规划。3.2 DecisionResult决策信息的结构化表达DecisionResult是 planning 模块决策层的输出它回答了“自车对周围障碍物打算怎么做”这个问题。在 Apollo 6.0 里它的核心结构包括MainDecision、ObjectDecision和StopReason等。message DecisionResult { optional MainDecision main_decision 1; optional ObjectDecisions object_decision 2; optional StopReason stop_reason 3; optional apollo.common.VehicleSignal vehicle_signal 4; }MainDecision描述自车自身的行为比如是否变道、是否停车、是否避让。ObjectDecision则是对每一个感知到的障碍物给出决策标签比如IGNORE、STOP、FOLLOW、YIELD、OVERTAKE。这些标签在可视化工具里会以不同颜色显示调试时非常直观。这里有个经验ObjectDecision里的object_id必须和感知模块输出的障碍物 ID 一致否则决策和感知就对不上。我在一次联调中就遇到过感知模块升级后 ID 生成规则变了导致 planning 的决策信息全部错位可视化里障碍物颜色乱成一团。后来在perception_obstacle.proto里统一了 ID 定义才解决。3.3 Scenario 与 ScenarioConfig场景化规划的基石Apollo 6.0 最显著的变化之一就是场景化规划。scenario.proto定义了Scenario消息和ScenarioType枚举planning_config.proto里则定义了ScenarioConfig。enum ScenarioType { UNKNOWN 0; CRUISE 1; CRUISE_URBAN 2; CRUISE_HIGHWAY 3; CHANGE_LANE 4; INTERSECTION 5; PARKING 6; ... } message Scenario { optional ScenarioType scenario_type 1; optional string scenario_name 2; optional ScenarioConfig scenario_config 3; optional StageType current_stage 4; }ScenarioType枚举定义了所有支持的场景类型Scenario消息则携带了当前场景的实例信息。current_stage字段表示场景状态机当前所处的阶段比如变道场景可能分为LANE_CHANGE_PREPARE、LANE_CHANGE_EXECUTE、LANE_CHANGE_FINISH等阶段。实际开发中如果你想新增一个自定义场景需要做三件事在ScenarioType里加枚举值在ScenarioConfig里加配置项然后在ScenarioManager里注册对应的场景插件。少一步都不行而且枚举值不能和已有的冲突否则会出现场景误判。3.4 PlanningConfig配置体系的模块化设计PlanningConfig是 planning 模块的总配置入口在 Apollo 6.0 里它被拆得比较细message PlanningConfig { optional apollo.common.VehicleType vehicle_type 1; optional TaskConfig default_task_config 2; repeated ScenarioConfig scenario_config 3; optional TopicConfig topic_config 4; optional LatencyStatsConfig latency_stats_config 5; optional LearningModelConfig learning_model_config 6; optional TrafficRuleConfig traffic_rule_config 7; }default_task_config是默认任务配置scenario_config是各场景的独立配置。这种设计的好处是不同场景可以覆盖默认配置实现“场景定制”。比如高速巡航场景的跟车距离可以设得比城区大通过scenario_config就能单独指定。topic_config定义了 planning 模块订阅和发布的 topic 名称这个在 6.0 里被独立出来方便做多模块联调时统一管理。latency_stats_config则是延迟统计配置用于监控规划耗时。注意修改PlanningConfig后一定要同步更新conf/目录下的.pb.txt配置文件否则运行时加载的还是旧配置。这个坑我踩过不止一次编译过了但行为没变查半天才发现是配置文件没更新。4. 实操从零编译并验证 planning proto4.1 环境准备与依赖检查在动手之前先确认你的 Apollo 环境是完整的。Apollo 6.0 推荐用 Docker 环境官方提供了apolloauto/apollo:dev-x86_64-18.04-20200914_2052这类镜像。如果你是在裸机上编译需要确保 protobuf 版本是 3.6.1 以上g 版本不低于 7.5。进入 Apollo 工作空间后先跑一遍依赖检查cd /apollo bash docker/scripts/dev_start.sh bash docker/scripts/dev_into.sh进入容器后检查 protobuf 编译器protoc --version如果输出libprotoc 3.6.1或更高就没问题。低于这个版本编译 planning proto 时可能会报Unrecognized syntax identifier proto2之类的错误。4.2 单独编译 planning proto 的方法Apollo 用的是 Bazel 构建系统。单独编译 planning 模块的 proto可以这样操作cd /apollo bazel build //modules/planning/proto:planning_proto这条命令会编译modules/planning/proto/BUILD里定义的planning_proto目标。如果你想编译所有 planning 相关的 proto可以用bazel build //modules/planning/proto/...编译成功后生成的.pb.h和.pb.cc文件会在bazel-bin/modules/planning/proto/目录下。你可以进去看看确认planning.pb.h里有没有你需要的消息类。4.3 用 Python 验证 proto 解析C 编译通过不代表 proto 定义就完全正确最好用 Python 再验证一遍。Apollo 的 Python 环境里已经装好了 protobuf 库你可以写个小脚本import sys sys.path.append(/apollo/bazel-bin/modules/planning/proto) from planning_pb2 import ADCTrajectory traj ADCTrajectory() traj.total_path_length 100.0 traj.total_path_time 10.0 # 添加一个轨迹点 point traj.trajectory_point.add() point.path_point.x 1.0 point.path_point.y 2.0 point.v 5.0 # 序列化再反序列化 data traj.SerializeToString() traj2 ADCTrajectory() traj2.ParseFromString(data) print(Path length:, traj2.total_path_length) print(Point count:, len(traj2.trajectory_point)) print(First point v:, traj2.trajectory_point[0].v)这段脚本能跑通说明 proto 定义和 Python 绑定都没问题。如果报ImportError检查sys.path是否指向了正确的bazel-bin目录。如果报字段不存在那就是 proto 版本对不上需要回头核对文件。4.4 常见编译错误与快速修复错误信息原因修复方法Import xxx.proto was not foundimport 路径错误或依赖未声明检查 BUILD 文件 deps确认路径相对于工作空间根目录Field number xxx has already been used字段编号冲突检查同一 message 内是否有重复编号重新分配Expected required, optional, or repeatedproto3 语法混入 proto2确认文件头是syntax proto2字段加修饰符Cannot find field xxx字段名拼写错误或版本不一致对照 proto 文件核对字段名注意大小写protoc: command not found环境变量未配置在 Docker 内执行或手动安装 protoc这张表里的错误我几乎每一个都遇到过。最坑的是字段编号冲突因为 proto 的字段编号一旦被使用就不能复用删掉字段后编号也不能给新字段用否则老数据反序列化会出错。所以改 proto 时删字段要谨慎最好用reserved标记废弃编号。5. 开发中高频踩坑与排查实录5.1 字段编号冲突导致的序列化异常前面提到字段编号冲突这里展开讲一个真实案例。有一次我在decision.proto里加了一个新字段随手给了个编号15结果编译过了但运行时报Protocol message DecisionResult has no field named xxx。查了半天才发现15这个编号在另一个分支上已经被用过了合并代码时冲突了。Apollo 的 proto 文件在多人协作时字段编号管理是个大问题。我的建议是新增字段前先全局搜索该 message 的所有字段编号选一个最大的加一。如果团队有规范最好维护一个编号分配表。另外删除字段时一定要用reserved保留编号message DecisionResult { reserved 15, 16; reserved old_field_name; // 新字段从 17 开始 }这样即使有人误用了15编译时也会直接报错而不是等到运行时才出问题。5.2 proto 版本不一致引发的运行时崩溃Apollo 6.0 的 proto 定义和 5.0 有大量不兼容改动。如果你拿 5.0 的代码去解析 6.0 的 proto 数据或者反过来就会出现各种诡异问题。比如 5.0 的ADCTrajectory里没有scenario字段用 5.0 的代码解析 6.0 的数据scenario字段会被忽略但不会报错导致你以为场景信息丢了。排查这类问题最直接的方法是打印 proto 的 descriptorfrom planning_pb2 import ADCTrajectory print(ADCTrajectory.DESCRIPTOR.fields_by_name.keys())把字段列表打出来和官方 6.0 的 proto 文件对比一眼就能看出差异。如果发现字段缺失或多余就说明版本对不上需要统一 proto 文件。5.3 配置文件与 proto 不匹配的排查思路Apollo 的配置文件是.pb.txt格式的文本 proto加载时会用 proto 定义去解析。如果配置文件里写了 proto 里不存在的字段加载会失败。常见报错是Error parsing text-format apollo.planning.PlanningConfig: xxx: Cannot find field。排查步骤很简单第一确认报错字段名第二在对应的 proto 文件里搜索这个字段第三如果找不到说明配置文件是旧版本的需要更新如果找到了但类型不对检查配置值的格式。比如枚举字段要写枚举名而不是数字布尔字段要写true或false。提示Apollo 提供了protoc --decode命令可以把二进制 proto 数据解码成文本方便对比。用法是protoc --decodeapollo.planning.ADCTrajectory planning.proto data.bin。5.4 调试信息读取的实用技巧ADCTrajectory里的debug字段是排查规划问题的关键。它包含了每个 task 的执行结果、参考线信息、ST 图数据等。但debug字段默认可能不填充需要在配置里打开。在planning_config.pb.txt里找到PlanningDebug相关的配置确保enable_debug之类的开关是打开的。然后在代码里规划结束后会把 debug 信息填进ADCTrajectory。读取时可以用 Python 脚本把 debug 信息打印出来from planning_pb2 import ADCTrajectory with open(trajectory.bin, rb) as f: traj ADCTrajectory() traj.ParseFromString(f.read()) if traj.HasField(debug): debug traj.debug print(Planning debug info:) print(debug) else: print(No debug info available)HasField是 proto2 特有的方法用来判断 optional 字段是否被设置。proto3 里没有这个方法所以如果你用的是 proto3 定义需要换一种判断方式。6. 自定义扩展如何安全地修改 planning proto6.1 新增字段的规范流程在 planning proto 里加字段不是随便找个编号写上就行。规范流程是这样的第一确定要加在哪个 message 里尽量加在已有的扩展点而不是新建 message。第二选一个未被使用的字段编号建议从当前最大编号加一。第三字段类型要选对能用optional就别用required避免破坏兼容性。第四加完后同步更新.pb.txt配置文件和相关代码。举个例子假设你想在ADCTrajectory里加一个planning_mode字段表示当前规划模式message ADCTrajectory { // 已有字段... optional string planning_mode 16; }编号选16是因为前面最大用到15。类型用string因为模式名是字符串。加完后在代码里填充这个字段在配置文件里不需要额外配置因为它是运行时数据。6.2 字段废弃与兼容性处理废弃字段比新增字段更麻烦。直接删掉字段会导致老数据解析失败所以正确做法是标记reserved并保留字段名和编号。如果字段只是暂时不用可以保留定义但不再填充这样老代码还能编译新代码忽略它即可。Apollo 官方在版本升级时就是这么做的。你可以翻翻 6.0 的 proto 文件里面有不少reserved标记都是历史遗留字段。这些标记不能删删了就可能破坏兼容性。6.3 多模块联调时的 proto 同步策略Planning 模块不是孤立的它和 perception、prediction、routing、control 都有数据交互。联调时proto 版本必须同步。我的经验是在项目根目录维护一份 proto 版本清单记录每个模块使用的 proto 文件哈希值。每次联调前先核对清单确保大家用的是同一版本。如果做不到哈希核对至少要做到修改 proto 后通知所有相关模块的负责人并在代码仓库里打 tag。Apollo 的代码仓库比较大proto 文件分散靠自觉同步很容易出问题最好有自动化检查工具。7. 几个容易被忽略的细节7.1 proto2 与 proto3 的混用陷阱Apollo 6.0 的 planning proto 用的是 proto2但有些依赖的 proto 可能是 proto3。proto2 和 proto3 在字段默认值、HasField方法、枚举处理上都有差异。混用时最容易出问题的是枚举proto3 的枚举第一个值必须是 0而且不能有required字段。如果你在 proto2 文件里 import 了一个 proto3 文件编译通常能过但运行时行为可能不符合预期。比如 proto3 的optional字段在未设置时返回默认值而 proto2 的optional字段可以用HasField判断是否设置。混用时HasField对 proto3 字段可能不可用。注意Apollo 6.0 里大部分 proto 是 proto2但common/proto/error_code.proto等少数文件是 proto3。引用时留意一下文件头的syntax声明。7.2 枚举值扩展的注意事项Planning 模块里有大量枚举比如ScenarioType、StopReason、ObjectDecisionType。扩展枚举时新值必须加在末尾不能插在中间否则会改变已有值的编号导致老数据解析错误。另外枚举值编号不能重复命名要遵循大写下划线风格。还有一个细节proto2 的枚举允许有别名即两个枚举名对应同一个编号但需要设置allow_alias true。Apollo 里有些枚举用了这个特性改的时候要注意别破坏别名关系。7.3 性能敏感场景下的 proto 使用建议Planning 模块对实时性要求很高proto 的序列化和反序列化开销不能忽视。几个优化建议第一避免在热路径上频繁创建和销毁 proto 对象尽量复用。第二repeated字段如果数据量大考虑用packed true提高编码效率。第三调试信息字段在生产环境可以关闭减少序列化开销。实测下来ADCTrajectory里trajectory_point数量在 100 左右时序列化耗时在毫秒级对规划周期影响不大。但如果 debug 信息全开序列化耗时可能翻倍。所以生产环境建议只开必要的 debug 字段。8. 我个人的一些实操体会折腾 Apollo 6.0 planning proto 这段时间最大的感受是proto 定义不是“文档”而是“契约”。它规定了模块之间怎么说话一旦定下来改动的成本很高。所以每次改 proto我都会问自己三个问题这个字段真的必要吗编号选对了吗下游模块知道吗另外Apollo 的 proto 文件虽然多但核心的就那么几个。把planning.proto、decision.proto、scenario.proto、planning_config.proto这四个吃透planning 模块的数据流就基本清楚了。剩下的都是细节遇到再查也不迟。最后分享一个小技巧Apollo 的bazel-bin目录里生成的.pb.h文件其实是最好的“文档”。它把 proto 定义翻译成了 C 类字段类型、方法名一目了然。比起翻 proto 文件直接看生成的头文件有时候更快。当然前提是你得先编译通过。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询