3D Object Detector PointRCNN

概述

PointRCNN 将目标检测任务分为两个阶段: 第一阶段:借助点云分割为前景点和背景,并以自下而上的方式直接从点云生成少量高质量的目标候选框; 第二阶段:再对候选目标框进行优化调整,细化目标框的边界。

网络框图

第一阶段

首先从原始点云数据中通过神经网络生成高度可信的3D边界框,该操作进一步缩小了3D边界框的搜索范围,让第二阶段重点强化这些候选框的细节。

backbone

Backbone网络

使用PointNet++(MSG)作为骨干网络,输出逐点的特征。添加一个分割头用于估计前景掩码,一个Box回归头用于生成3D候选框。

前景点分割网络

由于对室外场景来说,前景点的数量通常远小于背景点的数量,因此分割网络使用 Focus Loss(焦点损失) 函数来处理类别不平衡的问题。

12

\[\mathcal{L}_{focal} = -\alpha_{t}(1-p_{t})^{\lambda}\log(p_{t})\] 其中, \[ p_{t} = \begin{cases} p & \text{前景} \\ 1-p & \text{背景} \end{cases} \] 在训练点云分割时,保持原论文的默认参数,\(\alpha_{t}=0.25\)\(\lambda=2\)

基于bin的3D Box回归网络

为了约束生成的3D候选框,提出了基于bin的回归损失来评估对象的3D边界框。使用该方法模型训练时可以更快收敛。

基于Bin的定位图示

bin可以直观理解为刻度尺上的一个刻度,相对于直接使用回归任务对x和z进行学习,不如将这些变量视为一个整数(分类任务)和一个浮点数(回归任务)分别学习效果好。 其中,目标对象中心点位置可分解为如下表示: \[bin_x^{(p)}=\lfloor \frac{x^p-x^{(p)}+\mathcal{S}}{\delta} \rfloor, bin_z^{(p)}=\lfloor \frac{z^p-z^{(p)}+\mathcal{S}}{\delta} \rfloor\] \[{res_u^{(p)} \atop u\in\set{x,z}}=\frac{1}{C}\big(u^p-u^{(p)}+\mathcal{S}-(bin_u^{(p)}*\delta+\frac{\delta}{2})\big)\] \[res_y^{(p)}=y^p-y^{(p)}\] 其中,

  1. \((x^{(p)},y^{(p)},z^{(p)})\):表示受关注的前景点坐标;
  2. \((x^p,y^p,z^p)\):表示前景点对应对象的中心坐标;
  3. \(bin_{x}^{(p)}\)\(bin_z^{(p)}\):表示沿\(X\)\(Z\)轴分配的Ground-Truthbin;
  4. \(res_{x}^{(p)}\)\(res_z^{(p)}\):表示在被分配的bin中进一步细化位置的Ground-Truth残差;
  5. \(\mathcal{C}\):表示bin长度的标准化系数,即归一化到[-1,1]区间,原文中\(\mathcal{C}=\delta\)

表达式推导

考虑x轴方向的表示\(bin_x^{(p)}=\lfloor \frac{x^p-x^{(p)}+\mathcal{S}}{\delta} \rfloor\) 则量化后的中心点相对位置表示如下: \[\lfloor x^p-x^{(p)} \rfloor = bin_x^{(p)}*\delta-\mathcal{S}\] 实际中心点的相对位置为\(x^p-x^{(p)}\) 则残差可表示为 \[res_x^{(p)} = x^p-x^{(p)} - \lfloor x^p-x^{(p)} \rfloor\] 代入得 \[res_x^{(p)} = x^p-x^{(p)} - bin_x^{(p)}*\delta+\mathcal{S} \in [0,\delta)\] 由于\(x^p-x^{(p)} \geqq \lfloor x^p-x^{(p)} \rfloor\),故\(res_x^{(p)} \geqq 0\) 为了方便计算,将数据分布限制在正负区间,故对残差减去\(\frac{\delta}{2}\)\[res_x^{(p)} = x^p-x^{(p)}+\mathcal{S} -(bin_x^{(p)}*\delta+\frac{\delta}{2}) \in [-\frac{\delta}{2},\frac{\delta}{2})\] 考虑到超参数\(\delta\)的范围是不定的,需对残差输出做标准化处理,即 \[res_x^{(p)} = \frac{1}{\delta}\big(x^p-x^{(p)}+\mathcal{S}-bin_x^{(p)}*\delta+\frac{\delta}{2})\big) \in [-\frac{1}{2},\frac{1}{2})\]

