3DGS动态干扰过滤:Per-View高斯预测与训练无关方案
2026/9/1 12:34:07 网站建设 项目流程

日常做三维重建或 SLAM 相关项目时,大家很容易遇到一个头疼的问题:场景里总是混入“不该出现”的东西。比如你在马路上拍了一组照片,画面里不停有行人、汽车、自行车穿行;又比如你在景区扫描建筑,前后景里总是有游客“乱入”。这些干扰物在传统多视图几何和 NeRF 重建中会被当成真实场景的一部分,导致重建结果出现浮层、残影或者整体模糊。这几年 3DGS(3D Gaussian Splatting)大火,很多团队转入了高斯泼溅方案,但 Distractor(干扰物)问题依然存在,甚至因为高斯粒子可以“自由生长”,干扰物会更容易被表达成一组额外的高斯体。

在 3DGS 相关论文和社区讨论里,一个很受关注的方向是 Per-View Gaussian Predictions:不对全局模型做额外训练,而是利用逐视图的高斯预测结果,在渲染阶段或重建阶段把干扰物过滤掉。这就是所谓 Training-Free Distractor Filtering。本文不搬运论文原文,也不做实验复现,而是把这条技术路线的原理、与 3DGS 渲染管线的耦合点、核心思路和工程实践建议整理成一篇可以照着理解、照着改造的教程笔记。适合已经跑过 3DGS 训练,想进一步解决动态干扰问题,或者正准备入门 3DGS 实时渲染原理的读者。

在看技术细节之前,先明确一下我们讨论的范围:下面会先带大家速通 3D Gaussian Splatting 的关键原理,包括 3D 高斯的参数化、投影与α合成渲染流程,以及“远→近”排序在 GPU 上的混合方式;然后定义 Distractor Filtering 问题,分析为什么这个问题在 3DGS 上比 NeRF 更难处理;接着重点拆解 Per-View Gaussian Predictions 的核心思路,解释“为什么可以训练时不学习 Mask,也能在推理时过滤掉干扰物”;最后给出工程实现时可以参考的伪代码、命令和避坑建议。

1. 3DGS 核心原理速通

1.1 从点云到 3D 高斯

3D Gaussian Splatting 是一种显式的场景表示方法,它的基本单元是一系列三维高斯分布。每个高斯都携带一组可优化参数:

  • 中心位置 μ:高斯在三维空间中的中心点坐标。
  • 协方差矩阵 Σ:控制高斯在三个方向上的拉伸程度和朝向。
  • 颜色信息:通常用球谐系数(Spherical Harmonics,SH)表达,从不同方向观察颜色会变化。
  • 不透明度 α:控制该高斯对最终像素颜色的贡献权重。

这些高斯点在三维空间中分布,密度高且参数准确的地方表示一个清晰的表面;而如果某个区域是半透明的、模糊的,对应的高斯往往拥有更大的尺度或更低的不透明度。

和传统点云不同,3DGS 不是简单地把点投影到图像上,而是把每个高斯“泼溅”到二维图像平面,再通过光栅化合成最终图像。因为所有高斯都是可微的,训练阶段可以通过梯度下降优化每个高斯的属性。

1.2 3D 高斯的投影与渲染

把三维高斯投影到二维图像平面上,是 3DGS 渲染管线的核心。给定一个相机位姿,我们需要计算每个高斯在当前视角下的二维高斯分布。这一步在数学上可以看作对三维高斯做仿射变换的近似:

