pointpillar网络结构组成
- voxelizatio(pillarization), 把3d点云划分,得到pillar,并关联pillar的坐标和点云中的点
- pfe(pillar feature extracation),通过简单的pointnet网络,提取pillar内点的特征
- scatter to bev, 把pillar的特征,重新分布到2d的image上,得到2d sudo image
- 2d backbone + detector(ssd or centernet), 2d的目标检测网络,得到中间结果
- postprocess(decode + nms),后处理中间结果,得到最终的结果
目前网上开源的部署有:
1 apollo
https://github.com/ApolloAuto/apollo/tree/master/modules/perception/lidar
2 autoware
https://github.com/Autoware-AI/core_perception/tree/master/lidar_point_pillars
3 carkuls(centerpoint Open3D框架)
https://github.com/CarkusL/CenterPoint
4 abraham(centerpoint Open3D框架)
https://github.com/Abraham423/CenterPointTensorRT
5 nvidia(openpcdet框架)
https://github.com/NVIDIA-AI-IOT/CUDA-PointPillars
https://developer.nvidia.com/zh-cn/blog/detecting-objects-in-point-clouds-with-cuda-pointpillars/
对比如上开源的部署方式:
1 apollo
如其代码,分为5个部分进行部署
Preprocess(in_points_array, in_num_points);
auto pfe_output =
pfe_net_
.forward({tensor_pillar_point_feature, tensor_num_points_per_pillar,
tensor_pillar_coors})
.toTensor();
auto scattered_feature =
scattered_net_
.forward({pfe_output, tensor_pillar_coors, scattered_batch_size})
.toTensor();
auto backbone_feature = backbone_net_.forward({scattered_feature});
auto fpn_feature = fpn_net_.forward({backbone_feature});
auto bbox_head_output = bbox_head_net_.forward({fpn_feature}).toTuple();
postprocess_cuda_ptr_->DoPostprocessCuda(
bbox_pred.data_ptr<float>(),
cls_score.data_ptr<float>(),
dir_cls_preds.data_ptr<float>(),
dev_anchor_mask_, dev_anchors_px_, dev_anchors_py_, dev_anchors_pz_,
dev_anchors_dx_, dev_anchors_dy_, dev_anchors_dz_, dev_anchors_ro_,
dev_filtered_box_, dev_filtered_score_, dev_filtered_label_,
dev_filtered_dir_, dev_box_for_nms_, dev_filter_count_, out_detections,
out_labels);
2 autoware
如其代码,部署分为5部分,apollo和autoware很类似,不知道谁参考谁的
preprocess(in_points_array, in_num_points);
std::vector<float> pfe_out = pfe_engine_ptr_->schedule(pfe_net_input_);
scatter_cuda_ptr_->doScatterCuda(host_pillar_count_[0], dev_x_coors_,
dev_y_coors_, (float*)pfe_net_output,
dev_scattered_feature_);
RPN_Net_Output rpn_out = rpn_engine_ptr_->schedule(rpn_net_input_);
postprocess_cuda_ptr_->doPostprocessCuda((float*)rpn_buffers_[0],
(float*)rpn_buffers_[1],
(float*)rpn_buffers_[2],
dev_anchor_mask_,
dev_anchors_px_,
dev_anchors_py_,
dev_anchors_pz_,
dev_anchors_dx_,
dev_anchors_dy_,
dev_anchors_dz_,
dev_anchors_ro_,
dev_filtered_box_,
dev_filtered_score_,
dev_filtered_dir_,
dev_box_for_nms_,
dev_filter_count_,
out_detections);
5 nvidia
如下为流程图和代码,分为4步,前两步为预处理,中间为TRT推理engine,最后为后处理。这里和其他的网络还是有很大不同的,中间的一步,涵盖了其他网络的pfe, scatter to bev, net infer三步。这里的关键就是,scatter to bev的这个trt的插件,把前后两个网络串联起来了。带导出模型的script。
pre_->generateVoxels((float*)points_data, points_size,
params_input_,
voxel_features_,
voxel_num_points_,
coords_);
pre_->generateFeatures(voxel_features_,
voxel_num_points_,
coords_,
params_input_,
features_input_);
trt_->doinfer(buffers);
post_->doPostprocessCuda(cls_output_, box_output_, dir_cls_output_,
bndbox_output_);
3 carkusl
如代码,基本分为3步,preprocess, net infer, postprocess, 其中net infer在onnx导出时,分为三部分,pfe_onnx, rpn_onnx,通过scatterND,连接起来了,成为一个。这一点和nvidia的类似。其中包含导出模型的script。
preprocess(points, hostPillars, hostIndex, pointNum);
bool status = context->executeV2(buffers.getDeviceBindings().data());
postprocess(buffers, predResult);
4 abraham
如流程图,分为5步,同apollo和autoware.
preprocessGPU(dev_points, devicePillars,deviceIndices,
_PMask, _PBEVIdxs, _PPointNumAssigned, _BEVVoxelIdx, _VPointSum, _VRange, _VPointNum,
pointNum, POINT_DIM);
bool status = context->executeV2(buffers.getDeviceBindings().data());
scatter_cuda_ptr_->doScatterCuda(MAX_PILLARS, deviceIndices,static_cast<float*>(buffers.getDeviceBuffer(mParams.pfeOutputTensorNames[0])),
// static_cast<float*>(buffersRPN.getDeviceBuffer(mParamsRPN.inputTensorNames[0]) )) ;
dev_scattered_feature_);
status = contextRPN->executeV2( buffersRPN.getDeviceBindings().data());
postprocessGPU(buffersRPN, predResult, mParams.rpnOutputTensorNames,
dev_score_indexs_,
mask_cpu,
remv_cpu,
host_score_indexs_,
host_keep_data_,
host_boxes_,
host_label_);
结论
可以分为5步或者3步进行,其中的三部分,voxelization, scatter to bev, post process,有现成的c++,cuda实现,可以直接用。
pfe/scatternd/rpn, 这三部分通过pytorch->onnx->trt engine,也有相关的转化代码,可以参考,然后修改,生成自己的模型导出script。 这部分export模型的代码,应该通pytorch官方的模型导出的方法类似。
最后把分解的几部分的模型,串联起来,导入模型,运行engine。
最近学到的心得:
- torch保存模型,只会保存网络中带有参数的那部分参数
- 为了提速,一般会把网络预处理,后处理,中间特殊结构,单独拎出来,c++,cuda直接编程处理
- 如何拿出网络中的特殊结构,有两种办法:在pytorch export到onnx模型时,分部分建立网络,分部分导出onnx模型;导出完整的onnx模型后,再对onnx模型进行处理,删减,替换(替换的化,需要trt engine能够处理,特殊的得自己添加trt的plugin)
更进一步
4. 跑通了pointpillar的网络模型的导出,先完整的导出模型,然后再对onnx模型进行删减,替换,修改;
5. 安装tensorrt,并完成编译,可以运行demo
6. 为了转化centerpoint,更改点如下:
a. rpn head,需要优化,这部分,在导出onnx的时候,可以直接导出
b. post process,需要重新更改一下,之前的pointpillar是anchor based的输出进行decode,现在要基于center based的方法进行decode。