Ground-Truth 计算

参考官方代码库参数设置,\(S=3\),\(\delta=0.5\),假设真实的目标中心相对前景点的坐标为\((2.125,1.375)\),则对应的bin和res计算如下: X轴: \[bin_x^{(p)}=\lfloor\frac{2.125-0+3}{0.5}\rfloor=10\] \[res_x^{(p)}=\frac{1}{0.5}(2.125-0+3-(10*0.5+0.25))=-0.25\] 同理Z轴计算如下: \[bin_z^{(p)}=\lfloor\frac{1.375-0+3}{0.5}\rfloor=8\] \[res_z^{(p)}=\frac{1}{0.5}(1.375-0+3-(8*0.5+0.25))=0.25\]

回归计算图示

方位角计算

第一阶段

在第一阶段,考虑方位角的表示,将\(2\pi\)划分为\(n\)个bin,原文中\(n=12\),则\(\delta=\frac{\pi}{6}\)\(S=0\),故 \[ bin_{\theta}^{(p)}=\lfloor \frac{\theta^p-\theta^{(p)}+S}{\delta} \rfloor \] 量化方位角位角表示为\(\lfloor \theta^p-\theta^{(p)} \rfloor=bin_{\theta}^{(p)}*\delta - \mathcal{S}\),原始方位角偏差表示为\({\theta}^p-{\theta}^{(p)}\) 故残差可表示为 \[ res_{\theta}^{(p)}=\theta^p-\theta^{(p)} - \lfloor \frac{\theta^p-\theta^{(p)}}{\delta} \rfloor \] 代入得 \[ res_{\theta}^{(p)}=\theta^p-\theta^{(p)}+\mathcal{S}-bin_{\theta}^{(p)}*\delta \in [0,\delta) \] 减去偏执\(\frac{\delta}{2}\),保证数据正负分布 \[res_{\theta}^{(p)}=\theta^p-\theta^{(p)}+\mathcal{S}-bin_{\theta}^{(p)}*\delta-\frac{\delta}{2} \in [-\frac{\delta}{2},\frac{\delta}{2})\] 标准化处理得,除以系数\(\frac{\delta}{2}\)\[{res_{\theta}^{(p)}}=\frac{2}{\delta}\big(\theta^p-\theta^{(p)}+\mathcal{S}-(bin_x^{(p)}*\delta+\frac{\delta}{2})\big)\in[-1,1)\]

第二阶段

方位角示图

在第二阶段,进一步细化时,考虑方位角的表示,将\(\frac{\pi}{2}\)划分为\(n\)个bin,原文中\(n=12\),则\(\delta=\frac{\pi}{24}\)\(S=\frac{\pi}{4}\),故 \[bin_{\theta}^{(p)}=\lfloor \frac{\theta^p-\theta^{(p)}+S}{\delta} \rfloor\] 量化方位角位角表示为\(\lfloor \theta^p-\theta^{(p)} \rfloor=bin_{\theta}^{(p)}*\delta - \mathcal{S}\),原始方位角偏差表示为\({\theta}^p-{\theta}^{(p)}\) 故残差可表示为 \[res_{\theta}^{(p)}=\theta^p-\theta^{(p)} - \lfloor \frac{\theta^p-\theta^{(p)}}{\delta} \rfloor\] 代入得 \[res_{\theta}^{(p)}=\theta^p-\theta^{(p)}+\mathcal{S}-bin_{\theta}^{(p)}*\delta \in [0,\delta)\] 减去偏执\(\frac{\delta}{2}\),保证数据正负分布 \[res_{\theta}^{(p)}=\theta^p-\theta^{(p)}+\mathcal{S}-bin_{\theta}^{(p)}*\delta-\frac{\delta}{2} \in [-\frac{\delta}{2},\frac{\delta}{2})\] 标准化处理得,除以系数\(\frac{\delta}{2}\)\[{res_{\theta}^{(p)}}=\frac{2}{\delta}\big(\theta^p-\theta^{(p)}+\mathcal{S}-(bin_x^{(p)}*\delta+\frac{\delta}{2})\big)\in[-1,1)\]