[ \Sigma' = J W \Sigma W^T J^T ]

其中 (W) 是世界坐标系到相机坐标系的变换矩阵,(J) 是投影变换的仿射近似雅可比矩阵。

得到二维高斯后,每个高斯会在屏幕上覆盖一个椭圆区域。渲染时,GPU 会按照像素位置判断哪些高斯覆盖了当前像素,再按照深度(视点方向的距离)对这些高斯进行排序,最后执行 α 混合:

[ C = \sum_{i=1}^{N} c_i \alpha_i \prod_{j=1}^{i-1} (1 - \alpha_j) ]

这里的顺序很重要:高斯必须从远到近(far to near)排序,先处理远处的粒子,再层层叠加近处的粒子。这也是视频里经常提到的“3DGS 硬件混合远→近绘制”的来源。之所以能应用硬件加速,是因为 3DGS 的渲染不是逐射线求交,而是逐瓦片(tile)并行处理,现代 GPU 可以高效完成排序和混合。

1.3 关键迭代参数

3DGS 官方实现中有一套迭代和致密化(densification)流程。如果只是跑通默认训练,很多参数可以不动,但理解它们有助于后续改造。

参数默认值作用
iterations30000总迭代次数
position_lr_init0.00016位置初始学习率
position_lr_final0.0000016位置最终学习率
sh_degree3球谐阶数
densify_from_iter500从第 500 次迭代开始致密化
densify_until_iter15000到第 15000 次迭代停止致密化
densification_interval100每 100 次迭代检查一次
opacity_reset_interval3000每 3000 次迭代重置不透明度

可以这样理解参数的作用:前 500 次迭代基本只优化现有高斯的位置、颜色和不透明度,接着进入致密化阶段,系统会定期把梯度较大、覆盖不足的区域拆分出更多高斯。到 15000 次之后,场景结构基本稳定,接下来主要微调颜色和透明度。

了解这些参数后你会发现:3DGS 的训练本质上是一个“不断分配高斯资源”的过程。干扰物在这个机制下很容易被分配大量高斯,因为它们在多视图下不断位移,需要更多粒子去拟合那些偏差很大的观测。这为后面的 Distractor Filtering 埋下了重要伏笔。

2. Distractor Filtering 问题定义

2.1 什么是 Distractor

Distractor 在三维重建里指那些不属于目标场景、但出现在采集图像中的物体。最常见的类型包括:

  • 动态行人、车辆、动物。
  • 风吹动的树枝、水面反光、旗帜等半动态元素。
  • 拍摄过程中由于曝光或视角变化而产生的阴影、高光。
  • 临时堆放的器械、拍摄者自己的影子。

在 NeRF 盛行的时代,Distractor 问题就存在。经典做法是在训练时对这些干扰区域做 Mask,或者使用语义分割模型把行人、车辆剔除掉。但这些方案都需要额外的标注、额外的模型,而且一旦 Mask 不干净,重建边缘会出现明显撕裂。

2.2 为什么 3DGS 对 Distractor 更敏感

3DGS 的高斯基元本质上是一堆“自由粒子”。训练时,如果某个区域在不同视角下颜色不一致,优化器很容易在该区域生成半透明、细长的高斯来“硬凑”颜色。这意味着 Distractor 不仅不会被自动忽略,反而会消耗掉大量内存和显存,拖慢训练速度。

更麻烦的是,干扰物经常出现在相机近处。因为近处物体在图像中占的面积大,致密化机制会把更多高斯分配到近处干扰物上,而真正要重建的背景反而得不到足够的高斯资源。最终效果就是背景细节丢失,干扰物区域出现一层“雾状残影”。

传统上,大家在 3DGS 里处理这类问题有三种路线:

  1. 训练前预处理:用分割模型逐帧提取前景 Mask,把干扰物区域抠掉再训练。
  2. 训练中引入正则化:修改损失函数,让动态或不一致区域的高斯迅速降低不透明度。
  3. 训练后修复:重建完成后,利用深度或语义信息人工编辑高斯。

这三种路线各有问题:方案 1 依赖额外模型和繁琐的 Mask 管线;方案 2 需要精心设计损失权重,容易误伤正确区域;方案 3 只是事后补救。这时“Training-Free”的逐视图高斯预测方案就显得非常有吸引力。

2.3 Training-Free 的含义

Training-Free 并不是说整个系统不需要任何训练,而是说在过滤 Distractor 时,不需要额外训练一个 Mask 模型,也不需要重新训练 3DGS 模型。这个思路的关键是:

  • 利用已经训练好的 3DGS 场景模型。
  • 在推理阶段,对每个视图生成高斯预测结果。
  • 通过跨视图一致性判断某个高斯是否属于干扰物。
  • 渲染时只保留一致的高斯,过滤掉不一致的高斯。

换句话说,我们不是在训练阶段学习“干扰物长什么样”,而是在推理阶段利用多视图几何的约束动态判断“哪些高斯不可信”。

3. Per-View Gaussian Predictions 核心思想

3.1 为什么要做 Per-View 预测

3DGS 全局模型在优化时会把所有视图的信息融合在一起。假如某个区域只在少数视图中出现,其余视图被遮挡,那么该区域的深度的确难以确定。但如果我们在预测阶段保留“每个视图独立看到的高斯分布”,事情会变得简单:

  • 在单一视图上,干扰物和背景都表现为一组二维投影高斯。
  • 多个视图间,背景高斯的投影位置会严格符合多视图几何关系。
  • 干扰物高斯的投影位置在不同视图间会出现明显偏差。

传统全局优化把这种偏差平均掉了。而 Per-View 预测则保留偏差信息,作为过滤干扰物的依据。

打个比方:全局模型像一个人闭上一只眼,用两眼看到的模糊平均值重建场景;Per-View 预测则像先分别记录左眼和右眼看到的图像,再对比两者差异。差异越大的区域,越有可能是动态物体或遮挡边缘。

3.2 预测流程拆解

一个典型的 Per-View Gaussian Predictions 流程可以拆成四步:

  1. 对输入图像序列中的每一帧,运行一次轻量级的高斯预测模块,输出该帧对应的逐像素深度、置信度,或者一组二维高斯参数。
  2. 利用相机位姿,将这些逐视图预测结果反投影到三维空间,得到候选三维高斯。
  3. 对同一三维位置的多视图预测结果做一致性检查。如果多个视图对同一位置的深度和颜色预测一致,则认为该位置可信。
  4. 渲染或重建时,对不一致的高斯降低权重或直接剔除,实现 Distractor Filtering。

这里的核心不是“预测高斯的网络有多强”,而是“跨视图一致性校验”这个机制。因为 Distractor 的典型特征就是不一致:它在不同视角下深度不同、颜色不同,甚至几何完全对不上。

3.3 与显式 Mask 方法的核心区别

显式 Mask 方法可以类比为“在输入端做审查”:训练前先告诉模型哪些区域是干扰物。而 Training-Free 的 Per-View 方法可以类比为“在输出端做质检”:模型先正常预测,系统再检查不同视图之间是否矛盾。

维度Mask 预处理Per-View Gaussian Predictions
需要额外标注需要不需要
需要额外训练需要不需要
在什么阶段生效训练前推理/渲染时
对场景变化的适应能力较好
主要风险Mask 不干净导致重建破损快速运动导致一致性校验失效

这个对比其实反映了 3DGS 工程落地中的一个重要趋势:把“训练阶段的重活”转移到“推理阶段的轻量判断”上。在实时或弱标注场景中,后者往往更实用。

4. 从原理到代码:实践思路解析

4.1 官方 3DGS 训练/渲染流程

在理解 Per-View 预测之前,先确保你有一个能够正常训练和渲染的 3DGS 环境。以 3DGS 官方开源实现为例,常用命令如下。这里的版本需要根据你的项目实际情况调整,本文重点演示配置思路。

安装依赖:

git clone https://github.com/graphdeco-inria/gaussian-splatting --recursive cd gaussian-splatting conda env create --file environment.yml conda activate gaussian_splatting

准备好经过 COLMAP 处理的场景数据后,训练命令为:

python train.py -s /path/to/dataset -m /path/to/output

其中:

  • -s指向数据目录,目录下通常包含imagessparse等子目录。
  • -m指定模型输出目录,训练好的点云、参数文件和中间结果会写在这里。

训练完成后渲染:

python render.py -m /path/to/output

渲染脚本会读取训练好的模型,逐帧生成图像。如果你打开 render.py 源码,会看到它调用了gaussian_renderer中的渲染函数,核心输入是高斯模型和相机参数。

4.2 在渲染阶段加入 Distractor Filtering

我们要改造的位置是渲染阶段。假设我们已经在每个视图中获得了高斯预测的置信度图,那么过滤逻辑可以写成下面这样的伪代码思路。请注意,这是核心思路片段,需要根据你的实际项目结构调整。

# 文件路径:example_filter.py # 本文只是展示思路,需要按实际版本调整 def render_with_filtering(gaussians, viewpoint_camera, confidence_map, threshold=0.5): """ 在渲染阶段根据逐视图置信度过滤 Distractor。 gaussians : 已训练的 3DGS 模型 viewpoint_camera: 当前视角相机 confidence_map : 当前视角下每个像素的置信度,形状为 (H, W) threshold : 置信度低于该值的区域被认为可能是 Distractor """ # 3DGS 渲染前,先为每个高斯计算其在当前图像上的投影位置 # 这里使用官方渲染器的前置逻辑,核心是 project_gaussians means2D, depths, radii = project_gaussians( gaussians, viewpoint_camera, ) # 根据投影坐标采样该高斯的置信度 # 如果二维投影落在低置信度区域,说明该高斯大概率属于 Distractor keep_indices = [] for idx in range(len(gaussians)): x, y = means2D[idx].cpu().numpy() u = int(round(x)) v = int(round(y)) if 0 <= u < confidence_map.shape[1] and 0 <= v < confidence_map.shape[0]: confidence = confidence_map[v, u] else: confidence = 0.0 if confidence >= threshold: keep_indices.append(idx) # 过滤后渲染 filtered_gaussians = filter_gaussians(gaussians, keep_indices) rendered_image = rasterize_gaussians(filtered_gaussians, viewpoint_camera) return rendered_image

这里需要注意:实际项目中不可能逐像素 Python 循环遍历每个高斯,需要对“高斯投影坐标→置信度采样”做向量化。因为 3DGS 场景中的高斯数量动辄几十万甚至上百万,逐顶点循环会非常慢。

更工程化的做法是把置信度图作为纹理传入渲染内核,在 GPU 的 tile 排序阶段直接判断每个高斯是否落在低置信度像素。如果落在低置信度区域,该高斯的 α 值会被手动衰减。这样既能保留高性能渲染,又能达到过滤效果。

4.3 跨视图一致性校验示例

上面只展示了“如何利用置信度图过滤”,但置信度图从哪里来?一个自然的思路是使用跨视图一致性。下面给一个简化示例,演示如何比较两个视图对同一空间点的预测深度:

# 文件路径:consistency_check.py # 思路示例,需要按照实际预测结果调整 import numpy as np def depth_consistency_check(depth_map_1, depth_map_2, T_1_to_2, K, threshold=0.1): """ 检查两个视图之间的深度一致性。 depth_map_1: 视图1预测的深度图 depth_map_2: 视图2预测的深度图 T_1_to_2 : 从视图1坐标系到视图2坐标系的变换矩阵 K : 相机内参 threshold : 相对深度差异阈值 """ h, w = depth_map_1.shape inconsistent_mask = np.zeros((h, w), dtype=bool) u1, v1 = np.meshgrid(np.arange(w), np.arange(h)) # 视图1的像素坐标 ones = np.ones_like(u1) pixels_1 = np.stack([u1 * depth_map_1, v1 * depth_map_1, depth_map_1, ones], axis=-1) # 反投影到视图1相机坐标,再变换到视图2相机坐标 cam_coords_1 = np.linalg.inv(K) @ pixels_1.transpose(2, 0, 1).reshape(3, -1) cam_coords_1 = np.vstack([cam_coords_1, np.ones((1, cam_coords_1.shape[1]))]) cam_coords_2 = T_1_to_2 @ cam_coords_1 # 投影到视图2像素 projected_2 = K @ cam_coords_2[:3, :] projected_2 /= projected_2[2:3, :] u2 = projected_2[0].reshape(h, w) v2 = projected_2[1].reshape(h, w) depth_2_pred = cam_coords_2[2].reshape(h, w) # 采样视图2的真实深度 valid = (u2 >= 0) & (u2 < w) & (v2 >= 0) & (v2 < h) & (depth_2_pred > 0) sampled_depth_2 = np.zeros_like(depth_map_1) sampled_depth_2[valid] = depth_map_2[ np.clip(v2[valid].astype(int), 0, h - 1), np.clip(u2[valid].astype(int), 0, w - 1) ] # 相对深度差异 diff = np.abs(depth_2_pred - sampled_depth_2) / (depth_2_pred + 1e-8) inconsistent_mask = diff > threshold return inconsistent_mask

这段代码的核心思想是:如果视图 1 中某个像素属于静态场景,那么把它反投影到三维空间,再投影到视图 2 的像素位置,应该在视图 2 中找到接近的深度值。如果找不到,或者深度差异很大,说明该像素很大概率属于动态干扰物。

要注意的是,深度图预测本身存在噪声,而且遮挡边缘处即使静态场景也会产生不一致。因此实际实现中需要加入形态学腐蚀、置信度平滑等后处理,避免误删背景边缘。

4.4 训练与预测的两种落地形态

Per-View Gaussian Predictions 在实际工程中可以有两条落地路径:

路径一:轻量深度/残差预测网络 + 3DGS 渲染过滤。先离线训练一个轻量网络,对单张图像预测深度和置信度;在 3DGS 渲染时,利用置信度过滤 Distractor。这个方案的优点是预测网络是现成可微调的,容易部署。

路径二:训练时同时保存多个视图的高斯预测分支,推理时做交叉验证。这种方式更接近标题所示的研究思路,但工程复杂度更高,需要自建网络结构。

如果你只是想在项目中快速过滤动态行人或车辆,路径一通常是性价比最高的。你可以先用现有的单目深度估计模型生成置信度,再在 3DGS 渲染阶段做阈值过滤。整个过程不需要改变训练流程,也不需要重新训练 3DGS,这就是“Training-Free”在工程上的最大价值。

5. 常见问题与排查思路

在实现 Per-View 高斯预测或 Distractor Filtering 时,读者最容易遇到以下几类问题。

问题现象常见原因解决思路
过滤后背景出现大块空洞置信度阈值设置过高,误删了静态区域降低阈值,或对置信度图使用软权重而非硬过滤
动态物体没有被过滤干净干扰物运动速度较慢,跨视图深度差异小结合光流或语义信息辅助判断,不要只依赖深度一致性
渲染帧率严重下降置信度采样在 CPU 端逐高斯循环把置信度采样搬到 GPU 纹理采样中,矢量化处理
边缘区域闪烁遮挡边缘处深度不一致,被误判为 Distractor增加边缘掩膜,对深度差值做中值滤波
多视图深度预测尺度过大不同视图的深度预测尺度不统一用相机位姿和三角化结果对齐深度尺度,或直接回归视差
过滤结果对阈值敏感置信度分布本身不够平滑对置信度图做高斯平滑,使用自适应阈值

排查时建议按照下面顺序执行:

  1. 先可视化置信度图,确认置信度是否真的反映了 Distractor 区域。
  2. 关闭着色器或过滤逻辑,确认渲染本身正常。
  3. 使用固定阈值,检查过滤结果的像素级差异。
  4. 对误删区域标注错误类型,判断是深度一致性还是遮挡边缘问题。

6. 最佳实践与工程建议

6.1 不要过度追求“纯 Trick”

Training-Free 听起来很诱人,但它的有效性高度依赖逐视图预测的可靠性。如果单视图的深度预测本身就不可靠,那么跨视图一致性校验只会放大噪声。工程上建议先用强监督的深度模型作为 backbone,再考虑无监督方案。

6.2 把过滤逻辑放在渲染内核

如果要在实时或半实时系统中使用,过滤逻辑最好直接嵌入光栅化内核,而不是在 PyTorch 外部做 Python 循环。你可以先实现一个 CPU 版本验证效果,再迁移到 CUDA。保持高斯的属性、投影函数接口不变,只修改 α 混合阶段。

6.3 使用软权重代替硬剔除

硬剔除高斯可能导致重建表面出现裂缝。更稳妥的做法是根据置信度对高斯的 α 做一个平滑衰减:

[ \alpha' = \alpha \cdot \text{smoothstep}(threshold, threshold + margin, confidence) ]

这样低置信度高斯不会被完全删除,而是透明度降低,视觉效果更柔和。

6.4 做好日志与指标记录

实践过程中,需要记录几个关键指标:

  • 过滤前后渲染图像的 PSNR/SSIM。
  • 被过滤高斯的数量和占比。
  • 过滤区域的深度置信度分布。
  • 单帧渲染耗时。

通过指标对比,你可以避免“效果看起来变干净了,但定量指标反而下降”的陷阱。

6.5 注意安全边界

如果要把 Distractor Filtering 部署到线上巡检、自动驾驶、智慧城市等场景,请务必注意几个边界条件:

  • 方法不能用于人为隐藏真实目标,例如过滤掉行人后造成安全隐患。
  • 对动态目标的性质判断要谨慎,不要无条件视为干扰物。
  • 涉及生产环境变更前,需要在测试集上充分验证,并保留人工抽检流程。

技术本身是中性的,但过滤语义需要根据业务定义清楚:哪些动态目标是“干扰”,哪些是“必须关注的目标”。这个定义一旦错误,算法越有效,后果就越严重。

6.6 推荐的项目目录结构

一个可用于迭代的实验工程可以这样组织:

project/ ├── configs/ # 实验配置 │ ├── model.yaml │ └── filter.yaml ├── data/ # 数据集 ├── gaussian_splatting/ # 第三方 3DGS 代码 ├── modules/ │ ├── depth_predictor.py # 单视图深度/置信度预测 │ ├── consistency.py # 跨视图一致性校验 │ └── filter.py # 渲染过滤核心逻辑 ├── scripts/ │ ├── train_gaussians.sh │ ├── run_filter.py │ └── eval_metrics.py ├── outputs/ │ ├── model/ # 训练好的高斯模型 │ └── filtered_render/ # 过滤后渲染结果 └── README.md

这种拆分方式的好处是:深度预测、一致性校验、过滤渲染三个环节可以独立替换和测试。

7. 总结与下一步学习建议

本文围绕“Per-View Gaussian Predictions Enable Training-Free Distractor Filtering”这个方向,梳理了 3DGS 的核心渲染原理、Distractor Filtering 的问题定义,以及基于逐视图预测的过滤思路。重点想让大家明白三件事:第一,3DGS 的远→近混合渲染为逐像素置信度过滤提供了天然入口,不需要改动整个重建框架;第二,Distractor 的本质特征是跨视图不一致,因此一致性校验比语义 Mask 更通用;第三,Training-Free 不等于零成本,它把从训练阶段省下的成本转移到了推理阶段的预测和校验上,工程上需要平衡。

下一步,建议你从三个方向继续深入。如果你还没跑通 3DGS 官方代码,先把训练和渲染流程完整跑一遍,重点看 render.py 里高斯属性如何进入渲染内核;如果你已经能渲染,试着在渲染阶段加一个简单的“置信度乘以 α”的操作,观察对动态物体的影响;如果你对算法研究更感兴趣,可以读一读 3DGS 原论文的致密化公式,思考如何在高斯分裂和剪枝时融合跨视图一致性信息。

最后提醒一点:3DGS 相关的论文和开源代码迭代非常快,本文中的命令、参数和伪代码只是常见思路,落地时请参考你使用的具体版本。实际项目中优先关注两个风险点:一是显存占用,二是过滤逻辑对硬件的依赖。如果本文对你有帮助,可以收藏备用,也欢迎在评论区分享你在 Distractor Filtering 实践中的踩坑经验。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询