1. 项目说明
当前,3D 检测作为核心技术点,在机器人、增强现实等场景下应用广泛,发挥着至关重要的作用。传统依赖激光雷达的 3D 检测方法存在传感器昂贵难以部署,点云缺失纹理信息,分辨率低等诸多问题。
针对于此,开发单目 3D 检测模型,有效的利用图像相对于点云的种种优势,可以降低产业落地门槛,更广泛简单的部署到实际应用场景中。
单目的 3D 目标检测近几年一直是研究的热点,虽然往算法中添加先验知识,能够一定程度的提升准确率,但是也增加了获取标签的难度和算法设计的复杂性。
图 1 - 单目 3D 检测示例
方案难点:
深度信息缺失,由 2D 图像预测 3D 位置困难
相机传感器敏感,受环境影响(夜晚、雨天)等较大
图像层面,遮挡、截断等问题严重影响感知精度
2. 安装说明
2.1 环境要求
Python >= 3.6
paddlepaddle >= 2.0.2
cuda >= 9
boost 库
常见 Python 库# 安装库
! pip install shapely
2.2 解压数据及代码
项目代码在 M3D-RPN-2.0.tar
文件中,数据集在 kitti.tar
文件中,解压到合适路径即可使用。# 如果希望解压到其他目录
#可选择其他路径(默认 /home/aistudio )
! tar xf ~/data/data141443/kitti.tar
! unzip -qo ~/data/data141443/M3D-RPN-2.0.zip
! rm -rf __MACOSX
2.3 安装依赖# 删除已有软连接
! rm -rf ~/M3D-RPN-2.0/dataset/kitti_split1/training
! rm -rf ~/M3D-RPN-2.0/dataset/kitti_split1/validation%cd ~/M3D-RPN-2.0/
! python dataset/kitti_split1/setup_split.py
! sh dataset/kitti_split1/devkit/cpp/build.sh
! cd lib/nms && make
3. 数据准备
3.1 数据介绍
KITTI 数据集是一个用于自动驾驶场景下的计算机视觉算法测评数据集,由德国卡尔斯鲁厄理工学院(KIT)和丰田工业大学芝加哥分校(TTIC)共同创立。包含场景有市区、乡村和高速公路。本案例使用公开的 KITTI 数据集用于训练测试,共有 14999 张图片,分为训练集 3712 张,验证集 3769 张,测试集 7518 张。示例图片如下图所示:
图 2 - KITTI 数据集示例图片
3.2 数据结构
整个数据集包含图片 images,标签 labels 和相机参数 calib,每个标签文件种包含以下字段:
type—物体类别
truncated—是否截断
occluded—是否遮挡
alpha—观测角
bbox—障碍物2D框
dimension—障碍物的3D大小
location—障碍物的3D底面中心点位置
rotation_y—障碍物的朝向角
最终数据集文件组织结构为:
kitti └── training ├── calib ├── image_2 └── label_2
4. 模型选择
单目 3D 检测提供两种选择:基于 anchor 的方案和 anchor-free 的方案
基于anchor:从图像中估计出 3D 检测框,也无需预测中间的 3D 场景表示,可以直接利用一个区域提案网络,生成给点图像的 3D 锚点。不同于以往与类别无关的 2D 锚点,3D 锚点的形状通常与其语义标签有很强的相关性。
Anchor-free:将 2D 检测方法 CenterNet 扩展到基于图像的 3D 检测器,该框架将对象编码为单个点(对象的中心点)并使用关键点估计来找到它。此外,几个平行的头被用来估计物体的其他属性,包括深度、尺寸、位置和方向。
采用 anchor 的方法使用了 3D 障碍物的平均信息作为先验知识,3D 检测效果实际落地更好,所以我们采用经典的基于 anchor 的方法。在骨干网络部分,我们选择的是 DenseNet,这种网络建立的是前面所有层与后面层的密集连接,实现特征重用,有着省参数,扛过拟合等优点。我们提供了如下不同版本
图 3 - 不同版本 DenseNet
根据单目 3D 检测实时性的要求,这里我们选择了 DenseNet121 作为我们的骨干网络。
5. 模型训练
训练被拆分成了热身配置和主要配置。详细信息可查看 config 中的配置。
首先,在启动模型训练之前,可以修改配置文件中相关内容, 主要包括数据集的地址以及类别数量。对应到配置文件中的位置如下所示:
基础配置 solver_type: 'sgd' lr: 0.004 momentum: 0.9 weight_decay: 0.0005 max_iter: 50000 snapshot_iter: 10000 display: 20 do_test: True
数据集路径 dataset_test: 'kitti_split1' datasets_train: name: 'kitti_split1' anno_fmt: 'kitti_det' im_ext: '.png' scale: 1
还有一些其他的配置诸如优化器配置、标签信息、检测器样本等,可以在 config
目录下查看。- 启动热身配置训练 (不包含 depth-aware)! python train.py --conf=kitti_3d_multi_warmup
6. 模型评估
评估默认配置:output/kitti_3d_multi_warmup/conf.pkl
pkl配置 model: "model_3d_dilate" solver_type: "sgd" lr: 0.004 momentum: 0.9 max_iter: 50000 snapshot_iter: 10000 do_test: "True" test_scale: 512 crop_size: [512, 1760] mirror_prob: 0.5 distort_prob: -1 dataset_test: "kitti_split1" datasets_train: name: "kitti_split1" anno_fmt: "kitti_det" im_ext: ".png" scale: 1 ```可视化 `pkl` 配置文件方法import pickle import numpy as np PKL_PATH = 'output/kitti_3d_multi_warmup/conf.pkl' f = open(PKL_PATH,'rb') data = pickle.load(f) print(data)%cd ~/M3D-RPN-2.0 ! python test.py \ --conf_path output/kitti_3d_multi_warmup/conf.pkl \ --weights_path output/kitti_3d_multi_warmup/weights/iter50000.0_params.pdparams预模型 `output/kitti_3d_multi_warmup/weights/iter50000.0_params.pdparams` 效果 **Car** | | Easy | Mod | Hard | | ------------ | ----- | ----- | ----- | | 2D detection | 87.27 | 81.74 | 66.60 | | 3D BEV | 24.93 | 18.58 | 16.69 | | 3D detection | 19.10 | 15.69 | 13.15 | **Ped** | | Easy | Mod | Hard | | ------------ | ----- | ----- | ----- | | 2D detection | 72.47 | 58.28 | 50.07 | | 3D BEV | 4.12 | 4.55 | 3.44 | | 3D detection | 3.77 | 3.45 | 3.07 | **Cyclist** | | Easy | Mod | Hard | | ------------ | ----- | ----- | ----- | | 2D detection | 63.97 | 45.97 | 39.73 | | 3D BEV | 11.72 | 10.16 | 10.16 | | 3D detection | 10.56 | 10.07 | 10.07 |
7. 模型优化
本小节侧重展示在模型迭代过程中优化精度的思路
数据过滤:根据 bbox 可见程度、大小来过滤每个 bbox 标签,根据有无保留 bbox 过滤每张图片,整体平衡前后背景,保证训练的稳定性。
数据增强:主要使用 RandomFlip、Resize 两种数据增强策略
Anchor定义:模型输出
2D anchor定义 3D anchor定义
后处理优化: 根据将 3D 相关信息组成 3D 框,投影到图像上得到投影的八点框,取八点最小外接包围框与 2D 预测结果算 IOU,通过不断的调整旋转角 ry 或深度 z,来使得 IOU 最小。此算法利用了 2D 检测的结果要比 3D 检测的结果准确的先验知识,用 2D 框来纠正预测的 3D 属性,来达到优化 3D 定位精度的目的。整体框架如下图所示:
经过调整后,在 car 类前后效果对比如下:
3D detection | Easy | Mod | Hard |
---|---|---|---|
优化前 | 16.57 | 13.82 | 12.30 |
优化后 | 19.09 | 15.70 | 13.15 |
增量 | +2.52 | +1.88 | +0.85 |
8. 模型推理
推理过程包括两个步骤:1)导出推理模型 2)执行推理代码
导出推理模型
PaddlePaddle
框架保存的权重文件分为两种:支持前向推理和反向梯度的训练模型和只支持前向推理的推理模型。二者的区别是推理模型针对推理速度和显存做了优化,裁剪了一些只在训练过程中才需要的
tensor,降低显存占用,并进行了一些类似层融合,kernel 选择的速度优化。因此可执行如下命令导出推理模型。! python
export_model.py
–conf_path output/kitti_3d_multi_warmup/conf.pkl
–weights_path output/kitti_3d_multi_warmup/weights/iter50000.0_params.pdparams生成的推理模型位于 inference
目录,里面包含三个文件,分别为
inference.pdmodel
inference.pdiparams
inference.pdiparams.info。
其中 inference.pdmodel 用来存储推理模型的结构, inference.pdiparams 和 inference.pdiparams.info 用来存储推理模型相关的参数信息。
结果保存在 inference_result
目录下。! python infer.py
–conf_path output/kitti_3d_multi_warmup/conf.pkl
9. 模型可视化
! python vis.py
10. 模型部署
使用飞桨原生推理库 paddle-inference,用于服务端模型部署
总体上分为三步:
创建 PaddlePredictor,设置所导出的模型路径
创建输入用的 PaddleTensor,传入到 PaddlePredictor 中
获取输出的 PaddleTensor ,将结果取出
#include "paddle_inference_api.h" // 创建一个 config,并修改相关设置paddle::NativeConfig config; config.model_dir = "xxx"; config.use_gpu = false;// 创建一个原生的 PaddlePredictorauto predictor = paddle::CreatePaddlePredictor<paddle::NativeConfig>(config);// 创建输入 tensorint64_t data[4] = {1, 2, 3, 4}; paddle::PaddleTensor tensor; tensor.shape = std::vector<int>({4, 1}); tensor.data.Reset(data, sizeof(data)); tensor.dtype = paddle::PaddleDType::INT64;// 创建输出 tensor,输出 tensor 的内存可以复用std::vector<paddle::PaddleTensor> outputs;// 执行预测CHECK(predictor->Run(slots, &outputs));// 获取 outputs ...
更多内容详见 > C++ 预测 API介绍