上一篇文章《动手学无人驾驶(3):基于激光雷达3D多目标追踪》介绍了3D多目标追踪,多目标追踪里使用的传感器数据为激光雷达Lidar检测到的数据,本文就介绍如何基于激光雷达点云数据进行目标检测。
本文介绍论文:《PointRCNN: 3D Object Proposal Generation and Detection from Point Cloud》
Github项目地址:https://github.com/sshaoshuai/PointRCNN
KITTI数据集百度云下载地址:https://pan.baidu.com/s/1qDzslt5mwI_JkEbumGJSUA 提取码: ct4q
目录
1.KITTI数据集
KITTI目标检测数据集(http://www.cvlibs.net/datasets/kitti/eval_object.php?obj_benchmark=3d)中共包含7481张训练图片和7518张测试图片,以及与之相对应的点云数据和label以及calib文件。这里以训练集编号为000008的场景为例介绍KITTI数据集中calib和label文件所含信息。
(training/image_2/000008.png)
Calib文件:在KITTI数据采集过程中使用到了摄像头和激光雷达,采集到的数据坐标分别为摄像头坐标和激光雷达坐标下的测量值,这时就需要calib文件将摄像头与激光雷达进行标定,calib文件通过txt格式来保存。
velodyne:velodyne中存储着激光雷达点云采集到数据,数据以2进制格式存储(.bin),点云数据存储格式为(N,4)。N为激光线束反射点个数,4代表着:(x,y,z,r),分别返回在3个坐标轴上的位置和反射率。
label文件:label文件为标注数据,以txt格式保存,000008.txt中标注的object内容如下:
Car 0.88 3 -0.69 0.00 192.37 402.31 374.00 1.60 1.57 3.23 -2.70 1.74 3.68 -1.29
Car 0.00 1 2.04 334.85 178.94 624.50 372.04 1.57 1.50 3.68 -1.17 1.65 7.86 1.90
Car 0.34 3 -1.84 937.29 197.39 1241.00 374.00 1.39 1.44 3.08 3.81 1.64 6.15 -1.31
Car 0.00 1 -1.33 597.59 176.18 720.90 261.14 1.47 1.60 3.66 1.07 1.55 14.44 -1.25
Car 0.00 0 1.74 741.18 168.83 792.25 208.43 1.70 1.63 4.08 7.24 1.55 33.20 1.95
Car 0.00 0 -1.65 884.52 178.31 956.41 240.18 1.59 1.59 2.47 8.48 1.75 19 -1.25
DontCare -1 -1 -10 800.38 163.67 825.45 184.07 -1 -1 -1 -1000 -1000 -1000 -10
DontCare -1 -1 -10 859.58 172.34 886.26 194.51 -1 -1 -1 -1000 -1000 -1000 -10
DontCare -1 -1 -10 801.81 163.96 825.20 183.59 -1 -1 -1 -1000 -1000 -1000 -10
DontCare -1 -1 -10 826.87 162.28 845.84 178.86 -1 -1 -1 -1000 -1000 -1000 -10
关于label文件的理解可以参考下面的代码:
-
class Object3d(object):
-
''' 3d object label '''
-
def __init__(self, label_file_line):
-
data = label_file_line.split(
' ')
-
data[
1:] = [float(x)
for x
in data[
1:]]
-
-
# extract label, truncation, occlusion
-
self.type = data[
0]
# 'Car', 'Pedestrian', ...
-
self.truncation = data[
1]
# truncated pixel ratio [0..1]
-
self.occlusion = int(data[
2])
# 0=visible, 1=partly occluded, 2=fully occluded, 3=unknown
-
self.alpha = data[
3]
# object observation angle [-pi..pi]
-
-
# extract 2d bounding box in 0-based coordinates
-
self.xmin = data[
4]
# left
-
self.ymin = data[
5]
# top
-
self.xmax = data[
6]
# right
-
self.ymax = data[
7]
# bottom
-
self.box2d = np.array([self.xmin,self.ymin,self.xmax,self.ymax])
-
-
# extract 3d bounding box information
-
self.h = data[
8]
# box height
-
self.w = data[
9]
# box width
-
self.l = data[
10]
# box length (in meters)
-
self.t = (data[
11],data[
12],data[
13])
# location (x,y,z) in camera coord.
-
self.ry = data[
14]
# yaw angle (around Y-axis in camera coordinates) [-pi..pi]
-
-
def print_object(self):
-
print(
'Type, truncation, occlusion, alpha: %s, %d, %d, %f' % \
-
(self.type, self.truncation, self.occlusion, self.alpha))
-
print(
'2d bbox (x0,y0,x1,y1): %f, %f, %f, %f' % \
-
(self.xmin, self.ymin, self.xmax, self.ymax))
-
print(
'3d bbox h,w,l: %f, %f, %f' % \
-
(self.h, self.w, self.l))
-
print(
'3d bbox location, ry: (%f, %f, %f), %f' % \
-
(self.t[
0],self.t[
1],self.t[
2],self.ry))
2.PointRCNN
3D目标检测模型PointRCNN借鉴了PointNet++和RCNN的思想,提出了自底向上的生成和调整候选检测区域的算法,网络结构如下图所示:
PointRCNN的网络结构分为两个阶段:第一阶段自底向上生成3D候选预测框;第二阶段在规范坐标中对候选预测框进行搜索和微调,得到更为精确的预测框作为检测结果。
第一阶段:对3D点云数据进行语义分割和前背景划分,生成候选预测框,有如下三个关键步骤:
点云特征提取:通过PointNet++对点云数据进行编码和解码,提取点云特征向量。
前景点分割:根据提取的点云特征向量,使用focal loss区分前景点和背景点。focal loss能有效地平衡前景点和背景点比例失衡问题,从而得到更为准确的分类效果。
生成候选框:采用候选框箱模型(bin)的方法,将前背景点分割信息生成预测候选框。
举例来说,将候选框定义为参数(x,y,z,h,w,l,θ)表征的空间中的箱体,其中(x,y,z)为箱体中心坐标,( h,w,l)为箱体在中心坐标方向上的大小,θ为鸟瞰视角上(y方向从上往下看)箱体在x-z平面的角度。
bin的执行方式为:先根据前景点的分割信息粗分其所属的箱体;再在箱体内部对其做回归,得到箱体参数作为预测框;最后对预测框做NMS(Non-Max Suppress,非极大值抑制),得到最终预测候选框。
第二阶段:在规范坐标中微调候选预测框,获得最终的检测结果,有如下四个关键部分:
区域池化:对候选框内每个点的特征进行池化。
坐标转化:为了更好地获取局部信息,需要将多个候选区域中的前景点坐标(同一个坐标系)转化为局域坐标系中的规范坐标(以预测框为中心点的多个坐标系),如下图所示:
特征编码:将规范坐标时丢失的深度信息、规范后的坐标信息、前后背景语义信息等经过多层感知机提取特征,作为每个点的编码特征。
微调预测框:经过上一步编码后的特征,经PointNet++网络进行特征提取,最后回归得到局部坐标系下的3D预测框。
PointRCNN网络代码如下:
-
class PointRCNN(nn.Module):
-
def __init__(self, num_classes, use_xyz=True, mode='TRAIN'):
-
super().__init__()
-
-
assert cfg.RPN.ENABLED
or cfg.RCNN.ENABLED
-
-
if cfg.RPN.ENABLED:
-
self.rpn = RPN(use_xyz=use_xyz, mode=mode)
-
-
if cfg.RCNN.ENABLED:
-
rcnn_input_channels =
128
# channels of rpn features
-
if cfg.RCNN.BACKBONE ==
'pointnet':
-
self.rcnn_net = RCNNNet(num_classes=num_classes, input_channels=rcnn_input_channels, use_xyz=use_xyz)
-
elif cfg.RCNN.BACKBONE ==
'pointsift':
-
pass
-
else:
-
raise NotImplementedError
-
-
def forward(self, input_data):
-
if cfg.RPN.ENABLED:
-
output = {}
-
# rpn inference
-
with torch.set_grad_enabled((
not cfg.RPN.FIXED)
and self.training):
-
if cfg.RPN.FIXED:
-
self.rpn.eval()
-
rpn_output = self.rpn(input_data)
-
output.update(rpn_output)
-
-
# rcnn inference
-
if cfg.RCNN.ENABLED:
-
with torch.no_grad():
-
rpn_cls, rpn_reg = rpn_output[
'rpn_cls'], rpn_output[
'rpn_reg']
-
backbone_xyz, backbone_features = rpn_output[
'backbone_xyz'], rpn_output[
'backbone_features']
-
-
rpn_scores_raw = rpn_cls[:, :,
0]
-
rpn_scores_norm = torch.sigmoid(rpn_scores_raw)
-
seg_mask = (rpn_scores_norm > cfg.RPN.SCORE_THRESH).float()
-
pts_depth = torch.norm(backbone_xyz, p=
2, dim=
2)
-
-
# proposal layer
-
rois, roi_scores_raw = self.rpn.proposal_layer(rpn_scores_raw, rpn_reg, backbone_xyz)
# (B, M, 7)
-
-
output[
'rois'] = rois
-
output[
'roi_scores_raw'] = roi_scores_raw
-
output[
'seg_result'] = seg_mask
-
-
rcnn_input_info = {
'rpn_xyz': backbone_xyz,
-
'rpn_features': backbone_features.permute((
0,
2,
1)),
-
'seg_mask': seg_mask,
-
'roi_boxes3d': rois,
-
'pts_depth': pts_depth}
-
if self.training:
-
rcnn_input_info[
'gt_boxes3d'] = input_data[
'gt_boxes3d']
-
-
rcnn_output = self.rcnn_net(rcnn_input_info)
-
output.update(rcnn_output)
-
-
elif cfg.RCNN.ENABLED:
-
output = self.rcnn_net(input_data)
-
else:
-
raise NotImplementedError
-
-
return output
3.目标检测结果
介绍完KITTI数据集和PointRCNN模型后,现在用作者预训练好的模型进行目标预测。
1)首先是准备数据,从百度网盘里下载KITTI数据后按如下方式排放数据:
-
PointRCNN
-
├──
data
-
│ ├── KITTI
-
│ │ ├── ImageSets
-
│ │ ├──
object
-
│ │ │ ├──training
-
│ │ │ ├──calib & velodyne & label_2 & image_2
-
│ │ │ ├──testing
-
│ │ │ ├──calib & velodyne & image_2
-
├── lib
-
├── pointnet2_lib
-
├── tools
2)将预训练好的模型放入/tools文件夹下,执行如下命令,此时会对验证集进行预测:
python eval_rcnn.py --cfg_file cfgs/default.yaml --ckpt PointRCNN.pth --batch_size 1 --eval_mode rcnn --set RPN.LOC_XZ_FINE False
预测结果如下,这里预测的平均准确率略小于原作者预测的。
# 原作者预测结果 Car AP@ 0. 70, 0.70, 0.70: bbox AP: 96.91, 89.53, 88.74 bev AP: 90.21, 87.89, 85.51 3d AP: 89.19, 78.85, 77.91 aos AP: 96.90, 89.41, 88.54
# 使用预训练模型输出的结果 Car AP@ 0. 70, 0.70, 0.70: bbox AP: 90.3697, 78.9661, 76.0439 bev AP: 88.8698, 75.5572, 69.7311 3d AP: 83.3413, 66.9504, 60.1443 aos AP: 90.30, 78.51, 75.45
参考资料
飞桨火力全开,重磅上线3D模型:PointNet++、PointRCNN!
PointRCNN地址:https://github.com/sshaoshuai/PointRCNN
论文:《PointRCNN: 3D Object Proposal Generation and Detection from Point Cloud》
转载:https://blog.csdn.net/cg129054036/article/details/105372783