OctNet:在高分辨率下学习深度3D表示
摘要
- OctNet适用于稀疏3D数据的深度学习表示方法
- 本文的方法能够从深度和分辨率两个方面进行卷积网络的学习
- 利用输入数据的稀疏性,使用非平衡八叉树将空间按照层次分割,八叉树的叶子节点存储着 pooled feature representation
- 任务:3D object classification, orientation estimation 和 point cloud labeling
- 代码详见https://github.com/griegler/octnet
越靠近目标轮廓的体素响应越强。
1. 引言
- OctNet按照层次将3D空间分为一组非平衡八叉树,叶子节点的数量跟随着分辨率变化。八叉树中的每个叶子节点都存储着体素里包含的所有特征的pooled summary
- 本文展示了网络模型是如何对这种新的数据结构进行处理的
- 根据计算量低的特点,可以将分辨率设置的很高,高分辨率对于很多任务都是有帮助的
2. 相关工作
Dense Models
- 3D ShapeNets
- VoxNet
- auxiliary orientation loss + VoxNet
- generative models and auto-encoders
计算量太大,分辨率太低
Sparse Models
3. Octree Networks
之前有使用八叉树的方法,但是有缺点。八叉树是用指针实现的,在八叉树中访问任何一个元素都要从根节点开始向下寻找,因此复杂度与树的深度相关,当采用分辨率高的八叉树时,成本更高。
3.1 Hybrid Grid-Octree Data Structure
关键思想就是限制八叉树的最大深度,并且沿着规则的网格放置较浅的八叉树。尽管这种数据结构可能不像标准八叉树计算成本低,但是可以获得很高的压缩比。
还有一个优点,可以使用字符串表示,利GPGPU实现方法降低处理时间和效率。给定一个深度为3的shallow八叉树,使用73个bit表示整个八叉树:
- 0 bit表示根节点有没有被划分
- 1-8 bit表示是否孩子节点被划分
- 9-72 bit表示重孩子是否被划分
上述的所有不仅可以确定体素的深度,还能确定体素的大小。在不用指针指向父母和孩子节点情况下,仅需简单的数学计算就能检索:
pa ( i ) = ⌊ i − 1 8 ⌋ ch ( i ) = 8 ⋅ i + 1 \begin{aligned} \operatorname{pa}(i) &=\left\lfloor\frac{i-1}{8}\right\rfloor \\ \operatorname{ch}(i) &=8 \cdot i+1 \end{aligned} pa(i)ch(i)=⌊8i−1⌋=8⋅i+1
其中pa()表示父节点的索引,ch()表示孩子节点的索引。此外,在每个shallow tree上的所有叶子结点上还附加了一个数据容器用来存书特征向量。我们在相邻数据阵列中分配了shallow八叉树的数据。在这个阵列中与特定体素相关的偏移可以表示为:
data idx ( i ) = 8 ∑ j = 0 pa ( i ) − 1 bit ( j ) + 1 ⏟ nodes above i − ∑ j = 0 i − 1 bit ( j ) ⏟ split nodes pre i + m o d ( i − 1 , 8 ) ⏟ offset \operatorname{data} \operatorname{idx}(i)=\underbrace{8 \sum_{j=0}^{\operatorname{pa}(i)-1} \operatorname{bit}(j)+1}_{\operatorname{nodes}\operatorname{above} \operatorname{i}} -\underbrace{\sum_{j=0}^{i-1} \operatorname{bit}(j)}_{\operatorname {split}\operatorname {nodes}\operatorname {pre}\operatorname {i }} + \underbrace{mod (i-1,8)}_{\operatorname{offset }} dataidx(i)=nodesabovei 8j=0∑pa(i)−1bit(j)+1−splitnodesprei j=0∑i−1bit(j)+offset mod(i−1,8)
其中,mod 表示取模操作,bit返回的是树 bit-string 在 i i i 的值。所有shallow octree的数据阵列在训练和测试的时候都可以拼接到一个单独的邻接数据阵列中,以减少I/O。
举例:
为了方便说明,首先以四叉树为例,计算四叉树的父节点:
pa ( i ) = ⌊ i − 1 4 ⌋ \begin{aligned} \operatorname{pa}(i) &=\left\lfloor\frac{i-1}{4}\right\rfloor \end{aligned} pa(i)=⌊4i−1⌋
给定一个 bit-string 中的索引 i i i,接着计算含有数据体素的索引:
data idx ( i ) = 4 ∑ j = 0 pa ( i ) − 1 bit ( j ) + 1 ⏟ nodes above i − ∑ j = 0 i − 1 bit ( j ) ⏟ split nodes pre i + m o d ( i − 1 , 4 ) ⏟ offset \operatorname{data} \operatorname{idx}(i)=\underbrace{4 \sum_{j=0}^{\operatorname{pa}(i)-1} \operatorname{bit}(j)+1}_{\operatorname{nodes}\operatorname{above} \operatorname{i}} -\underbrace{\sum_{j=0}^{i-1} \operatorname{bit}(j)}_{\operatorname {split}\operatorname {nodes}\operatorname {pre}\operatorname {i }} + \underbrace{mod (i-1,4)}_{\operatorname{offset }} dataidx(i)=nodesabovei 4j=0∑pa(i)−1bit(j)+1−splitnodesprei j=0∑i−1bit(j)+offset mod(i−1,4)
现在给出 bit-string :1 0101 0000 1001 0000 0100 ,画出四叉图
s s s表示split node, v v v表示leaf node,我们最终就是想表示这个 v v v 的索引。
以四叉树中的51为例,计算其父节点为12
- 等式中的第一项就是要把49以前的所有node算出来,+1的意思就是把根节点加上,最后得到结果为17。
- split node是不能算作leaf node索引中的,所以要把49之前所有的split node减掉,这就是第二项做的事情。
- 最后,通过模操作算一算51和49之间的偏差是多少,为2
最终的结果索引为 17-6+2=13
3.2 Network Operations
卷积网络中最常用的三个操作:
- convolution
- pooling
- unpooling
Notation
T i , j , k T_{i, j, k} Ti,j,k表示在体素 ( i , j , k ) (i, j, k) (i,j,k)处的3D张量 T T T,假设一个hybrid gridoctree包含 D × H × W D \times H \times W D×H×W个shallow八叉树,每个八叉树的深度为3。 O [ i , j , k ] O[i, j, k] O[i,j,k]表示包含体素 ( i , j , k ) (i, j, k) (i,j,k)结构的最小单元值。在 i 1 ≠ i 2 ∨ j 1 ≠ j 2 ∨ k 1 ≠ k 2 i_{1} \neq i_{2} \vee j_{1} \neq j_{2} \vee k_{1} \neq k_{2} i1=i2∨j1=j2∨k1=k2 情况下, O [ i 1 , j 1 , k 1 ] O\left[i_{1}, j_{1}, k_{1}\right] O[i1,j1,k1] 和 O [ i 2 , j 2 , k 2 ] O\left[i_{2}, j_{2}, k_{2}\right] O[i2,j2,k2] 可能会得到相同的体素。通过 ( ⌊ i 8 ⌋ , ⌊ j 8 ⌋ , ⌊ k 8 ⌋ ) \left(\left\lfloor\frac{i}{8}\right\rfloor,\left\lfloor\frac{j}{8}\right\rfloor,\left\lfloor\frac{k}{8}\right\rfloor\right) (⌊8i⌋,⌊8j⌋,⌊8k⌋)获得shallow八叉树的索引,在最精细分辨率下的体素局部索引为 ( m o d ( i , 8 ) , m o d ( j , 8 ) , m o d ( k , 8 ) ) (\bmod (i, 8), \bmod (j, 8), \bmod (k, 8)) (mod(i,8),mod(j,8),mod(k,8))。
在给定notation后,从grid-octree O O O到具有兼容维度张量 T T T的映射为:
oc2ten : T i , j , k = O [ i , j , k ] . \text { oc2ten : } T_{i, j, k}=O[i, j, k] \text {. } oc2ten : Ti,j,k=O[i,j,k].
这里可以理解成:我们要找 ( i , j , k ) (i,j,k) (i,j,k)的值,即 ∀ cell ∈ O \forall \text{cell} \in O ∀cell∈O:
- 能包住 ( i , j , k ) (i,j,k) (i,j,k)
- 大小又是最小的cell
这个cell的值就是位置 ( i , j , k ) (i,j,k) (i,j,k)的tensor。
因此可能会有好几个不同的位置,因为都在同一个cell中,所以value一样。
逆变换为:
ten 2 o c : O [ i , j , k ] = p o o l — v o x e l s ( i ˉ , j ˉ , k ˉ ) ∈ Ω [ i , j , k ] ( T i ˉ , j ˉ , k ˉ ) \operatorname{ten} 2 \mathrm{oc}: O[i, j, k]= \underset{(\bar{i}, \bar{j}, \bar{k}) \in \Omega[i, j, k]}{\operatorname{ pool_—voxels }}\left(T_{\bar{i}, \bar{j}, \bar{k}}\right) ten2oc:O[i,j,k]=(iˉ,jˉ,kˉ)∈Ω[i,j,k]pool—voxels(Tiˉ,jˉ,kˉ)
其中, pool_voxels ( ⋅ ) (\cdot) (⋅)操作为平均池化,或是最大池化操作,将 T T T中的体素池化为包含位置 ( i , j , k ) (i, j, k) (i,j,k)的最小grid-octree,记为 Ω [ i , j , k ] \Omega[i, j, k] Ω[i,j,k]。这种池化是必要的,因为一个 O O O中单独的体素包含了 T T T中的 8 3 = 512 8^{3}=512 83=512个元素,取决于 ∣ Ω [ i , j , k ] ∣ |\Omega[i, j, k]| ∣Ω[i,j,k]∣的大小。
在给定上面两个定义的情况下,定义在3D张量上的网络操作
f
f
f可以表示为:
g
(
O
)
=
ten
2
oc
(
f
(
oc
2
ten
(
O
)
)
)
.
g(O)=\operatorname{ten} 2 \operatorname{oc}(f(\operatorname{oc} 2 \operatorname{ten}(O))) .
g(O)=ten2oc(f(oc2ten(O))).
但是,这样做的效率很低,存储稠密的张量也限制了分辨率,因此,直接在hybrid grid-octree数据结构上进行网络操作。
3.2.1 Convolution
对于单独的特征,使用带有3D卷积核 W ∈ R L × M × N W \in \mathbb{R}^{L \times M \times N} W∈RL×M×N对3D张量 T T T进行卷积可以写为:
T i , j , k out = ∑ l = 0 L − 1 ∑ m = 0 M − 1 ∑ n = 0 N − 1 W l , m , n ⋅ T i ^ , j ^ , k ^ in T_{i, j, k}^{\text {out }}=\sum_{l=0}^{L-1} \sum_{m=0}^{M-1} \sum_{n=0}^{N-1} W_{l, m, n} \cdot T_{\hat{i}, \hat{j}, \hat{k}}^{\text {in }} Ti,j,kout =l=0∑L−1m=0∑M−1n=0∑N−1Wl,m,n⋅Ti^,j^,k^in
其中 i ^ = i − l + ⌊ L / 2 ⌋ , j ^ = j − m + ⌊ M / 2 ⌋ , k ^ = k − n + ⌊ N / 2 ⌋ \hat{i}=i-l+\lfloor L / 2\rfloor, \hat{j}=j-m+\lfloor M / 2\rfloor, \hat{k}=k-n+\lfloor N / 2\rfloor i^=i−l+⌊L/2⌋,j^=j−m+⌊M/2⌋,k^=k−n+⌊N/2⌋
相似地,在grid-octree数据机构上的卷积可以表示为:
O out [ i , j , k ] = p o o l — v o x e l s ( i ˉ , j ˉ , k ˉ ) ∈ Ω [ i , j , k ] ( T i ˉ , j ˉ , k ˉ ) T i , j , k = ∑ l = 0 L − 1 ∑ m = 0 M − 1 ∑ n = 0 N − 1 W l , m , n ⋅ O i n [ i ^ , j ^ , k ^ ] \begin{aligned} O^{\text {out }}[i, j, k] &=\underset{(\bar{i}, \bar{j}, \bar{k}) \in \Omega[i, j, k]}{\operatorname{ pool_—voxels }}\left(T_{\bar{i}, \bar{j}, \bar{k}}\right)\\ T_{i, j, k} &=\sum_{l=0}^{L-1} \sum_{m=0}^{M-1} \sum_{n=0}^{N-1} W_{l, m, n} \cdot O^{\mathrm{in}}[\hat{i}, \hat{j}, \hat{k}] \end{aligned} Oout [i,j,k]Ti,j,k=(iˉ,jˉ,kˉ)∈Ω[i,j,k]pool—voxels(Tiˉ,jˉ,kˉ)=l=0∑L−1m=0∑M−1n=0∑N−1Wl,m,n⋅Oin[i^,j^,k^]
尽管该式计算产生的结果与带有 o c 2 t e n oc2ten oc2ten, t e n 2 o c ten2oc ten2oc wrapper的式子相同但是我们现在定义了一个计算更为高效的卷积算子。
对于较小的卷积核和较大的体素, T i , j , k T_{i, j, k} Ti,j,k在体素的一小部分是不变的,这是因为它的不变support O in [ i ^ , j ^ , k ^ ] O^{\text {in }}[\hat{i}, \hat{j}, \hat{k}] Oin [i^,j^,k^]。因此,我们只需要计算体素内的一次卷积,接着沿着体素表面再进行卷积,其中support发生变化,这是因为邻近体素的值发生了变化。这样计算量减少了4倍。
对 ∀ ( i , j , k ) ∈ cell Ω ( i , j , k ) \forall(i,j,k) \in \text{cell} \Omega(i,j,k) ∀(i,j,k)∈cellΩ(i,j,k)做卷积,若octree大小为8 3 ^3 3,kernel大小为3 3 ^3 3,那么需要做 8 3 ⋅ 3 3 = 13 , 824 8^3\cdot3^3=13,824 83⋅33=13,824次乘法。但是,我们可以采取更有效的做法。
(a)观察到大部分中心的值是固定的。因此,我们只需要在中心內一计算一次卷积并將結果乘以 8 3 8^3 83
(b-d)另外,我們只需要在 voxel 的 cornerr、edges 和 faces 上計算 kernel 的截斷版本。這樣的實作方法很有效率,因為我們總共只需要:
- 對中心 constant 部分進行 3 3 = 27 3^3 =27 33=27 次乘法
- 對 corners 進行 8 ⋅ 19 = 152 8 \cdot 19 =152 8⋅19=152 次乘法
- 對 edges 進行 12 ⋅ 6 ⋅ 15 = 1080 12 \cdot 6 \cdot 15 =1080 12⋅6⋅15=1080 次乘法
- 對 faces 進行 6 ⋅ 6 2 ⋅ 9 = 1944 6 \cdot 6^2 \cdot 9 =1944 6⋅62⋅9=1944 次乘法
總共產生 27 + 152 + 1080 + 1944 = 3203 27 + 152 + 1080 + 1944 = 3203 27+152+1080+1944=3203 次乘法,是本來 13,824 的 23.17%,能有效降低計算的時間複雜度。
3.2.2 Pooling
2
3
^3
3的最大池化将输入张量
T
in
T^{\text{in}}
Tin分成2
3
^3
3个不重叠的区域,并且计算每个区域内的最大值:
T
i
,
j
,
k
out
=
max
l
,
m
,
n
∈
[
0
,
1
]
(
T
2
i
+
l
,
2
j
+
m
,
2
k
+
n
in
)
,
T_{i, j, k}^{\text {out }}=\max _{l, m, n \in[0,1]}\left(T_{2 i+l, 2 j+m, 2 k+n}^{\text {in }}\right),
Ti,j,kout =l,m,n∈[0,1]max(T2i+l,2j+m,2k+nin ),
其中
T
in
∈
R
2
D
×
2
H
×
2
W
T^{\text {in }} \in \mathbb{R}^{2 D \times 2 H \times 2 W}
Tin ∈R2D×2H×2W,
T
out
∈
R
D
×
H
×
W
T^{\text {out }} \in \mathbb{R}^{D \times H \times W}
Tout ∈RD×H×W。
为了在grid-octree数据结构上进行池化操作,对于输入为 2 D × 2 H × 2 W 2 D \times 2 H \times 2 W 2D×2H×2W个shallow octree的 O in O^{\text {in }} Oin ,输出为包含 D × H × W D \times H \times W D×H×W 个shallow octree的 O out O^{\text {out }} Oout 。
O in O^{\text {in }} Oin 中的每一个体素减为一半,并且在shallow八叉树里复制了一个更深层。 O in O^{\text {in }} Oin 在第三层的体素被池化:
O out [ i , j , k ] = { O in [ 2 i , 2 j , 2 k ] if vxd ( 2 i , 2 j , 2 k ) < 3 P else P = max l , m , n ∈ [ 0 , 1 ] ( O in [ 2 i + l , 2 j + m , 2 k + n ] ) , \begin{aligned} &O^{\text {out }}[i, j, k]= \begin{cases}O^{\text {in }}[2 i, 2 j, 2 k] & \text { if } \operatorname{vxd}(2 i, 2 j, 2 k)<3 \\ P & \text { else }\end{cases} \\ &P=\max _{l, m, n \in[0,1]}\left(O^{\text {in }}[2 i+l, 2 j+m, 2 k+n]\right), \end{aligned} Oout [i,j,k]={Oin [2i,2j,2k]P if vxd(2i,2j,2k)<3 else P=l,m,n∈[0,1]max(Oin [2i+l,2j+m,2k+n]),
其中 vxd ( ⋅ ) \operatorname{vxd}(\cdot) vxd(⋅)计算在shallow octree中被索引体素的深度。
3.2.3 Unpooling —— Semantic segmentation
U-shaped network
最简单的unpooling策略是使用最近邻插值:
T
i
,
j
,
k
out
=
T
⌊
i
/
2
⌋
,
⌊
j
/
2
⌋
,
⌊
k
/
2
⌋
in
.
T_{i, j, k}^{\text {out }}=T_{\lfloor i / 2\rfloor,\lfloor j / 2\rfloor,\lfloor k / 2\rfloor}^{\text {in }} .
Ti,j,kout =T⌊i/2⌋,⌊j/2⌋,⌊k/2⌋in .
其中
T
in
∈
R
D
×
H
×
W
T^{\text {in }} \in \mathbb{R}^{D \times H \times W}
Tin ∈RD×H×W ,
T
out
∈
R
2
D
×
2
H
×
2
W
T^{\text {out }} \in \mathbb{R}^{2 D \times 2 H \times 2 W}
Tout ∈R2D×2H×2W 。
在hybrid grid-octree 数据结构上定义一个相似地操作:
O
out
[
i
,
j
,
k
]
=
O
in
[
⌊
i
/
2
⌋
,
⌊
j
/
2
⌋
,
⌊
k
/
2
⌋
]
.
O^{\text {out }}[i, j, k]=O^{\text {in }}[\lfloor i / 2\rfloor,\lfloor j / 2\rfloor,\lfloor k / 2\rfloor] \text {. }
Oout [i,j,k]=Oin [⌊i/2⌋,⌊j/2⌋,⌊k/2⌋].
该操作改变了数据结构:shallow octree的数量减少了8倍,因为在0层的深度生成了一个新的shallow octree,其他顶点的大小变为了两倍。
为了捕获细节,体素可以根据对应池化层的原始八叉树再次以高分辨率进行划分。
4. Experimental Evaluation
Task:
- 3D shape classification
- 3D orientation estimation
- semantic segmentation of 3D point clouds
Orthogonal techniques:
- data augmentation
- joint 2D/3D modeling
- ensemble learning
4.1 3D Classification
Dataset:ModelNet10
The influence of the input resolution on memory usage, runtime and classification accuracy.
Resolution: From 8 3 ^3 3 to 256 3 ^3 3 voxels
每个网络都包含几个模块,不断对半减少分辨率直到8 3 ^3 3。每个块中包含了两个卷积层(3 3 ^3 3个滤波器,步数为1)和一个最大池化层(2 3 ^3 3个滤波器,步数为2)。第一个块中特征图的数量为8,然后逐块增加6,最后一个块后面添加一个带有512个单元的全连接层,输出层为10。卷积层和第一个全连接层后面的激活函数为ReLU。
损失函数为交叉损失函数。
Epoch = 20
Batch_size=32
Optimizer=Adam
Initial learning rate=0.001,decreased by a factor of 10 after 15 epochs.
4.2. 3D Orientation Estimation
先在一组单个分类的3D形状上训练一个模型,然后预测相同类别内任意一个3D形状的方向。
更简洁的说法是,给定一个未知位姿的目标类别实例,估算与canonical pose的旋转。
类别选用ModelNet10中的chair,将它们沿着每个轴随机旋转 ± 15 ° \pm15° ±15°。使用四元数表示旋转,损失函数为欧式损失: ϕ = arccos ( 2 ⟨ q 1 , q 2 ⟩ 2 − 1 ) \phi=\arccos \left(2\left\langle q_{1}, q_{2}\right\rangle^{2}-1\right) ϕ=arccos(2⟨q1,q2⟩2−1)。
4.3 3D Semantic Segmentation
Dataset:RueMonge2014
Unpooling+U-shaped network
首先将点云映射到grid-octree结构中。如果在一个叶子节点中包含了多个点云,那就i对输入特征进行平均,并且计算真实标签的最大投票用于训练。特征便是the binary voxel occupancy, the RGB color, the normal vector and the height above ground。
Encoder:4 block,each block comprises 2 convolution layers (3 3 ^3 3filters, stride 1) followed by one max-pooling.
Decoder:4 block,each block comprises 2 convolution layers (3 3 ^3 3filters, stride 1) followed by a guided unpooling layer. 还要和encoder中相同分辨率的特征进行拼接。
Loss:Cross entropy loss
Optimizer:Adam
Learning rate:0.0001
5. 结论
- 在学习表示多视图3D重建中,处理高分辨率体素形状的能力是很重要的。