
日常做三维重建或 SLAM 相关项目时大家很容易遇到一个头疼的问题场景里总是混入“不该出现”的东西。比如你在马路上拍了一组照片画面里不停有行人、汽车、自行车穿行又比如你在景区扫描建筑前后景里总是有游客“乱入”。这些干扰物在传统多视图几何和 NeRF 重建中会被当成真实场景的一部分导致重建结果出现浮层、残影或者整体模糊。这几年 3DGS3D 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 HarmonicsSH表达从不同方向观察颜色会变化。不透明度 α控制该高斯对最终像素颜色的贡献权重。这些高斯点在三维空间中分布密度高且参数准确的地方表示一个清晰的表面而如果某个区域是半透明的、模糊的对应的高斯往往拥有更大的尺度或更低的不透明度。和传统点云不同3DGS 不是简单地把点投影到图像上而是把每个高斯“泼溅”到二维图像平面再通过光栅化合成最终图像。因为所有高斯都是可微的训练阶段可以通过梯度下降优化每个高斯的属性。1.2 3D 高斯的投影与渲染把三维高斯投影到二维图像平面上是 3DGS 渲染管线的核心。给定一个相机位姿我们需要计算每个高斯在当前视角下的二维高斯分布。这一步在数学上可以看作对三维高斯做仿射变换的近似[ \Sigma J W \Sigma W^T J^T ]其中 (W) 是世界坐标系到相机坐标系的变换矩阵(J) 是投影变换的仿射近似雅可比矩阵。得到二维高斯后每个高斯会在屏幕上覆盖一个椭圆区域。渲染时GPU 会按照像素位置判断哪些高斯覆盖了当前像素再按照深度视点方向的距离对这些高斯进行排序最后执行 α 混合[ C \sum_{i1}^{N} c_i \alpha_i \prod_{j1}^{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 什么是 DistractorDistractor 在三维重建里指那些不属于目标场景、但出现在采集图像中的物体。最常见的类型包括动态行人、车辆、动物。风吹动的树枝、水面反光、旗帜等半动态元素。拍摄过程中由于曝光或视角变化而产生的阴影、高光。临时堆放的器械、拍摄者自己的影子。在 NeRF 盛行的时代Distractor 问题就存在。经典做法是在训练时对这些干扰区域做 Mask或者使用语义分割模型把行人、车辆剔除掉。但这些方案都需要额外的标注、额外的模型而且一旦 Mask 不干净重建边缘会出现明显撕裂。2.2 为什么 3DGS 对 Distractor 更敏感3DGS 的高斯基元本质上是一堆“自由粒子”。训练时如果某个区域在不同视角下颜色不一致优化器很容易在该区域生成半透明、细长的高斯来“硬凑”颜色。这意味着 Distractor 不仅不会被自动忽略反而会消耗掉大量内存和显存拖慢训练速度。更麻烦的是干扰物经常出现在相机近处。因为近处物体在图像中占的面积大致密化机制会把更多高斯分配到近处干扰物上而真正要重建的背景反而得不到足够的高斯资源。最终效果就是背景细节丢失干扰物区域出现一层“雾状残影”。传统上大家在 3DGS 里处理这类问题有三种路线训练前预处理用分割模型逐帧提取前景 Mask把干扰物区域抠掉再训练。训练中引入正则化修改损失函数让动态或不一致区域的高斯迅速降低不透明度。训练后修复重建完成后利用深度或语义信息人工编辑高斯。这三种路线各有问题方案 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 流程可以拆成四步对输入图像序列中的每一帧运行一次轻量级的高斯预测模块输出该帧对应的逐像素深度、置信度或者一组二维高斯参数。利用相机位姿将这些逐视图预测结果反投影到三维空间得到候选三维高斯。对同一三维位置的多视图预测结果做一致性检查。如果多个视图对同一位置的深度和颜色预测一致则认为该位置可信。渲染或重建时对不一致的高斯降低权重或直接剔除实现 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指向数据目录目录下通常包含images、sparse等子目录。-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, threshold0.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, threshold0.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), dtypebool) 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增加边缘掩膜对深度差值做中值滤波多视图深度预测尺度过大不同视图的深度预测尺度不统一用相机位姿和三角化结果对齐深度尺度或直接回归视差过滤结果对阈值敏感置信度分布本身不够平滑对置信度图做高斯平滑使用自适应阈值排查时建议按照下面顺序执行先可视化置信度图确认置信度是否真的反映了 Distractor 区域。关闭着色器或过滤逻辑确认渲染本身正常。使用固定阈值检查过滤结果的像素级差异。对误删区域标注错误类型判断是深度一致性还是遮挡边缘问题。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 实践中的踩坑经验。