跳到主要内容

PointNet:直接处理点集的深度网络

论文:PointNet: Deep Learning on Point Sets for 3D Classification and Segmentation
作者:Charles R. Qi, Hao Su, Kaichun Mo, Leonidas J. Guibas
arXiv:1612.00593v2
本地文件:/root/other/paper/1612.00593v2.pdf

这篇论文解决什么问题

PointNet 讨论的是一个很基础的问题:

能不能不把点云转成 voxel 或多视图图片,而是直接把原始点集送进神经网络?

在 PointNet 之前,很多 3D 深度学习方法会先做表示转换:

输入方式做法问题
voxel把 3D 空间离散成体素网格内存和计算随分辨率立方增长
multi-view把 3D 物体渲染成多张 2D 图依赖视角设计,不方便点级任务
mesh CNN在网格上做卷积拓扑不规则,泛化复杂
handcrafted feature人工设计点云特征任务相关,表达能力有限

PointNet 的核心主张是:点云本身就是一种简单、统一的表示,可以直接学习。

PointNet 任务总览的重绘示意图

图源说明:根据 Qi et al., PointNet 的任务设定概念重绘,非论文原图。

点云输入的三个性质

论文把点集输入的困难概括成三个性质。

1. 无序性

点云是集合:

S={x1,x2,,xn}S=\{\mathbf{x}_1,\mathbf{x}_2,\ldots,\mathbf{x}_n\}

集合没有顺序。也就是说:

[p1, p2, p3]

和:

[p3, p1, p2]

应该表示同一个点云。

因此网络必须满足 permutation invariance:

f(x1,x2,,xn)=f(xπ(1),xπ(2),,xπ(n))f(\mathbf{x}_1,\mathbf{x}_2,\ldots,\mathbf{x}_n) = f(\mathbf{x}_{\pi(1)},\mathbf{x}_{\pi(2)},\ldots,\mathbf{x}_{\pi(n)})

其中 π\pi 是任意排列。

2. 点之间有几何关系

点不是孤立的。点之间的距离、邻域和局部结构都有意义。

例如一把椅子的腿、靠背、扶手,都是由局部点集形成的几何结构。

3. 对几何变换应有鲁棒性

同一个物体经过旋转、平移后,类别不应该改变。

所以网络要尽量对刚体变换保持稳定。

PointNet 的核心结构

PointNet 的结构非常简洁:

  1. 对每个点独立使用同一个 MLP。
  2. 用 max pooling 聚合所有点的特征。
  3. 得到一个全局 shape feature。
  4. 用全局特征做分类,或和局部点特征拼接后做分割。

PointNet 网络结构的重绘示意图

图源说明:根据 Qi et al., PointNet 的 architecture 概念重绘,非论文原图。

可以写成:

f(S)γ(maxxiSh(xi))f(S) \approx \gamma \left( \max_{\mathbf{x}_i\in S} h(\mathbf{x}_i) \right)

其中:

  • hh:逐点 shared MLP。
  • max\max:对所有点做 element-wise max pooling。
  • γ\gamma:后续 MLP,用于分类或分割。

模型架构:输入与输出

PointNet 的输入是点集,输出取决于任务:分类输出一个全局类别,分割输出每个点的标签。

通用输入

输入点云可以写成:

S={x1,x2,,xn}S=\{\mathbf{x}_1,\mathbf{x}_2,\ldots,\mathbf{x}_n\}

每个点通常是 3D 坐标:

xi=(xi,yi,zi)R3\mathbf{x}_i=(x_i,y_i,z_i)\in\mathbb{R}^3

如果带有法向、颜色或其他属性,也可以扩展成:

xiRd\mathbf{x}_i\in\mathbb{R}^d

论文实验中,分类任务常用 n=1024n=1024 个点,因此输入张量可以理解为:

n x 3

也就是 1024 个点,每个点 3 个坐标。

shared MLP 的输入与输出

