文章目录
前言
近期需要熟悉Apollo的感知模块,因此在查阅官方文档以及各种资料后记录了整体的过程
Apollo 简介
Apollo (阿波罗)是一个开放的、完整的、安全的平台,将帮助汽车行业及自动驾驶领域的合作伙伴结合车辆和硬件系统,快速搭建一套 属于自己的自动驾驶系统。
开放能力、共享资源、加速创新、持续共赢是 Apollo 开放平台的口号。百度把自己所拥有的强大、成熟、安全的自动驾驶技术和数据开 放给业界,旨在建立一个以合作为中心的生态体系,发挥百度在人工智能领域的技术优势,为合作伙伴赋能,共同促进自动驾驶产业的发 展和创新。
Apollo 自动驾驶开放平台为开发者提供了丰富的车辆、硬件选择,强大的环境感知、高精定位、路径规划、车辆控制等自动驾驶软件能 力以及高精地图、仿真、数据流水线等自动驾驶云服务,帮助开发者从 0 到 1 快速搭建一套自动驾驶系统。
Apollo 8.0框架
Apollo 从1.0 一直持续更新发展到如今的Apollo 9.0,那么这里选择 Apollo 8.0进行以下的学习和实践
Apollo 8.0 的主要新特征如下:
工程框架引入软件包管理
- 发布模块软件包:基于软件包规范对 Apollo 源码按照模块粒度制作成DEB软件包并发布,方便复用。
- 全新安装方式:基于软件包的安装方式节省了大量编译时间,提供更快的安装部署体验。
- 便捷扩展方案:基于软件包的扩展方案,可以更快地实现自己的组件,还可以共享给其他开发者。
感知框架与模型升级
- 新增感知模型:新增基于 Center-based 的二阶段的障碍物检测模型 CenterPoint、CaDDN 视觉障碍物检测模型、BEV PETR 视觉障碍物检测模型。
- 开放模型训练流程:通过支持 Paddle3D 打通模型训练流程,可以选择 Paddle3D 已经训练好的模型,也可以自定义训练模型。
- 提升部署效率:通过引入模型 meta 信息,规范模型输入、输出、预处理等信息;提供模型管理工具一键部署模型,提升部署效率;升级了感知流水线框架,更加清晰且更易配置。
- 丰富可视化验证工具:通过 Dreamview 提供新的感知样例数据包并丰富了感知结果可视化工具,用户可本地验证算法的效果。
工具链升级
- 支持本地仿真调试:提供基于 Dreamview 提供本地仿真容器,支持本地 PnC 仿真调试。
- 便捷仿真场景管理:提供基于云端 Studio 的仿真场景创建、编辑与分组管理,一键云端场景下载至本地 Dreamview。
以下提供了相关的Apollo的官方文档以及相关资料
Apollo github 官方文档
Apollo 9.0 自动驾驶开放平台文档
Apollo 8.0 自动驾驶开放平台文档
注:以下内容仅供参考
一、Apollo 8.0 环境配置
以下内容以 Apollo 8.0 为例
注:以下将采用Apollo源码安装配置的方式进行安装
1.1 拉取仓库并设置环境路径
# 创建一个文件夹
mkdir apollo
cd apollo
#以下方法二选一
# 使用github拉取
git clone https://github.com/ApolloAuto/apollo.git
# 或者使用gitee拉取
git clone https://gitee.com/ApolloAuto/apollo.git
# 设置环境路径
echo "export APOLLO_ROOT_DIR=$(pwd)" >> ~/.bashrc && source ~/.bashrc
1.2 拉取镜像脚本
确保机子已经安装配置好 docker,安装配置可见 Ubuntu深度学习环境配置全流程 中的docker配置过程
bash docker/scripts/dev_start.sh
报错:docker/scripts/dev_start.sh: 行 357: /usr/bin/pip3: 没有那个文件或目录
表示找不到pip3
# 查看pip3安装位置
which pip3
解决方案:如果发现未安装pip3,则安装一下
sudo apt-get install python3-pip
然后开始继续拉取下载…
直到输出以下信息,则表示成功
1.3 进入docker容器并安装GPU驱动
进入 apollo 的 docker 容器
bash docker/scripts/dev_into.sh
查看是否存在gpu驱动
nvidia-smi
输出如下,则表示docker内未安装gpu驱动
1.4 把显卡驱动文件移动到docker目录下,并运行安装
推荐使用run文件安装,安装步骤可以参考:Ubuntu深度学习环境配置全流程
sudo ./NVIDIA-Linux-xxxxx --no-x-check -a -s --no-kernel-module
报错:找不到modprobe
尝试1:pip 安装这个包
但是报错,找不到这个包
sudo apt-get install module-init-tools kmod
解决方案:需要更新一下apt,然后再install
sudo apt-get update
sudo apt-get install module-init-tools kmod
附上国内镜像源的网址
中科大: https://pypi.mirrors.ustc.edu.cn/simple/ (推荐)
清华大学:https://pypi.tuna.tsinghua.edu.cn/simple
阿里云:http://mirrors.aliyun.com/pypi/simple
豆瓣:http://pypi.douban.com/simple
验证
安装成功后,继续运行显卡驱动安装包
安装结束后如输入nvidia-smi,输出显卡信息则表示安装成功
1.5 将docker保存至本地(可选)
查看当前运行容器
docker ps
保存
-p 后面输入上面显示的ID,冒号后面输入自己对该镜像的命名
docker commit -p xxxx apolloauto/apollo:nnnn
通过下面的命令实现进入Apollo Development Docker容器、退出Apollo Development Docker容器
# 进入
bash docker/scripts/dev_into.sh
# 退出
exit
1.6 配置 Apollo 内部容器
运行编译命令
./apollo.sh build
报错:某个仓库连接不上
报错信息: ERROR: An error occurred during the fetch of repository ‘build_bazel_rules_swift’:
解决方案:是因为网络问题,重新试一次命令,重新编译
进入漫长的编译过程…
输出以下信息则编译完成
二、本地电脑演示运行Apollo 8.0
接下来开始实际演示运行Apollo 8.0
首先确保处于Apollo 8.0 的docker环境中(进入docker后终端前面的用户信息会变化)
如果没有则:
通过命令进入docker
bash docker/scripts/dev_into.sh
确保当前在docker环境内,而后进行以下操作
2.1 连接网络页面UI 并打开 DreamView
./scripts/bootstrap.sh
Ctrl + 鼠标左键点击弹出来的网址
通过 http://localhost:8888 打开UI界面
将会打开Apollo 8.0 的DreamView 界面
2.2 下载演示数据
# 方法1. 运行下载脚本
cd /apollo/docs/02_Quick Start/demo_guide/
./record_helper.py demo_3.5.record
# 方法2. 也可以手动下载
wget https://apollo-system.cdn.bcebos.com/dataset/6.0_edu/demo_3.5.record
2.3 运行读取数据并运行演示视频
循环播放
这只是 DreamView 将 record 数据包的数据单纯的回放,就像播放录好的视频一样。
cyber_recorder play -f docs/demo_guide/demo_3.5.record -l
出现演示界面则成功
2.4 监控Channel数据
可以另外开启一个终端,并进入docker环境
然后输入以下命令:
cyber_monitor
即可打开 cyber_monitor ,该工具可以用于监控播放中 record包的Channel数据
- 使用 -h 选项,获取帮助信息:
cyber_monitor -h
channel 信息默认显示为红色,当有数据流经 channel 时,对应的行就会显示成绿色。
2.5 可视化原始数据
另外开启一个终端,并进入docker环境
然后输入以下命令:
cyber_visualizer
即可开启cyber_visualizer的可视化UI界面,该工具用于可视化原始的lidar数据、camera数据等
三、核心软件模块概览
Apollo 自动驾驶应用层提供了感知、预测、规划、控制、人机交互等数十个核心模块。这些模块之间的交互关系如下图所示:
注:橘色箭头线段代表数据流向,黑色箭头线段代表处理流程流向。
-
Perception(感知)
感知模块识别自动驾驶车辆周围的世界。感知中有两个重要的子模块:障碍物检测和交通灯检测。 -
Prediction(预测)
预测模块预测感知障碍物的未来运动轨迹。 -
Routing(路由)
路由模块告诉自动驾驶车辆如何通过一系列车道或道路到达其目的地。 -
Planning(规划)
规划模块规划自动驾驶车辆的时间和空间轨迹。 -
Control(控制)
控制模块通过产生诸如油门,制动和转向的控制命令来执行规划模块产生的轨迹。 -
CanBus
CanBus是将控制命令传递给车辆硬件的接口。它还将底盘信息传递给软件系统。 -
HD-Map(高精地图)
该模块类似于库(Library)。它不是发布和订阅消息,而是经常用作查询引擎支持,以提供关于道路的特定结构化信息。 -
Localization(定位)
定位模块利用来自 GPS,LiDAR 和 IMU 设备的信息来评估自动驾驶车辆的位置。 -
HMI(人机交互)
人机交互模块,或者说 Apollo 中 DreamView 是一个用于查看车辆状态,测试其他模块以及实时控制车辆功能的模块。 -
Monitor(监控)
监控车辆中所有软硬件模块的监控系统。 -
Guardian
功能安全模块,用于干预 Monitor 模块检测到的失效情况并采取相应的动作。
对应的代码工程项目的文件目录大概如下:
文件夹 | 功能 |
---|---|
cyber | 存放cyberRT通信框架相关文件 |
docs | 存放各种说明文档、API文档、配置文档 |
data | 存放地图包、数据包、日志等文件 |
modules | 存放Apollo 主要核心模块包括:定位,预测,感知,规划 |
third_party | 存放第三方库 |
scripts | 存放运行脚本 |
tools | 存放各种工具 |
3.1 Perception(感知)
目前包括的感知算法有:
- 多模态多视角的感知算法
- 视觉信号灯识别
- 视觉车道线检测
- 视觉障碍物识别
- 激光点云障碍物识别
- 多传感器融合障碍物识别
- 车路协同感知融合
3.2 Prediction(预测)
3.3 HD-Map(高精地图)与 Localization(定位)
- RTK 定位
- 多传感器融合(MSF)定位
3.4 Planning(规划)
目前包含的基于场景的规划算法有:
- Public Road 规划器
- Lattice 规划器
- Navi 规划器
- RTK 规划器
- 基于模型的规划器
- 基于模型的 Open Space 规划器
3.5 Control(控制)
- PID 控制器
- LQR 控制器
- MPC 控制器
- MRAC 双循环控制器
四、Perception Module
4.1 感知流程及算法
Apollo 自动驾驶感知按照传感器分为camera 感知、lidar 感知、radar感知。特别地,camera 感知由交通信号灯感知、车道线感知、物体目标感知三种;lidar 感知则主要负责3d目标的感知;radar感知主要负责目标的跟踪。
4.1.1 红绿灯感知检测
- TRAFFIC_LIGHT_DETECTION
- TRAFFIC_LIGHT_RECOGNITION
- SEMANTIC_REVISER
红绿灯被定义成 5 种状态:红、黄、绿、黑、未知
4.1.2 Camera障碍物感知检测
Camera障碍物感知检测的pipeline:
在modules/perception/pipeline/config/camera_detection_pipeline.pb.txt
中的配置文件,说明了Camera障碍物感知检测的流程,分为
Camera障碍物感知检测的配置文件的调用读取流程:
4.1.3 LiDAR障碍物感知检测
激光雷达检测用于 3D 目标检测,它的输入是激光雷达点云,输出为检测到的物体的类型和坐标
它的流水线配置文件在 modules/perception/pipeline/config/lidar_detection_pipeline.pb.txt
中,一共分为 7 个阶段
lidar_detection_pipeline.pb.txt 文件中定义了多个阶段
分别为:
- POINTCLOUD_PREPROCESSOR 点云预处理阶段
- POINTCLOUD_DETECTION_PREPROCESSOR 点云检测预处理阶段
- MAP_MANAGER 地图阶段
- CNN_SEGMENTATION 具体的检测算法阶段
- POINTCLOUD_DETECTION_POSTPROCESSOR 点云检测后处理阶段
- OBJECT_BUILDER 目标生成阶段
- OBJECT_FILTER_BANK 目标过滤阶段
(关于pipeline的详细介绍后续会在另一篇文章补充)
如下图,可以看到apollo8.0 的lidar检测算法是 cnn segmentation
下面有该 cnn segmentation算法具体文件的配置情况
4.2 感知Demo实践
确保docker内部环境已经编译成功,然后启动docker环境,启动Dreamview
# 进入环境
bash docker/scripts/dev_into.sh
# 启动Dreamview
./scripts/bootstrap.sh
如果想重启Dreamview,则先把它推出了然后再开启
注:以下这行命令为退出 Dreamview 指令:
bash scripts/bootstrap.sh stop
然后再次开启 Dreamview 即可
或者直接运行重启 Dreamview 的命令
./scripts/bootstrap.sh restart
4.2.1 下载并解压数据包
下载数据包
wget https://apollo-system.bj.bcebos.com/dataset/6.0_edu/sensor_rgb.tar.xz
解压数据包并放置到/apollo/apollo-r8.0.0/data/bag
为了在 Docker 中可以访问 record,需要将下载的数据包复制到 /apollo/data/bag 目录下
sudo mkdir -p ./data/bag/
sudo tar -xzvf sensor_rgb.tar.xz -C ./data/bag/
4.2.2 在 Dreamview 中选择车型和地图
- 在 Dreamview 右上角的 --vehicle-- 中,选择 mkz_example,
- 在 Dreamview 右上角的 --map-- 中,选择 Sunnyvale Big Loop。
车辆选择后,程序会把会把 calibration/data 目录下对应的车型参数和传感器内外参文件拷贝到作用目录下。地图选择后在 Dreamview 下便可以正常显示当前地图。
4.2.3 启动所需模块
开启一个docker终端,使用命令启动Transform 模块
cyber_launch start /apollo/modules/transform/launch/static_transform.launch
Transform 模块启动成功
当然启动transform模块也可以通过 Dreamview 里面的transform控件打开实现,如下图:
另外开一个docker终端,启动image decompression 模块(可选)
当需要启动camera感知模块,并且后续播放的record包中含有 压缩的图像数据的话,需要启动该模块
# 启动 image decompression 模块可以将压缩的图像数据解压缩
cyber_launch start modules/drivers/tools/image_decompress/launch/image_decompress.launch
image decompression 模块启动成功
可能的报错:启动 Transform 模块报错
报错信息: [mainboard]this process will be terminated due to duplicated node[static_transform], please ensure that each node has a unique name.RROR Process [mainboard_default_97611] has finished. [pid 97618, cmd mainboard -d /apollo/modules/transform/dag/static_transform.dag -p mainboard_default_97611 -s CYBER_DEFAULT].
解决方案:确保在Dreamview里面选择了车型和地图
4.2.5 启动感知检测模块并在Dreamview 上可视化检测结果
启动所有感知模块或者启动camera的配置需要一些操作,可能遇到无法启动的情况,后续也会再另一篇文章中补充
这里可以尝试直接启动lidar感知
启动所有感知模块:
cyber_launch start /apollo/modules/perception/production/launch/perception_all.launch
注:该命令会同时开启所有的感知检测模块,若想单独启动某一检测模块可以参考以下的命令
除了启动所有感知模块之外,还可以按需单独启动某个模块
模块一、启动camera感知模块
在启动时终端可能出现一些红色的警告提示,只要进程不退出就行,模型可以正常启动。
警告提示的原因有很多,可能是地图不匹配,transform不对,以及各种配置不正常等
- 方式一 通过 cyber_launch 启动所有模块
启动所有camera感知检测模块:
cyber_launch start /apollo/modules/perception/production/launch/perception_camera.launch
注:该命令会同时开启所有的camera感知检测模块,若想单独启动某一检测模块可以参考以下的命令
- 单独启动红绿灯感知检测模块(可选)
cyber_launch start /apollo/modules/perception/production/launch/perception_trafficlight.launch
- 单独启动车道线感知检测模块(可选)
cyber_launch start /apollo/modules/perception/production/launch/perception_trafficlight.launch
- 方式二 通过 mainboard 启动相机感知模块
启动全部相机感知模块
mainboard -d modules/perception/production/dag/dag_streaming_perception_camera.dag
- 单独启动车道线检测模块(可选)
mainboard -d ./modules/perception/production/dag/dag_streaming_perception_lane.dag
模块二、启动LiDAR感知模块
- 方式一 通过 cyber_launch 启动 lidar 模块
cyber_launch start modules/perception/production/launch/perception_lidar.launch
- 方式二 通过 mainboard 启动 lidar 感知模块
mainboard -d modules/perception/production/dag/dag_streaming_perception_lidar.dag
4.2.6 验证
注:在 Docker 环境下执行如下命令,通过下面两种方法可以判断模块是否启动完毕:
- 在终端执行如下命令,查看 GPU 的占用情况:
watch -n 0.1 nvidia-smi
- 当 mainboard 进程显存占用不再变化,证明模型已经完成启动。
tail -f /apollo/data/log/mainboard.INFO
4.2.7 播放record数据包并查看感知效果
#直接播放
cyber_recorder play -f data/bag/xxxxx.record
# 如果包中含有感知结果数据,则需要屏蔽掉,运行这个以查看感知算法的检测结果
cyber_recorder play -f data/bag/xxxxx.record -k /apollo/perception/obstacles /apollo/prediction
4.3 基于 Apollo 进行感知算法开发
4.3.1 模型训练与导出
目前常见的 2d 图像目标识别模型有 YOLO, Faster-RCNN等,常见的Lidar目标识别模型有 Pointpillar 等.
其实可以用任意深度学习框架来训练模型都可以,例如 paddlepaddle, pytorch, tensorflow
CenterPoint 模型学习与训练
下面给出一些关于CenterPoint模型解读的文章以供参考:
官方文档及文章:
文章及博客:
模型的训练可以参考Paddle3D开源的CenterPoint代码: Paddle3D-CenterPoint
当然也可以基于pytorch、caffe、TensorFlow进行训练
这里放上Paddle3D优化后的CenterPoint在nuScenes Val set数据集上的表现:
模型 | 体素格式 | mAP | NDS | V100 TensorRT FP32(FPS) | V100 TensorRT FP16(FPS) |
---|---|---|---|---|---|
CenterPoint | 2D-Pillars | 50.97 | 61.30 | 50.28 | 63.43 |
CenterPoint | 3D-Voxels | 59.25 | 66.74 | 21.90 | 26.93 |
PointPillars 模型
待记录…
模型导出
将训练时保存的动态图模型文件导出成推理引擎能够加载的静态图模型文件。
python tools/export.py --config configs/centerpoint/centerpoint_pillars_02voxel_nuscenes_10sweep.yml --model /path/to/model.pdparams --save_dir /path/to/output
- config [必填] 训练配置文件所在路径
- model [必填] 训练时保存的模型文件model.pdparams所在路径
- save_dir [必填] 保存导出模型的路径,save_dir下将会生成三个文件:centerpoint.pdiparams 、centerpoint.pdiparams.info和centerpoint.pdmodel
4.3.2 制作record数据包并配置(可选)
可以使用自己采集的数据包,当然也可以使用公开数据集如 nuscenes、KITTI、Waymo
Apollo 提供了数据集转 record 包的工具,将数据集中的数据转换成 Apollo 可以使用的 record 包,用于测试感知模块的效果好坏。
数据包制作工具具体使用方式可以参考 modules/tools/adataset/README.md
数据集转 record 包的工具在 modules/tools/adataset/adataset
目录下
下面以 nuscenes 的 mini 数据集为例:
nuscenes 官网
进入官网,注册一个账号
找到nuScenes点击
往下找,找到mini数据集,下载nuscenes 的 mini 数据集
nuScenes-Mini
-maps
-samples
-sweeps
-v1.0-mini
通过以下命令来生成数据包,nuscenes_dataset_path 为 nuscenes 数据集 raw data 的存放路径,生成好的文件保存在当前路径,默认名称为result.record。
安装 adataset 库
pip3 install adataset
报错:
ERROR: Could not build wheels for opencv-python, which is required to install pyproject.toml-based projects
解决方案:
pip install opencv-python==3.4.1.15
WARNING: The script lzf is installed in '/home/chenzhenghua/.local/bin' which is not on PATH.
Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
WARNING: The script cyber_record is installed in '/home/chenzhenghua/.local/bin' which is not on PATH.
Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
WARNING: The script adataset is installed in '/home/chenzhenghua/.local/bin' which is not on PATH.
Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
通过adataset 库可以实现转换
安装后
// record
adataset -d=n -i=path/to/nuScenes-Mini
// calibration
adataset -d=n -i=path/to/nuScenes-Mini -t=cal
// pcd
adataset -d=n -i=path/to/nuScenes-Mini/samples/LIDAR_TOP/n015-2018-11-21-19-38-26+0800__LIDAR_TOP__1542801007446751.pcd.bin -t=pcd
但是不知道为什么安装了adataset 库,但是还是显示没有该命令
因此,作者选择用modules/tools/adataset/adataset/main.py
的源码执行,根据提示参数选择命令
- -d 后面加数据集类型(n:nuscenes, k:KITTI, w:Waymo)
- -i后面加源数据
- -o后面加输出的文件夹路径
转换生成record文件
通过以下命令将nuscenes数据转换成record文件
python3 modules/tools/adataset/adataset/main.py -d n -i ./data/bag/mini/ -o ./data/bag/ -t rcd
验证
用 cyber_recorder play -f 随便播放一个record,通过启动cyber_visualizer和cyber_monitor查看是否有数据
docker中运行以下命令
cyber_visualizer
cyber_monitor
选择添加点云、添加图像,然后选择对应的通道即可可视化以下点云以及6个相机的通道
有数据通道表示数据转换成功
转换生成传感器内外参文件
通过以下命令来生成传感器内外参文件,生成好的文件保存在当前路径,根据传感器名称进行区分。
python3 modules/tools/adataset/adataset/main.py -d n -i ./data/bag/mini/ -o ./data/bag/ -t cal
转换成功后得到
修改传感器外参配置文件
启动 transform 模块是通过启动 modules/transform/dag/static_transform.dag
的启动文件启动
该文件中指定了关于transform 的config文件的路径
transform 模块主要实现各个传感器之间的坐标转换,因此只涉及外参
config文件如下:
可以看到,其中指定了每个传感器的外部参数文件
extrinsic_file {
frame_id: "novatel"
child_frame_id: "velodyne"
file_path: "/apollo/modules/drivers/lidar/velodyne/params/velodyne128_novatel_extrinsics.yaml"
enable: true
}
extrinsic_file {
frame_id: "localization"
child_frame_id: "novatel"
file_path: "/apollo/modules/localization/msf/params/novatel_localization_extrinsics.yaml"
enable: true
}
extrinsic_file {
frame_id: "novatel"
child_frame_id: "CAM_FRONT"
file_path: "/apollo/modules/perception/data/params/CAM_FRONT_extrinsics.yaml"
enable: true
}
extrinsic_file {
frame_id: "novatel"
child_frame_id: "CAM_FRONT_RIGHT"
file_path: "/apollo/modules/perception/data/params/CAM_FRONT_RIGHT_extrinsics.yaml"
enable: true
}
extrinsic_file {
frame_id: "novatel"
child_frame_id: "CAM_FRONT_LEFT"
file_path: "/apollo/modules/perception/data/params/CAM_FRONT_LEFT_extrinsics.yaml"
enable: true
}
extrinsic_file {
frame_id: "novatel"
child_frame_id: "CAM_BACK"
file_path: "/apollo/modules/perception/data/params/CAM_BACK_extrinsics.yaml"
enable: true
}
extrinsic_file {
frame_id: "novatel"
child_frame_id: "CAM_BACK_LEFT"
file_path: "/apollo/modules/perception/data/params/CAM_BACK_LEFT_extrinsics.yaml"
enable: true
}
extrinsic_file {
frame_id: "novatel"
child_frame_id: "CAM_BACK_RIGHT"
file_path: "/apollo/modules/perception/data/params/CAM_BACK_RIGHT_extrinsics.yaml"
enable: true
}
因此可以创建一个modules/transform/conf/nuscenes_static_transform_conf.pb.txt
,将里面的配置参数改成刚刚生成的参数文件所对应的路径即可
4.3.3 模型部署
在Apollo框架中除了可以使用自带模型库里面的模型之外,还可以添加自定义新的检测算法
可参考以下文档:
添加新的camera检测算法
添加新的lidar检测算法
添加新的camera匹配算法
添加新的fusion融合系统
算法添加的主要步骤
- 定义一个继承基类 base_xxxx_detector 的类
如下面所示:尽量保证在apollo::perception::xxxx命名空间里面
namespace apollo {
namespace perception {
namespace xxxx {
class XxxxxxDetector : public base_xxxx_detector {
}; // class XxxxxxDetector
} // namespace xxxx
} // namespace perception
} // namespace apollo
-
实现新类 NewXxxxDetector
NewXxxxDetector至少需要重写base_xxxx_detector中定义的接口Init(),Detect()和Name()。可以参考对应传感器的算法实现,其中Init()函数负责完成加载配置文件,初始化类成员等工作;而Detect()则负责实现算法的主体流程。 -
为新类 NewXxxxDetector 配置config和param的proto文件(可选)
如果参数适配,也可以直接使用现有的proto文件,或者对现有proto文件进行更改。
proto文件主要用于配置消息的发送的,如cnn_segmentation算法的protocol文件位于modules/perception/lidar/lib/detector/cnn_segmentation/proto
。 需要配置两个proto文件,一个是xxxx_param.proto文件,另一个是xxxx_config.proto文件。
其中camera:
- 根据模型结构修改对应的proto文件:Camera Detector通过blob数据结构来管理和存储模型的输入和输出数据。用户需要根据模型结构,在该模型对应的proto文件中添加和对应的输入输出项,可参考smoke.proto文件的NetworkParam字段,并添加对应的proto配置文件,参考smoke-config.pt。
- 同Lidar Detection类似,Camera Detection中的Detect()函数同样包含了图像数据的预处理,前处理、推理、后处理、感知结果发布等流程。通过frame->data_provider->GetImage()函数,用户可以获取图像数据,通过inference::ResizeGPU()函数,可以对图像数据进行resize等预处理操作。
- 后处理完成后,需要根据模型的特点,将2d bounding-box、3d bounding-box存储到std::vectorbase::ObjectPtr结构中。具体可参考region_output.cc中的get_smoke_objects_cpu函数。
其中lidar:
- 更新config文件使新的算法生效
在完成以上步骤后,新检测算法便可在Apollo系统中生效。
lidar 和 camera 的配置文件方式略有不同,但都大同小异,根据官方示例添加配置即可
或者直接在原有的配置文件上修改
Paddle Inference
Paddle Inference 文档
Paddle Inference 是飞桨的原生推理库, 提供服务器端的高性能推理能力。
在模型的推理部分,有涉及关于 Paddle 的推理接口,可以参考官方文档的解释
4.3.4 编译
需要根据自己新添加的算法去配置编译的build文件
1. 修改算法的编译配置文件:
如: modules/perception/lidar/lib/detector/cnn_segmentation/BUILD
:
其中包括了源文件,头文件,依赖库的指定
load("@rules_cc//cc:defs.bzl", "cc_library")
load("@local_config_cuda//cuda:build_defs.bzl", "cuda_library")
load("//tools:cpplint.bzl", "cpplint")
package(default_visibility = ["//visibility:public"])
cc_library(
name = "cnn_segmentation",
srcs = ["cnn_segmentation.cc"],
hdrs = ["cnn_segmentation.h"],
deps = [
":disjoint_set",
":feature_generator",
":util",
"//cyber",
"//modules/common/adapters:adapter_gflags",
"//modules/perception/base",
"//modules/perception/inference:inference_factory",
"//modules/perception/inference:inference_lib",
"//modules/perception/inference/tensorrt:rt_net",
"//modules/perception/inference/utils:inference_util_lib",
"//modules/perception/lib/config_manager",
"//modules/perception/lidar/lib/ground_detector/spatio_temporal_ground_detector",
"//modules/perception/lidar/lib/interface",
"//modules/perception/lidar/lib/roi_filter/hdmap_roi_filter",
"//modules/perception/pipeline/proto/stage:cnnseg_config_cc_proto",
"//modules/perception/lidar/lib/detector/cnn_segmentation/proto:cnnseg_param_cc_proto",
"//modules/perception/lidar/lib/detector/cnn_segmentation/proto:spp_engine_config_cc_proto",
"//modules/perception/lidar/lib/detector/cnn_segmentation/spp_engine",
"//modules/perception/lidar/lib/detector/ncut_segmentation:ncut_segmentation",
"//modules/perception/lidar/lib/interface:base_lidar_detector",
"//modules/perception/proto:perception_config_schema_cc_proto",
"//modules/perception/pipeline:stage",
"@com_google_absl//:absl",
],
alwayslink = True,
)
cc_library(
name = "disjoint_set",
hdrs = ["disjoint_set.h"],
)
cc_library(
name = "feature_generator",
srcs = [
"feature_generator.cc",
":feature_generator_cuda",
],
hdrs = ["feature_generator.h"],
deps = [
":util",
"//modules/perception/base",
"//modules/perception/lidar/lib/detector/cnn_segmentation/proto:cnnseg_param_cc_proto",
"@com_google_googletest//:gtest",
"@eigen",
],
)
cuda_library(
name = "feature_generator_cuda",
srcs = ["feature_generator.cu"],
hdrs = ["feature_generator.h"],
deps = [
":util",
"//modules/perception/base",
"//modules/perception/lidar/lib/detector/cnn_segmentation/proto:cnnseg_param_cc_proto",
"@com_google_googletest//:gtest",
"@eigen",
"@local_config_cuda//cuda:cudart",
],
)
cc_library(
name = "util",
hdrs = ["util.h"],
)
cc_test(
name = "cnn_segmentation_test",
size = "large",
srcs = ["cnn_segmentation_test.cc"],
deps = [
":cnn_segmentation",
"//modules/perception/common:perception_gflags",
"@com_google_googletest//:gtest_main",
],
)
#cc_test(
# name = "feature_generator_test",
# size = "small",
# srcs = ["feature_generator_test.cc"],
# deps = [
# ":feature_generator",
# "//modules/perception/common:perception_gflags",
# "//modules/perception/lidar/common:pcl_util",
# "@com_google_googletest//:gtest_main",
# "@opencv2//:core",
# "@opencv2//:highgui",
# ],
#)
cpplint()
2. 修改自己添加的proto文件的编译配置
如modules/perception/lidar/lib/detector/cnn_segmentation/proto/BUILD
## Auto generated by `proto_build_generator.py`
load("@rules_proto//proto:defs.bzl", "proto_library")
load("@rules_cc//cc:defs.bzl", "cc_proto_library")
load("//tools:python_rules.bzl", "py_proto_library")
package(default_visibility = ["//visibility:public"])
cc_proto_library(
name = "spp_engine_config_cc_proto",
deps = [
":spp_engine_config_proto",
],
)
proto_library(
name = "spp_engine_config_proto",
srcs = ["spp_engine_config.proto"],
)
py_proto_library(
name = "spp_engine_config_py_pb2",
deps = [
":spp_engine_config_proto",
],
)
cc_proto_library(
name = "cnnseg_param_cc_proto",
deps = [
":cnnseg_param_proto",
],
)
proto_library(
name = "cnnseg_param_proto",
srcs = ["cnnseg_param.proto"],
)
py_proto_library(
name = "cnnseg_param_py_pb2",
deps = [
":cnnseg_param_proto",
],
)
再修改代码以及编译文件后需要重新执行编译命令,编译命令如下:
./apollo.sh build_opt_gpu
关于具体的感知算法源码分析,后续也会在其他地方解析
4.4 新感知算法的测试
首先保证先进入docker,并且已经编译完成
./docker/scripts/dev_into.sh
./apollo.sh build_opt_gpu
4.4.1 准备数据包以及相关模型、配置文件
-
record 数据包(必备)
将自己准备的 record 数据包移动到/apollo
工作目录下即可。 -
车型配置文件(可选)
将自己准备的对应车型配置文件解压至modules/calibration/data
目录下。 -
高精地图(可选)
将依据自己使用的高精地图文件解压并放置到modules/map/data
目录下。
如下图若在modules/calibration/data
创建了一个nuscenes_example,那么在车型配置文件中则可以看到并选择
配置文件可以参考modules/calibration/data/perception_test_v1
进行相应的配置
map文件同理
注意:上面的车型配置文件以及高精地图配置文件为可选项,也就是如果自己有实验车型的文件可以导入,或者录制的高精地图可以导入。若没有则可以不选
若没有对应的车型配置文件,则选择如下默认设置,而后需要按 4.2.2 中提到的要求放置对应的外参config
首先修改modules/transform/dag/static_transform.dag
启动文件中指定的cofig路径,可以自己创建一个.pd.txt文件
如这里以nuscenes为例,创建了一个nuscenes_static_transform_conf.pb.txt的文件
然后修改modules/transform/conf/nuscenes_static_transform_conf.pb.txt
下的file path成前面转换出来的外参yaml文件
4.4.2 准备新的模型文件以及算法编译的配置
确保以下的准备工作:
- 已经导出合适格式的模型权重文件
- 已经添加了继承base_xxxxx_detector的新算法的定义和实现
- 已经添加对应的proto文件以及config文件,camera和lidar更新方式略有不同根据实际情况进行配置
(camera:modules/perception/production/conf/perception/camera/obstacle.pt
,lidar:modules/perception/production/conf/perception/lidar/config_manager.config
) - 已经添加对应传感器的外参文件(可以通过修改
static_transform_conf.pb.txt
, 也可以通过配置车辆参数文件实现) - 已经修改了build的编译配置
4.4.3 修改启动配置文件、算法模型权重配置文件
由于通过 modules/perception/production/dag/dag_streaming_perception_lidar.dag
启动lidar模块,因此需要修改对应的dag文件。
(如果启动的是其他感知模块,则去对应的dag文件修改,这里以启动lidar为例)
dag文件中定义了各种关于lidar感知的组件其中找到类命为:LidarDetectionComponent
如下可以看到lidar检测组件的config文件的路径:
(如果自己实际的感知组件与默认配置文件不同,则需要自行创建并配置路径)
根据路径找到modules/perception/production/conf/perception/lidar/velodyne128_detection_conf.pb.txt
,config指定的是传感器的配置文件,具体包含定义的传感器名字以及该传感器负责的检测算法的config文件夹的路径:
(如果自己实际的传感器配置与默认配置文件不同,则需要自行创建并配置路径)
根据文件中的路径找到modules/perception/pipeline/config/lidar_detection_pipeline.pb.txt
,该文件是具体配置了lidar感知的流程:
(如果自己实际的感知检测算法与默认配置文件不同,则需要自行创建并配置路径)
找到CNN_SEGMENTATION对应的stage_config:这里指定了CNN_SEGMENTATION算法对应的参数文件、模型网络结构的proto文件、权重caffemodel文件、engine文件
(如果自己实际的感知检测算法的权重以及模型文件与默认配置文件不同,则需要自行创建并配置路径)
注:这里的权重文件不一定规定是caffemodel
4.4.4 订阅相对应的感知数据通道
以nuscenes为例,查看record里面的数据通道
找到对应需要启动的感知模块的启动文件,
如modules/perception/production/dag/dag_streaming_perception_lidar.dag
下的lidar启动文件
注释掉原本订阅的lidar通道,并增加 nuscenes 数据集中的record中的点云数据通道
其他感知模块的启动文件同理
修改完配置文件后,再编译一下
./apollo.sh build_opt_gpu
4.4.5 感知启动!!
- 启动感知模块
以启动 lidar 感知模块为例
mainboard -d modules/perception/production/dag/dag_streaming_perception_lidar.dag
稍等片刻,待模块启动后
- 重启 DreamView,并按F5刷新
# 开启DreamView
./scripts/bootstrap.sh
# 若之前已经开启DreamView,选择重启DreamView
./scripts/bootstrap.sh restart
- 点击页面左侧状态栏 Module Controller 模块启动 transform 模块
- 播放record数据
# 直接播放数据
cyber_recorder play -f data/bag/xxxxx.record
# 如果record中含有感知结果数据, 则需要屏蔽原本record中的感知结果数据
cyber_recorder play -f data/bag/xxxxx.record -k /apollo/perception/obstacles /apollo/prediction
- 验证
可以在DreamView中查看感知的检测结果
五、补充学习
这一章补充了 Apollo 中有关 Cyber RT 的相关知识、proto 文件的使用、pipeline文件的使用、bazel 编译工具等的相关知识
5.1 Cyber RT
Cyber RT 是专为自动驾驶场景而设计的高性能运行时框架
Cyber RT 里有Talker/Listener、Service/Client 以及 Parameter Service 三种通信模式
参考:
CyberRT文档
apollo介绍之Cyber Component
Cyber RT模块加载流程简介
apollo介绍之Cyber Component
5.1.1 常用术语
-
Component
在自动驾驶系统中,模块(如感知、定位、控制系统等)在 Cyber RT 下以 Component 的形式存在。不同 Component 之间通过 Channel 进行通信。Component 概念不仅解耦了模块,还为将模块拆分为多个子模块提供了灵活性。 -
Channel
Channel 用于管理 Cyber RT 中的数据通信。用户可以发布/订阅同一个 Channel,实现 P2P 通信。 -
Task
Task 是 Cyber RT 中异步计算任务的抽象描述。 -
Node
Node 是 Cyber RT 的基本组成部分。每个模块都包含一个 Node 并通过 Node 进行通信。通过在节点中定义 Reader/Writer 或 Service/Client,模块可以具有不同类型的通信形式。 -
Reader/Writer
Reader/Writer 通常在 Node 内创建,作为 Cyber RT 中的主要消息传输接口。 -
Service/Client
除 Reader/Writer 外,Cyber RT 还提供了用于模块通信的 Service/Client 模式。它支持节点之间的双向通信。当对服务发出请求时,客户端节点将收到响应。 -
Parameter
参数服务在 Cyber RT 中提供了全局参数访问接口。它是基于 Service/Client 模式构建的。 -
服务发现
作为一个去中心化的框架,Cyber RT 没有用于服务注册的主/中心节点。所有节点都被平等对待,可以通过“服务发现”找到其他服务节点。使用 UDP 用来服务发现。 -
CRoutine
参考协程(Coroutine)的概念,Cyber RT 实现了 Coroutine 来优化线程使用和系统资源分配。 -
Scheduler
为了更好地支持自动驾驶场景,Cyber RT 提供了多种资源调度算法供开发者选择。 -
Message
Message 是 Cyber RT 中用于模块之间数据传输的数据单元。 -
Dag 文件
Dag 文件是模块拓扑关系的配置文件。您可以在 dag 文件中定义使用的 Component 和上游/下游通道。 -
Launch 文件
Launch 文件提供了一种启动模块的简单方法。通过在 launch 文件中定义一个或多个 dag 文件,可以同时启动多个模块。 -
Record 文件
Record 文件用于记录从 Cyber RT 中的 Channel 发送/接收的消息。回放 Record 文件可以帮助重现 Cyber RT 之前操作的行为。
5.1.2 Cyber Component
为什么使用启动mainboard命令会调用对应的算法呢?
接下来针对Cyber Component调用原理及流程分析了其中的奥秘
Cyber Component调用原理及流程:
以使用dag启动lidar感知模块为例
mainboard -d modules/perception/production/dag/dag_streaming_perception_lidar.dag
以上命令主要启动了cyber/mainboard/mainboard.cc
下的main函数:
通过 apollo::cyber::Init() 初始化了cyber
在controller.Init()中 调用了LoadAll()函数,用于加载输入的参数
随后则进入Cyber RT的模块加载的流程
Cyber RT的模块加载机制主要分为两部分:
- 编译期进行模块注册
- 运行期加载模块并初始化
编译期工作
cyber/component/component.h
中的CYBER_REGISTER_COMPONENT(name) 的宏对应cyber/class_loader/class_loader_register_macro.h
中的 CLASS_LOADER_REGISTER_CLASS_INTERNAL
其中:
模板参数Derived的值为CommonComponentSample,即本component对应的类名
模板参数Base的值为apollo::cyber::ComponentBase,是CommonComponentSample的间接基类
UniqueID是一个唯一的整数,由编译期宏__COUNTER__实现
上面的宏定义了一个名为ProxyType##UniqueID的结构体,UniqueID保证了该结构体是全局唯一的,然后用这个结构体定义了一个静态全局变量
运行期工作
运行期主要负责加载lidar动态库 libperception_component_lidar.so,然后创建类CommonComponentSample的实例对象并初始化。
通过dag文件启动的入口在 cyber/mainboard/mainboard.cc
、
首先实例化了一个 ModuleController 的对象,用于启动模块,并且把module_args作为参数传入
ModuleController controller(module_args);
if (!controller.Init()) {
controller.Clear();
AERROR << "module start error.";
return -1;
}
在 controller.Init() 阶段则调用了 cyber/mainboard/module_controller.cc
中的 ModuleController::LoadAll() 函数,该函数主要是解析传入的dag文件中的参数
然后,其中通过调用 LoadModule() 函数,LoadModule() 函数通过调用class_loader_manager_.LoadLibrary()来加载编译生成的libperception_component_lidar.so 动态链接库的文件,在for循环中则是根据配置信息初始化各个component实例
1. 首先进行动态库加载
- class_loader_manager_.LoadLibrary()
在class_loader_manager_.LoadLibrary()中 new 了一个class_loader::ClassLoader的对象,并把传入path。
在ClassLoader这个类的构造函数中调用了utility::LoadLibrary - utility::LoadLibrary()
在cyber/class_loader/utility/class_loader_utility.cc
的utility::LoadLibrary()中 new 了一个 SharedLibrary对象。在SharedLibrary构造函数中调用了dlopen函数,该函数用于打开动态库并返回句柄,也就是加载了libperception_component_lidar.so动态库。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
在这加载前LoadLibrary中已通过SetCurActiveClassLoader和SetCurLoadingLibraryName设置了上下文信息,下面通过GetCurActiveClassLoader和GetCurLoadingLibraryName获取相关信息。
然后和对象cyber/class_loader/utility/class_loader_utility.h
中的RegisterClass中new_class_factory_obj做绑定。最后将类工厂new_class_factory_obj加到factory_map中,map的key为类的名字CommonComponentSample, value为工厂对象new_class_factory_obj。 factory_map又维护在另一个map中,该map作为静态变量由函数GetClassFactoryMapMap维护,它的key是Base的类名,即上面的typeid(Base).name()。
//map的key为字符串CommonComponentSample,value为类工厂对象
using ClassClassFactoryMap =
std::map<std::string, utility::AbstractClassFactoryBase*>;
//map的key为CommonComponentSample的基类apollo::cyber::ComponentBase的名字,value为上面的map
using BaseToClassFactoryMapMap = std::map<std::string, ClassClassFactoryMap>;
2. 然后进行Component 初始化
cyber/mainboard/module_controller.cc
中,在for循环中根据类的名字进行初始化,通过调用ClassLoaderManager::CreateClassObj() 来创建,返回的是指向各个Component的 shared_ptr,并维护在一个vector中
- 具体的,在ClassLoader::CreateClassObj 中调用了在
cyber/class_loader/utility/class_loader_utility.h
的utility::CreateClassObj() :
其中根据基类名称查找由<class_name, utility::AbstractClassFactoryBase*>组成的map
查找class_name对应的类工厂,即 LidarDetectionComponent对应的类工厂,最后由类工厂创建LidarDetectionComponent 实例对象
在结束class_loader_manager_.LoadLibrary()、CreateClassObj()之后就完成了参数的配置并且base指针将指向LidarDetectionComponent对象
最后在cyber/mainboard/module_controller.cc
里面 使用base->Initialize进行初始化。
其中LidarDetectionComponent继承于模板类Component 并 override了Initialize函数,模板类Component又继承于ComponentBase。Initialize最终执行了在 cyber/component/component.h
中模板类Component的Initialize,其中又调用了LidarDetectionComponent::Init(),完成了LidarDetectionComponent的初始化。
最后创建协程任务,并且注册"Process()"回调,当数据到来的时候,唤醒对象的协程任务执行"Process()"处理数据。同时Process会调用Proc,然后Proc又会调用modules/perception/onboard/component/lidar_detection_component.cc
中的 InternalProc,再调用lidar_detection_pipeline_->Process,并把数据传入进行处理
最后通过指针调用 modules/perception/lidar/lib/detector/center_point_detection/center_point_detection.cc
的CenterPointDetection::Process,从而完成启动感知模块
5.2 Pipeline
modules/perception/pipeline/pipeline.cc
中的
这里主要是对pipeline文件中定义的算法、处理模块进行遍历初始化
关于CyberRT component中如何调用 pipeline中定义的具体算法,将会在另一篇文章中讲述
5.3 Protocol Buffers 格式文件(protobuf)
protobuf 是一种由 Google 开发的与语言无关、平台无关的高效可扩展的序列化数据格式(其作用类似JSON文件、XML文件)
protobuf 可以定义消息结构,然后序列化或反序列化,通常用于网络通信、数据存储等场景,Protobuf 利用字段编号与特殊的编码方法巧妙地减少了要传递的信息量,并且使用二进制格式,相比于 JSON 的文本格式,更节省空间。
因此在Apollo自动驾驶系统中的消息传输过程选择采用了protobuf这种高效的、有较好实时性的文件格式进行传递
此外,为了方便开发,在vscode中可以选择下载 vscode-proto3
这个插件来编写proto文件
下面就是 modules/perception/lidar/lib/detector/cnn_segmentation/proto/cnnseg_param.proto
中有关cnnseg算法的proto文件
以该文件为例,进行分析
其中常用的字段类型分为以下几种:
整数类型:
int32
int64
uint32
unint64
sint32
sint64
浮点数类型:
float
double
字符串类型:
string
布尔类型:
bool
常用的proto文件编写格式:
- 指定使用的proto文件版本,这里指定使用protobuf的版本2
syntax = "proto2";
- 通过message 定义一个消息,这里定义了一个关于CNNSeg lidar感知算法程序的消息
message CNNSegParam {
.......
}
- 在消息中定义所需的字段,字段前面的optional表示可选字段;string、NetworkParam等表示字段的类型(特别地,NetworkParam也可以是自定义的嵌套消息类型);model_type、network_param等表示字段的名称; = 1 、 = 2表示字段名称的id号。
如:optional FeatureParam feature_param = 3; 表示定义一个可选字段,字段类型为FeatureParam,字段名称为feature_param,字段id编号为3
// for cnnseg algorithm
optional string model_type = 1 [default = "CaffeNet"];
optional NetworkParam network_param = 2;
optional FeatureParam feature_param = 3;
optional bool do_classification = 4 [default = true];
optional bool do_heading = 5 [default = true];
optional uint32 gpu_id = 6 [default = 0];
......
在完成proto文件编写后,结合apollo的BUILD文件,使用Bazel 编译工具,即可完成消息的处理
5.4 Bazel 编译工具
使用 talker-listener 通信
待记录…
总结
以上记录了Apollo 8.0框架的使用以及感知模块实践的流程,包括Apollo 8.0 环境配置、本地电脑运行Apollo 8.0、Apollo 8.0感知模块解析与实践、Cyber RT等。
有些模块和算法尚未整理,存在一些不足,后续也会慢慢补充,也欢迎大家留言讨论
最后感谢Apollo团队!