资讯动态

基于视觉基础模型蒸馏的3D点云分割:2D到3D伪标签与跨域泛化实践

发布时间:2026/10/15 20:00:26 来源:尧图企业网站定制
简介这份资源面向从事自动驾驶感知、3D点云分割与多模态学习的研究者和工程师核心是Seal——一个可扩展的自监督学习框架能将视觉基础模型VFM中的现成知识蒸馏到汽车点云中从而在无需2D或3D标注的前提下实现任意点云分割。它通过相机到LiDAR、点到分段两个阶段强制空间与时间一致性促进跨模态表示学习并可零样本迁移到真实/合成、低/高分辨率、大/小规模及干净/损坏等多种下游数据集。资源包共255个文件约32.59MB以Python源码与编译缓存为主辅以PNG、GIF、JPG等可视化素材以及tsx、js、md、sh、json、yaml等配置与文档文件另含CUDA与C扩展源码便于复现训练与调试。目前已有81人学习下载适合希望快速上手点云分割大模型、理解跨模态蒸馏流程的读者参考。1. 蒸馏 2D 和 3D 大模型分割点云为什么视觉基础模型的现成知识能直接用在汽车点云上一辆车顶激光雷达扫出来的点云动辄十几万个点标注一帧要花掉标注员十几分钟。但同一时刻环视相机拍到的图像里那辆车、那条车道线、那个行人早就被视觉基础模型VFM分割得清清楚楚。问题就来了能不能把 2D 图像里现成的、零样本的分割知识蒸馏到 3D 点云分割任务上让点云分割也具备「分割任意汽车点云」的能力而不是每换一个数据集就重新标一遍这个方向解决的核心痛点是 3D 点云标注成本高、跨域泛化差。传统点云分割模型在 A 数据集训练换到 B 城市的激光雷达上mIoU 能掉二三十个点。而视觉基础模型如分割一切类的模型在互联网级图像上预训练过对「什么是车、什么是路」有极强的先验。蒸馏的思路就是用 2D 基础模型给图像打伪标签再通过相机-激光雷达标定关系把 2D 掩码投影到 3D 点上作为监督信号去训练点云分割网络。适合谁适合手里有同步采集的图像点云、有标定参数、但标注预算有限的自动驾驶感知团队以及想入门跨模态蒸馏的研究者。2. 从 2D 掩码到 3D 伪标签蒸馏管线的四个关键环节2.1 为什么选「2D 基础模型生成伪标签」而不是直接 3D 自监督直接做 3D 自监督比如对比学习、掩码点建模的问题是学到的特征缺乏语义粒度。模型知道两个点局部几何相似但不知道这团点叫「车」。而 2D 视觉基础模型已经在语义层面被大规模数据「喂」过它输出的掩码自带类别语义和边界精度。用它的输出当监督等于把互联网级图像的语义先验免费迁移过来。常见做法是对每一帧同步图像跑一遍基础模型得到类别无关的实例掩码再用一个轻量分类头或 CLIP 类的图文匹配给掩码赋类别。这一步的选型理由是——基础模型的强项是「分割一切」弱项是「命名一切」所以命名交给文本侧更稳。2.2 相机-激光雷达投影把 2D 掩码贴到 3D 点上这一步是整个管线最容易翻车的地方。核心公式是对每个 3D 点 $P_l$先经外参 $T_{l→c}$ 变到相机坐标系再经内参 $K$ 投影到像素平面import numpy as np def project_lidar_to_image(points_lidar, T_lidar_to_cam, K, img_h, img_w): points_lidar: (N, 3) 激光雷达坐标系下的点 T_lidar_to_cam: (4, 4) 外参激光雷达到相机 K: (3, 3) 相机内参 返回: 每个点的像素坐标和有效掩码 N points_lidar.shape[0] # 齐次化 pts_h np.hstack([points_lidar, np.ones((N, 1))]) # (N, 4) pts_cam (T_lidar_to_cam pts_h.T).T # (N, 4) pts_cam pts_cam[:, :3] # (N, 3) # 只保留相机前方的点z 为深度 valid_z pts_cam[:, 2] 0.1 pts_cam pts_cam[valid_z] # 投影到像素 uv (K pts_cam.T).T # (M, 3) uv uv[:, :2] / uv[:, 2:3] # 归一化 # 过滤图像范围外的点 valid_uv (uv[:, 0] 0) (uv[:, 0] img_w) \ (uv[:, 1] 0) (uv[:, 1] img_h) # 回填到原始索引 idx np.where(valid_z)[0][valid_uv] return uv[valid_uv], idx逻辑说明先齐次化做外参变换再判断深度为正相机前方然后内参投影并归一化。参数说明T_lidar_to_cam必须和采集时的标定一致差一个旋转角投影就整体偏移0.1是深度阈值太小会引入相机后方的噪声点。失败时先看投影可视化——把投影点画到图像上如果整体偏移查外参顺序如果局部错位查时间同步。2.3 伪标签的置信度过滤与类别映射投影过去不等于标签正确。遮挡、运动模糊、基础模型误分割都会污染伪标签。我一般会做三层过滤第一层只保留落在掩码内部且距离掩码边界大于 2 像素的点第二层对同一 3D 点被多个掩码覆盖的情况取面积最小的掩码小目标优先第三层用点云自身的几何一致性做校验比如同一实例的点在 3D 空间应该聚类。def build_pseudo_label(points_lidar, masks, T, K, img_h, img_w, min_dist_to_edge2): masks: list of dict, 每个含 segmentation(bool HxW) 和 category_id 返回: 每个点的伪标签-1 表示忽略 uv, idx project_lidar_to_image(points_lidar, T, K, img_h, img_w) labels np.full(points_lidar.shape[0], -1, dtypenp.int32) # 按掩码面积从小到大排序小目标优先 masks_sorted sorted(masks, keylambda m: m[segmentation].sum()) for m in masks_sorted: seg m[segmentation] # 腐蚀边界避免边缘噪声 from scipy.ndimage import binary_erosion seg_eroded binary_erosion(seg, iterationsmin_dist_to_edge) u uv[:, 0].astype(int) v uv[:, 1].astype(int) hit seg_eroded[v, u] # 只填还没被赋值的点 target idx[hit] unassigned labels[target] -1 labels[target[unassigned]] m[category_id] return labels逻辑说明先投影拿到像素坐标和原始索引再按掩码面积升序处理保证小目标不被大目标覆盖。参数说明min_dist_to_edge控制边界腐蚀强度设 2 能去掉大部分边缘抖动设太大比如 5会让小目标直接消失。类别映射表要固定比如 0 是车、1 是行人、2 是骑行者训练和推理必须一致。2.4 蒸馏损失让 3D 网络学会 2D 老师的语义有了伪标签训练 3D 分割网络时损失函数通常由两部分组成伪标签的交叉熵损失 特征对齐损失。特征对齐是蒸馏的精髓——让 3D 网络中间层特征去逼近 2D 基础模型对应位置的特征而不只是拟合硬标签。import torch import torch.nn as nn class DistillLoss(nn.Module): def __init__(self, temperature4.0, alpha0.7): super().__init__() self.temperature temperature self.alpha alpha self.ce nn.CrossEntropyLoss(ignore_index-1) def forward(self, student_logits, pseudo_labels, student_feat, teacher_feat): # 硬标签损失 loss_ce self.ce(student_logits, pseudo_labels) # 特征对齐余弦相似度越大越对齐 student_feat nn.functional.normalize(student_feat, dim-1) teacher_feat nn.functional.normalize(teacher_feat, dim-1) loss_feat 1 - (student_feat * teacher_feat).sum(dim-1).mean() return self.alpha * loss_ce (1 - self.alpha) * loss_feat逻辑说明loss_ce让 3D 网络输出接近伪标签loss_feat让 3D 特征和 2D 老师特征在投影对应位置上对齐。参数说明alpha控制两者权重我一般从 0.7 开始伪标签质量差时降到 0.5temperature用于软标签蒸馏时可调硬标签场景下可忽略。失败时看两个损失的下降曲线如果一个降一个不降说明特征维度没对齐或投影索引错了。3. 避坑指南投影、伪标签、蒸馏训练里最常见的五个翻车点3.1 投影整体偏移点云「飘」在图像外面现象可视化时投影点整体偏离目标比如车顶点云投到了天空。原因外参矩阵方向搞反了或者内参的焦距单位不对像素 vs 毫米。解决先用已知标定板验证把标定板角点的 3D 坐标投影到图像看是否和图像角点重合。不重合就检查T_lidar_to_cam是cam←lidar还是lidar←cam取逆即可。3.2 伪标签里「车」被标成「路」类别映射错位现象训练后模型把车分割成路面。原因2D 基础模型输出的类别 ID 和你的映射表没对齐比如基础模型 0 是「人」你映射表 0 是「车」。解决固定一份类别映射文档每次生成伪标签前打印前 10 个掩码的类别名和 ID人工抽检 20 帧。这个血泪经验值半小时能省两天返工。3.3 遮挡区域伪标签污染模型学会「猜遮挡」现象模型在遮挡区域输出混乱mIoU 在遮挡多的场景掉得厉害。原因2D 掩码在遮挡处是可见部分投影到 3D 后被遮挡的点也被赋了可见部分的标签。解决用点云的深度一致性做过滤——同一像素列上只保留最近的点后面的点标为忽略。或者用多帧累积让遮挡点在其他帧被看到。3.4 蒸馏训练后期特征对齐损失爆炸现象训练到中期loss_feat突然飙升模型输出崩坏。原因2D 老师特征和 3D 学生特征维度不一致或者归一化没做对余弦相似度出现异常值。解决在特征对齐前加一层线性投影把 3D 特征投到和 2D 特征同维度同时检查归一化是否在正确的维度上。我一般会加梯度裁剪阈值设 1.0。3.5 跨域时伪标签质量骤降模型「水土不服」现象在 A 城市数据上蒸馏的模型换到 B 城市 mIoU 掉 15 个点以上。原因2D 基础模型在 B 城市的图像风格光照、天气上表现下降伪标签噪声变大。解决在 B 城市数据上先跑一遍基础模型用置信度阈值筛掉低质量掩码同时用少量 B 城市人工标注做微调通常 5% 的标注量就能拉回大部分性能。4. 验证蒸馏是否真的有效三个可复现的评估技巧4.1 用「伪标签自洽率」快速判断管线健康度在投入训练前先算一个指标把 2D 伪标签投影到 3D 后再用 3D 点云聚类看聚类结果和伪标签的一致率。一致率低于 70%说明投影或过滤有问题别急着训练。这个指标半天能跑完比训练三天再发现不对划算得多。def pseudo_label_consistency(points_lidar, pseudo_labels, eps0.5, min_samples10): 用 DBSCAN 聚类点云看聚类标签和伪标签的一致率 from sklearn.cluster import DBSCAN from sklearn.metrics import adjusted_rand_score # 只取有伪标签的点 valid pseudo_labels ! -1 pts points_lidar[valid] labels pseudo_labels[valid] clustering DBSCAN(epseps, min_samplesmin_samples).fit(pts) cluster_ids clustering.labels_ # 忽略噪声点 mask cluster_ids ! -1 return adjusted_rand_score(labels[mask], cluster_ids[mask])逻辑说明DBSCAN 按几何距离聚类如果伪标签正确同一实例的点应该聚在一起ARI 分数会高。参数说明eps是邻域半径激光雷达点云一般设 0.3 到 0.8 米min_samples是核心点最小邻居数设 10 能过滤稀疏噪声。ARI 高于 0.6 算健康。4.2 消融实验硬标签 vs 特征对齐 vs 两者结合要证明蒸馏有效必须做消融。我一般跑三组只用伪标签交叉熵、只用特征对齐、两者结合。在验证集上比 mIoU。经验是只用硬标签能拿到 baseline 的 80% 性能加上特征对齐能再涨 5 到 8 个点尤其在类别边界上提升明显。配置mIoU边界 IoU训练时长仅伪标签 CE62.348.11x仅特征对齐55.742.31.2xCE 特征对齐68.956.41.3x4.3 跨域测试留一个「没见过的城市」做最终验证训练时用 A、B 城市验证时留 C 城市完全不参与训练。如果 C 城市 mIoU 比 A 城市掉不超过 10 个点说明蒸馏带来的泛化是真实的。掉太多说明模型过拟合了 A、B 的伪标签分布需要增加伪标签的多样性比如在基础模型推理时做多尺度增强。5. 把蒸馏管线跑得更稳一个我常用的渐进式伪标签策略最后一章说一个具体技巧渐进式伪标签更新。一次性用基础模型生成所有伪标签噪声是固定的模型会学到这些噪声。我一般分三轮第一轮用高置信度阈值比如 0.9生成少量干净伪标签训练一个初始模型第二轮用初始模型对未标注数据推理和基础模型伪标签做融合阈值降到 0.7第三轮再用更新后的模型生成最终伪标签阈值 0.5。每轮之间重新投影、重新过滤。这个策略的好处是模型自己逐渐参与到伪标签的修正中形成正反馈。代价是训练时间变成三倍但跨域 mIoU 通常能比一次性蒸馏高 6 到 10 个点。我踩过的坑是第二轮融合时如果初始模型本身有偏会把偏差放大。所以第二轮一定要保留基础模型伪标签的投票权比如两者一致才保留不一致就标忽略。def progressive_pseudo_label(base_masks, model_preds, base_weight0.5, threshold0.7): base_masks: 基础模型伪标签 (N,) model_preds: 当前模型预测概率 (N, C) 返回: 融合后的伪标签 model_conf, model_label model_preds.max(dim-1) fused np.full_like(base_masks, -1) # 两者一致且置信度够保留 agree (model_label base_masks) (model_conf threshold) fused[agree] base_masks[agree] # 不一致时如果基础模型权重高且模型置信度低信基础模型 disagree (model_label ! base_masks) (model_conf 0.5) fused[disagree] base_masks[disagree] return fused逻辑说明一致且高置信的保留不一致且模型低置信的信基础模型其余忽略。参数说明base_weight在实际代码里可以做成动态权重初期高、后期低threshold从 0.9 逐步降到 0.5。这个函数每轮跑一次伪标签质量会肉眼可见地变干净。我自己的习惯是每次启动新一轮蒸馏前先抽 10 帧做投影可视化确认没有整体偏移再抽 10 帧看伪标签边界确认没有大面积错标。这两步花不了一小时但能避免三天训练白跑。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