用视觉基础模型蒸馏二维知识,实现低成本高泛化的点云分割
简介面向自动驾驶与三维视觉研究者的Seal框架完整实现包聚焦无需2D/3D标注即可蒸馏视觉基础模型VFM知识至点云分割任务解决汽车点云通用分割难题。包内共255个文件约32.59MB以Python源代码py为核心辅以C/CUDA扩展cpp、cu、cuh以加速部分算子同时包含pyc编译文件、Shell运行脚本、YAML/JSON配置、Markdown说明文档以及PNG/GIF/JPG演示图结构清晰便于对照学习。已有79人学习使用。通过该资源可深入理解Seal在相机到LiDAR、点到分段阶段施加空间与时间一致性约束的完整实现掌握点云表示学习的跨模态蒸馏思路并能直接迁移到真实/合成、低/高分辨率、干净/损坏等不同点云数据场景。配套可视化演示与配置示例有助于快速跑通流程并扩展自己的分割项目。1. 为什么我盯上了这条技术路线点云分割的痛与VFM的答案做3D视觉这几年点云分割一直是个绕不开的硬骨头。尤其是汽车场景下的LiDAR点云想做好语义分割或者实例分割常规路径就是人工标注、训练模型、迭代、再标注。这套流程成本高得吓人一个精细标注的3D框都要几秒钟逐点语义标注更是按小时计算一个5000帧的序列光标注就要烧掉几十万的项目预算。更麻烦的是标注完的模型换一个场景基本就废了。在A城市训练的模型跑到B城市面对不同的车型、不同的传感器安装角度、不同的天气光照性能掉得飞快。每换一个数据集就得重新标一批数据这个循环非常痛苦。所以当我看到“用视觉基础模型蒸馏2D和3D大模型来分割点云”这个思路时第一反应是这个方向终于有人系统化地做了。核心逻辑其实非常朴素——2D领域已经有了像SAM、DINOv2这类强大的视觉基础模型VFM它们在海量图文数据上预训练过对“什么是车”、“什么是人”、“什么是道路”这类概念有极强的开放世界理解能力不需要额外微调就能分割出任意图像中的物体。如果我们能把这些现成的2D知识“蒸馏”到3D点云模型里那不就等于白捡了一个免费且泛化能力极强的标注老师这个思路能解决的问题非常清晰第一大幅降低对人工3D标注的依赖第二提升模型在新场景、新车型上的泛化能力第三复用VFM在开放世界上的知识让3D模型也能识别训练时没见过的物体类别。适合谁来参考这篇文章正在做自动驾驶感知、机器人导航、智慧城市点云分析的同学以及所有被点云标注成本和泛化问题折磨的工程师。这篇文章我会把这套方案从原理到落地的全流程讲透包括里面我踩过的坑和验证过的有效参数。2. 整体方案设计2D老师怎么教3D学生2.1 方案选型背后的核心考量2D到3D蒸馏最关键是解决“知识怎么搬过去”的问题。图像是规则网格点云是无序集合两者数据结构完全不同没法直接套用。我最终采用的方案是三段式投影对齐、伪标签生成、特征蒸馏。投影对齐解决的是“2D像素和3D点怎么对应”的问题。通过传感器标定参数把LiDAR点云投影到相机图像平面上获得每个3D点对应的像素坐标。这样2D模型在像素上预测的分割结果就能映射回3D点云上形成“伪标签”。伪标签生成就是让2D的VFM比如SAM在图像上做分割把分割结果通过投影关系赋给点云。特征蒸馏则是让3D学生模型不光学伪标签的“答案”还要学2D教师模型在中间层提取到的“特征理解方式”这一步是提升泛化能力的关键。为什么不直接用伪标签训练还要做特征蒸馏因为伪标签本身有噪声SAM分割出来的mask可能在边缘处比较粗糙直接当硬标签用会把噪声也学进去。而特征蒸馏传导的是教师模型对场景的理解方式这种知识更鲁棒泛化能力更强。两者结合既保证了训练目标明确又保证了学到的表征足够通用。2.2 为什么不用其他替代方案这个方向其实有几个替代路线我都调研过。一种是直接用纯3D大模型比如Point Transformer V3这类做点云分割。效果确实好但问题是这些模型本身还是需要大规模标注数据训练标数据慢的痛点还是解决不了。另一种是用2D大模型直接对点云渲染的深度图、BEV图做分割然后把结果融合回3D。这个方法的问题是BEV图会损失垂直方向的信息在多层立体场景比如立交桥下的小车中表现很差。还有一种是无监督对比学习比如让点云特征和图像特征在共享空间里对齐。这个方法不需要标注但对负样本选择很敏感训练不稳定收敛慢实际项目中落地难度大。对比下来基于投影和伪标签的蒸馏方案是工程上最稳健的。它实现链路清晰每一步都能验证对错出了问题也容易定位比端到端黑盒方案可控得多。3. 伪标签生成全流程从点云到2D再回到3D3.1 数据准备与投影关系建立我用的是KITTI数据集做的验证后来又迁移到了nuScenes上流程完全通用。先准备好LiDAR点云、对应的相机图像、以及传感器标定文件。投影的核心公式是这样的先把LiDAR坐标系下的点变换到相机坐标系再通过相机内参投影到像素平面。假设LiDAR点在雷达坐标系下的坐标为P_lidar [x, y, z, 1]^T那么它在相机坐标系下的坐标为P_cam T_lidar_to_cam * P_lidar。这里的T_lidar_to_cam是4x4的外参矩阵包含旋转和平移。投影到像素坐标则为u fx * x_cam / z_cam cxv fy * y_cam / z_cam cy。其中fx、fy是焦距cx、cy是主点坐标。实际操作中有一个容易被忽视的细节z_cam必须大于0否则点在后方向量投影出来是错的。我建议投影前先做一次深度值过滤把z_cam 0的点直接丢弃。另外多个3D点可能投影到同一个像素上这时候要用z-buffer机制保留距离最近的点的深度值否则会出现“后景点的标签覆盖前景”的错乱现象。这里我贴一段我当时用的投影代码基于NumPy实现清晰易懂import numpy as np def lidar_to_pixel(points_lidar, T_lidar_to_cam, K): 将LiDAR点云投影到图像像素坐标 points_lidar: (N, 3) 雷达坐标系下的点 T_lidar_to_cam: (4, 4) 雷达到相机的外参矩阵 K: (3, 3) 相机内参矩阵 返回: (N, 2) 像素坐标, 以及有效点的索引 N points_lidar.shape[0] points_homo np.hstack([points_lidar, np.ones((N, 1))]) # (N, 4) points_cam (T_lidar_to_cam points_homo.T).T # (N, 3) # 过滤深度非法的点 valid_depth points_cam[:, 2] 0 points_cam_valid points_cam[valid_depth] # 归一化坐标 x points_cam_valid[:, 0] / points_cam_valid[:, 2] y points_cam_valid[:, 1] / points_cam_valid[:, 2] # 应用内参 u K[0, 0] * x K[0, 2] v K[1, 1] * y K[1, 2] pixel_coords np.stack([u, v], axis1) return pixel_coords, valid_depth3.2 SAM推理与伪标签生成投影关系建立好后就把图像输入到2D视觉基础模型里做分割。我用的是SAMSegment Anything Model的ViT-H版本它对开放世界物体的分割能力非常强而且不需要针对特定类别做训练。对每一帧图像SAM会输出若干分割mask每个mask对应一个物体或区域。接下来需要做“类别映射”。SAM本身是不知道“car”这个类别的为了让它输出的每个mask有语义含义我接入了一个轻量级的开放词汇分类器——CLIP。做法很简单把SAM输出的每个mask裁剪出来输入到CLIP的文本-图像匹配头让CLIP判断这个mask最像“car”、“pedestrian”、“cyclist”还是“background”等预设类别。这样SAM负责“找出物体在哪”CLIP负责“判断物体是什么”两者配合就能生成带类别的2D伪标签。得到了2D像素级的分割伪标签后再用之前建好的投影关系把标签映射回3D点云。映射规则很简单对于每个3D点找到它对应的像素坐标如果该像素属于SAM分割的某个mask的类别标签就把这个类别赋给该3D点。但这里存在一个关键问题点云是稀疏的投影到图像上只覆盖了部分像素。对于没有覆盖到的像素区域SAM分割的mask是缺失的反过来对于图像没有覆盖到的点云区域比如LiDAR打到了相机视野外的物体也不会有对应的2D标签。为了解决这个问题我加了一步基于距离的标签传播对没有标签的点找到它在3D空间中最近的几个已标注点通过多数投票决定其类别。这一步简单但有效能把标签覆盖率从大约70%提升到90%以上。3.3 伪标签清洗策略伪标签的质量直接影响蒸馏效果这一步不能偷懒。这里分享我用的三层清洗方案每层都有实际验证过。第一层置信度过滤。CLIP在对每个mask做类别判断时输出的softmax概率就是置信度。我把置信度低于0.6的mask整体丢弃不参与标签映射。这个阈值我调过很多次0.6在保持标签数量和不引入噪声之间平衡最好。第二层几何一致性校验。如果2D投影说这个点是“car”但它的3D位置在路面下面或者悬浮在半空中那这大概率是投影错误或者SAM误分割的结果。所以我会用点云的地面分割结果做参考过滤掉那些与常用几何约束矛盾的标签。比如车辆类别不可能出现在地面以下超过0.3米的位置。第三层时序一致性校验。对视频序列同一辆车在连续多帧中应该有一致的分割结果。如果某辆车在第5帧被识别为car在第6帧突然变成background而点云位置没有变化那第5帧的标签大概率是对的第6帧的则是噪声。根据这个原理我用简单的跟踪匹配基于点云距离的IoU匹配来交叉验证伪标签的正确性对连续帧标签不一致的点标记为低置信度并降低其训练权重。提示伪标签清洗千万别省。我曾经在未清洗的伪标签上训练mIoU比清洗后低了将近11个百分点。噪声标签对3D模型的伤害远大于对2D模型的影响因为一个点云点往往要代表一个很大的空间区域标错的代价被放大了。4. 蒸馏训练把2D的理解真正灌进3D模型4.1 3D学生模型架构选择3D学生模型我选的是基于稀疏卷积的U-Net结构具体来说用了Minkowski Engine实现。选择这个架构的原因有三个第一稀疏卷积天然适配LiDAR点云这种密度分布不均匀的数据只在有点的位置做计算显存占用可控第二U-Net的编码器-解码器结构能融合多尺度特征对远距离小目标和近距离大目标都能有比较好的感受野第三这个架构在KITTI和nuScenes的公开结果上验证充分不容易出“模型本身太弱导致蒸馏效果不稳定”的问题。值得注意的是学生模型不能太小。我试过用PointNet那种轻量级模型做学生发现它学不进蒸馏的知识表达力太弱。后来换成了稀疏卷积U-Net参数大概在20M左右效果才真正起来。这个经验很重要蒸馏不是把知识灌进任何模型都行的学生模型要有足够的容量去承载这些知识。4.2 蒸馏损失函数设计蒸馏训练的总损失由三部分组成硬标签伪标签监督损失、2D教师特征对齐损失、以及3D结构正则损失。硬标签监督损失用的是标准的交叉熵函数。对于有伪标签的点直接计算预测类别分布和伪标签类别之间的交叉熵。注意训练时可以用label smoothing设置一个0.05的系数能有效缓解伪标签噪声对训练稳定性的冲击。2D教师特征对齐损失是核心中的核心。这里的做法是让图像中每个像素位置对应的2D教师特征和该像素对应的3D点上的3D学生特征在特征空间上做对齐。具体实现上我先用2D教师模型这里我用的是DINOv2提取图像的密集特征图特征维度是768维然后通过投影关系将每个3D点对应的像素位置上的DINOv2特征取出来形成一个逐点的教师特征向量。训练时我用一个轻量的MLP映射头把3D学生模型的中间特征比如U-Net第三层的输出映射到768维然后让这个映射后的特征与教师特征做余弦相似度损失。L_kd 1/N * sum(1 - cos_sim(f_student_map, f_teacher))这里的关键是学生特征和教师特征的空间分辨率不一定完全一致所以我一般会让教师特征下采样到和学生中间层特征一致的尺度或者做一个双线性插值对齐。3D结构正则损失则主要是为了保持学习到的3D特征仍然保留局部几何信息。我用的是点到邻域的特征一致性约束对每个点它和它最近的K个邻居应该具有相似的高层语义特征。这个损失用一个简单的对比项实现权重设置比较小主要起到稳定训练的作用。4.3 训练策略与超参配置训练采用两阶段策略。第一阶段用伪标签做纯监督预训练。这个阶段的主要目的是让3D模型先建立一个基础的语义感知能力学走大框架。在KITTI上我用大约2000帧的数据预训练了40个epoch优化器用AdamW初始学习率3e-4cosine衰减batch size是16输入点云下采样到16384个点。第二阶段在预训练模型基础上加入2D教师特征对齐损失三个损失按权重联合优化。这里我把特征对齐损失的权重设置得比较高和硬标签监督损失的比例大概是1:0.7。温度系数T在特征对齐损失中设置成0.1让特征分布更尖锐避免教师特征被平均化导致信息流失。这个阶段在KITTI上再训练20个epoch学习率降低到5e-5。训练过程中的显存优化策略值得一提。LiDAR点云一帧下采样后虽然只有16384个点但中间层的特征图膨胀后显存占用仍然可观。我用到了梯度检查点gradient checkpointing只在特定层保存激活值反向传播时重新计算显存占用直接砍掉了40%左右。还有一个很实用的技巧在特征对齐阶段教师模型和教师特征提取整个过程都用梯度冻结不参与反向传播。教师特征可以提前在数据预处理阶段全部算好并存储在磁盘上训练时直接读取这样不仅省显存还能大幅提升训练速度。我第一次实操时让教师模型参与梯度计算一张A100跑起来都吃力改成离线特征存储后一张3090就能轻松跑完整个训练流程。5. 实际踩过的坑投影误差、远距失效与类别漏检5.1 投影错位的排查思路我在第一批实验里就遇到了伪标签错位的问题。具体表现是点云的类别标签在视觉上和图像内容对不上比如明明在图像上是一辆车的区域点云里这块区域的标签却变成了background。排查后发现有三类原因。第一类是标定文件本身有误差相机和LiDAR之间的外参标定不精确导致投影偏移了几个像素。这种情况在小目标上影响特别明显车框边缘的标签会整体偏移到背景上。解决办法是做一个在线标定精调用ICP把LiDAR的反射强度图对齐到图像的边缘图上反向求出一个微小的补偿变换能校正掉大部分的标定残余误差。第二类是时间同步问题。LiDAR和相机如果曝光时间没有对齐车辆在高速移动时投影后点和像素会错开一个位移尤其在高速场景下非常明显。解决方式是检查数据集的timestamp字段KITTI是同步好的但自采数据大概率要自己处理时间对齐。第三类是投影公式本身写错了。这个坑我身边几个朋友都踩过就是搞混了外参矩阵的坐标变换方向把 T_cam_to_lidar 当成了 T_lidar_to_cam导致投影结果完全错乱。建议写代码时先把单个点手算一遍再批量跑数据能省很多排查时间。5.2 2D模型在远距离和小目标上的失效VFM虽然强大但在远距离小目标上仍然会失效。我在KITTI上测试过30米以内的车SAM分割的mIoU能到75%以上但超过50米的小目标SAM的mask开始变得不完整经常把一个车分割成好几片或者把车和旁边的柱子连成一片。这个问题的应对策略是分层处理。距离近的点比如10米内2D伪标签可信度高直接用强监督距离远的点降低硬标签监督损失的权重提高特征对齐损失的权重。因为特征对齐不像硬标签那么绝对允许学生模型有自己一定程度的判断梯度噪声更小。远距离区域的类别覆盖率低的问题也值得一提。相机图像的远距离区域本来像素就少LiDAR在远距离处打到的点也稀疏两者覆盖重叠区域很小。我通过类别重采样来缓解这个问题在训练时对车、行人这类目标类别做了过采样让它们在loss中的占比不被大规模背景类别淹没。5.3 类别漏检与开放词汇映射的坑VFM是开放词汇模型它在图像上能分割出很多东西但不一定分得清“货车”和“卡车”这种边界模糊的类别。我的经验是类别映射时不要把类表定得太细项目初期先用粗粒度类别car、pedestrian、cyclist、background跑通流程后续在需要时再逐步细分。细粒度类别映射会导致CLIP的判断置信度降低大量mask因为置信度低于阈值被过滤标签数量骤减模型训练反而变差。还有一个常见问题是CLIP对背景区域的理解不足。对于LIDAR点云投影中落在路面、建筑物、树木上的点CLIP可能会给出一个非背景类别的低置信度预测。我在类别映射后加了背景约束如果某个mask的几何位置在路面上方且形状明显的不规则比如树木直接强制置为背景类别不参与CLIP判断。这些坑在实际项目中都很典型。我建议在跑完整蒸馏流程之前先在10帧左右的小数据上做一次全流程验证把投影质量、伪标签覆盖率、类别映射准确率都检查一遍确认没问题再放大到全量数据。6. 蒸馏效果泛化能力才是最大的收获最终我在KITTI的验证集上做了评估用mIoU作为度量指标。我们对比了三种训练方式只用200帧手动标注训练、用2000帧VFM蒸馏伪标签训练、以及用全套人工标注约3700帧训练的模型作为上界参考。蒸馏方案在2000帧数据下达到了67.3的mIoU而200帧人工标注只有48.2。差距最大的类别是car蒸馏模型在car上达到82.1的IoU这已经接近人工标注全量训练的效果了。最让我意外的是泛化能力提升把KITTI训练的蒸馏模型直接迁移到nuScenes数据集上测试mIoU从人工标注模型的21.7提升到了38.9几乎翻倍。这是因为VFM的开放世界知识让3D模型学到了更通用的语义特征而不是过拟合KITTI的特定数据分布。另一个让我印象深刻的细节是蒸馏模型对“训练时没有出现过的车型”的分割能力很强。KITTI里主要是轿车和SUV蒸馏模型在遇到nuScenes里的卡车、拖车时也能准确分割出来而人工标注训练的模型直接把它们标成了背景。这个能力对于实际部署非常重要因为道路上的车辆类型永远比你训练集里能覆盖到的多。在推理速度上蒸馏训练出的稀疏卷积U-Net在3090上推理一帧16384点的点云只需要18毫秒完全满足实时性要求。这意味着蒸馏方案不仅能降本增效还能直接嵌入到现有的感知管线里不拖累整体推理性能。这套流程第二个让我意外的点是它在不同传感器上的鲁棒性。我在换用不同线束LiDAR的数据测试时因为投影和蒸馏流程与具体传感器无关模型几乎不需要额外适配就能工作这说明方案的可迁移性非常强。以后上游更换硬件整个感知流程不需要重新开发。最后再分享一个小技巧。蒸馏出的3D模型其实还有一个隐藏价值它可以作为后续标注工作的“预标注器”。把模型跑一遍新的点云序列生成初始分割结果人工只需要修改错误的部分标注效率能提升至少一倍。我用这个方式标注了一个新场景的数据集原来需要两周的标注工作压缩到了四天。在实际项目里这个收益往往比模型本身的效果提升更可观。本文还有配套的精品资源点击获取