PointNet 对每个点独立使用同一个 MLP:

h(xi)fih(\mathbf{x}_i)\rightarrow \mathbf{f}_i

输入是单个点 xi\mathbf{x}_i,输出是该点的高维特征 fi\mathbf{f}_i。因为所有点共享同一个 hh,所以网络不会依赖点的输入顺序。

对所有点得到:

{f1,f2,,fn}\{\mathbf{f}_1,\mathbf{f}_2,\ldots,\mathbf{f}_n\}

max pooling 的输入与输出

max pooling 接收所有点特征,输出一个全局 shape feature:

u=maxi=1nfi\mathbf{u} = \max_{i=1}^{n}\mathbf{f}_i

这里的 max 是逐维取最大值。输出 u\mathbf{u} 描述整个点云,而不是某一个点。

分类网络的输入与输出

分类任务的流程是:

n x 3 points
-> shared MLP
-> max pooling
-> global feature
-> classifier MLP
-> k class scores

输入:

SRn×3S\in\mathbb{R}^{n\times 3}

输出:

y^Rk\hat{\mathbf{y}}\in\mathbb{R}^{k}

其中 kk 是类别数。y^\hat{\mathbf{y}} 经过 softmax 后得到每个类别的概率,例如 chair、table、sofa 等。

分割网络的输入与输出

分割任务需要给每个点预测一个标签,因此输出不是一个全局类别,而是 nn 个点级预测。

流程是:

n x 3 points
-> local point features
-> max pooling gets global feature
-> concatenate local feature and copied global feature
-> shared segmentation MLP
-> n x m point labels

输入:

SRn×3S\in\mathbb{R}^{n\times 3}

输出:

Y^Rn×m\hat{\mathbf{Y}}\in\mathbb{R}^{n\times m}

其中 mm 是点级标签类别数。对第 ii 个点,网络输出:

y^iRm\hat{\mathbf{y}}_i\in\mathbb{R}^{m}

它表示该点属于各个 part 或 semantic class 的概率。

任务输入中间表示输出
分类n×3n\times 3 点云全局 shape featurekk 个类别分数
部件分割n×3n\times 3 点云local feature + global featuren×mn\times m 个点级类别分数
语义分割室内场景点云,可带额外属性local feature + scene/global feature每个点的语义类别

输出可以用来干什么

PointNet 的输出用途取决于具体任务。

输出可以用来做什么具体含义
全局类别分数 y^Rk\hat{\mathbf{y}}\in\mathbb{R}^{k}点云分类判断整个点云属于哪个类别,例如 chair、table、car
softmax 概率置信度分析不只知道预测类别,还能知道模型对各类别的信心
global feature u\mathbf{u}形状描述 / 下游特征作为整个点云的紧凑表示,供检索、匹配、聚类或其他网络使用
点级标签 Y^Rn×m\hat{\mathbf{Y}}\in\mathbb{R}^{n\times m}part segmentation给物体每个点分配部件标签,例如椅子的靠背、椅面、椅腿
点级语义标签scene semantic segmentation给室内场景中的每个点标出 wall、floor、chair、table 等语义类别
T-Net 输出的变换矩阵点云对齐把输入点云或中间特征变换到更规范的坐标/特征空间

分类输出常用于“这个点云是什么物体”:

point cloud
-> PointNet
-> class scores
-> predicted object category

分割输出常用于“每个点属于什么部分”:

point cloud
-> PointNet segmentation network
-> point-wise labels
-> colored point cloud / part mask / semantic map

需要注意:PointNet 本身通常不直接输出 mesh,也不直接补全缺失几何。它更擅长从已有点云中提取全局特征、做分类和点级标注。如果把 PointNet 作为其他重建模型的 encoder,那么它输出的 global feature 可以继续输入 DeepSDF、Occupancy Network 或其他 decoder,用来生成隐式场或网格。

为什么 max pooling 能处理无序点集

