Apollo自动驾驶比赛源码实战:规划控制调试与自动化复测
简介Apollo自动驾驶比赛解析源码包是一份面向Apollo星火自动驾驶比赛参赛者的实战资源围绕人行横道、红绿灯、借道绕行、慢速车绕行以及施工区域减速慢行等赛题完整给出了从场景分析、行为决策到代码实现与调试的解决路径。包体非常精简共4个文件包含Markdown说明文档、HTML可视化界面、inscode工程文件以及gitignore配置压缩包大小约6KB可直接用于Dreamview环境下的本地测试与代码编译。已有514人学习下载。内容介绍了Dreamview的操作要点、赛事编译缓存的加速技巧以及各赛题的行为规划实现例如根据行人是否通过人行道来构建STOP墙并设定停车时长在红绿灯场景中通过参数配置与逻辑判断控制车辆的起步和停止借道或慢速车绕行时识别目标并触发绕行施工区域则提前减速慢行。配合示例代码和说明文档读者能够快速定位关键函数和参数理解整个自动驾驶决策流程并将其迁移到自己的比赛工程中。1. Apollo自动驾驶比赛解析为什么说源码在手才是真正的底气Apollo自动驾驶比赛表面比的是谁的车在限定场景里跑得稳实际比的是谁更早把项目源码读进脑子里。赛道来来去去就那几种路口让行、无保护左转、限速跟驰、窄道变道评测系统不看过程漂不漂亮只看你输出的轨迹有没有撞车、压线、超速、别停别人。比赛给的“题目”说到底就是一个自动驾驶数据集加配套数据包代码仓库和仿真工具都是公开的源码就摆在你面前——你以为的“参赛门槛”其实是“谁能把手伸进规划和控制层改得对”。这篇文章只讲一件事怎么把一个Apollo比赛题拆到底、跑起来、找病根再把改法固化成一个能重复验证的流程。适合两类人想以赛代练学自动驾驶的学生和想评估这个开源平台源码可落地性的工程师。2. 比赛题拆到Apollo源码里先搞懂每个场景的扣分从哪来2.1 一场比赛任务最常见的三类扣分点停车、限速、变道拿到一个比赛题第一步不是急着跑数据而是先把它翻译成“评测系统会怎么扣分”。常见的比赛包不管包装成什么主题落到轨迹层面的无非三类定点停车类路口停止线、靠边停车点、让行标志。扣分项是越过停止线、停车位置离线太远、再起步反应太慢。速度限制类限速路段、弯道限速、施工区域减速。扣分项是超速这个最冤枉因为你功能都正常只是速度规划没吃到限速信息。变道与汇流类匝道汇入、前方静态障碍绕行、车道封闭。扣分项是压线、骑线、和旁边车距离过近。我建议拿到赛题先画一张表把“场景、扣分项、涉及模块”三列填满再动手。因为扣分点直接告诉你要改哪个模块——停车和限速的根子在planning的决策和速度规划变道压线既有planning参考线选择的问题也有control跟踪精度的问题而感知在大部分比赛包里只是背景板。先把扣分点钉死后面调参才不会东一榔头西一棒子。2.2 Apollo源码里比赛真正的入口规划与控制两个包该读哪几行Apollo的源码目录非常大如果把perception、localization、map、routing全读一遍一个月都打不住。比赛实战里我一般只盯六个目录按优先级排源码目录模块职责比赛价值modules/planning场景决策与轨迹规划主战场停车、限速、变道都在这层modules/control纵向/横向控制指令生成决定轨迹跟踪精度压线问题常在这modules/prediction障碍物轨迹预测无保护左转、汇流场景必看modules/perception障碍物检测与融合主要确认信号有没有丢很少动它modules/localization车辆位姿数据包质量排查入口modules/dreamview_plus可视化与调试一切回放和观察的入口新手最容易犯的错是把时间花在perception上觉得“车没识别到障碍物所以规划没反应”。实际比赛中评测系统给你的数据包已经把感知结果算好了你改perception既影响不了评测输入还可能把链路搞乱。正确的顺序是用cyber工具把perception话题的障碍物列表导出来确认车“看到”了障碍物然后立刻转去planning看它为什么没做出反应。planning里最值得先读的是scenario管理相关的代码——它决定车进入哪个场景逻辑。停车让行是stop_sign_scenario限速是speed_bounds变道是lane_change。每次比赛我都是先确认场景是否被正确识别再看该场景下轨迹为什么不合格。这比直接调一堆速度权重参数要高效得多。2.3 先建立baselineDemo包全链路点亮后才知道改动有没有效果不要一上来就在比赛包上反复试。我见过太多人第一天就拿着赛题包跑结果连编译问题、话题缺失、回放卡顿混在一起根本分不清哪个问题该找哪个模块。正确做法是先跑通平台自带的Demo数据把它当成全链路体检。Demo跑通的意义不是让你熟悉界面而是给你一条“已知正确”的基准线。过一遍Demo你该记住的不是画面的样子而是这几个信号在正常时是什么表现planning话题有轨迹输出、control话题有油门刹车指令、车辆位姿连续没有跳变、DreamView的车身朝向和轨迹贴合。有了这个baseline之后换比赛包时只要对比信号差异就能很快定位是数据问题还是代码问题。我习惯把这一步也当作“源代码检查”——Demo包能跑说明你的Apollo源码版本、编译产物和运行环境三者是匹配的。后面每一行改动都基于这个干净起点出了偏差不会被环境问题干扰。3. 把Apollo源码跑起来从容器编译到离线仿真全链路点亮3.1 把源码送进容器最小环境要求与两条启动命令Apollo的源码拉下来之后直接在本机编译不是不行但依赖容易互相污染。常规做法是放进容器里编译和运行这也是评测量环境最常见的套路。准备一台Linux机器至少16G内存、8核以上磁盘留80G以上——源码、编译产物、数据包都很占空间。先把用户加进docker组避免每条命令都要sudo# 把当前用户加入docker组重登后生效 sudo usermod -aG docker $USER # 验证docker是否可用 docker info --format {{.ServerVersion}}然后进入Apollo源码根目录启动容器环境# 启动容器首次会自动拉取基础镜像耐心等 bash docker/scripts/dev_start.sh # 进入容器之后所有编译和运行命令都在容器内执行 bash docker/scripts/dev_into.shdev_start.sh是平台的运维脚本会检查本机docker环境、挂载源码目录、分配容器资源。如果机器之前装过别的深度学习镜像可能出现容器名冲突脚本会明确报错按提示清理同名容器即可。3.2 编译参数怎么选Debug还是Release、并行度多少容器起来后第一件事是编译。这里有一个直接影响调试体验的选择# 方案ADebug编译产物大、编译慢但便于打断点和看完整堆栈 ./apollo.sh build --debug # 方案BRelease编译产物小、编译快运行时性能更好 ./apollo.sh build比赛调参阶段我推荐用Release就够了——我们不改C核心逻辑大多是调配置和看行为Release跑仿真帧率更高回放更流畅。只有当你需要动planning或control的C代码、并且要通过日志定位崩溃点时才切Debug重新编译。机器核数多的话可以限制并行编译数避免编译把内存吃满导致整机卡死。常见做法是用环境变量控制# 限制最多4个并行编译任务内存紧张时很管用 export MAX_JOBS4 ./apollo.sh build --debug第一次编译耗时较长十几分钟到一小时不等取决于机器配置和是否清过缓存。如果中途失败不要立刻重跑先看最后几行报错——缺依赖库就去安装对应系统包纯编译错误就定位到具体文件。盲目重跑大概率在同一个地方再次翻车。3.3 从DreamView到Sim Control离线跑通一场比赛的完整流程编译通过后启动可视化与调试入口# 编译完成后在容器内启动DreamView bash scripts/bootstrap.sh启动成功后浏览器打开本地DreamView页面能正常看到地图和车辆模型就说明服务起来了。这时关键的切换是在DreamView的Task Manager里把模式从默认的“线下感知”切到Sim Control也就是让仿真系统直接使用数据包提供的信息并输出规划与控制结果不依赖真实传感器和底盘。Sim Control模式下需要手动播放数据包。常见做法是在数据包路径准备好后用播放命令把数据喂给系统# 在容器内播放比赛数据包 cyber_recorder play -f /apollo/data/record/game_qualifier.record -l播放开始后切回DreamView如果车辆开始沿着地图行驶、planning和control话题有持续输出就说明全链路已经通了。此时你做任何规划或控制参数的修改都能直接观察到行为变化。我从Demo切换到比赛包时还会先做一次“信息核对”确认比赛包里有几个话题、有没有图像话题、定位话题的时间戳连续不连续。这步只要一分钟但能省掉后面大量的排查时间。4. 解析比赛数据包三条必用命令与五个关键话题4.1 用cyber_recorder读比赛盘info、play、dump三条必用命令比赛数据包本质是一个自动驾驶数据集的回放载体Apollo全平台统一使用自己的录制格式。很多从ROS过来的人第一反应是找rosbag相关工具但Apollo是另一套命令记住一条原则所有回放和分析都用带recorder关键字的命令。查看比赛包的基本信息和话题列表# 列出数据包里的所有话题、消息数量和起止时间 cyber_recorder info /path/to/game_qualifier.record回放数据包喂给DreamView和各个模块# 播放数据包-l表示循环播放 # 不带-l播完自动停止加-r 2.0可以2倍速播放 cyber_recorder play -f /path/to/game_qualifier.record -l把某个话题的消息导出成可读文本这是定位问题的关键手段# 导出planning话题到标准输出-v打印相对完整的内容 cyber_recorder dump -f /path/to/game_qualifier.record -c /apollo/planning -vdump命令等价于在回放的同时把特定通道的报文打印出来。比赛调试时我经常同时开两个终端一个终端跑play另一个终端反复跑dump查planning输出。这样不用依赖DreamView的界面延迟能直接看到规划轨迹里每个点的速度、朝向、时间戳。4.2 比赛盘里值得盯着的五个关键话题数据包里话题可能有几十个但比赛分析只要盯住五个它们对应从感知到控制的主链路话题名关键信号用途/apollo/perception/obstacles障碍物位置、类别、速度确认车“看到”了什么/apollo/prediction/obstacles障碍物的预测轨迹判断预测是否合理/apollo/planning规划轨迹点序列主分析对象/apollo/control油门、刹车、转向指令确认控制是否执行/apollo/localization/pose车辆位姿和时间戳排查数据质量一个典型的排查顺序是用dump看planning是否有轨迹输出如果没有再看prediction是否有障碍物如果prediction也是空的那就是perception或数据包本身的问题。这个过程能快速把责任模块锁定。时间戳是这里最常被忽略的信息。比赛数据包录制过程中如果定位信号有过中断回放时会出现车“瞬移”或者轨迹跳变——这通常不是代码问题而是数据包本身掉了帧。遇到这种情况先用dump看一眼localization话题的时间戳是否连续再做判断。4.3 把长盘切成可复测场景split切片与场景回归一整场比赛数据包通常有好几分钟里面包含多个场景。每次回放都从头跑既慢又难定位。更科学的做法是把数据包按时间窗口切成小片段让每一个片段对应一个独立场景。# 从520秒截到1080秒切出只包含目标场景的片段 cyber_recorder split -f /path/to/game_qualifier.record \ -o /path/to/scenario_stopline.record \ --start 520 --end 1080切片的本质是保留原包内所有话题在指定时间窗口的数据各话题的时间戳不重写所以切片后依然可以正常回放。比赛后期我通常维护一个切片清单哪个片段对应路口让行、哪个片段对应限速区间、哪个片段对应变道避让。每次改动只跑对应片段把验证时间从几分钟压缩到几十秒。这里有一个非常重要的习惯每跑通一个切片就把该片段的planning输出落盘保存。因为切片的回放结果是可复现的改动前后对比同一段的轨迹输出就能精确量化这次改动的效果。不要凭感觉判断“好像变好了”要把改动前后的输出数据放在一起比。5. Apollo比赛复现路上的五个坑从编译到回放的血泪清单5.1 编译到一半机器假死风扇狂转现象执行./apollo.sh build时编译到某个模块后进度条长时间不动系统响应变慢查看系统负载发现多个编译进程占满CPU内存也接近耗尽。原因Apollo的编译脚本默认会尽量利用机器所有核16G内存的机器在编译大型模块时内存被并行任务吃满导致系统进入过载状态。解决用环境变量限制并行度重新编译并给容器设置资源上限。我后来固定在docker设置里给容器分配8核和12G内存再配合MAX_JOBS参数编译稳定很多。这条经验在我换了三台不同配置的机器后都验证有效。5.2 DreamView里车原地不动进度条却在走现象Sim Control模式下数据包播放正常时间轴在推进但画面里的车始终停在起点。检查planning话题也没有输出。原因最常见的是Sim Control模式没真正激活DreamView仍然停留在默认模式另一种情况是播放的record话题与当前地图不匹配系统没有找到有效路由。解决先检查Task Manager是否显示Sim Control已打开再确认播放的数据包话题完整特别是定位话题和地图匹配。如果这两步都正常用dump命令手动看localization和planning两个话题是否有数据流动哪个话题是空的就往哪个方向排查。5.3 提高回放倍速后车辆轨迹开始抽搐现象一倍速回放一切正常为了提高效率用2倍速播放结果planning轨迹出现明显的来回摆动停车点也跟着漂移。原因倍速播放会导致各话题的消息到达时序发生改变planning模块拿到的是迟到的感知和定位帧在预测时做了不合理的补偿输出的轨迹自然就抖了。解决比赛调试阶段一律用一倍速回放不要为了省时间开倍速这条铁律能帮你躲开大量玄学问题。真正需要加速时应该用切片工具把长包切成短片段而不是提高播放速度。5.4 改了参数重跑行为一点没变现象在DreamView的界面里调整了某个planning参数再次回放时车的表现和之前一模一样像是没保存。原因DreamView界面上改的参数只在运行期临时生效模块重启后加载的还是磁盘上的配置文件。界面改动不会写回源码目录里的conf文件。解决改参数要直接改源码模块对应的配置文件改完后重启对应模块或整个DreamView。我有一次调了半天界面没有效果后来才意识到改错了地方浪费了两个小时。5.5 比赛包里没有图像话题感知模块一直空转现象回放某个比赛数据包时perception长时间没有障碍物输出怀疑代码有问题。用info命令查看话题列表发现包内根本没有camera相关话题只有几个雷达话题。原因比赛数据包不一定包含全传感器很多评测盘只保留定位和障碍物检测结果图像话题直接被去掉了。感知模块没图像可处理自然不会有输出。解决遇到感知无输出的情况第一步永远是查看话题列表确认数据包里有什么而不是去调感知参数。如果比赛包本来就没有图像话题那这个包的意图就是让你聚焦规划和控制感知部分只要确认有障碍物话题即可。6. 把比赛题变成自动化复测一个可以重复执行的评分脚本比赛后期真正的效率提升来自自动化。我把回放、落盘、校验三步串成一个脚本每次改动后跑一遍输出一份“改动-分数”对照让迭代有据可依。先用catch工具把planning输出落盘再写一个简单的Python脚本做规则校验from pathlib import Path def check_planning(export_file, speed_limit10.0): 对dump出的planning文本做基础规则校验 speed_limit: 按赛题限速要求调整 frames 0 over_speed 0 for line in Path(export_file).read_text().splitlines(): if trajectory_point not in line: continue frames 1 # 解析速度字段这里按dump -v的实际格式切分 try: velocity float(line.split(v:)[1].split(,)[0]) except (IndexError, ValueError): continue if velocity speed_limit: over_speed 1 return {总帧数: frames, 超速帧数: over_speed, 超速比例: over_speed / max(frames, 1)} # 调用示例把dump输出重定向到文本文件后执行 result check_planning(planning_dump.txt, speed_limit10.0) print(result)这个脚本的阈值和判断完全可以按赛题规则扩展比如增加压线检测、停车距离检测。核心思路是把主观的“看起来还行”变成客观的数值指标。我现在的习惯是每次改动前先跑一次基准脚本拿到baseline改完后跑第二次两次结果直接对比。这个习惯帮我躲过了很多“改好了但说不清哪里好”的糊涂账。比赛拼到最后一轮比的就是谁的迭代速度快、谁的错误定位准。希望这篇文章能帮你把同样的路走得顺一点希望帮到你。本文还有配套的精品资源点击获取