一、MinkowskiEngine简介

第一次见到MinkowskiEngine,应该是在两个月之前了,当时也没有去留意这个库。最近读了一些点云的论文,发现还是有不少论文的源码是基于MinkowskiEngine的,包括PointContrast(ECCV 2020),DGR(Deep Global Registration, CVPR 2020),Learning Multiview 3D Point Cloud Registration(CVPR 2020)和FCGF(Fully Convolutional Geometric Features, ICCV 2019)等,所以决定去了解一下这个库。

MinkowskiEngine是稀疏张量自动微分库,致力于高维空间稀疏数据的操作。它支持所有的神经网络层,如Conv, Pooling, Unpooling和广播操作。基于MinkowskiEngine,可以实现点云的分割、分类、重建、补全、检测等任务。

MinkowskiEngine是在4D Spatio-Temporal ConvNets: Minkowski Convolutional Neural Networks[CVPR 2019]被提出的,主页,源码。目前已更新到v0.5版本,且正处于更新中。

二、Minkowski Convolution的定义

  • 点云数据的表示

    MinkowskiEngine把点云表示成两部分: 坐标矩阵 C C C和特征矩阵 F F F。

    C = [ x 1 y 1 z 1 b 1 : : x N y N z N b N ] C = \left[ \begin{matrix} x_1 & y_1 & z_1 & b_1\\ & : & : & \\ x_N & y_N & z_N & b_N \end{matrix} \right] C=⎣⎡​x1​xN​​y1​:yN​​z1​:zN​​b1​bN​​⎦⎤​ 和 F = [ f 1 T : f N T ] F = \left[ \begin{matrix} f_1^T \\ : \\ f_N^T \end{matrix} \right] F=⎣⎡​f1T​:fNT​​⎦⎤​

    其中 ( x i , y i , z i ) (x_i, y_i, z_i) (xi​,yi​,zi​)表示点云的坐标, b i b_i bi​表示 ( x i , y i , z i ) (x_i, y_i, z_i) (xi​,yi​,zi​)属于batch中的哪个点云(MinkowskiEngine也是把点云组织成batch进行训练), N N N表示1个batch中所有点的数量, f i T f_i^T fiT​表示第 i i i个点的特征,可以是1维或者3维或者其它维度的。

    这样的表示,相比于3D卷积(X, Y, Z, D)的表示,可以节省空间。[N << XYZ -> N * 4 + N*D << XYZD, << 表示远小于]

  • 常规3D卷积:

    x u out = Σ i ∈ V ( K ) W i x u + i in , for u ∈ Z 3 \text{x}_u^{\text{out}} = \Sigma_{\text{i} \in V(K)}W_i\text{x}_{u + \text{i}}^{\text{in}}, \quad \text{for} \quad u \in \mathbb Z^3 xuout​=Σi∈V(K)​Wi​xu+iin​,foru∈Z3

    u ∈ Z 3 u \in \mathbb Z^3 u∈Z3表示3D坐标, K K K表示卷积中的kernel_size,V(K)是3维空间中的offsets集合, W i ∈ N out × N in W_i \in \mathbb N^{\text {out}} \times \mathbb N^ \text{in} Wi​∈Nout×Nin

  • Minkowski 卷积

    x u out = Σ i ∈ N ( u , C in ) W i x u + i in , for u ∈ C out \text{x}_u^{\text{out}} = \Sigma_{\text{i} \in N(u, \mathbb C^{\text{in}})}W_i\text{x}_{u + \text{i}}^{\text{in}}, \quad \text{for} \quad u \in \mathbb C^{\text{out}} xuout​=Σi∈N(u,Cin)​Wi​xu+iin​,foru∈Cout

    对于常规3D卷积,可以看到变化的是 u ∈ C out u \in \mathbb C^{\text{out}} u∈Cout和 i ∈ N ( u , C in ) \text{i} \in N(u, \mathbb C^{\text{in}}) i∈N(u,Cin)。 C in \mathbb C^{\text{in}} Cin和 C out \mathbb C^{\text{out}} Cout是预定义的稀疏张量的输入坐标和输出坐标, N ( u , C in ) = { i ∣ u + i ∈ C in , i ∈ V ( K ) } N(u, \mathbb C^{\text{in}}) = \lbrace \text{i} | u + \text{i} \in \mathbb C^{\text{in}}, i \in V(K) \rbrace N(u,Cin)={i∣u+i∈Cin,i∈V(K)}。因此,相比于常规卷积,不是每一个(x, y, z)位置都会有一个卷积的输出,同时并不是每一个offset位置都会参与计算卷积。

