时间:2025-07-17 作者:游乐小编
本文基于PaddlePaddle框架复现了VoxelNet算法,这是一种基于体素的3D目标检测算法,在KITTI数据集上开展实验并提供预训练模型和在线体验。VoxelNet含特征学习网络、卷积中间层和区域候选网络,通过划分点云为体素、提取特征等实现检测。复现过程参考相关改进项目,解决了内存泄漏等问题,取得一定检测精度。
本项目基于PaddlePaddle框架复现了基于体素的3D目标检测算法VoxelNet,在KITTI据集上进行了实验。 项目提供预训练模型和AiStudio在线体验NoteBook。
背景
3D检测广泛用于自主导航、家政机器人以及AR/VR。LIDAR提供可靠的深度信息用于准确定位目标并表征其形状。
现有方法
将LIDAR投影到某个视角作为输入;采用3D体素,提取人工设计的体素特征;虽然有PointNet和Pointnet++这类点云学习网络,但还无法处理大规模点云数据。这篇文章利用网络学习体素点特征,只使用点云实现了快速高效的3D目标检测。
算法解释
VoxelNet由三个功能块组成:特征学习网络、卷积中间层和区域候选网络。下面一一介绍:
特征学习网络首先将三维点云划分为一定数量的Voxel(就是将空间划分为一个一个栅格,用格子表示格子里的点云)并对这些voxel进行分组,再经过点的随机采样(每个格子的最大点云采样数量这里是T=35)以及归一化后,对每一个非空Voxel使用若干个VFE(Voxel Feature Encoding)层进行局部特征提取,得到Voxel-wise Feature。这里的VFE模型其实就是FC全连接模型。最后的输出形状为128×10×400×352.
卷积中间层为了聚合周围环境voxels的信息,使用3D卷积对4D tensor进行卷积,并进行reshape到3D tensor。每个卷积中间层顺序应用3D卷积、BN层和ReLU层。举例:输入尺寸(4D tensor)是128 × 10 × 400 × 352,输出尺寸(经过Convolutional Middle Layers之后)是64 × 2 × 400 × 352,然后reshape到 128 × 400 × 352变成3D tensor(注意到128 × 400 × 352正是BEV视图上的栅格尺寸)。
区域候选网络在提取到特征后,利用RPN模块预测候选检测框。如图所示,该网络包含三个全卷积层块(Block),每个块的第一层通过步长为2的卷积将特征图采样为一半,之后是三个步长为1的卷积层,每个卷积层都包含BN层和ReLU操作。将每一个块的输出都上采样到一个固定的尺寸并串联构造高分辨率的特征图。最后,该特征图通过两种二维卷积被输出到期望的学习目标:概率评分图(Probability Score Map)和回归图(Regression Map)
损失函数Probability Score Map的输出通道是2,分别对应positive和negative的分数,Regression map输出通道为14维,对于每个回归的Bounding box都用7维来表示,也就是中心位置 、候选框的长宽高和航向角,另外两个旋转轴默认为0,原因是地面水平。同理,假设预测的anchor用小标a表示,因此可定义如下的残差:
其中,是anchor框底部的对角线长度,采用的目的是用对角线齐次归一化和。然后定义可以最终的损失函数:
损失函数前面两项是正则化分类损失,其中和分别表示softmax层对正锚和负锚的分数,采用的是交叉熵表示,和为正定平衡系数。最后一项是回归损失,和是正锚的回归输出和ground truth,采用的是Smooth L1损失。
详细的参数设定需要查看论文才能更好理解~
论文:
[1] Yin Zhou, Oncel Tuzel. Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition (CVPR), 2018. VoxelNet: End-to-End Learning for Point Cloud Based 3D Object Detection博客参考:
VoxelNet: End-to-End Learning for Point Cloud Based 3D Object Detection【3D目标检测】VoxelNet:End-to-End Learning for Point Cloud Based 3D Object Detection解读项目参考:
https://github.com/qianguih/voxelnet
github repo实现精度 easy: 53.43 moderate:48.78 hard:48.06
https://github.com/traveller59/second.pytorch
由于该论文并未提供开源的代码,目前也找不到能够复现其论文中指标的项目。 因此本项目根据参考项目(voxelnet-tensorflow)和该论文后续的算法改进版本(second)进行了复现。
指标解释:>机器学习算法评估指标——3D目标检测
评价3D目标检测结果的指标主要是3D AP 和Bev AP。其含义是当预测框与真值框的交并比(IOU)大于一定阈值时,认为预测框正确的数量与所有真值框的比例。
IOU即两个框的相交范围与并集范围的比例。
在KITTI val数据集(50/50 split as paper)的测试效果如下表。
Car AP@0.70, 0.70, 0.70:bbox AP:90.26, 86.24, 79.26bev AP:89.92, 86.04, 79.143d AP:77.00, 66.40, 63.24aos AP:38.34, 37.30, 33.19Car AP@0.70, 0.50, 0.50:bbox AP:90.26, 86.24, 79.26bev AP:90.80, 89.84, 88.883d AP:90.75, 89.32, 87.84aos AP:38.34, 37.30, 33.19Car coco AP@0.50:0.05:0.95:bbox AP:67.72, 63.70, 61.10bev AP:67.13, 63.44, 61.153d AP:53.45, 48.92, 46.34aos AP:28.82, 27.54, 25.55登录后复制
预训练权重和日志:百度网盘 | AiStudio存储
2、当将分类损失改为FocalLoss以及加入针对aos的direction分类损失时(后续实验表明direction损失只对aos起作用,可不用)
Car AP@0.70, 0.70, 0.70:bbox AP:90.19, 85.78, 79.38bev AP:89.79, 85.26, 78.933d AP:81.78, 66.88, 63.51aos AP:89.81, 84.55, 77.71Car AP@0.70, 0.50, 0.50:bbox AP:90.19, 85.78, 79.38bev AP:96.51, 89.53, 88.593d AP:90.65, 89.08, 87.52aos AP:89.81, 84.55, 77.71Car coco AP@0.50:0.05:0.95:bbox AP:67.15, 63.05, 60.58bev AP:68.90, 63.78, 61.083d AP:54.88, 49.42, 46.82aos AP:66.89, 62.19, 59.23登录后复制
预训练权重和训练日志:百度网盘 | AiStudio存储
另外,论文中没提及的细节,本项目均参考Second项目的实施。
仓库内的log文件夹下存放有两个训练日志和可视化曲线日志。
约15分钟.
In [1]%cd /home/aistudio/!rm -rf kitti/!mkdir -p kitti/training/velodyne_reduced!mkdir -p kitti/testing/velodyne_reduced!unzip data/data50186/data_object_calib.zip -d kitti/!unzip data/data50186/image_training.zip -d kitti/training/!unzip data/data50186/data_object_label_2.zip -d kitti/training/!unzip data/data50186/velodyne_training_1.zip -d kitti/training/!unzip data/data50186/velodyne_training_2.zip -d kitti//training/!unzip data/data50186/velodyne_training_3.zip -d kitti/training/!unzip data/data50186/image_testing.zip -d kitti/testing/!unzip data/data50186/velodyne_testing_1.zip -d kitti/testing/!unzip data/data50186/velodyne_testing_2.zip -d kitti/testing/!unzip data/data50186/velodyne_testing_3.zip -d kitti/testing/!mv kitti/training/training/* kitti/training/!rm -rf kitti/training/training/!mv kitti/testing/testing/* kitti/testing/!rm -rf kitti/testing/testing/!mkdir kitti/training/velodyne!mv kitti/training/velodyne_training_1/* kitti/training/velodyne/!mv kitti/training/velodyne_training_2/* kitti/training/velodyne/!mv kitti/training/velodyne_training_3/* kitti/training/velodyne/!rm -rf kitti/training/velodyne_training_1!rm -rf kitti/training/velodyne_training_2!rm -rf kitti/training/velodyne_training_3!mkdir kitti/testing/velodyne!mv kitti/testing/velodyne_testing_1/* kitti/testing/velodyne!mv kitti/testing/velodyne_testing_2/* kitti/testing/velodyne!mv kitti/testing/velodyne_testing_3/* kitti/testing/velodyne!rm -rf kitti/testing/velodyne_testing_1!rm -rf kitti/testing/velodyne_testing_2!rm -rf kitti/testing/velodyne_testing_3!mv kitti data/登录后复制
至此,数据集的结构:
└── KITTI_DATASET_ROOT ├── training <-- 7481 train data | ├── image_2 <-- for visualization | ├── calib | ├── label_2 | ├── velodyne | └── velodyne_reduced <-- empty directory └── testing <-- 7580 test data ├── image_2 <-- for visualization ├── calib ├── velodyne └── velodyne_reduced <-- empty directory登录后复制
3,712 data samples fortraining and 3,769 data samples for validation
最适合的环境配置:
python版本:3.7.4PaddlePaddle框架版本:2.2.1CUDA 版本: NVIDIA-SMI 450.51.06 Driver Version: 450.51.06 CUDA Version: 11.0 cuDNN:7.6注意: 由于PaddlePaddle/cuDNN本身的BUG,CUDA 10.1版本当batch size > 2时会报如下错误:
OSError: (External) CUDNN error(7), CUDNN_STATUS_MAPPING_ERROR. [Hint: 'CUDNN_STATUS_MAPPING_ERROR'. An access to GPU memory space failed, which is usually caused by a failure to bind a texture. To correct, prior to the function call, unbind any previously bound textures. Otherwise, this may indicate an internal error/bug in the library. ] (at /paddle/paddle/fluid/operators/conv_cudnn_op.cu:758)登录后复制
因此单卡如果环境不是CUDA 11.0以上,config文件中batch size设置为2即可,后续通过训练的accum_step参数开启梯度累加起到增大bs的效果。设置accum_step=8即表示bs=16,并做相应config文件的初始学习率调整。
In [ ]!pip install distro shapely pybind11 pillow fire memory_profiler psutil scikit-image==0.14.2!pip install numpy==1.17.0!pip install numba==0.48.0登录后复制
(由于Notebook不支持导入当前整个项目到Python环境,以下操作在终端命令行执行)
export NUMBAPRO_CUDA_DRIVER=/usr/lib/x86_64-linux-gnu/libcuda.soexport NUMBAPRO_NVVM=/usr/local/cuda/nvvm/lib64/libnvvm.soexport NUMBAPRO_LIBDEVICE=/usr/local/cuda/nvvm/libdevice登录后复制
export PYTHONPATH=$PYTHONPATH:/home/aistudio/VoxelNet登录后复制
从label中分类别抽取真值信息以及对点云进行降采样。(约7分钟)
cd /home/aistudio/VoxelNet/voxelnet/python create_data.py create_kitti_info_file --data_path=/home/aistudio/data/kitti # Create kitti infospython create_data.py create_reduced_point_cloud --data_path=/home/aistudio/data/kitti # Create kitti reduced pointpython create_data.py create_groundtruth_database --data_path=/home/aistudio/data/kitti # Create kitti gt登录后复制
打印信息如下:
Generate info. this may take several minutes.Kitti info train file is saved to /home/aistudio/data/kitti/kitti_infos_train.pklKitti info val file is saved to /home/aistudio/data/kitti/kitti_infos_val.pklKitti info trainval file is saved to /home/aistudio/data/kitti/kitti_infos_trainval.pklKitti info test file is saved to /home/aistudio/data/kitti/kitti_infos_test.pkl[100.0%][===================>][40.86it/s][01:44>00:00] [100.0%][===================>][35.31it/s][01:47>00:00] [100.0%][===================>][39.13it/s][03:49>00:00] [100.0%][===================>][28.71it/s][01:53>00:00] load 14357 Car database infosload 2207 Pedestrian database infosload 734 Cyclist database infosload 1297 Van database infosload 56 Person_sitting database infosload 488 Truck database infosload 224 Tram database infosload 337 Misc database infos登录后复制
voxelnet/configs/car.configs
train_input_reader: { ... database_sampler { database_info_path: "/home/aistudio/data/kitti/kitti_dbinfos_train.pkl" ... } kitti_info_path: "/home/aistudio/data/kitti/kitti_infos_train.pkl" kitti_root_path: "/home/aistudio/data/kitti"}...eval_input_reader: { ... kitti_info_path: "/home/aistudio/data/kitti/kitti_infos_val.pkl" kitti_root_path: "/home/aistudio/data/kitti"}登录后复制
设置注意事项:
1、若训练要开启梯度累加选项,则:
学习率的decay_steps按照梯度累加后的batch size对应的总steps来设置。train_config.steps则按未梯度累加时对应的初始batch size对应的总steps来设置2、 配置文件需放置于voxelnet/configs/***.py
训练一个epoch, V100 16G大约15分钟。显存占用11G左右。
python ./pypaddle/train.py train --config_path=./configs/config.py --model_dir=./output登录后复制
V100 16G 大约5分钟
python ./pypaddle/train.py evaluate --config_path=./configs/config.py --model_dir=./output --ckpt_path=./output/voxelnet-278400.ckpt登录后复制
3D可视化需要GUI,Notebook环境不支持动态GUI调用显示。需在本地测试。 详细查看README.md。
为了方便查看预测结果,下面的cell提供了一个在notebook中查看二维bev视角的可视化例子,可以在notebook执行。 由于只保留了相机视角范围内的结果(Points that are projectedoutside of image boundaries are removed(in Paper Section 3.1)),所以车身后面没有检测框。
In [3]%cd /home/aistudio/VoxelNet/!export PYTHONPATH=$PYTHONPATH:/home/aistudio/VoxelNetimport paddleimport numpy as npimport matplotlib.pyplot as pltimport picklefrom pathlib import Pathimport voxelnet.pypaddle.builder.voxelnet_builder as voxelnet_builderimport voxelnet.builder.voxel_builder as voxel_builderimport voxelnet.builder.target_assigner_builder as target_assigner_builderimport voxelnet.pypaddle.builder.box_coder_builder as box_coder_builderfrom voxelnet.data.preprocess import merge_voxelnet_batchfrom voxelnet.configs import cfg_from_config_py_filefrom voxelnet.utils import visdef example_convert_to_paddle(example, dtype=paddle.float32, ) -> dict: example_paddle = {} float_names = [ "voxels", "anchors", "reg_targets", "reg_weights", "bev_map", "rect", "Trv2c", "P2" ] for k, v in example.items(): if k in float_names: example_paddle[k] = paddle.to_tensor(v, dtype=dtype) elif k in ["coordinates", "labels", "num_points"]: example_paddle[k] = paddle.to_tensor( v, dtype=paddle.int32) elif k in ["anchors_mask"]: example_paddle[k] = paddle.to_tensor( v, dtype=paddle.uint8) else: example_paddle[k] = v return example_paddlepaddle.set_device('gpu') # 设置cpu/gpuconfig_path = "home/aistudio/VoxelNet/voxelnet/configs/config.py"config = cfg_from_config_py_file(config_path)input_cfg = config.eval_input_readermodel_cfg = config.model.voxelnetckpt_path = "/home/aistudio/VoxelNet/voxelnet/output/voxelnet-278400.ckpt"model_cfg.voxel_generator.point_cloud_range = [0, -40, -3, 70.4, 40, 1]voxel_generator = voxel_builder.build(model_cfg.voxel_generator)####################### BUILD TARGET ASSIGNER######################bv_range = voxel_generator.point_cloud_range[[0, 1, 3, 4]]box_coder = box_coder_builder.build(model_cfg.box_coder)target_assigner_cfg = model_cfg.target_assignertarget_assigner = target_assigner_builder.build(target_assigner_cfg, bv_range, box_coder)net = voxelnet_builder.build(model_cfg, voxel_generator, target_assigner)net.eval()state = paddle.load(ckpt_path)net.set_state_dict(state)out_size_factor = model_cfg.rpn.layer_strides[0] // model_cfg.rpn.upsample_strides[0]grid_size = voxel_generator.grid_sizefeature_map_size = grid_size[:2] // out_size_factorfeature_map_size = [*feature_map_size, 1][::-1]anchors = target_assigner.generate_anchors(feature_map_size)["anchors"]anchors = anchors.reshape((1, -1, 7))info_path = input_cfg.kitti_info_pathroot_path = Path(input_cfg.kitti_root_path)with open(info_path, 'rb') as f: infos = pickle.load(f)info = infos[564] # 测试目标点云# print(info)v_path = info['velodyne_path']v_path = str(root_path / v_path)points = np.fromfile( v_path, dtype=np.float32, count=-1).reshape([-1, 4])voxels, coords, num_points = voxel_generator.generate(points, max_voxels=40000)print(voxels.shape)# add batch idx to coords# coords = np.pad(coords, ((0, 0), (1, 0)), mode='constant', constant_values=0)image_idx = info['image_idx']rect = info['calib/R0_rect'].astype(np.float32)Trv2c = info['calib/Tr_velo_to_cam'].astype(np.float32)P2 = info['calib/P2'].astype(np.float32)example = { "anchors": anchors, "voxels": voxels, "num_points": num_points, "num_voxels": np.array([voxels.shape[0]], dtype=np.int64), "coordinates": coords, "rect": rect, "P2": P2, "Trv2c":Trv2c, 'image_idx': image_idx}batch_example = [example]examples = merge_voxelnet_batch(batch_example)examples = example_convert_to_paddle(examples)with paddle.no_grad(): pred = net(examples)[0]boxes_lidar = pred["box3d_lidar"].detach().cpu().numpy()vis_voxel_size = [0.1, 0.1, 0.1]vis_point_range = [-50, -30, -3, 50, 30, 1]bev_map = vis.point_to_vis_bev(points, vis_voxel_size, vis_point_range)bev_map = vis.draw_box_in_bev(bev_map, vis_point_range, boxes_lidar, [0, 255, 0], 2)# plt.savefig('/home/aistudio/val564.png')plt.imshow(bev_map)paddle.device.cuda.empty_cache()登录后复制
/home/aistudio/VoxelNetcfg_file must be located in ./configs/***.py...load config from home/aistudio/VoxelNet/voxelnet/configs/config.py...(13282, 35, 4)登录后复制
登录后复制
复现心得:
这篇论文在第四届论文复现赛的时候我就进行了复现,并未成功,相差甚远。由于论文没有开源代码,网络上也没有达到论文精度的项目,很难从头开始自己全部重写。于是我换了一个思路,既然这篇论文是非常经典的论文,后续肯定有人基于这个思路进行改进。果然找到了second。second这篇论文几乎重现了voxelnet的所有方法(但据作者说并没有完全复现原始的voxelnet),不过加入了稀疏卷积使得速度和精度得到了巨大提升。于是我的思路就变成了从second中去掉它改进的部分内容,使其复原原始的voxelnet,来减少自己重写代码的工作量。
由于之前第四届比赛时的经验,这次遇到的问题都不多,或者说当时已经遇到了,直接拿我当时的代码进行替换,一个函数一个函数输入输出对齐测试即可。并且只需要对照X2Paddle进行对齐即可。
第四届和这一次遇到的主要问题都是内存泄漏问题。不过这一次,找到了问题所在。
1.这里总结一下几个常犯的内存泄漏错误原因。
loss在用于取值打印或者用于其他计算时,没有.detach()或者.numpy()如果model本身内部要存一些每一次前向计算后的指标,指标一定也要在前向计算后detach再赋值到model内的变量(这是我找了超级久的泄漏问题)。2.enisum函数。这个函数虽然paddle2.2提供了,但是还有bug。用的时候报错了。
我在paddlenlp(https://github.com/PaddlePaddle/PaddleNLP/blob/develop/paddlenlp/ops/einsum.py) 中找到了可以正常使用的版本。
3.mask赋值。问题如下: 写了一个mask(bool类型)的赋值函数:
def mask_slice_v1(data, mask): """ 问题: data.shape = [x,y], type: float... mask.shape = [x] ,type: bool in torch, can do it by data[mask] to get shape:[x,y] result. 但是padlde目前还不行。 """ data_shape = data.shape mask = mask.unsqueeze(-1) # [x,1] mask = paddle.tile(mask,[1,data_shape[1]]) # [x,y] slice = paddle.masked_select(data,mask) # [x*y] return slice.reshape([-1,data_shape[1]]) # [x,y]登录后复制
相关信息:
@inproceedings{Yin2018voxelnet, author={Yin Zhou, Oncel Tuzel}, title={VoxelNet: End-to-End Learning for Point Cloud Based 3D Object Detection}, booktitle = {Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition (CVPR)}, year = {2018}}登录后复制
2021-11-05 11:52
手游攻略2021-11-19 18:38
手游攻略2021-10-31 23:18
手游攻略2022-06-03 14:46
游戏资讯2025-06-28 12:37
单机攻略