之所以想写这篇博客,主要原因在于阅读别人的代码时候,首先希望把流程架构弄清楚,然后才方便进行修改。second.pytorch代码量比较大,刚开始拿到时候,我也是一头雾水,硬着头皮往下面去看,配置环境(没有跑起来的建议去下载我的docker镜像,深度学习的利器,避免二次配置软件环境问题),让其跑起来方便调试来进行阅读。话不多说,现在开始进行简要分析一下second.pytorch点云检测这部分代码。
通过pycharm打开second.pytorch代码之后,左边的工程树显示一堆代码,看着就有点郁闷。下面逐个进行简单的说明,最后通过pointpillars算法为例,来进行简单的pipeline流程阐述。
second.pytorch -------- |---images |---second ----|---apex |---torchplus |---builder |---configs |---core |---data |---framework |---kittiviewer |---protos |---pytorch ------|---builder |---spconv |---core |---utils |---models |---utils上述的代码主要工程树,简单说下每个文件夹下面的代码功能: apex与spconv是进行second.pytorch安装的第三方依赖库; builder为基础网络的构建基础代码; configs为网络参数配置文件夹; core为基础功能文件夹,包括anchor、box_coder等些实现; data文件夹为数据处理模块; framework文件暂不清楚,好像是测试模块; kittiviewer很显然为可视化功能模块; protos模块读取内部的proto才构成py文件,具体不太清楚(理解后来填坑); pytorch文件夹为second.pytorch的核心,涉及训练、预测、网络等代码; utils为基础功能文件夹;
下面就以pointpillars算法为例,来进行pipeline的说明,以快速加深对该框架的理解。下面看下pointpillars算法流程图:
在看pointpillars算法流程图之前,首先来看一下论文的pipeline:
从上图可以看出pointpillars算法与之前的VoxelNet的框架基本一致,主要有三个部分组成:1. PillarFeatureNet:特征编码网络,在Bird-View的图下,将点云数据编码成为稀疏的伪图像;
2. PillarFeatureScatter:卷积中间网络,对伪图像进行backbone网络提取特征信息;
3. RPN:区域生成网络,用于分类与回归3D框,不过需要注意的是点云的Bird-View下最后一层特征层feature_map不能很小;
在Review这部分代码时候,second.pytorch代码主要涉及比较错乱,代码之间的交错比较多,可能需要仔细的梳理一下关系。voxel_builder、target_assign_builder、second_builder、box_coder_builder、optimizer_builder等等。在这里如果你想将该算法最终转换至TensorRT推理引擎下进行加速的话,那么可以参考我的那部分代码,修改网络之间的数据结构以此保证模型转换能够使用trt加速推理:传送门。当然你如果只是想阅读该算法在pytorch下的性能等来部署的话,可以参考second.pytorch的官方code:传送门,因为我的也是在这个代码基础上改动一下。
接下来主要分析几个主要的py文件:
----------> |---------> train.py |---------> voxelnet.py |---------> pointpillars.py |---------> rpn.py对于pointpillars算法最主要的几个代码文件,从程序运行的流程来简要解释一下。由于输入voxel为动态的变化网络,那么在中间的子网络就会随着动态的改变输入的维度。但是,使用TensorRT进行onnx中间件模型优化加速时候,模型的输入维度必须固定一个系数。那么,就需要对网络设置一个最大的维度,如果voxel的维度小于设置的最大维度,那么进行补零来进行输入维度的对齐。
Anchors生成参数配置:
target_assigner: { anchor_generators: { anchor_generator_stride: { sizes: [1.6, 3.9, 1.56] # wlh strides: [0.32, 0.32, 0.0] # if generate only 1 z_center, z_stride will be ignored offsets: [0.16, -39.52, -1.78] # origin_offset + strides / 2 rotations: [0, 1.57] # 0, pi/2 matched_threshold : 0.6 unmatched_threshold : 0.45 } } ... }在对代码进行review的时候,你会发现anchors是固定的生成,与点云的输入无关;所以,你要想更加适配的生成对应的anchors系数,你需要对target_assigner里面的相关系数进行设置,其中anchor_generator_stride包括三维bbox相关信息,长宽高、中心点的偏移量、旋转角度等。这部分anchors的设置对最终的目标判定有很大的影响,设置错误的容易不收敛。
NMS加速predict模块:
在目录second/pytorch/core/box_torch_ops.py中使用nms功能模块函数,原始采取的cpu版本的nms,可以使用gpu模块的nms进行加速。最粗暴的一种改变方式:
def rotate_nms(): # row 507 # ret = np.array(rotate_nms_cc(dets_np, iou_threshold), dtype=np.int64) ret = np.array(rotate_nms_gpu(dets_np, iou_threshold), dtype=np.int64)RPN系列网络输入feature_map维度对应不上:
如果运行时候遇见RPN相关网络的输入feature_map的维度不是[1, 64, 496, 432]的话,请查看你的训练配置文件里面的voxel_generator中的point_cloud_range与voxel_size的范围是否与之前的相同。当然,适当的修改voxel中的范围可以减少输入pointcloud的个数与pillars的个数,从而降低输入到后面网络的tensor张量,提升预测的speed。
model: { second: { network_class_name: "VoxelNet" voxel_generator { full_empty_part_with_mean: false point_cloud_range : [0, -39.68, -3, 69.12, 39.68, 1] voxel_size : [0.16, 0.16, 4] max_number_of_points_per_voxel : 100 } ......在train还是在evaluate功能部分:
为什么要这么说呢?是希望你时刻记住自己是在做训练还是预测模块,主要是代码量比较大,有涉及模型的onnx导出部分的修改,切记不同的功能时候有些代码是需要进行修改的。训练时候,需要把网络的pfelayer的返回return voxel_feature进行注释掉,相反导出onnx时候需要使用。还有就是pillarscatter在forward时候,训练与导出的batch_size不同,所以需要记住或者直接传入变量batch_size进行修改。
在train时候相关tricks:
相信你打开训练的config文件时候,觉得也有点多的参数,但是并不是所有的参数都会使用到。有些时候会出现训练卡住无法继续往下训练的情况,主要的原因可能在于学习率initial_learning_rate设置与类别中的database_sample与database_prep_steps这些变量的设置。每次训练的时候model_dir是需要没有创建的,当然你也可以修改代码去掉这一功能。最后,想说的是每个变量的意义仍然需要进一步的确定与研究,改变参数所带来的影响等等。如果你想看一下网络导出成onnx后,使用tensorrt的加速效果,那么请你参考PointPillars-TensorRT加速。
如果你的point_cloud_range设置比较小,voxel_size的比较大的话,那么相对应的你的database_sampler里面对于car或者person的name_to_max_num与min_num_points_pairs阈值需要相对应的进行修改。理由很简单,不同的range与划分voxel时候,获取每个pillar点云是不同的。
database_sampler { database_info_path: "/data/sets/kitti_second/kitti_dbinfos_train.pkl" sample_groups { name_to_max_num { key: "Car" value: 15 } } database_prep_steps { filter_by_min_num_points { min_num_point_pairs { key: "Car" value: 5 } } } database_prep_steps { filter_by_difficulty { removed_difficulties: [-1] } } global_random_rotation_range_per_object: [0, 0] rate: 1.0 }说实话second.pytorch中的代码比较庞大,涉及的不仅仅只是一个pointpillars算法,还有voxelnet不同功能模块的算法,所以涉及的参数变量较多,整体上理解整个的代码框架只能根据文件夹的分布来简单理解一下。所以代码都是一样,需要理解整个框架的分布功能,然后找一个算法理解整个pipeline的部分,最后进行细节逐个击破。关于点云的密集程度对pointpillars算法的影响,请参考这篇博客:Robustness of PointPillars Under Noisy Attack。最后,由于代码比较错综复杂,希望你可以早点享受该算法的效果吧。
https://arxiv.org/abs/1812.05784 https://github.com/traveller59/second.pytorch https://towardsdatascience.com/the-state-of-3d-object-detection-f65a385f67a8 https://paperswithcode.com/paper/pointpillars-fast-encoders-for-object https://deeplearn.org/arxiv/104450/pointpainting:-sequential-fusion-for-3d-object-detection http://carina.cse.lehigh.edu/PointPillars_Robustness/
