GaussDet:让 2D 开放词汇检测器为 3D 高斯做语义定位
一句话总结
GaussDet 把”给 3D 高斯场景打语义标签”这件事,从”把 CLIP 特征硬塞进每个高斯里”换成了”用现成的 2D 开放词汇检测器在多个视角投票”,不仅能做开放词汇分割,还能零样本支持”桌子左边那把椅子”这种指代表达定位(referring expression grounding)。
为什么这个问题重要?
3D Gaussian Splatting(3DGS)已经成为新视角合成的事实标准之一:渲染快、质量高、可编辑性强。但一个纯几何/外观的 3DGS 场景是”哑巴”的——它知道每个点的颜色和位置,却不知道”这是什么”。在具身智能(embodied AI)、机器人抓取、AR 标注这些场景里,我们需要的是”把杯子拿过来”而不是”渲染出杯子所在位置的像素”。这就要求场景具备开放词汇的语义理解能力,并且最好能支持自然语言里更复杂的指代表达,而不只是简单名词。
现有主流方案(如 LangSplat、Gaussian Grouping)的做法是把 CLIP 的高维视觉-语言特征蒸馏进每个高斯的属性里,渲染时插值出像素级的语言特征,再和文本编码做相似度匹配。这条路有两个绕不开的坑:
- 实例分组依赖弱监督:要么需要预先指定实例数量,要么依赖 SAM 等工具做自底向上分组,分组噪声会直接污染语义。
- CLIP 的语义粒度太粗:CLIP 擅长”这是一张猫的图片”这类全局语义匹配,但很难处理”靠近窗户的那张椅子”这种需要空间关系推理的指代表达。
GaussDet 的核心创新在于:不再依赖稠密的 CLIP 特征蒸馏,而是直接调用离散的、带指代定位能力的 2D 开放词汇检测器(如 Grounding DINO 一类模型),把检测器在多视角下的判断投票聚合到 3D 实例上,用聚合本身作为去噪手段。
背景知识
3D 表示方式怎么选
| 表示 | 优点 | 缺点 |
|---|---|---|
| 点云 | 直接、稀疏高效 | 缺少连续表面信息 |
| 体素/隐式(NeRF) | 连续、可微 | 渲染慢,编辑困难 |
| 3D Gaussian Splatting | 渲染快(实时)、显式、可编辑 | 表示离散,实例边界天然模糊 |
3DGS 用一堆带位置、协方差、颜色、不透明度的各向异性高斯椭球表示场景,渲染时按深度排序做 alpha blending。这个”显式 + 可附加属性”的特性,正是语言蒸馏类方法能给每个高斯挂上一个特征向量的原因——也是 GaussDet 给每个高斯挂”实例特征”而非”语言特征”的基础。
现有语言蒸馏方法回顾
LangSplat、Gaussian Grouping 这类方法本质上是:
\[F(\mathbf{p}) = \sum_{i \in N} f_i \, \alpha_i \prod_{j=1}^{i-1}(1-\alpha_j)\]这和 3DGS 原始的颜色渲染公式形式完全一致,只是把颜色 $c_i$ 换成了高维语言特征 $f_i$。问题在于 $f_i$ 要么是 CLIP 特征本身(维度高、训练慢、需要 PCA 压缩),要么是依赖 SAM 分组得到的实例 ID 编码——一旦 SAM 在某个视角下分错了,错误会被蒸馏进场景里,难以靠单视角自我纠正。
开放词汇 2D 检测器
像 Grounding DINO、OWL-ViT 这类模型,输入图片 + 文本(可以是名词,也可以是带空间关系的指代表达),直接输出对应的检测框,并且经过大规模图文对训练,具备较强的短语定位(phrase grounding)能力。GaussDet 的思路是:与其把这种能力”蒸馏”进 3D 场景,不如在推理时多视角调用它,再把结果投影回 3D。
核心方法
直觉解释
把场景想象成一群学生在不同角度观察同一个物体,每个学生(视角)独立用检测器给出判断:”这块区域是椅子,置信度 0.8”。GaussDet 做的事情是:
- 先把高斯分成若干”3D 实例组”(不依赖语义,只是几何/外观上的聚类);
- 把每个实例组投影渲染到所有可见视角;
- 在每个视角上跑 2D 开放词汇检测器,看检测框和渲染出来的实例区域重叠多少;
- 把所有视角的”投票”汇总成一个该实例的标签分布——这就是论文里的 View-Aggregated Semantic Label Distribution(VASD)。
多视角投票本质上是一种集成(ensemble)去噪:单个视角检测器可能因遮挡、角度问题判断失误,但只要多数视角判断一致,错误就会被平均掉。
数学细节
实例特征渲染:和颜色渲染同构,每个高斯 $i$ 附带一个低维实例嵌入 $e_i$(不是 CLIP 特征,只用于做 3D 聚类):
\[E(\mathbf{p}) = \sum_{i \in N} e_i \, \alpha_i \prod_{j=1}^{i-1}(1-\alpha_j)\]通过对比学习(同一 2D mask 内的像素嵌入拉近,不同 mask 拉远)训练 $e_i$,再聚类得到离散的 3D 实例划分 ${G_k}$。
VASD 聚合:对实例 $k$,在视角集合 $\mathcal{V}_k$(实例可见的视角)上聚合标签 $\ell$ 的票数:
\[\text{VASD}_k(\ell) = \frac{1}{Z_k}\sum_{v \in \mathcal{V}_k} w_v \cdot \text{IoU}\!\left(M_k^v, B_\ell^v\right) \cdot s_\ell^v\]其中 $M_k^v$ 是实例 $k$ 在视角 $v$ 下的渲染掩码,$B_\ell^v$ 是检测器给出的标签 $\ell$ 的检测框,$s_\ell^v$ 是该检测的置信度,$w_v$ 是视角可见度权重(越正对、面积越大权重越高),$Z_k$ 归一化。
查询时:
- 简单名词查询:直接在 VASD 上取 $\arg\max_\ell \text{VASD}_k(\ell)$ 做开放词汇分割;
- 指代表达查询(如”桌子左边的椅子”):把整句话直接喂给检测器的 grounding 接口(检测器本身支持),在多视角下重复上面的投票流程,不需要重新训练,这是”零样本扩展”的关键。
Pipeline 概览
多视角RGB+位姿 → 3DGS重建(+实例嵌入属性)
→ 渲染实例特征图 → 3D聚类得到实例组 {G_k}
→ 渲染每个实例组到所有可见视角
→ 每视角跑开放词汇检测器(简单查询/指代表达)
→ IoU加权投票聚合 → VASD_k
→ 查询时取argmax或重复指代表达流程
实现
下面是根据论文思路编写的最小化示例代码,用于理解算法骨架,并非论文官方实现。摘要中没有公开代码仓库链接。
环境配置
pip install torch torchvision open3d numpy
# 实际工程中还需要 gsplat / diff-gaussian-rasterization 做高斯渲染
# 以及一个开放词汇检测器,如 Grounding DINO 的推理接口
1. 高斯实例嵌入的对比训练
import torch
import torch.nn.functional as F
def instance_contrastive_loss(rendered_embed, sam_mask_ids):
"""
rendered_embed: (H, W, D) 渲染出的实例嵌入图
sam_mask_ids: (H, W) 同一张图上 SAM 给出的 2D mask id(仅用于训练监督)
"""
feats = rendered_embed.reshape(-1, rendered_embed.shape[-1])
ids = sam_mask_ids.reshape(-1)
feats = F.normalize(feats, dim=-1)
# 随机采样像素对,避免 H*W 平方级计算
idx = torch.randint(0, feats.shape[0], (4096,))
a, b = feats[idx], feats[idx[torch.randperm(4096)]]
same = (ids[idx] == ids[idx[torch.randperm(4096)]]).float()
sim = (a * b).sum(-1) # cosine similarity
# 同实例拉近,不同实例推远(margin=0.2)
loss = same * (1 - sim) + (1 - same) * F.relu(sim - 0.2)
return loss.mean()
2. 3D 实例分组(聚类)
from sklearn.cluster import DBSCAN
def group_gaussians(instance_embed):
"""
instance_embed: (N, D) 每个高斯学到的实例嵌入
返回每个高斯所属的 3D 实例 id,-1 表示噪声/未分组
"""
embed_np = F.normalize(instance_embed, dim=-1).detach().cpu().numpy()
clustering = DBSCAN(eps=0.15, min_samples=20, metric="cosine").fit(embed_np)
return torch.from_numpy(clustering.labels_)
3. 多视角语义投票聚合(VASD,核心贡献)
import numpy as np
def aggregate_vasd(instance_masks_per_view, detections_per_view, view_weights):
"""
instance_masks_per_view: dict[view_id] -> {instance_id: bool_mask(H,W)}
detections_per_view: dict[view_id] -> list of (label, box_mask, score)
view_weights: dict[view_id] -> float,越正对/面积越大权重越高
"""
votes = {} # instance_id -> {label: accumulated_score}
for view_id, inst_masks in instance_masks_per_view.items():
dets = detections_per_view.get(view_id, [])
w_v = view_weights[view_id]
for inst_id, mask in inst_masks.items():
votes.setdefault(inst_id, {})
for label, box_mask, score in dets:
inter = np.logical_and(mask, box_mask).sum()
union = np.logical_or(mask, box_mask).sum() + 1e-6
iou = inter / union
if iou > 0:
votes[inst_id][label] = votes[inst_id].get(label, 0.0) + w_v * iou * score
# 归一化为分布 VASD_k(label)
vasd = {}
for inst_id, label_scores in votes.items():
total = sum(label_scores.values()) + 1e-6
vasd[inst_id] = {l: s / total for l, s in label_scores.items()}
return vasd
4. 查询接口
def query_open_vocab(vasd, query_label):
return [inst for inst, dist in vasd.items()
if dist.get(query_label, 0) == max(dist.values(), default=0) and query_label in dist]
def query_referring(scene_renderer, detector, instance_groups, expression):
"""
指代表达查询:直接把整句表达喂给检测器的 grounding 接口,
在多视角下复用 aggregate_vasd 的投票逻辑,无需重新训练。
"""
masks_per_view = scene_renderer.render_instance_masks(instance_groups)
dets_per_view = {v: detector.ground(img, expression)
for v, img in scene_renderer.get_views()}
vasd = aggregate_vasd(masks_per_view, dets_per_view, scene_renderer.view_weights)
return max(vasd, key=lambda k: sum(vasd[k].values()))
3D 可视化
import open3d as o3d
def visualize_instance(gaussian_centers, instance_ids, target_id):
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(gaussian_centers)
colors = np.tile([0.7, 0.7, 0.7], (len(gaussian_centers), 1))
colors[instance_ids == target_id] = [1.0, 0.2, 0.2] # 高亮目标实例
pcd.colors = o3d.utility.Vector3dVector(colors)
o3d.visualization.draw_geometries([pcd])
实际效果是:选中实例后,对应的高斯点云在 3D 空间中以红色高亮显示,可以直接旋转视角检查分割边界是否贴合物体几何。
实验
数据集说明
- LeRF-OVS:LeRF 论文配套的开放词汇分割基准,场景含复杂物体和长尾类别,常用于检验 CLIP 蒸馏类方法。
- ScanNet:室内真实扫描数据集,带稠密语义标注,适合评估开放词汇分割在真实噪声数据下的鲁棒性。
- Ref-LeRF:论文为评估指代表达定位新构建/扩展的基准,标注形式是自然语言指代表达 + 对应 3D 实例,而非简单类别名。
这三个数据集的共同点是都需要多视角图像 + 相机位姿,获取门槛和普通 3DGS 重建一致(手机环绕拍摄 + COLMAP 位姿估计即可),不需要额外的语言标注(推理阶段用现成检测器,不需要为每个场景标注语言数据)。
定量评估
摘要中明确给出的硬指标是:在严格零样本设置下,指代表达定位任务相比已有方法取得 16.7% 的 mIoU 提升。下表结构供参考,具体绝对数值需以论文正文表格为准:
| 方法 | 开放词汇分割 mIoU | 指代定位 mIoU(零样本) | 备注 |
|---|---|---|---|
| LangSplat | 中等 | 不支持/弱 | 依赖 CLIP 蒸馏 |
| Gaussian Grouping | 中等 | 不支持/弱 | 实例分组依赖 SAM |
| GaussDet(本文) | 持续提升 | +16.7%(相对最佳基线) | 检测器直接做指代定位 |
定性结果
论文报告的典型现象是:在物体密集、相互遮挡较多的场景里,CLIP 蒸馏类方法容易出现”语义渗透”——相邻物体的语言特征互相污染,边界模糊;而 GaussDet 因为投票是基于离散检测框的 IoU,实例边界更干净,但代价是依赖前置的 3D 聚类质量——如果聚类把两个物体并成一组,再好的投票也救不回来。
工程实践
实际部署考虑
- 实时性:3DGS 渲染本身可以做到实时(数十到上百 FPS),但 GaussDet 的语义查询阶段需要对多个视角逐一跑一次 2D 检测器推理,这部分不是实时的,更适合”离线建库 + 在线查询缓存结果”的模式,而不是每帧实时跑检测器。
- 硬件需求:3DGS 训练/渲染一张消费级 GPU(如 RTX 3090/4090,24GB 显存)足够;2D 检测器(Grounding DINO 等)额外占用显存,多视角批量推理建议单独排队执行,避免和高斯渲染抢显存。
- 内存占用:每个高斯额外挂一个低维实例嵌入(如 16~32 维)相比挂高维 CLIP 特征(512/768 维)省内存得多,这是相对 LangSplat 类方法的一个工程优势。
数据采集建议
- 场景需要足够的视角覆盖度,尤其是目标物体的多个侧面都要被拍到——投票机制依赖多视角一致性,单视角覆盖不足会让 VASD 退化成”单视角结果”,丧失去噪能力。
- 避免强烈光照变化导致检测器在不同视角给出不一致的判断,这会直接稀释票数。
常见坑
- 3D 聚类把多个物体并成一组 → 后续投票无法修正语义;建议适当调小 DBSCAN 的
eps或换用基于密度自适应的聚类,并对聚类数量做合理性检查。 - 检测器在小物体/远距离视角误检 → 用渲染掩码面积过滤掉过小、过于倾斜的视角贡献,避免低质量视角主导投票。
- 指代表达涉及场景级关系(”离门最近的箱子”) → 单帧 2D 检测器通常理解不了全局空间关系,这类查询效果会明显下降,需要额外的几何后处理而不能完全依赖检测器本身。
什么时候用 / 不用?
| 适用场景 | 不适用场景 |
|---|---|
| 静态室内/物体场景,多视角覆盖充分 | 动态场景、视角覆盖稀疏 |
| 需要支持自然语言指代表达查询 | 只需要固定类别的语义分割(专用分割模型更划算) |
| 物体边界清晰、遮挡适中 | 物体高度堆叠粘连,3D 聚类难以分离 |
| 离线建图 + 在线查询的应用模式 | 需要逐帧实时语义理解(如高速机器人避障) |
与其他方法对比
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| LangSplat | 像素级连续语言特征,查询灵活 | CLIP 语义粒度粗,无法做指代定位 | 简单开放词汇分割 |
| Gaussian Grouping | 实例分组与重建联合优化 | 依赖 SAM 分组质量,预设实例数敏感 | 实例数量已知或可控的场景 |
| GaussDet(本文) | 复用强大 2D 检测器能力,支持零样本指代定位,显存友好 | 推理阶段需多次调用检测器,依赖前置 3D 聚类质量 | 需要复杂语言查询的具身智能/AR 场景 |
我的观点
GaussDet 体现的是一个挺务实的工程思路:与其费力把一个能力(CLIP 的语义理解)蒸馏进 3D 表示里再损失掉一部分精度,不如在推理时直接复用更强、更新更快的 2D 基础模型,3D 重建只负责提供几何一致性和多视角聚合的”骨架”。这种”3D 提供结构,2D 模型提供语义”的分工,未来大概率会随着 2D 检测/分割基础模型继续变强而持续受益,不需要重新训练 3D 部分。
但它也没有摆脱开放词汇 3D 理解的根本难题:3D 实例分组质量始终是上限,投票聚合只能减噪、不能纠正系统性的分组错误;而涉及全局空间关系的复杂查询,单帧 2D 检测器的理解力依然有限。距离真正可靠的”自然语言操控 3D 场景”,还需要在 3D 几何推理和语言模型之间做更深的结合,而不只是多视角投票这一层去噪。
Comments