第296篇 点云深度学习之体素化方法——3D卷积的方案
上篇聊了PointNet系列直接对原始点云做特征提取思路很优雅。但你可能也注意到了PointNet对局部结构的捕捉其实不够强处理大规模点云时效率也一般。这就引出了一个很自然的问题能不能把点云变成某种规则结构然后用上成熟的卷积操作答案是体素化。把无序的点云转成三维网格再用3D卷积去处理。这条路从VoxNet开始一路发展到3D卷积、Sparse CNN在自动驾驶和机器人感知中都有实际应用。体素化的基本思路点云是一堆无序的三维坐标点每个点带反射强度等属性。卷积操作需要规则网格输入就像图像是二维像素矩阵一样。体素化做的事情就是把三维空间切分成均匀的小格子每个格子叫一个voxel体素。点落在哪个格子里那个格子就被标记为 occupied。# 简单的体素化逻辑 import numpy as np def voxelize(points, voxel_size, grid_size): # points: (N, 3), voxel_size: [0.1, 0.1, 0.1] coords np.floor(points / voxel_size).astype(int) voxel_grid np.zeros(grid_size, dtypenp.float32) for c in coords: voxel_grid[c[0], c[1], c[2]] 1.0 return voxel_grid这段代码展示了核心思想把连续坐标离散化成网格索引。实际工程中会复杂得多比如处理反射强度、多个点落在同一个体素的情况等。多个点落在同一个体素时怎么处理最简单的做法是取最大值就是二值化——有就是1没有就是0。稍微好一点的做法是取这些点的特征均值。更精细的做法是像VoxelNet那样在每个体素内部用一个小PointNet来提取特征保留体素内点的分布信息。这个选择直接影响最终效果后面聊PointPillars时会再提到。体素化有个关键参数是分辨率。分辨率太高网格太大内存吃不消分辨率太低细节丢失。假设场景范围是80m×80m×6m体素大小0.1m那网格就是800×800×60差不多3840万个格子。用float32存储一个样本就要150MB。这在2016年VoxNet提出的时候是个大问题。从VoxNet到3D卷积的演进VoxNet是2016年的工作算是体素化3D卷积的开山之作。它的结构很简单输入是二值化的体素网格经过几层3D卷积和池化接全连接层做分类。3D卷积和2D卷积的区别在于卷积核多了一个深度维度。2D卷积核是H×W3D卷积核是D×H×W。卷积核在三个维度上滑动每个位置做内积。import torch.nn as nn # 3D卷积示例 conv3d nn.Conv3d( in_channels1, out_channels32, kernel_size5, stride2, padding2 ) # 输入: (batch, 1, D, H, W) # 输出: (batch, 32, D/2, H/2, W/2)VoxNet的问题在于内存。整个体素网格都要送进网络大部分格子是空的室外场景中物体只占很小比例但计算量一点没少。MIDMulti-View Input Deeping和VoxelNet试图解决这个问题。VoxelNet的思路是把点云分成若干小区域每个区域独立做体素化和特征提取最后拼接起来做检测。这减少了单次的计算量但整体复杂度还是很高。真正让体素化方法实用化的是稀疏卷积。SECONDSparsely Embedded Convolutional Detection是2018年的重要工作。它在VoxelNet的基础上引入了稀疏卷积只计算有数据的体素位置。效果上SECOND在KITTI数据集上达到了当时的SOTA同时推理速度比VoxelNet快了好几倍。这证明了稀疏卷积这条路是可行的。稀疏卷积只计算有数据的地方核心观察体素网格中大部分位置是空的。为什么要在空的地方做卷积稀疏卷积的思路很直接——只对有体素的格子做卷积运算。听起来简单实现起来需要专门的数据结构和算法。# 使用MinkowskiEngine的稀疏卷积 import MinkowskiEngine as ME # 输入只需要非空体素的坐标和特征 coords torch.tensor([[0,0,0],[1,0,0],[1,1,0]]) feats torch.randn(3, 16) sparse_input ME.SparseTensor(feats, coords) # 稀疏3D卷积只计算非空位置 conv ME.MinkowskiConvolution(16, 32, kernel_size3) output conv(sparse_input)MinkowskiEngine是韩国KAIST开发的稀疏卷积库在点云处理领域用得非常多。它的关键优化是用哈希表存储非空体素的坐标卷积时只遍历非空位置和卷积核覆盖范围内的非空邻居。这个优化带来的加速是数量级的。一个800×800×60的网格可能只有几十万个非空体素。稀疏卷积的计算量从几千万降到几十万内存占用也大幅下降。TorchSparse是另一个选择MIT开发的接口和PyTorch更贴近。两个库在精度上基本一致性能差异取决于具体的网络结构和硬件。面试中常被追问的几个问题面试官听到你做过体素化方案通常会追问几个点。第一个是分辨率的选择。体素大小直接影响检测精度和推理速度。实际项目中一般做实验在不同分辨率下跑mAP和推理时间的曲线找pareto最优点。自动驾驶中常见的体素大小是0.05m到0.2m之间。第二个是坐标系的选取。体素网格放在哪里一般有两种做法以传感器为中心或者以某个固定世界坐标为中心。以传感器为中心的好处是近处分辨率相对更高缺点是网格需要跟着传感器移动。第三个是多帧融合的问题。单帧点云很稀疏特别是远处的物体可能只有几个点。把多帧点云融合到一个体素网格中可以显著增加信息量。但这涉及到运动补偿需要用位姿信息把不同时刻的点云对齐到同一坐标系。第四个是体素化带来的量化误差。连续坐标被离散化后点的实际位置会偏移半个体素大小。对于需要精确位置的任务比如位姿估计这个误差可能不可接受。解决办法是在体素特征中加入亚体素级别的偏移信息。体素化方法在机器人中的应用场景在自动驾驶领域体素化方案曾经是主流。Waymo早期的感知系统就大量使用体素特征。虽然近年来BEV鸟瞰图方案越来越流行但很多BEV方案的底层仍然用到了体素化。在机器人领域体素化方法特别适合做三维场景理解和障碍物检测。比如仓储机器人的导航需要实时检测货架和地面上的障碍物。把深度相机或激光雷达的点云体素化后做语义分割可以区分地面、货架、行人等不同类别。体素化还有一个好处天然支持三维空间推理。点云方法做空间关系推理比较困难因为点是无序的。体素化之后每个体素都有明确的三维坐标相邻关系一目了然做空间查询和路径规划都更方便。拿体素化方案和PointNet做个对比。PointNet的优势在于不丢失任何点的信息没有量化误差。但它的缺点是局部特征提取弱需要用max pool来聚合全局信息对局部结构的感知靠的是MLP隐式学习。体素化方案虽然引入了量化误差但3D卷积天然能捕捉局部邻域的结构信息这在检测任务上优势明显。实际项目中检测任务一般选体素化方案分类和分割任务两种都可以试试。给你的建议如果你在做点云相关的项目体素化方案是必须了解的。即使最终选了PointNet系列或者BEV方案理解体素化的思路也有助于你做方案对比。建议动手跑一下MinkowskiEngine的示例代码。装好库之后从最简单的稀疏卷积开始逐步搭建一个体素化的分类网络。用ModelNet40数据集做实验这个数据集小跑起来快。面试时聊到体素化重点讲清楚三个东西为什么需要稀疏卷积、分辨率怎么选、体素化的优缺点。能把这三个问题说清楚面试官会觉得你是真正做过实验的。上一篇第295篇 点云深度学习之PointNet系列下一篇预告第297篇 点云3D目标检测——CenterPoint/PointPillars