PointNet 最关键的设计是使用 symmetric function。

一个 symmetric function 的输出不依赖输入顺序,例如:

max(a,b,c)=max(c,a,b)\max(a,b,c)=\max(c,a,b)

对点云来说,如果每个点先经过同一个 hh 得到特征:

h(xi)RKh(\mathbf{x}_i)\in\mathbb{R}^K

然后对所有点取逐维最大值:

u=maxxiSh(xi)\mathbf{u} = \max_{\mathbf{x}_i\in S}h(\mathbf{x}_i)

那么无论点的输入顺序怎么变,最终 u\mathbf{u} 都不变。

max pooling 实现置换不变性的数值示意图

图源说明:根据 Qi et al., PointNet 的 symmetric function 思想重绘,非论文原图。

一个具体数值例子:为什么换顺序不影响 max pooling?

假设有 3 个点,经过 shared MLP 后得到:

h(p1)=[1,5,2]h(p_1)=[1,5,2]h(p2)=[4,2,7]h(p_2)=[4,2,7]h(p3)=[3,6,1]h(p_3)=[3,6,1]

对三个向量逐维取最大值:

max(h(p1),h(p2),h(p3))=[4,6,7]\max(h(p_1),h(p_2),h(p_3)) = [4,6,7]

如果输入顺序换成:

p2,p3,p1p_2,p_3,p_1

则:

max(h(p2),h(p3),h(p1))=[4,6,7]\max(h(p_2),h(p_3),h(p_1)) = [4,6,7]

结果完全一样。

这就是 PointNet 能直接处理无序点集的核心原因:它没有试图给点排序,而是用 max pooling 消除了顺序影响。

为什么不用 RNN 加随机排列来处理无序点云?

一种看似自然的做法是:把点云当成一个序列输入 RNN。

例如同一个点云:

S={p1,p2,p3,,pn}S=\{p_1,p_2,p_3,\ldots,p_n\}

可以随机打乱成很多顺序:

p1 -> p2 -> p3 -> ... -> pn
p3 -> p1 -> p2 -> ... -> pn
p7 -> p4 -> p1 -> ... -> pn

然后用这些随机排列训练 RNN,希望模型逐渐学会“不在乎输入顺序”。

这个想法的问题是:RNN 的结构本身不是置换不变的。

RNN 的隐藏状态按顺序递推:

ht=ϕ(ht1,xt)\mathbf{h}_t = \phi(\mathbf{h}_{t-1},\mathbf{x}_t)

也就是说,第 tt 个点的处理结果依赖前面已经看过哪些点。

如果输入顺序变了,隐藏状态的演化路径也会变:

p1 -> p2 -> p3

和:

p3 -> p2 -> p1

虽然包含同样的点,但 RNN 看到的是两条不同的序列。

因此,随机排列训练最多只能让模型在训练分布里“尽量适应不同顺序”,但不能从结构上保证:

f(p1,p2,p3)=f(p3,p2,p1)f(p_1,p_2,p_3) = f(p_3,p_2,p_1)

这就是论文提到 “OrderMatters” 的原因:对序列模型来说,输入顺序确实会影响计算过程,无法被完全忽略。

对于几十个元素的短序列,RNN 可能还能通过大量随机排列增强来获得不错的经验鲁棒性。

但点云通常有成百上千个点。例如论文实验常用:

n=1024n=1024

这时可能的排列数量是:

1024!1024!

这个数量大到不可能靠数据增强覆盖。

而且 RNN 逐点处理,序列越长:

  • 计算越慢,难以并行。
  • 长距离依赖更难学。
  • 训练更不稳定。
  • 输入顺序带来的偏差更明显。

PointNet 的思路更直接:不要试图让一个有序模型“学会无序”,而是在结构上使用 symmetric function。

例如 max pooling 天然满足:

max(h(p1),h(p2),h(p3))=max(h(p3),h(p2),h(p1))\max(h(p_1),h(p_2),h(p_3)) = \max(h(p_3),h(p_2),h(p_1))

所以 PointNet 的置换不变性不是靠训练碰运气学出来的,而是由网络结构保证的。

这也是 PointNet 相比 RNN 方案更适合大规模点云的关键原因。

分类网络

分类任务只需要输出一个全局类别。

流程是:

n x 3 points
-> input transform
-> shared MLP
-> feature transform
-> shared MLP
-> max pooling
-> global feature
-> MLP classifier
-> class scores

论文中常见设置是输入 1024 个点,每个点是 (x,y,z)(x,y,z)

全局特征一般是 1024 维。

分类头输出 kk 个类别分数。

分割网络

分割任务需要给每个点输出一个标签。

只用全局特征不够,因为每个点需要知道自己在物体上的局部位置。

PointNet 的做法是:

  1. 先得到每个点的 local point feature。
  2. 再得到整个点云的 global feature。
  3. 把 global feature 复制回每个点。
  4. 将 local feature 和 global feature 拼接。
  5. 对每个点输出 part label 或 semantic label。

所以分割网络使用的是:

local feature + global feature

直观地说:

  • local feature 让点知道“我附近是什么形状”。
  • global feature 让点知道“我属于哪个整体物体或场景”。

例如同样是一段细长结构,在椅子上可能是 chair leg,在桌子上可能是 table leg。全局信息能帮助点级分类消除歧义。

T-Net:学习对齐变换

点云分类和分割应该对刚体变换比较稳定。

PointNet 使用一个小网络 T-Net 来预测变换矩阵:

  • input transform:预测 3×33\times 3 矩阵,对齐输入坐标。
  • feature transform:预测 64×6464\times 64 矩阵,对齐中间特征。

也就是说,T-Net 会学习一个变换:

xi=Axi\mathbf{x}_i' = A\mathbf{x}_i

让输入点云更接近网络喜欢处理的 canonical pose。

T-Net 对齐和 critical point set 的重绘示意图

图源说明:根据 Qi et al., PointNet 的 alignment network 与 critical point set 概念重绘,非论文原图。

feature transform 的正则

论文希望 feature transform 矩阵接近正交矩阵,因此加了正则项:

Lreg=IAATF2L_{reg} = \left\| I-AA^T \right\|_F^2

如果 AA 接近正交矩阵,它更像旋转或反射,不会任意拉伸特征空间。

这样可以减少变换矩阵过度扭曲特征。

理论解释:PointNet 学到了什么

论文给出一个重要形式:

f({x1,,xn})γ(MAXxiSh(xi))f(\{\mathbf{x}_1,\ldots,\mathbf{x}_n\}) \approx \gamma \left( \operatorname{MAX}_{\mathbf{x}_i\in S} h(\mathbf{x}_i) \right)

这个结构可以近似连续的 set function。

更有意思的是,max pooling 使得全局特征实际上由一小部分点决定。

论文称这些点为:

critical point set

因为每一维 max pooled feature 都来自某个点。若有 KK 维全局特征,那么最多有 KK 个点真正决定这些 max 值。

critical point set 为什么能解释鲁棒性?

设:

u=maxxiSh(xi)\mathbf{u} = \max_{\mathbf{x}_i\in S}h(\mathbf{x}_i)

如果某个点没有在任何一维上取得最大值,那么删掉它不会改变 u\mathbf{u}

例如:

h(p1)=[1,5,2]h(p_1)=[1,5,2]h(p2)=[4,2,7]h(p_2)=[4,2,7]h(p3)=[3,6,1]h(p_3)=[3,6,1]

max pooled feature 是:

[4,6,7][4,6,7]

其中:

  • 第 1 维最大值 44 来自 p2p_2
  • 第 2 维最大值 66 来自 p3p_3
  • 第 3 维最大值 77 来自 p2p_2

所以真正影响全局特征的是 p2p_2p3p_3