三、从使用的角度看MinkowskiEngine

卷积中常见的操作包括Conv, BN, Pooling, FC, transposed Conv等,本章节基于两个很简单的分类网络和分割网络在MinkowskiEngine环境实验上述操作。实验的环境是Ubuntu14.4, Cuda10.2, PyTorch 1.5, Python 3.7, MinkowskiEngine v0.5(下面的实验代码对于其它环境或许也同样支持)。

3.0 网络的输入数据

为了方便观察数据,设batch size=2,第一个点云P1中具有10个点,第二个点云P2中具有6个点,每个点的特征是3维的。下面代码生成P1和P2点云,并转换成MinkowskiEngine的输入数据格式。

import numpy as np
import torch.nn as nn
import MinkowskiEngine as MEdef print_title(s, data):print('='*20, s, '='*20)print(data)if __name__ == '__main__':origin_pc1 = 100 * np.random.uniform(0, 1, (10, 3))feat1 = np.ones((10, 3), dtype=np.float32)origin_pc2 = 100 * np.random.uniform(0, 1, (6, 3))feat2 = np.ones((6, 3), dtype=np.float32)print_title('origin_pc1', origin_pc1)print_title('origin_pc2', origin_pc2)coords, feats = ME.utils.sparse_collate([origin_pc1, origin_pc2], [feat1, feat2])print_title('coords', coords)print_title('feats', feats)input = ME.SparseTensor(feats, coordinates=coords)print_title('input', input)

上述程序的输出:

==================== origin_pc1 ====================
[[83.28334147 28.87414665 44.48401738][43.04924052 34.66068275 28.201644  ][22.51394645 53.53203799 25.68239097][11.39696393 27.68488056 18.02263419][68.04944494 78.4874799  33.54077384][83.11021987 95.29080943 72.42599245][68.96104764 64.38640545 56.64488121][61.26343854 35.13968286 10.67545387][95.5847873  56.20865881  5.97082126][63.43547357 75.31685552 67.71327187]]
==================== origin_pc2 ====================
[[21.01681082 32.60864402 14.68910937][76.90920828 40.72511594 17.21551445][67.84378491 80.58219012 43.75387818][45.97922404 77.97593435  3.17289328][39.91144138 80.02990713 44.97847053][ 1.55805162 57.33833007 92.04541106]]
==================== coords ====================
tensor([[ 0, 83, 28, 44],[ 0, 43, 34, 28],[ 0, 22, 53, 25],[ 0, 11, 27, 18],[ 0, 68, 78, 33],[ 0, 83, 95, 72],[ 0, 68, 64, 56],[ 0, 61, 35, 10],[ 0, 95, 56,  5],[ 0, 63, 75, 67],[ 1, 21, 32, 14],[ 1, 76, 40, 17],[ 1, 67, 80, 43],[ 1, 45, 77,  3],[ 1, 39, 80, 44],[ 1,  1, 57, 92]], dtype=torch.int32)
==================== feats ====================
tensor([[1., 1., 1.],[1., 1., 1.],[1., 1., 1.],[1., 1., 1.],[1., 1., 1.],[1., 1., 1.],[1., 1., 1.],[1., 1., 1.],[1., 1., 1.],[1., 1., 1.],[1., 1., 1.],[1., 1., 1.],[1., 1., 1.],[1., 1., 1.],[1., 1., 1.],[1., 1., 1.]])
==================== input ====================
SparseTensor(coordinates=tensor([[ 0, 83, 28, 44],[ 0, 43, 34, 28],[ 0, 22, 53, 25],[ 0, 11, 27, 18],[ 0, 68, 78, 33],[ 0, 83, 95, 72],[ 0, 68, 64, 56],[ 0, 61, 35, 10],[ 0, 95, 56,  5],[ 0, 63, 75, 67],[ 1, 21, 32, 14],[ 1, 76, 40, 17],[ 1, 67, 80, 43],[ 1, 45, 77,  3],[ 1, 39, 80, 44],[ 1,  1, 57, 92]], dtype=torch.int32)features=tensor([[1., 1., 1.],[1., 1., 1.],[1., 1., 1.],[1., 1., 1.],[1., 1., 1.],[1., 1., 1.],[1., 1., 1.],[1., 1., 1.],[1., 1., 1.],[1., 1., 1.],[1., 1., 1.],[1., 1., 1.],[1., 1., 1.],[1., 1., 1.],[1., 1., 1.],[1., 1., 1.]])coordinate_map_key=coordinate map key:[1, 1, 1]coordinate_manager=CoordinateMapManagerCPU([1, 1, 1]:    CoordinateMapCPU:16x4algorithm=MinkowskiAlgorithm.DEFAULT)spatial dimension=3)

