第1章:软件架构总览——自动驾驶软件栈的经典分层模型

各位同学,欢迎来到《自动驾驶软件栈分层架构深度解析》的第一课。

今天咱们聊聊整个软件栈的骨架——也就是那个经典的“感知-规划-控制”三层模型。我做了这么多年自动驾驶,见过不少团队上来就撸代码,结果写到一半发现各模块之间根本对不上。嗯,这就是典型的“架构先行”没做到位。

1.1 为什么一定要分层?

你想想看,一辆车在路上跑,它需要“看路”、“想怎么走”、“动方向盘和油门”。这三个动作,本质上就是三个完全不同类型的问题。

  • 感知层:处理传感器数据,回答“我周围有什么?”
  • 规划层:基于感知结果,回答“我接下来要去哪?”
  • 控制层:执行规划指令,回答“我怎么动才能到那里?”

我个人习惯把这三层比作一个公司的三个部门:感知是情报部,规划是参谋部,控制是执行部。情报部搞错了,参谋部再聪明也没用;参谋部指令不清,执行部再猛也白搭。

核心原则:每一层只关心自己的输入和输出,不越界。感知层不需要知道控制器的PID参数,控制层也不需要理解BEV(鸟瞰视角)是怎么生成的。

1.2 感知层:从原始数据到结构化信息

感知层是软件栈的“眼睛”和“耳朵”。它的输入是摄像头、激光雷达、毫米波雷达、超声波等传感器的原始数据。输出呢?是结构化的环境模型。

举个例子,摄像头拍到的是一帧1920x1080的RGB图像,但感知层要输出的是:

  • 前方50米处有一辆白色轿车,速度约60km/h
  • 右侧车道线是虚线,距离本车0.8米
  • 路口红绿灯显示为绿灯,剩余时间15秒

我在项目中遇到过最头疼的事,就是感知模块把一辆静止的卡车识别成了“天空”。嗯,后来查原因,是训练数据里卡车样本太少。所以这里要提醒大家:感知层的鲁棒性,往往不取决于算法有多炫,而取决于数据有多全

避坑指南:我曾经见过一个团队,花了大半年优化目标检测的mAP(平均精度),结果忽略了传感器标定误差。最后发现,算法再准,标定偏了2度,所有感知结果都跟着偏。所以,感知层的第一道坎,其实是传感器标定。

1.3 规划层:从环境理解到行为决策

规划层拿到感知层输出的结构化信息后,要回答一个更复杂的问题:“我该怎么走?”

规划层内部其实还可以再细分:

子模块 职责 典型输出
任务规划 决定宏观路径(A点到B点) 导航路线、车道级路径
行为规划 决定驾驶行为(跟车、变道、超车) 行为指令:保持车道、准备变道
运动规划 生成具体轨迹(时间-位置-速度曲线) 一条平滑的轨迹线

说白了,任务规划告诉你“从北京到上海走G2高速”,行为规划告诉你“前方有慢车,准备变到左侧车道”,运动规划则告诉你“接下来3秒内,方向盘打5度,油门开度20%”。

我个人习惯把运动规划的输出叫做“时空走廊”——它不仅要规划位置,还要规划每个时刻的速度和加速度。为什么?因为如果只规划路径不规划速度,控制层拿到一个急转弯的路径,却不知道要减速,那车就翻了。

1.4 控制层:从轨迹到执行

控制层是软件栈的“手脚”。它的输入是规划层给出的轨迹(包含位置、速度、加速度、横摆角等),输出是发给执行器的具体指令:方向盘转角、油门开度、制动压力。

控制层最经典的方法是PID控制和模型预测控制(MPC)。我刚开始做控制时,觉得PID太简单,非要上MPC。结果呢?MPC调参调了两个月,车还在原地画圈。后来老老实实从PID起步,先把车跑起来,再逐步优化。

注意:控制层是离硬件最近的模块,也是最容易出事故的地方。我曾经见过一次,控制模块的代码里有一个除零错误,导致车辆在测试时突然急刹。从那以后,我要求所有控制代码必须做边界值测试——比如速度为零时、方向盘打到极限时,代码能不能正确处理。

1.5 数据流交互:各层之间怎么“说话”?

三层架构定好了,接下来最关键的问题:数据怎么在层之间流动?

我建议用“发布-订阅”模式。每一层把自己的输出发布到某个主题(Topic)上,下游模块订阅自己需要的主题。这样做的好处是:

  • 解耦:感知层换了算法,只要输出格式不变,规划层不用改代码
  • 可扩展:想加一个紧急制动模块?直接订阅感知层的障碍物信息就行
  • 可调试:每个Topic都可以录制成bag文件,回放分析

举个例子,数据流大概是这样的:

传感器数据 → 感知层 → 障碍物列表、车道线、交通标志
                ↓
          规划层 → 目标轨迹(时间序列的位置/速度/加速度)
                ↓
          控制层 → 方向盘转角、油门、制动指令
                ↓
          车辆执行器 → 车辆运动

你想想看,如果感知层输出的障碍物列表里,有一个障碍物的ID是乱码,规划层怎么处理?所以,数据接口的定义一定要严格。我习惯用protobuf定义每个Topic的消息格式,字段名、类型、取值范围、单位,全都写清楚。

关键点:数据流中还有一个容易被忽略的东西——时间戳。每个消息都必须带上时间戳,而且所有模块的时钟必须同步。否则,规划层拿到的是100毫秒前的障碍物信息,控制层拿到的是50毫秒前的轨迹,整个系统就乱套了。

1.6 小结

好了,这一章我们聊了自动驾驶软件栈的经典三层模型:

  • 感知层:把传感器数据变成结构化环境信息
  • 规划层:把环境信息变成可执行的轨迹
  • 控制层:把轨迹变成车辆执行指令

这三层之间通过定义良好的接口进行数据交互,每一层各司其职,不越界。嗯,听起来很简单对吧?但实际落地时,每一层都有无数的坑等着你。接下来的章节,我会逐一深入每个模块,把我在项目中踩过的坑、总结的经验,全都掏出来给你们看。

下一章,咱们深入感知层,聊聊传感器融合的那些事儿。