如果删掉 p1p_1

max(h(p2),h(p3))=[4,6,7]\max(h(p_2),h(p_3)) = [4,6,7]

全局特征不变。

这解释了为什么 PointNet 对缺失点、扰动点有一定鲁棒性:只要关键点还在,很多非关键点的缺失不会改变全局描述。

实验结果

论文验证了三个主要任务:

  1. ModelNet40 形状分类。
  2. ShapeNet part segmentation。
  3. 室内场景 semantic segmentation。

主要结论:

  • 在 ModelNet40 上,PointNet 在 3D 输入方法中达到当时很强的效果。
  • 在 ShapeNet part segmentation 上,PointNet 达到 state-of-the-art mIoU。
  • 在室内场景语义分割中,PointNet 明显优于 handcrafted feature baseline。
  • 相比 voxel 和 multi-view 方法,PointNet 更高效,复杂度随点数近似线性增长。

论文还做了鲁棒性实验:

  • 随机删除点。
  • 插入 outlier 点。
  • 对点坐标添加 Gaussian noise。

实验显示 PointNet 对这些扰动相当稳定。

优点

PointNet 的优点:

  • 直接吃原始点云,不需要 voxelization。
  • 天然适配无序点集。
  • 结构简单,速度快。
  • 同一个框架可以做分类、part segmentation、semantic segmentation。
  • 理论上解释了 max pooling、critical points 和鲁棒性。
  • 对点云缺失和噪声有较强鲁棒性。

局限

PointNet 的主要局限也很明显:

  • 每个点先被独立处理,局部邻域结构建模较弱。
  • max pooling 只保留最强响应,可能丢掉细粒度局部关系。
  • 对复杂局部几何,表达能力不如后来的 PointNet++。
  • T-Net 只能学习整体对齐,不能替代真正的局部几何建模。

这也是 PointNet++ 后续要解决的问题:在点云上建立 hierarchical local feature learning。

和 DeepSDF 的关系

PointNet 和 DeepSDF 都是 3D 深度表示学习的重要基线,但思路不同:

方法输入/表示核心思想输出
PointNet点集对无序点集做 permutation-invariant learning分类或点级标签
DeepSDF连续隐式场用 latent code + MLP 表示 SDFSDF 值 / 隐式表面

可以这样理解:

  • PointNet 更像是“理解已有点云”。
  • DeepSDF 更像是“生成或补全连续形状场”。

在实际系统中,它们可以互补:

  • PointNet 类网络可用于点云编码、分类、分割。
  • DeepSDF 类网络可用于形状补全、隐式重建、表面提取。

我的理解

PointNet 的关键贡献不是网络很深,而是它抓住了点云的本质:

点云是无序集合,所以网络结构必须从一开始就尊重集合的无序性。

它用 shared MLP 处理每个点,用 max pooling 聚合全局信息。这个设计非常简单,但非常有力。

真正值得记住的是:

point-wise featuremaxglobal shape descriptor\text{point-wise feature} \quad \xrightarrow{\max} \quad \text{global shape descriptor}

这个思想后来影响了大量点云网络。

阅读时要抓住的主线

  1. 点云是无序集合,不能直接当序列或规则网格处理。
  2. shared MLP 保证每个点用同一套函数提特征。
  3. max pooling 保证置换不变性。
  4. global feature 用于分类。
  5. local + global feature 用于分割。
  6. T-Net 提供输入和特征对齐。
  7. critical point set 解释了 PointNet 的鲁棒性。

后续问题

读完这篇后,可以继续追问:

  1. PointNet 为什么局部几何建模不足?
  2. PointNet++ 如何用局部邻域和层次结构改进 PointNet?
  3. max pooling 和 average pooling 在点云任务中有什么差别?
  4. T-Net 学到的变换矩阵是否总是可解释?
  5. PointNet 能否作为 DeepSDF 或其他隐式重建方法的 encoder?