可以看到:

  • ME.utils.sparse_collate把P1和P2点云数据的坐标进行了量化,并组成了batch的格式, 0表示属于P1点云数据,1表示属于P2点云数据
  • ME.SparseTensor把数据转换成了SparseTensor,MinkowskiEngine需要的数据格式。SparseTensor包括coordinates和features的信息。

3.1 分类网络

这里只实现Conv(3, 64) + BN + ReLU + GlobalPooling + FC(64, 32)的简单分类网络。

import numpy as np
import torch.nn as nn
import MinkowskiEngine as MEclass ExampleNetwork(ME.MinkowskiNetwork):def __init__(self, in_feat, out_feat, D=3):super(ExampleNetwork, self).__init__(D)self.conv = nn.Sequential(ME.MinkowskiConvolution(in_channels=in_feat,out_channels=64,kernel_size=3,stride=1,dilation=1,bias=False,dimension=D),ME.MinkowskiBatchNorm(64),ME.MinkowskiReLU())self.pooling = ME.MinkowskiGlobalPooling(ME.PoolingMode.GLOBAL_AVG_POOLING_KERNEL)self.linear = ME.MinkowskiLinear(64, out_feat)def forward(self, x):out = self.conv(x)print('conv: ', out.coordinates.size(), out.features.size())out = self.pooling(out)print('pooling: ', out.coordinates.size(), out.features.size())out = self.linear(out)print('linear: ', out.coordinates.size(), out.features.size())return outif __name__ == '__main__':origin_pc1 = 100 * np.random.uniform(0, 1, (10, 3))feat1 = np.ones((10, 3), dtype=np.float32)origin_pc2 = 100 * np.random.uniform(0, 1, (6, 3))feat2 = np.ones((6, 3), dtype=np.float32)coords, feats = ME.utils.sparse_collate([origin_pc1, origin_pc2], [feat1, feat2])input = ME.SparseTensor(feats, coordinates=coords)net = ExampleNetwork(in_feat=3, out_feat=32)output = net(input)for k, v in net.named_parameters():print(k, v.size())

程序运行结果如下:

conv:  torch.Size([16, 4]) torch.Size([16, 64])
pooling:  torch.Size([2, 4]) torch.Size([2, 64])
linear:  torch.Size([2, 4]) torch.Size([2, 32])
conv.0.kernel torch.Size([27, 3, 64])
conv.1.bn.weight torch.Size([64])
conv.1.bn.bias torch.Size([64])
linear.linear.weight torch.Size([32, 64])
linear.linear.bias torch.Size([32])

