6D位姿估计聊完了,这篇讲3D点云处理。机器人用激光雷达和深度相机获取的3D数据就是点云——一堆三维坐标点。怎么让神经网络理解这些无序的、不规则的点集?PointNet给出了答案。

PointNet是2017年Charles Qi等人提出的,第一次用深度学习直接处理原始点云。在此之前,点云处理主要靠手工特征(FPFH、SHOT等)。PointNet证明了端到端学习点云特征是可行的,开启了点云深度学习的新纪元。

面试中点云相关的追问通常围绕:点云的无序性怎么处理、PointNet的核心创新、PointNet++的改进。搞懂PointNet系列,3D视觉的基础就有了。

一、点云数据的特殊性

点云和图像有本质区别。图像是规则的网格结构——像素排列整齐,相邻像素有固定的空间关系。点云是无序的集合——N个点没有固定顺序,每个点只有xyz坐标(可能还有颜色、法向量等属性)。

这意味着:网络对输入点的排列顺序必须是不变的(排列不变性)。不管点怎么排列,输出应该一样。CNN的卷积操作天然依赖空间顺序,不能直接用在点云上。

# 点云数据示例
points = np.array([
    [1.0, 2.0, 3.0],  # 点1: (x, y, z)
    [1.1, 2.1, 3.2],  # 点2
    [0.9, 1.9, 2.8],  # 点3
    # ... N个点
])  # shape: (N, 3)

另一个挑战是密度不均匀。点云中不同区域的点密度可能差异很大——离传感器近的地方密,远的地方稀。网络要能处理这种不均匀性。

二、PointNet:开创性工作

PointNet的核心思路:对每个点独立做MLP(多层感知机),然后用对称函数(max pooling)聚合所有点的信息。

# PointNet核心结构
class PointNet(nn.Module):
    def __init__(self):
        self.mlp1 = nn.Sequential(
            nn.Conv1d(3, 64, 1),    # 每个点独立变换
            nn.ReLU(),
            nn.Conv1d(64, 128, 1),
            nn.ReLU(),
            nn.Conv1d(128, 1024, 1)
        )
        # Max pooling实现排列不变性
        self.pool = nn.AdaptiveMaxPool1d(1)
        self.classifier = nn.Sequential(
            nn.Linear(1024, 512),
            nn.Linear(512, num_classes)
        )
    
    def forward(self, x):
        # x: (B, 3, N)
        feat = self.mlp1(x)        # (B, 1024, N)
        global_feat = self.pool(feat)  # (B, 1024, 1)
        return self.classifier(global_feat.squeeze(-1))

每个点独立过MLP,得到1024维的特征向量。max pooling从N个特征向量中选出每个维度的最大值,得到一个全局特征。这个全局特征对点的排列顺序不变——不管点怎么排列,max pooling的结果都一样。

PointNet的局限:对局部结构的建模不够。每个点独立变换后直接做全局max pooling,丢失了点与点之间的局部关系。一个物体上相邻的两个点和不相邻的两个点,在PointNet看来没有区别。对于需要理解局部几何的任务(如语义分割、法向量估计),PointNet的表现不够好。另外,PointNet的T-Net(空间变换网络)只能做全局对齐,不能处理局部形变。

三、PointNet++:局部+全局

PointNet++(2017)解决了PointNet的局限。核心思想:分层提取局部特征,从局部到全局逐步构建理解。

FPS(Farthest Point Sampling):从N个点中选出K个代表性点。每次选离已选点集最远的点。这样选出的点均匀分布在点云上,覆盖性好。

Ball Query:对每个选出的点,找到它半径范围内的所有邻居。和KNN不同,ball query用固定半径,能自适应不同密度的区域。

局部特征提取:对每个局部区域的点做PointNet(MLP+max pooling),得到该区域的特征。然后在更高层把这些区域特征当作新的"点",继续采样、分组、提取特征。

# PointNet++的Set Abstraction层
class SetAbstraction(nn.Module):
    def __init__(self, npoint, radius, nsample, mlp):
        self.npoint = npoint    # FPS采样点数
        self.radius = radius    # ball query半径
        self.nsample = nsample  # 每组最多取几个点
        self.mlp = build_mlp(mlp)
    
    def forward(self, xyz, feat):
        # 1. FPS采样
        new_xyz = fps(xyz, self.npoint)
        # 2. Ball query分组
        grouped = ball_query(new_xyz, xyz, self.radius, self.nsample)
        # 3. 局部MLP + max pooling
        new_feat = self.mlp(grouped)
        new_feat = max_pool(new_feat)
        return new_xyz, new_feat

多层Set Abstraction之后,点越来越少,特征越来越抽象。最后做全局max pooling得到全局特征,接分类头。

PointNet++比PointNet精度高很多,但速度也慢很多。FPS和ball query都是O(N)的操作,多层堆叠后计算量不小。实时场景需要考虑效率优化。

实际使用中,点云的预处理也很重要。输入PointNet/PointNet++之前,点云通常要做归一化(平移到原点、缩放到单位球内)。点云数量N要固定(不足补零、超出采样)。这些预处理对最终精度有显著影响。

四、后续发展

PointNet之后,点云深度学习快速发展。

DGCNN:用动态图卷积替代PointNet的独立MLP。在特征空间中构建KNN图,用EdgeConv在图上做卷积。能更好地捕获局部几何结构。EdgeConv对每对相邻点做MLP,然后max pooling聚合邻居信息。图结构在每层都重新构建(动态的),因为特征空间中的邻居关系和原始坐标空间不同。

Point Transformer:用self-attention处理点云。每个点关注其他所有点,注意力权重由空间距离和特征相似度共同决定。精度高但计算量大。

PointNeXt:PointNet++的全面改进版。更好的训练策略、更大的模型、更高效的架构。在分类和分割任务上达到SOTA。

五、面试高频追问

Q:为什么max pooling能实现排列不变性? A:max pooling对集合中的元素取最大值。不管元素的顺序怎么变,最大值不变。同理,sum pooling和mean pooling也是排列不变的。但max pooling效果最好——它能选出最显著的特征。

Q:PointNet++的FPS太慢怎么办? A:几种方案。随机采样替代FPS(牺牲均匀性换速度)。网格采样(把空间分成网格,每个网格取一个点)。用CUDA优化FPS的实现。实时场景通常用随机采样或者网格采样。

Q:点云处理在机器人中有哪些应用? A:环境建图(SLAM)、障碍物检测与分割、物体识别与位姿估计、可通行区域分析。激光雷达点云是自动驾驶和移动机器人的核心感知数据。PointNet系列主要用于物体级任务(识别、分割、位姿估计),SLAM通常用传统方法或者轻量级网络。

PointNet系列是3D点云深度学习的基石。点云数据特性、PointNet核心架构、PointNet++局部特征学习、后续发展方向,这四个方面理解了,3D视觉的基础就搭好了。点云处理在机器人领域应用越来越广泛,值得持续深入关注。下一篇我们聊体素化方法。


PointNet系列是3D点云深度学习的开创性工作。点云数据特殊性、PointNet排列不变性设计、PointNet++多尺度特征学习、后续发展方向,这四个维度覆盖了点云处理的核心知识。

上一篇:第294篇 6D位姿估计 

下一篇聊体素化方法。

如果这篇文章对你有帮助,欢迎点赞支持一下,你的鼓励是我持续更新的动力!

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