Box回归流程

76 channel regression

如下图所示,计算完真值bin和res后,作者将bin视为分类任务进行学习,也就是将bin看作是一个个类别(共12类),将res视为回归任务。

RPN-ProposalLayer

推理阶段,对于基于bin预测的参数\(x,z,\theta\),首先选择预测置信度最高的bin中心。 整体3D边界框回归损失\(\mathcal{L}_{reg}\)表示如下:

  • 基于bin的回归 \[\mathcal{L}_{bin}^{(p)}=\sum_{u\in\set{x,z,\theta}}{\mathcal{F}_{cls}(\widehat{bin}_u^{(p)},bin_u^{(p)}) + \mathcal{F}_{reg}(\widehat{res}_u^{(p)},res_u^{(p)})}\]
  • 直接回归 \[\mathcal{L}_{res}^{(p)}=\sum_{v\in\set{y,h,w,l}}{\mathcal{F}_{cls}(\widehat{res}_v^{(p)},res_v^{(p)})}\]
  • 总体回归 \[\mathcal{L}_{reg}=\frac{1}{N_{pos}}\sum_{p\in{pos}}{(\mathcal{L}_{bin}^{(p)}+\mathcal{L}_{res}^{(p)})}\] 其中, \(N_{pos}\):表示前景点的数量; \(\widehat{bin}_u^{(p)} \text{和} \widehat{res}_u^{(p)}\):表示预测的bin分配和前景点p的残差; \({bin}_u^{(p)} \text{和} {res}_u^{(p)}\):表示计算的bin和res目标真值; \(\mathcal{F}_{cls}\):表示交叉熵分类损失; \(\mathcal{F}_{reg}\):表示平滑L1损失;

删除冗余Proposal

就算每个前景点预测一个proposal Box,仍然会输出很多Box,所以下一步就是如何去除冗余的预测框数量。

Proposal Box

基于距离的NMS

基于鸟瞰视图的IoU进行NMS,以生成少量高质量的proposals。

  • 训练阶段 使用IoU=0.85阈值做NMS计算,保留300个proposal来训练第二阶段子网络。
  • 推理阶段 使用IoU=0.80阈值做NMS计算,保留前100个用于细化第二阶段子网络。

相关代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
def decode_bbox_target(roi_box3d, pred_reg, loc_scope, loc_bin_size, num_head_bin, anchor_size,
get_xz_fine=True, get_y_by_bin=False, loc_y_scope=0.5, loc_y_bin_size=0.25, get_ry_fine=False):
"""
:param roi_box3d: (N, 7)
:param pred_reg: (N, C)
:param loc_scope:
:param loc_bin_size:
:param num_head_bin:
:param anchor_size:
:param get_xz_fine:
:param get_y_by_bin:
:param loc_y_scope:
:param loc_y_bin_size:
:param get_ry_fine:
:return:
"""
anchor_size = anchor_size.to(roi_box3d.get_device())
per_loc_bin_num = int(loc_scope / loc_bin_size) * 2
loc_y_bin_num = int(loc_y_scope / loc_y_bin_size) * 2

# recover xz localization
x_bin_l, x_bin_r = 0, per_loc_bin_num
z_bin_l, z_bin_r = per_loc_bin_num, per_loc_bin_num * 2
start_offset = z_bin_r

x_bin = torch.argmax(pred_reg[:, x_bin_l: x_bin_r], dim=1)
z_bin = torch.argmax(pred_reg[:, z_bin_l: z_bin_r], dim=1)

pos_x = x_bin.float() * loc_bin_size + loc_bin_size / 2 - loc_scope
pos_z = z_bin.float() * loc_bin_size + loc_bin_size / 2 - loc_scope

if get_xz_fine:
x_res_l, x_res_r = per_loc_bin_num * 2, per_loc_bin_num * 3
z_res_l, z_res_r = per_loc_bin_num * 3, per_loc_bin_num * 4
start_offset = z_res_r