从上面可以看到:

  • 网络中间层的输出和预期是一样的(要注意的是,pooling 之后的size变成(2, 4)和(2, 64),是因为输入的数据的batchsize=2)
  • 查看网络的参数也可以通过PyTorch中named_parameters()进行遍历,卷积层的参数的size略有不同,PyTorch中应该是(3, 3, 3, 3, 64)的格式,MinkowskiEngine直接变成了(27, 3, 64)。

3.2 分割网络

输入是两个点云P1, P2,分别具有100个点和6个点,网络的经过Conv(3, 64, stride=2) + BN + ReLU + transposed Conv(64, 4),结构代码如下所示:

import numpy as np
import torch
import torch.nn as nn
import MinkowskiEngine as ME
import MinkowskiEngine.MinkowskiFunctional as MEFclass ExampleNetwork(ME.MinkowskiNetwork):def __init__(self, in_feat, out_feat, D=3):super(ExampleNetwork, self).__init__(D)self.conv =  ME.MinkowskiConvolution(in_channels=in_feat,out_channels=64,kernel_size=3,stride=2,dilation=1,bias=False,dimension=D)self.bn = ME.MinkowskiBatchNorm(64)self.conv_tr = ME.MinkowskiConvolutionTranspose(in_channels=64,out_channels=4,kernel_size=3,stride=2,dilation=1,bias=False,dimension=D)def forward(self, x):print('input: ', x.coordinates.size(), x.features.size())out = self.conv(x)print('conv: ', out.coordinates.size(), out.features.size())out = self.bn(out)print('bn: ', out.coordinates.size(), out.features.size())out = MEF.relu(out)print('relu: ', out.coordinates.size(), out.features.size())out = self.conv_tr(out)print('conv_tr', out.coordinates.size(), out.features.size())return outif __name__ == '__main__':origin_pc1 = 5 * np.random.uniform(0, 1, (100, 3))feat1 = np.ones((100, 3), dtype=np.float32)origin_pc2 = 100 * np.random.uniform(0, 1, (6, 3))feat2 = np.ones((6, 3), dtype=np.float32)coords, feats = ME.utils.sparse_collate([origin_pc1, origin_pc2], [feat1, feat2])input = ME.SparseTensor(feats, coordinates=coords)net = ExampleNetwork(in_feat=3, out_feat=32)output = net(input)print(torch.equal(input.coordinates, output.coordinates))print(torch.equal(input.features, output.features))

输出结果为:

input:  torch.Size([74, 4]) torch.Size([74, 3])
conv:  torch.Size([31, 4]) torch.Size([31, 64])
bn:  torch.Size([31, 4]) torch.Size([31, 64])
relu:  torch.Size([31, 4]) torch.Size([31, 64])
conv_tr torch.Size([74, 4]) torch.Size([74, 4])
True
False

通过实验可以观察得到:

  • 原始的点云数量应该是100 + 6个,经过量化后输出网络的点云数量是74个。
  • stride=2的卷积使得点云的数量从74->31个,可以理解为降低了点云的分辨率(对比图像)
  • transposed Conv把31个点的点云又重新恢复到74个点,倒数第二个True,表示input和output的坐标是一致的,也就是说通过conv + tr_conv,点云的数量和在tensor中的顺序并不会改变。

四、总结

  • MinkowskiEngine可以以较低显存实现3D卷积的操作
  • MinkowskiEngine使用方便,Conv, BN, ReLU, transposed Conv等的调用方式同PyTorch,个人感觉可以轻松实现分类、分割等网络架构。
  • MinkowskiEngine在把数据输入网络之前,会对数据进行量化,会导致部分点云数据的信息丢失。

初次接触MinkowskiEngine,理解不对的地方欢迎大家指正

