1. mmdet3d安装过程
其中,推荐安装python=3.7的虚拟环境,因为在kitti数据集可视化过程中需要使用到mayavi包,而这个包的vtk依赖不支持python 3.8版本。
# 创建虚拟环境 conda create --name openmmlab python=3.7 -y conda activate openmmlab # 安装深度学习框架 # Linux conda install pytorch==1.11.0 torchvision==0.12.0 torchaudio==0.11.0 cudatoolkit=11.3 # Windows pip install torch==1.11.0+cu102 torchvision==0.12.0+cu102 torchaudio==0.11.0 --extra-index-url https://download.pytorch.org/whl/cu102 # 安装mmdet3d开源算法库 pip install openmim mim install mmcv-full mim install mmdet mim install mmsegmentation git clone https://github.com/open-mmlab/mmdetection3d.git cd mmdetection3d pip install -e .
ps:在linux可能出现的问题不会有太多,但是如果是window中安装,出现pycocotools无法正常安装的问题,原因是没有vc++的编译器,安装一个visual studio 2019的专业版就可以正常安装了mmdet了,而mmdet的依赖就是pycocotools。
这个问题的详细解决方法见:error: Microsoft Visual C++ 14.0 or greater is required. Get it with “Microsoft C++ Build Tools“
2. KITTI数据集准备
在官网下载对应的数据:http://www.cvlibs.net/datasets/kitti/eval_object.php?obj_benchmark=3d
安装官方文档对数据进行组织:https://mmdetection3d.readthedocs.io/zh_CN/latest/datasets/kitti_det.html
ps:官方提供的数据切分指令如果wget无法正常下载访问,可以在/etc/hosts中自行添加网页的IP地址(IP地址的查询服务链接:https://www.ip138.com/),例如:
wget -c https://raw.githubusercontent.com/traveller59/second.pytorch/master/second/data/ImageSets/test.txt --no-check-certificate --content-disposition -O ./data/kitti/ImageSets/test.txt
这个问题的详细解决方法,我也用博客贴出:Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|0.0.0.0|:443… failed
处理后的文件夹结构应该如下:
kitti ├── ImageSets │ ├── test.txt │ ├── train.txt │ ├── trainval.txt │ ├── val.txt ├── testing │ ├── calib │ ├── image_2 │ ├── velodyne │ ├── velodyne_reduced ├── training │ ├── calib │ ├── image_2 │ ├── label_2 │ ├── velodyne │ ├── velodyne_reduced │ ├── planes (optional) ├── kitti_gt_database │ ├── xxxxx.bin ├── kitti_infos_train.pkl ├── kitti_infos_val.pkl ├── kitti_dbinfos_train.pkl ├── kitti_infos_test.pkl ├── kitti_infos_trainval.pkl ├── kitti_infos_train_mono3d.coco.json ├── kitti_infos_trainval_mono3d.coco.json ├── kitti_infos_test_mono3d.coco.json ├── kitti_infos_val_mono3d.coco.json
这里我不太理解mmdet3d转换出来的这些.pkl与.coco.json文件,所以下面写了个测试代码来对其进行详细了解。
2.1 了解 .coco.json 文件
对于 .coco.json 后缀的文件,可以通过两种方式对其进行读取,如下所示:
# 方法1: json_path = r'../data/kitti/kitti_infos_train_mono3d.coco.json' with open(json_path, 'r') as fr: json_file = json.load(fr) # 方法2: cocojson_path = '../data/kitti/kitti_infos_train_mono3d.coco.json' cocojson_file = mmcv.load(cocojson_path)
ps:如果由于某些原因导致json文件的内容被情况,也就是变成了一个空的.coco.json文件,这时候使用以上的两种方法读取数据是会报错的,这一点需要尤其注意。(在测试过程中我就是出现了这个题,还排查了许久的原因)
其中,查看kitti_infos_train_mono3d.coco.json 和 kitti_infos_test_mono3d.coco.json内容如下所示,有固定字段组成的字典形式,由annotation、images、categories组成,其中训练集有标注信息,而验证集没有标注信息,其字段信息如下所示:
其中的images又是由多个字典组成,如下所示:
不过在训练的时候,我看见配置文件中其实使用的pkl文件,所以这里生成的.coco.json文件更多的可能是使用一些2D的经典目标检测算法来训练kitti数据集的。也就是说,生成的.coco.json文件,使得我们可以使用mmdetection的开源算法库(检测2d的算法)来训练kitti数据集。
2.2 了解 .pkl 文件
使用一般的手段是不能正常读取.pkl文件的,np.fromfile 与 json.load 都不能正常读取,因为这是mmdet中独特的数据存储格式。对于网上没有这种文件的详细介绍,此时就可以转换思路通过如何生成 .pkl 文件入手,知道了其如何生成就应该知道其存储的内容是什么。
在代码层面,首先在 tools/create_data.py 中的 kitti_data_pre 函数中,作为数据处理的入口。之后可以发现,代码中是通过 mmcv.dump 与 mmcv.load 来进行pkl格式数据与.coco.json格式数据的写入与导出,但是数据本身就是一个字典形式。具体的构造在 get_kitti_image_info 函数中,函数的相关注释如下:
KITTI annotation format version 2: { [optional]points: [N, 3+] point cloud [optional, for kitti]image: { image_idx: ... image_path: ... image_shape: ... } point_cloud: { num_features: 4 velodyne_path: ... } [optional, for kitti]calib: { R0_rect: ... Tr_velo_to_cam: ... P2: ... } annos: { location: [num_gt, 3] array dimensions: [num_gt, 3] array rotation_y: [num_gt] angle array name: [num_gt] ground truth name array [optional]difficulty: kitti difficulty [optional]group_ids: used for multi-part object } }
这里,直接利用 mmcv.load 来读取 kitti_test.pkl 数据,pkl_file = mmcv.load(file=pkl_path) ,其内容如下所示:
可以发现,其作为一个列表元素,每个训练数据都被构建成了一个字典。每个字典由4大部分组成:inage、point_cloud、calib、annos。其与注释内容是相符合的,其中可以使用 mmcv.track_iter_progress 可以按顺序对字典进行提取:for info in mmcv.track_iter_progress(kitti_infos)
其中,对于这4个部分,官方资料中有解释到:https://mmdetection3d.readthedocs.io/zh_CN/latest/datasets/kitti_det.html
可以发现,其实这里的annos就是kitti中的label文件的数据。而对于label_2的介绍,可以参考https://blog.csdn.net/qq_37534947/article/details/106628308,label文件是kitti中object的标签和评估数据,以“000001.txt”文件为例,包含样式如下:
每一行代表一个object,每一行都有16列分别表示不同的含义,具体如下:
- 第1列(字符串):代表物体类别(type)
总共有9类,分别是:Car、Van、Truck、Pedestrian、Person_sitting、Cyclist、Tram、Misc、DontCare。其中DontCare标签表示该区域没有被标注,比如由于目标物体距离激光雷达太远。为了防止在评估过程中(主要是计算precision),将本来是目标物体但是因为某些原因而没有标注的区域统计为假阳性(false positives),评估脚本会自动忽略DontCare区域的预测结果。
- 第2列(浮点数):代表物体是否被截断(truncated)
数值在0(非截断)到1(截断)之间浮动,数字表示指离开图像边界对象的程度。
- 第3列(整数):代表物体是否被遮挡(occluded)
整数0、1、2、3分别表示被遮挡的程度。
- 第4列(弧度数):物体的观察角度(alpha)
取值范围为:-pi ~ pi(单位:rad),它表示在相机坐标系下,以相机原点为中心,相机原点到物体中心的连线为半径,将物体绕相机y轴旋转至相机z轴,此时物体方向与相机x轴的夹角,如图1所示。
- 第5~8列(浮点数):物体的2D边界框大小(bbox)
四个数分别是xmin、ymin、xmax、ymax(单位:pixel),表示2维边界框的左上角和右下角的坐标。
- 第9~11列(浮点数):3D物体的尺寸(dimensions)分别是高、宽、长(单位:米)
- 第12-14列(整数):3D物体的位置(location)分别是x、y、z(单位:米),特别注意的是,这里的xyz是在相机坐标系下3D物体的中心点位置。
- 第15列(弧度数):3D物体的空间方向(rotation_y)取值范围为:-pi ~ pi(单位:rad),它表示,在照相机坐标系下,物体的全局方向角(物体前进方向与相机坐标系x轴的夹角)
- 第16列(整数):检测的置信度(score)要特别注意的是,这个数据只在测试集的数据中有。
2.3 .bin文件、.pkl文件、.coco.json文件的查看代码
如何查看KITTI数据集涉及的这些.bin文件,以及mmdetection3d统一的.pkl与.coco.json格式,以下我写了个代码已实现对这些文件的查看。
参考代码:
import numpy as np import os import json import mmcv # os.chdir(path='') root = os.getcwd() print(root) bin_path = '../data/kitti/kitti_gt_database/1000_Car_0.bin' pkl_path = '../data/kitti/kitti_infos_train.pkl' # 原来是 test_mono3d.coco.json由于名称的问题,读取不了数据 json_path = r'../data/kitti/test.json' os.path.exists(bin_path), "{} is not exists".format(bin_path) os.path.exists(json_path), "{} is not exists".format(json_path) bin_file = np.fromfile(file=bin_path, dtype=np.float32, count=-1) pkl_file = np.fromfile(file=pkl_path, dtype=np.float32, count=-1) print(bin_file.shape) print(pkl_file.shape) # with open(json_path, 'r') as fr: # json_file = json.load(fr) # print(json_file) pkl_path = '../data/kitti/kitti_infos_train.pkl' kitti_infos = mmcv.load(file=pkl_path) # print(pkl_file) json_path = r'../data/kitti/kitti_infos_train_mono3d.coco.json' with open(json_path, 'r') as fr: json_train_file = json.load(fr) # print(json_file) cocojson_path = '../data/kitti/kitti_infos_test_mono3d.coco.json' json_test_file = mmcv.load(cocojson_path)
补充:使用 mmcv.load 不仅仅可以读取,coco.json和.pkl文件,其一共支持5种格式的数据格式。如下所示:
file_handlers = { 'json': JsonHandler(), 'yaml': YamlHandler(), 'yml': YamlHandler(), 'pickle': PickleHandler(), 'pkl': PickleHandler() }
3. KITTI数据集训练
如果你在单个机器上启动多个任务,比如,在具有8块显卡的机器上进行2个4块显卡训练的任务,你需要为每个任务指定不同的端口(默认为29500)以避免通信冲突。
如果你使用 dist_train.sh 启动训练任务,可以在命令中设置端口:
CUDA_VISIBLE_DEVICES=0,1,2,3 PORT=29500 ./tools/dist_train.sh ${CONFIG_FILE} 4 CUDA_VISIBLE_DEVICES=4,5,6,7 PORT=29501 ./tools/dist_train.sh ${CONFIG_FILE} 4
端口的设置还有另外的两种方法:
# 方法1:通过 --options 设置端口 CUDA_VISIBLE_DEVICES=0,1,2,3 GPUS=4 ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} config1.py ${WORK_DIR} --options 'dist_params.port=29500' CUDA_VISIBLE_DEVICES=4,5,6,7 GPUS=4 ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} config2.py ${WORK_DIR} --options 'dist_params.port=29501' # 方法2:通过修改配置文件(来设置不同的通信端口 dist_params = dict(backend='nccl', port=29500) dist_params = dict(backend='nccl', port=29501)
详细的训练过程这里就不贴出来的,和mmdetection、mmfewshot等的训练过程是一样的。详细可以参考:MMOpenLab使用专栏
经过测试,出现的使用问题:
1)训练MVXNET:弹出非法访问的问题(解决方法是降低学习率,以实现单卡运行的学习率配置)
2)训练Point RCNN:占用显存不断增加,最终溢出(官方还没有修复这个问题)
成功测试的算法:SECOND、3D-SSD、PointPillars、Part-A2、SASSD
4. KITTI数据集评估标准
以PointPillars为例,训练完之后会有一个评估结果(以下结果是截取了Car这个类的结果,还有另外两个类的结果没有放上来)
----------- AP11 Results ------------ Car AP11@0.70, 0.70, 0.70: bbox AP11:90.5385, 89.3699, 86.2703 bev AP11:89.6290, 86.8373, 79.6047 3d AP11:85.9857, 76.4022, 73.5934 aos AP11:90.38, 88.89, 85.46 Car AP11@0.70, 0.50, 0.50: bbox AP11:90.5385, 89.3699, 86.2703 bev AP11:90.6688, 89.9088, 89.0676 3d AP11:90.6654, 89.8380, 88.8712 aos AP11:90.38, 88.89, 85.46 Overall AP11@easy, moderate, hard: bbox AP11:79.9252, 74.4630, 71.3123 bev AP11:77.1720, 68.2989, 63.5469 3d AP11:73.1700, 62.1137, 58.4967 aos AP11:74.50, 68.65, 65.49 ----------- AP40 Results ------------ Car AP40@0.70, 0.70, 0.70: bbox AP40:95.6966, 92.1547, 87.4279 bev AP40:92.4469, 88.1739, 83.6487 3d AP40:87.8139, 76.5133, 73.3604 aos AP40:95.51, 91.61, 86.58 Car AP40@0.70, 0.50, 0.50: bbox AP40:95.6966, 92.1547, 87.4279 bev AP40:95.9824, 94.8116, 91.7013 3d AP40:95.9314, 94.6114, 90.0106 aos AP40:95.51, 91.61, 86.58 Overall AP40@easy, moderate, hard: bbox AP40:82.7911, 75.4843, 71.5740 bev AP40:78.7391, 68.7362, 64.2875 3d AP40:74.1775, 61.6529, 57.5156 aos AP40:76.47, 68.73, 64.88
对于上述的结果,下面分别对特定的名词进行解释说明:
4.1 bbox、bev、3d、aos
深度学习算法的检测指标通常由bbox、bev、3d、aos四个检测指标,其含义分别如下所示:
- bbox:2D检测框的准确率
- bev:BEV视图下检测框的准确率
- 3d:3D检测框的准确率
- aos:检测目标旋转角度的准确率
4.2 AP11与AP40
- AP11:表示11点插值平均精度,在kitti 3D中R11={0,0.1,0.2,……,1},是等间距的recall level
- AP40:表示40点插值平均精度,将R11修改为R40={1/40,2/40,3/40,……,1},同样是等间距的recall level
ps:论文《Disentangling Monocular 3D Object Detection》证明AP11是不准确的,因为当模型可以提供一个精度极小,仅仅是>0的一个单一目标,此时R=0时的精度即为1,那么AP11的平均精度即为1/11,这个精度已经超过了很多的方法,所以是不合理的。所以后续修改为AP40。
4.3 Car AP11@0.70, 0.70, 0.70与Car AP11@0.70, 0.50, 0.50
- AP11@0.70, 0.70, 0.70分别代表bbox,bev,3d在0.70阈值下的平均精度
- AP11@0.70, 0.50, 0.50分别代表bbox,bev,3d在0.70,0.50,0.50不同阈值下的平均精度
这里可以发现,评估bbox只有0.70这个阈值,所以可以发现bbox的两行数据都是一样的,而对于bev与3d来说,0.50的阈值比0.70的阈值要宽松,所以第二组的结果(阈值0.50)一般要比第一组的结果(阈值0.70)要高。
4.4 bbox AP11:90.5385, 89.3699, 86.2703
无论是bbox,还是bev、3d、aos,每个评价指标在某一个阈值下都会有3组结果,这三组结果分别对应的是easy、moderate和hard下的评估结果。难度越来越大,所以数值也越来越小,所以这三组数值一般是呈递减状态。
4.5 mAP
一般论文中的实验结果都会贴上一个mAP的最终结果,这个结果就是moderate mAP的结果。比如,在刚刚的PointPillars实验结果中,对于car类别的AP11结果如下所示,其中76.4022就是作为car这个类基准排名的主要指标。
Car AP11@0.70, 0.70, 0.70: 3d AP11:85.9857, 76.4022, 73.5934
而对于全部3个类别的AP11结果如下所示,那么62.1137就是作为3类(3 Class)基准排名的主要指标。
Overall AP11@easy, moderate, hard: 3d AP11:73.1700, 62.1137, 58.4967
现在,来查看mmdetection3d中PointPillars的结果:
可以发现,其实刚刚我跑出来的结果Class的AP是76.4022,而3 Class的AP是62.137。这个结果和官方跑出来的77.6和64.07差不多。使用以下指令,来测试刚刚训练好的PointPillars的最新模型:
python tools/test.py configs/pointpillars/hv_pointpillars_secfpn_6x8_160e_kitti-3d-3class.py \ work_dirs/hv_pointpillars_secfpn_6x8_160e_kitti-3d-3class/latest.pth \ --eval mAP
输出结果:
可以看见,无论是Car=76.4022还是3 Class=62.1137,都与训练时期的验证结果差不多,所以训练期间的验证结果还是可信的。
4.6 aos(Average Orientation Similarity)
aos的名称为平均方向相似性(这里是利用了AP11来进行计算),计算公式如下:
其中,r代表物体检测的召回率。在因变量r下,方向相似性s∈[0,1]被定义为所有预测样本与ground truth余弦距离的归一化:
其中D®表示在召回率r下所有预测为正样本的集合,∆θ(i) 表示检出物体i的预测角度与ground truth的差。为了惩罚多个检出匹配到同一个ground truth,如果检出i已经匹配到ground truth设置δi = 1,否则δi = 0。
简要分析:我们希望aos的值越大越好,越大也就说明预测角度与ground truth的值越相似,那么这个是如何实现的呢。在余弦函数中,如果预测角度与ground turth的值越接近,那么它们差值就越接近0,余弦值是越靠近1,也就是余弦越大,那么整个公式的求和平均也是越大的。而如果预测值与ground truth不像,严重预测错误,那么其差值就会变大,一个比较大的差值在余弦公式中是比较小的,甚至可能是负数,导致1+cos的结果很小,从而使得整体的结果偏小。所以aos公式可以一定程度的判断方向的预测正确性。
4.7 easy、moderate、hard的定义
KITTI数据集中easy、moderate、hard根据标注框是否被遮挡、遮挡程度和框的高度进行定义的,具体数据如下:
- 简单:最小边界框高度:40像素,最大遮挡级别:完全可见,最大截断:15%
- 中等:最小边界框高度:25像素,最大遮挡水平:部分遮挡,最大截断:30%
- 困难:最小边界框高度:25像素,最大遮挡级别:难以看到,最大截断:50%
参考资料:
1. MMDet3d官方文档
2. What is the resuls meaning? #185
3. KITTI数据集3d目标检测的评价的含义
4. 机器学习算法评估指标——3D目标检测
5. 点云感知算法面试知识点(一)