x_res_norm = torch.gather(pred_reg[:, x_res_l: x_res_r], dim=1, index=x_bin.unsqueeze(dim=1)).squeeze(dim=1)
z_res_norm = torch.gather(pred_reg[:, z_res_l: z_res_r], dim=1, index=z_bin.unsqueeze(dim=1)).squeeze(dim=1)
x_res = x_res_norm * loc_bin_size
z_res = z_res_norm * loc_bin_size

pos_x += x_res
pos_z += z_res

# recover y localization
if get_y_by_bin:
y_bin_l, y_bin_r = start_offset, start_offset + loc_y_bin_num
y_res_l, y_res_r = y_bin_r, y_bin_r + loc_y_bin_num
start_offset = y_res_r

y_bin = torch.argmax(pred_reg[:, y_bin_l: y_bin_r], dim=1)
y_res_norm = torch.gather(pred_reg[:, y_res_l: y_res_r], dim=1, index=y_bin.unsqueeze(dim=1)).squeeze(dim=1)
y_res = y_res_norm * loc_y_bin_size
pos_y = y_bin.float() * loc_y_bin_size + loc_y_bin_size / 2 - loc_y_scope + y_res
pos_y = pos_y + roi_box3d[:, 1]
else:
y_offset_l, y_offset_r = start_offset, start_offset + 1
start_offset = y_offset_r

pos_y = roi_box3d[:, 1] + pred_reg[:, y_offset_l]

# recover ry rotation
ry_bin_l, ry_bin_r = start_offset, start_offset + num_head_bin
ry_res_l, ry_res_r = ry_bin_r, ry_bin_r + num_head_bin

ry_bin = torch.argmax(pred_reg[:, ry_bin_l: ry_bin_r], dim=1)
ry_res_norm = torch.gather(pred_reg[:, ry_res_l: ry_res_r], dim=1, index=ry_bin.unsqueeze(dim=1)).squeeze(dim=1)
if get_ry_fine:
# divide pi/2 into several bins
angle_per_class = (np.pi / 2) / num_head_bin
ry_res = ry_res_norm * (angle_per_class / 2)
ry = (ry_bin.float() * angle_per_class + angle_per_class / 2) + ry_res - np.pi / 4
else:
angle_per_class = (2 * np.pi) / num_head_bin
ry_res = ry_res_norm * (angle_per_class / 2)

# bin_center is (0, 30, 60, 90, 120, ..., 270, 300, 330)
ry = (ry_bin.float() * angle_per_class + ry_res) % (2 * np.pi)
ry[ry > np.pi] -= 2 * np.pi

# recover size
size_res_l, size_res_r = ry_res_r, ry_res_r + 3
assert size_res_r == pred_reg.shape[1]

size_res_norm = pred_reg[:, size_res_l: size_res_r]
hwl = size_res_norm * anchor_size + anchor_size

# shift to original coords
roi_center = roi_box3d[:, 0:3]
shift_ret_box3d = torch.cat((pos_x.view(-1, 1), pos_y.view(-1, 1), pos_z.view(-1, 1), hwl, ry.view(-1, 1)), dim=1)
ret_box3d = shift_ret_box3d
if roi_box3d.shape[1] == 7:
roi_ry = roi_box3d[:, 6]
ret_box3d = rotate_pc_along_y_torch(shift_ret_box3d, - roi_ry)
ret_box3d[:, 6] += roi_ry
ret_box3d[:, [0, 2]] += roi_center[:, [0, 2]]

return ret_box3d

第二阶段

在获得第一阶段生成的一系列3D边界框后,我们需要进一步优化Box的位置和朝向细节。为了学习每个proposal的局部特征,作者提出了根据每个3D预选框的位置,从第一阶段汇集3D点(xyz, depth, intensity)和相应的点特征,便于后续更好得进行框得细节优化。

区域池化

扩大候选框范围

如下图所示,将候选框得范围扩大一个常数\(\eta\),表达如下:

  • 第一阶段生成的候选框 \[b_i=(x_i,y_i,z_i,h_i,w_i,l_i,\theta_i)\]
  • 扩大\(\eta\)后的候选框 \[b_i^e=(x_i, y_i, z_i, h_i+\eta, w_i+\eta, l_i+\eta, \theta_i)\]