点云中的Minkowski卷积相关推荐

  1. CVPR2020:4D点云语义分割网络(SpSequenceNet)

    CVPR2020:4D点云语义分割网络(SpSequenceNet) SpSequenceNet: Semantic Segmentation Network on 4D Point Clouds 论 ...

  2. CVPR2019论文题目中文列表

    英文题目 中文题目   Finding Task-Relevant Features for Few-Shot Learning by Category Traversal 少镜头学习中用类别遍历法寻 ...

  3. (九:2020.08.27)CVPR 2019 追踪之论文纲要(译)

    CVPR 2019 追踪之论文纲要(修正于2020.08.28) 讲在前面 论文目录 讲在前面 论坛很多博客都对论文做了总结和分类,但就医学领域而言,对这些论文的筛选信息显然需要更加精细的把控,所以自 ...

  4. 点云上的卷积神经网络及其部分应用

    本次公开课由李伏欣老师主讲,李伏欣老师是美国俄勒冈州立大学助力教授,公开课主要介绍了涵盖3D点云领域的研究,并重点介绍了李老师近期的最新工作内容. 公开课回放链接:https://www.shenla ...

  5. 使用稀疏 4D 卷积对 3D LiDAR 数据中的运动对象进行后退分割(IROS 2022)

    点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 作者丨泡泡机器人 来源丨 泡泡机器人SLAM Receding Moving Object Segme ...

  6. 3D点云|云上的卷积神经网络及其部分应用

    本次公开课由深蓝学院开设,李伏欣老师主讲,主要介绍了涵盖3D点云领域的研究,并重点介绍了李老师近期的最新工作内容. 李伏欣         美国俄勒冈州立大学助理教授 本次分享首先介绍了最近几年两篇经 ...

  7. CVPR2020:点云分析中三维图形卷积网络中可变形核的学习

    CVPR2020:点云分析中三维图形卷积网络中可变形核的学习 Convolution in the Cloud: Learning Deformable Kernels in 3D Graph Con ...

  8. 计算机鹅点云,CVPR 2020 | 用于点云中3D对象检测的图神经网络

    论文原文:Point-GNN: Graph Neural Network for 3D Object Detection in a Point Cloud 论文地址:https://www.amine ...

  9. CVPR 2021 | RfD-Net: 从点云中重建三维物体实例

    点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 基于点云的场景理解是目前特别具有挑战性的任务,本文作者提出了一种从三维场景点云中重建高精度物体网格的学 ...

最新文章

  1. 代码重构之没有理由拒绝Lambda表达式
  2. 关于Juniper ScreenOS MIP/VIP地址说明
  3. PicGo五分钟打造你的私人图床(稳定、快速、免费)
  4. java 不规则 拼图_Java中不一致的操作会扩大规则
  5. Java8-初识Lambda
  6. Office 365 Pro Plus 离线安装包及自定义部署工具下载地址
  7. 【java】画图和监听事件的应用
  8. 长虹智慧厨房解决方案,让你AI上智慧家居生活
  9. 肖维勒准则matlab_肖维勒准则.PPT
  10. 推荐几个在线SQL编程的网站,良心!
  11. java 获取某一日期的0点0分0秒和23点59分59秒
  12. centos7安装redis并设置开机启动
  13. 第三章 项目立项管理
  14. sturts调用支付宝接口。
  15. 2020年iOS 和Android程序员请开始修炼内功
  16. python控制nao机器人_python 程序控制NAO机器人行走
  17. Android软件开发实例:用客户端写博客
  18. 刘易远:普通人该如何改变自己的阶层?
  19. zig语言代替C语言进行裸机开发的尝试-2023年笔记
  20. 把机顶盒刷成Linux操作系统

热门文章

  1. AIoT赋能 科大讯飞携手旭辉共筑智慧地产
  2. 一款基于 Spring Boot 开发的 OA 项目,接私活必备!
  3. VERSA美国VGG-4422-U-A240
  4. 英语日期序数词的写法?什么时候加st?什么时候加th?1~31号分别是怎么加的?
  5. C# 使用SQLite
  6. CSDN也被盗号......
  7. linux在文件里写入,在linux下,如何将shell里输出的信息写入到文件里呢?
  8. aix vios 命令总结(王老师讲解)
  9. 如何自学图像编程(转)
  10. python批量修改文件名代码_python脚本批量修改文件名