ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

PointNet++ vs PointTransformer:3D点云处理选型实战对比

PointNet++ vs PointTransformer:3D点云处理选型实战对比 3D点云处理选型实录PointNet与PointTransformer我到底该用哪个做3D点云深度学习这两年我被问得最多的问题就是分类和分割任务到底该用PointNet还是PointTransformer两台方法的名字在论文里挨着出现实验表格里也总是前后脚但真正落到自己数据集上、调参数、看显存、掐时间的时候差别就出来了。两者都是点云特征提取的主流骨架但一个靠“局部区域逐层抽象”一个靠“自注意力全局交互”思路完全不同适用场景也完全不是一回事。这篇文章不对着论文复读公式而是从实际选型和工程落地的角度把PointNet和PointTransformer的底层设计逻辑、训练手感、显存占用、适合的数据规模统统拆开聊一遍。如果你是准备入坑点云深度学习的学生或者正在做点云检测、分割落地的工程师这篇内容应该能帮你少走不少弯路。1. 整体设计思路拆解一个像“压缩编码”一个像“全局投票”1.1 PointNet的层次化局部抽象从局部到全局的“分治”思路要理解PointNet得先知道它爹PointNet干了什么。PointNet直接对整片点云做max pooling思路非常粗暴不管输入是一万个点还是一百万个点每个点先通过共享的多层感知机提升特征维度然后全局max池化变成一维全局特征再接分类或分割头。好处是结构简单、理论优雅坏处也很致命——它天然不感知局部结构。你丢三个点组成的小三角形和一个大平面上的三个点全局特征几乎一样因为max pooling只保留了最显著的信息局部几何关系被丢了。PointNet的解法是“分层喂数据”。借鉴了CNN里卷积核在图像局部区域滑动的思想它先对点云做最远点采样挑出骨架点然后在每个骨架点周围用ball query圈一个固定半径的邻域把邻域内的点交给一个mini-PointNet去提取局部特征。这一层做完点的数量变少但每个点的特征感受野变大——它看到的不再是“自己一个点”而是周围半径范围内的所有点。做完一层再做一层第二次采样的“一个骨架点”实际上对应了原始点云上一大片区域的几何信息。这个过程可以堆叠很多层每一层都在把细节信息抽象成更高层的语义信息。这个设计的好处非常清楚局部几何被显式建模同时因为每层都在降采样计算量被控制得很可控。这也是为什么至今PointNet依旧在不少工业场景里当主力特征提取器——效率和效果的平衡点很舒服。坏处也明显固定半径的邻域对点云密度变化很敏感。室内点云扫描经常是近处密、远处稀你同一个radius近处能圈进去五十个点远处可能只有五个点在球里局部特征自然不稳定。针对这个论文里提了多尺度分组和multi-resolution grouping两种补救但工程上做起来显存和速度都要额外付账。1.2 PointTransformer的自注意力所有点之间“自由连接”PointTransformer走的是另一条路——把自然语言处理里大杀四方的Transformer注意力机制搬到点云上。核心操作是每个点的特征不是只由自己局部的邻居说了算而是由整个点云中它“关注”的那些点共同加权决定。注意力权重的计算方式就是Query和Key做内积然后softmax归一化再用这个权重去加权聚合Value。点云里每个点都能和所有点建立联系全局依赖关系是天然的不用一层一层堆局部抽象去“挤出”全局信息。但注意“每个点都和所有点算注意力”这句话听起来很性感算起来很崩溃——完整注意力是O(N^2)复杂度点云动辄几万点起步直接算一遍能让你的显存原地爆炸。所以工程上用的PointTransformer基本都会限制注意力计算在某个局部邻域内最常见的就是用k近邻代替全连接。本质上是“注意力局部化”全局视野没有了但每个点仍然能在这个局部邻域里做空间自适应加权和PointNet的固定半径卷积式聚合相比有了更强的点与点之间关系建模能力。这里还有一个特别容易被忽略的细节——位置编码。Transformer本身不感知点的空间位置如果直接把点特征丢进去注意力权重只会基于特征相似度来算空间结构就白瞎了。所以PointTransformer里设计了一个位置编码模块让特征相似度的同时考虑两个点之间的三维相对坐标甚至法向等几何信息。这个位置编码是PointTransformer性能能打的关键也是它在更大数据上卷得过PointNet系列的核心原因。1.3 两者的核心差异到底在哪里用大白话总结一下两个模型的气质PointNet是“从局部到整体的逐层抽象”——低层看到的是小块表面几何高层把这些几何聚合成更大的结构概念然后汇总成全局特征PointTransformer则是“每个点主动去寻找和自己相关的点再融合它们的信息”——类似于在点云里做信息传递和投票特征直接携带空间上下文。正因为气质不同两个模型的优缺点也错开了。PointNet结构简单显存和计算量大概可控对数据量和训练技巧要求不那么苛刻但局部特征受邻域半径限制面对密度不均或复杂场景特征表达能力有上限。PointTransformer特征表达能力更强特别是在大规模、复杂场景数据上全局上下文建模的优势很突出但训练起来更“挑食”注意力机制对数据量、超参数比如邻域点数K、注意力头数、初始化方式都很敏感落地时工程调参的复杂度明显高一大截。2. 核心细节解析与实操要点别被名字骗了细节全是坑2.1 最远点采样和ball queryPointNet的速度瓶颈和优化空间很多刚入坑的同学以为PointNet耗时间的地方是那个mini-PointNet其实跑个profiling就明白了时间和显存大头全在采样和邻居查询上。最远点采样是用来选骨架点的原理极简先随机选一个点然后每次选离已选点集最远的点直到凑齐N个骨架点。这个方法能保证采样出来的点尽可能均匀覆盖整个空间但它是一个串行的贪心算法每一步都要计算剩余所有点到当前点集的最远距离在GPU上很难完全并行化点一多整个流程就卡住了。ball query则是固定半径范围查询和图像的卷积滑动窗口不同点云是无序的你得先建空间索引常见方案是k-d树或八叉树。而正因为点是稀疏分布在三维空间里查询时经常出现某些区域点很少、某些区域点挤成一团的情况。我自己测试过一个8万点的室内场景光FPS和ball query这两个预处理就占了整个训练前向时间的近30%这还是已经用了grid采样加速之后的结果。工程上想提速常见解法有这么几条用grid sample降采样替代全部FPS层或者干脆把FPS换成随机采样——很多分割任务对骨架点均匀性没那么敏感随机采样直接省掉一大截耗时。ball query也可以用torch的cubin算子或者用k近邻近似替代。我们实际做室内分割的时候把第二层和第三层的FPS改成随机采样mIoU只掉了0.3到0.5个点训练时间却省了将近四成很划算的买卖。2.2 邻域点数量K和半径r决定特征表达能力的两个旋钮无论PointNet还是PointTransformer邻域的选择都是核心超参数。PointNet用的是半径rball query固定半径PointTransformer更像标准KNN直接给固定邻域点数K通常取16或32。别小看这两个数字它们直接决定了每个点“能看到”多大范围的信息也决定了特征提取的稳定性和最终精度。K太小每个点只能看到极小范围特征容易碎局部几何信息不够后面想抽象全局特征就更难K太大除了显存和计算成本飞涨还会引入大量距离很远的“弱相关”点把局部结构的特异性给稀释了。我在ShapeNet分类任务上做过一组对比K从16升到32分类acc能提升0.6个点左右但训练时间慢了一半。K从32升到64精度几乎不再涨显存占用却涨了近70%。所以大多数任务K取16到32是甜点区。PointNet固定半径的问题在于点云密度不均是不可避免的。同样是0.2米的半径桌子表面的点可能圈进来40个墙角处可能只有5个。这时候如果mini-PointNet用max pooling聚合局部特征点数太少的那组特征方差会很大输出特征不够稳定。实操建议是如果你的数据密度变化很大优先考虑PointTransformer——它对密度不均的鲁棒性明显好于PointNet因为KNN固定K值的方式天然保证了“每个邻域内点数都一样”不会出现有的邻域空荡荡的情况。2.3 退火式训练策略想让PointTransformer收敛光调学习率是不够的PointTransformer刚上手最容易遇到的问题就是不收敛或者收敛很慢。原因不复杂注意力机制把每个点都和其他点建立联系参数空间搜索范围更大优化难度更高加上位置编码引入的额外几何特征训练动力学比PointNet复杂得多。PointNet用AdamW配一个cosine退火学习率就能顺利跑起来PointTransformer换同样的配置loss经常卡在某个地方不动。我的实操经验是先禁用位置编码跑10个epoch让网络先学会用纯特征做注意力这时候loss会正常下降然后再打开位置编码从头训练。不要小看这个热身策略实测下来能让PointTransformer在S3DIS这样的大场景分割任务上训练早期loss下降速度接近翻倍。另外学习率峰值要压低PointNet可以用1e-3PointTransformer最好从5e-4起步warmup也必不可少至少3000步直接从头顶着峰值学习率开训基本必炸。数据增强方面两者也是两套脾气。PointNet对旋转扰动、尺度缩放这些全局变换很敏感加上去涨点明显PointTransformer因为注意力机制本身就内含了点之间的相对关系对全局刚体变换的鲁棒性天然好一些但反而更需要随机drop点、局部遮挡这类“破坏性”增强来防止过拟合。这个差异我是在一次对比实验里发现的——只加全局随机旋转时PointTransformer分类精度只涨了0.2个点PointNet涨了2个点再加上随机drop 20%点以后PointTransformer又反超了1.5个点。2.4 显存和推理速度不只是“谁更大”的问题很多论文喜欢秀参数量和FLOPs但实际工程里更关心的是显存占用和推理速度。PointNet的显存大头在中间层的点特征缓存PointTransformer的大头在注意力矩阵——你以为你只算局部邻域的注意力就不吃显存了但每个特征平面上都得存一份Q、K、V还要存注意力权重做反向传播叠加起来非常可观。实测在同样的输入规模单卡训练输入8192个点下PointTransformer显存占用大约是PointNet的1.5到2倍。推理速度则要分场景。点云分类这种小规模场景PointTransformer实际上比PointNet快因为它在全局特征抽取上用更少的层数就搞定了前向过程中的采样和邻居查询也少。但到了分割这种大场景、点数众多的时候PointNet反而有优势——它的层次化结构天然适合降采样高分辨率的精细特征只保留在浅层后面的计算都嵌在低分辨率骨架点上PointTransformer则每一层都要维护同样规模的邻域关系计算量随输入点数基本线性往上走很容易把推理时延拉到上千毫秒。所以做自动驾驶点云分割、机器人实时感知这类任务PointNet依然是性价比很高的选择。3. 实操过程与核心环节实现从零跑通一个分类对比实验3.1 数据集准备与预处理细节这里我用一个相对公平的对比方案在ModelNet40上做点云分类然后到S3DIS上做室内语义分割两类任务都测只看一个任务容易得出偏颇结论。ModelNet40是CAD模型采样出的点云干净、密度均匀、类别明确适合用来做模型能力的“探针”S3DIS是真实扫描场景点密度变化大、类别不平衡墙面天花板占了绝大多数点、还有各种遮挡噪声适合做工程落地能力的“试金石”。预处理上有几个容易踩的坑。ModelNet40标准做法是每类随机采样1024个点注意是“随机采样”而非“最远点采样”否则测试集和训练集的数据分布会不一致。归一化方面建议把点云中心平移到原点并缩放到单位球内帮助模型收敛。S3DIS则是按房间处理先把整个房间的点云按0.02m体素降采样再分块输入。分块大小我用的1m乘以1m高度全保留相邻块之间重叠0.5m做数据增强。块过小会丢失全局上下文块过大显存扛不住1m是一个权衡后的经验值。还有一个必须做的步骤评估指标要分开算。分类任务直接用整体准确率和类别平均准确率分割任务要看mIoU和mAcc。mIoU就是每个类别的IoU交并比取平均mAcc是每个类别的像素准确率取平均。类别不平衡的数据集上整体准确率会欺骗你——S3DIS里天花板类别占了接近三成的点模型全预测成天花板整体acc也能到60%以上mIoU却可能不到20%。专注看mIoU别被acc骗了。3.2 环境搭建与核心代码结构环境方面两个模型都是PyTorch生态CUDA算子部分pointnet2_ops和点云分组相关操作需要编译。这里给一个踩过坑的提醒PointNet的原版开源代码在PyTorch 1.10以上版本直接编译大概率报错因为里面用到了一些被移除的API。我自己是直接用OpenPCDet或者Open3D 0.17以上版本里集成的PointNet实现省去编译烦恼。PointTransformer也有官方实现但同样建议找一个适配当前PyTorch版本的fork没必要在原版代码上死磕。整个训练流程的核心结构大概是这样的数据加载阶段把原始点云转换成[N, 3]的张量再做归一化和增强模型前向阶段输入坐标和特征输出每个点的预测或者全局分类损失函数阶段分类用交叉熵分割用带类别权重的交叉熵权重项可以设置成类别频率的倒数再取log这样可以有效缓解类别不平衡问题优化器阶段PointNet用AdamW加cosine退火PointTransformer用AdamW加warmup加线性衰减。这里特别说一下配置读取的问题。两个模型官方仓库都是yaml配置风格建议你自己建一个统一的config系统把输入点数、采样策略、邻域大小、K值、dropout比例、训练epoch数这些全部参数化。我见过太多人改模型结构时直接在代码里硬改参数最后复现不出实验结果这就是配置管理没做到位。3.3 训练细节和超参数记录分类任务上两个模型都训练250个epochbatch size设为32输入点数为1024。PointNet这边学习率峰值设置为1e-3warmup 500步最后50个epoch做线性衰减到1e-5PointTransformer这边学习率峰值5e-4warmup 3000步然后按cosine退火到1e-5。优化器都是AdamWweight decay设1e-4标签平滑设为0.1——这个小技巧对点云分类很有帮助能让两个模型都稳定地涨1个点左右。分割任务上输入是1m乘以1m的块每个块体素降采样到8192个点batch size 8训练100个epoch。分割任务我加了随机旋转、随机平移、随机drop点三种增强旋转范围正负180度drop比例在0到0.2之间随机取。PointNet的dropout设0.5PointTransformer的dropout设0.1——注意力机制本身就有一定的正则化效果dropout设太高反而会欠拟合。关于GPU选择如果你的显存是24GBPointNet分割任务可以比较轻松跑到8192点加batch size 16PointTransformer则建议降到batch size 8或者把点数降到4096。32GB的卡上两者都从容不少。我当初用一张RTX 3090做实验PointNet分类任务一步迭代大概0.4秒PointTransformer大概0.7秒分割任务差距更大PointTransformer一个step要吃3.2秒PointNet只要1.8秒。这个差距在长训练周期里会被放大得很明显。3.4 最终实验结果对比两个任务两种画风在ModelNet40分类上PointTransformer的优势很直接。我们用同样配置各训练三组取平均PointNet整体准确率约89.1%类平均准确率约86.4%PointTransformer整体准确率达到92.3%类平均准确率约89.8%。前者是PointNet的正常水平后者也和论文量级吻合。分类任务里全局特征起主导作用Transformer的全局注意力建模优势在这里完全发挥了出来尤其是飞机、椅子的腿部、桌子的支撑结构这些细部几何PointTransformer能把它们分开PointNet经常会混淆。但到了S3DIS分割任务画风就变了。PointNet的mIoU是54.7%PointTransformer是56.2%差距从分类的3个百分点缩到了1.5个百分点。考虑到PointTransformer训练时间更长、显存占用更高这个提升其实有点尴尬。细分来看PointTransformer在“椅子”“桌子”这类语义清晰、形状规整的类别上精度更高而在“墙面”“地面”这类大面积平整表面上PointNet和高斯过程平滑先验结合的方案甚至精度更高。这背后的原因是平整表面的局部特征本质上是尺度不变的平面拟合问题PointNet的局部聚合方式对这类“局部模板匹配”任务更直接有效注意力机制带来的全局感受野在这里反而是浪费。4. 常见问题与排查技巧实录4.1 模型训练不收敛loss卡住不动这个问题在PointTransformer上非常典型。如果loss在训练开始后几百步内完全不动先别调学习率检查位置编码是否生效。最简单的方法是把位置编码的输出打印出来看数值范围是否合理——如果数值全为零或数量级在1e-9以下基本就是位置编码的初始化有问题。另外确保输入点云坐标已经归一化到类似[-1, 1]的范围如果不归一化位置编码中的相对坐标会变得很大softmax之后注意力权重很容易退化成one-hot分布梯度就传不动了。PointNet这边不收敛的情况少见一些但有一种情况很隐晦用了Grid Sampling代替FPS之后如果grid大小设置过大导致采样后的骨架点数量不足后续的ball query会查询到大量空邻域特征就会全是零或噪声。我的建议是每次调整采样策略后都打印一下每层实际输出的点数和邻域内平均点数如果平均值小于预期的一半那多半是采样或者参数设置有硬伤。4.2 显存突然爆掉加一行代码就能解决PointTransformer训练时最容易出现显存突然溢出的情况尤其是在输入点数变化比较大的分割任务里。一个容易被忽略的原因是PyTorch的autograd机制会在反向传播前缓存所有中间变量而注意力权重矩阵则是显存大户。如果输入点数是8192K32那么每个注意力头都会产生一个8192乘以32的权重矩阵再乘以特征维度和头数积少成多很可观。解法有三种一是打开torch.utils.checkpoint把Transformer的各个block包进checkpoint函数里用重计算换显存代价是前向变慢大约20%二是用混合精度训练Automatic Mixed Precision能把显存占用压缩到原来的60%左右而且对精度几乎没有负面影响——这是我最推荐优先尝试的方案三是降低邻域K值从32降到24显存释放非常明显损失精度通常可接受。PointNet显存溢出则多数发生在中间层的点数过大时可以通过在每一层后加池化或者调整采样比率来解决。4.3 分割结果出现“椒盐噪声”后处理救一手模型分割输出经常会出现一些零星错分的点比如天花板上突然冒出一小片“桌子”。这种情况在PointTransformer上更常见因为它对单个点的分类决策更“独立”邻域一致性约束弱毕竟注意力机制就是让每个点自己决定关注谁。最简单的后处理是用KNN投票做平滑对每个点的预测标签统计它在原始点云里最近K个点的标签取众数作为修正标签。实测K取16能让S3DIS的mIoU提升0.5到1.2个点而且几乎不伤真正细小的物体边界。进阶做法是条件随机场但训练和推理成本较高对于大多数应用场景KNN投票平滑已经足够了。4.4 多卡训练时PointTransformer的batch size要小心如果你用分布式数据并行训练PointTransformer每个GPU上的batch size加起来可能会超过单卡能承受的上限导致频繁的OOM。除了简单地减小全局batch size还可以用梯度累积来“模拟”更大的batch size但注意要相应调整学习率和warmup步数。另外一个更实用的技巧是预处理阶段把输入点数砍到4096数据增强里再做一次随机降采样到4096这样单卡batch size 16也能跑起来效果几乎不降。本质上很多任务的点数冗余度比你想象的高与其硬撑8192点不如控制好数据分布。5. 选型建议你的场景到底该用哪一个综合我的实际经验可以从数据规模和任务复杂度两个维度来考虑选型。如果你的数据量在几千到几万级别的点云片段任务偏分类或者形状理解且训练资源有限PointNet是很稳的选择——收敛快、显存友好、调参成本低能在没那么大的数据上拿到可靠的效果。如果数据量到了几十万甚至百万级别任务是大规模室内场景分割或者自动驾驶点云感知想要在复杂背景里准确分离出细长杆状物、行人的腿、车辆后视镜这类精细结构PointTransformer的全局建模优势就会体现出来值得用多卡资源去换精度。还有一个更实际的建议如果项目周期紧先跑PointNet的baseline。它收敛快、那一套参数相对成熟能在两三天内给你一个合理的底分。然后在这个基础上把模型骨干替换成PointTransformer别的训练配置不变先看涨跌。如果涨了再做精细调参;如果没涨甚至跌了基本可以判断你的任务对全局上下文没那么敏感果断留PointNet,把省出来的算力留给数据清洗和模型集成。我个人在实际项目里的体会是PointTransformer并不是PointNet的全面升级版而是各有各的主场。点云分类这种“世界很大但要总结成一句话”的任务全局注意力几乎是降维打击但在密集预测和实时推理场景下PointNet的高效层级结构依然是很能打的架构。所以别被“新架构必胜”的惯性思维带跑多跑两组你自己的数据比什么结论都靠谱。最后分享一个小技巧如果你要同时对比这两个模型尽量把数据增强、优化器、学习率策略和评估指标都完全对齐只改模型结构。否则你最后对比的其实是训练技巧的差异而不是模型本身的能力。这个坑我替你们踩过了教训很深。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进