扩大候选框
扩大范围对性能的影响

特征聚合(ROI_Pooling)

ROI Pooling and transformation

对扩大后的候选框内的所有点进行特征聚合,主要包括:

  1. 原始点云坐标\((x,y,z)\)
  2. 激光反射强度\(intensity\)
  3. 原始点云深度信息(d);
  4. 原始点的类别信息(前景点,背景点)m;
  5. 第一阶段由PointNet++网络生成的\(C\)维特征向量;

标准坐标变换

标准坐标系统的定义:

  1. 坐标原点是候选框的中心;
  2. X和Z轴近似与地面平行,X轴指向候选框的朝向;
  3. Y轴垂直向下;
标准坐标变换

局部特征

尽管标准坐标变换能够提升局部空间特征学习的稳定性,但却不可避免得丢失了每个物体得深度信息。例如,由于激光雷达传感器的角度扫描分辨率固定,远处物体的点通常要比附近物体少得多。为了补偿丢失的深度信息,作者引入了点到传感器的距离信息,并加入到点的特征中: \[d^{(p)}=\sqrt{(x^{(p)})^2,(y^{(p)})^2,(z^{(p)})^2 }\]

候选框细化的特征学习

如下图所示,通过结合变换后的局部空间点\(\tilde{p}\)和以及他们的来自第一阶段的全局语义特征\(f^{(p)}\),来进一步细化框和置信度。

feature merge

进一步获得用于置信度分类和框细化的判别性特征向量。

SA Box Reg

候选框细化损失

作者继续采用基于bin的回归损失来细化候选框,如果真实框的3D IoU大于0.55,则将其分配给3D候选框用以框的细化学习。同时,3D候选框和相应的3D真实框转换到标准坐标系统下,即 - 3D proposal Box \[b_{i}=(x_{i},y_{i},z_{i},h_{i},w_{i},l_{i},\theta{i})\] - 3D ground-truth Box \[b_{i}=(x_{i}^{gt},y_{i}^{gt},z_{i}^{gt},h_{i}^{gt},w_{i}^{gt},l_{i}^{gt},\theta{i}^{gt})\] 相应坐标变换后为: - 3D proposal Box(CT) \[\tilde{b_{i}}=(0,0,0,h_{i},w_{i},l_{i},0)\] - 3D ground-truth Box(CT) \[\tilde{b_{i}}=(x_{i}^{gt}-x_i,y_{i}^{gt}-y_i,z_{i}^{gt}-z_i,h_{i}^{gt},w_{i}^{gt},l_{i}^{gt},\theta{i}^{gt}-\theta_i)\]\(i\)个候选框的中心位置训练目标\((bin_{\vartriangle{x}}^i,bin_{\vartriangle{z}}^i,res_{\vartriangle{x}}^i,res_{\vartriangle{z}}^i,res_{\vartriangle{y}}^i)\)设置与之前的相同,除了使用较小的搜索范围\(\mathcal{S}\)来细化3D候选框的位置。至于3D Box的大小任然直接回归残差\((res_{\vartriangle{h}}^i,res_{\vartriangle{w}}^i,res_{\vartriangle{l}}^i)\)

细化损失

\[\mathcal{L}_{refine}=\frac{1}{\lVert\mathcal{B}\rVert}\sum_{i \in \mathcal{B}}{\mathcal{F}_{cls}(prob_i,label_i)} + \frac{1}{\lVert\mathcal{B_{pos}}\rVert}\sum_{i \in \mathcal{B_{pos}}}{(\tilde{\mathcal{L}}_{bin}^{i}+\tilde{\mathcal{L}}_{res}^{i})}\] 其中 \(\mathcal{B}\):第一阶段的候选框集合; \(\mathcal{B_{pos}}\):保留用于回归的正候选框;

最后基于鸟瞰视角的定向NMS,其IoU阈值=0.01,去除重叠的边界框。

实验数据

参考

  1. PointRCNN: 3D Object Proposal Generation and Detection from Point Cloud