在 3D Gaussian Splatting(3DGS)项目里,室外场景重建最让人头疼的往往不是算法本身,而是采集照片里那些不该出现的临时物体:路人、车辆、飞鸟、围挡、施工机械。传统做法要么手动抠图,要么单独训练一个语义分割模型去擦除,工作量大不说,换个场景效果往往还要重新调。最近看到 Per-View Gaussian Predictions 这个方向,提出用逐视角高斯预测来做 Training-Free 的干扰物过滤。核心变化是把“哪些像素是干扰物”的判断,从全局优化完成后的后处理,提前到每个视角单独预测高斯时的一致性检查。对正在做街景、景区、园区数字化重建的团队来说,这个思路值得花时间拆一遍。适合的人群包括:正在跑 3DGS 的同学、做三维重建前处理的开发者、以及被手工遮罩折磨的项目负责人。下面把它的思路、适用场景和落地要点拆一遍。
1. 先理解 3DGS 里干扰物为什么会让重建结果变差
1.1 3DGS 的基本表示方式和重建流程
3D Gaussian Splatting 的核心,是用一堆三维高斯原语来表示一个场景。每个原语都带有一组属性:中心位置、协方差或者说旋转缩放、不透明度、颜色球谐系数。重建过程不是像传统 MVS 那样先生成稠密点云再做网格,而是直接对这些高斯原语做可微渲染,把渲染结果和输入照片比较,再反向更新参数。训练完成后,模型保存下来的就是一个高斯集合,运行时只需要按视角排序并做 splatting 绘制,就能得到比较真实的新视角图像。
这个流程在室内静态场景里效果很好。但到了真实室外,问题马上出现:如果照片里有一个路人站在同一个位置被 20 张照片拍到,模型会把他当成场景的一部分,专门生成一组高斯去拟合。于是最终模型里多了一坨“半透明的人影”,新视角渲染时它还会跟着视角变化产生奇怪的高光。
1.2 动态物体、临时物体、视角相关遮挡分别会造成什么现象
在实际效果上,干扰物一般会造成三类问题。
第一类是动态物体拖影和鬼影。行人、汽车在拍摄过程中移动,不同照片里出现在不同位置,全局优化时无法同时拟合,就会出现半透明拖影。渲染新视角时,这些区域看起来像蒙了一层雾。
第二类是临时静态物体。比如一个施工围挡在某一小段拍摄时间内一直存在,它本身是静止的,如果这段时间的视角覆盖够多,模型也会很完整地把它重建出来。这类物体最难处理,因为它在空间上完全满足三维一致性,只是它“不应该属于这个场景”。
第三类是遮挡。干扰物挡住背景时,如果背景只在少数几张照片里可见,模型对背景的几何估计就容易漂移,最终渲染出来背景发虚或者有空洞。这种情况即使后期把干扰物删掉,背景也补不回来。
1.3 为什么手动遮罩和语义分割方案很难做到通用
常见的兜底方案是手工 Mask。在几十上百张照片里把路人抠掉,费时费力,而且边缘不准确。另一个方案是训练语义分割模型,检测 person、car 类别再生成 Mask。它的问题不是不能做,而是依赖预训练模型的应用范围。街景数据集训练出来的模型,到了园区、工地、文物景区,类别和外观差异会带来漏检;要把某个特定场景里出现的新物体滤掉,还得重新准备标注。
所以,一个不需要重新训练、又能利用三维一致性的过滤方法,天然就有吸引力。这正是 Per-View Gaussian Predictions 这类思路的出现背景。
2. Per-View 高斯预测和 Training-Free 过滤的核心逻辑
2.1 什么叫 per-view 高斯预测:逐视角预测而不是全局拟合
传统 3DGS 是所有照片共享一组高斯,优化过程本身就是在做全局拟合。Per-View Gaussian Predictions 的提法不一样:先对每个视角,或者说每个局部视角组,单独预测一组高斯表示,再在这个基础上做跨视角比较。
单独预测的意义在于,某个视角看到的内容是“这个角度下场景结构 + 这段拍摄时间内出现的干扰物”的混合。如果只从单个视角看,人和背景都在画面里,模型没法区分。但把多个视角的预测结果放到同一坐标系下对齐之后,真正的场景结构会在不同视角之间互相印证,而干扰物通常只在部分视角里存在,或者在不同视角预测里位置不稳定、形态不连续。
2.2 跨视角一致性:区分“稳定的场景结构”和“一次性的临时物体”
这里的关键判断标准是跨视角一致性。场景里的墙面、地面、雕塑、固定设施,不管从哪个角度看,对应的高斯原语应该在同一空间位置附近稳定出现。而一个只在 5 张照片里存在的路人,在另外 30 张照片里根本没有对应的高斯;或者同一个运动物体在不同视角预测里出现的位置有明显跳变。
用一致性打分去筛掉“支持视角数不足”的高斯原语,就完成了干扰物过滤。这种思路在传统 MVS 里很常见,用多视角几何一致性剔除错误匹配。放在 3DGS 里,区别在于它不是检查像素或深度图,而是检查每一组高斯原语在跨视角预测中的支持程度。这个角度理解起来会更顺。
2.3 训练自由不等于魔法:它的适用范围和前提条件
Training-Free 的意思是,过滤这个环节本身不需要再额外训练一个分类器或者分割模型,也不需要对每个场景做一次专门的 mask 训练。但它还是依赖 3DGS 本身的基础优化,只是把“过滤”从训练后的手工处理,变成了一个基于几何一致性的计算过程。
所以这里有一个很现实的前提:输入照片的位姿必须足够准,视角之间要有足够多的重叠。如果相机位姿本身漂移严重,跨视角对齐就是错的,一致性判断也会错。另一个前提是干扰物和背景之间有足够的视差变化。一个站在离相机特别远、又被几十张连续照片覆盖的固定围挡,依旧可能被当成场景真实结构,因为它在空间和时间上都非常稳定。这种边界要在使用前想清楚。
3. 复现和实测前的环境准备与数据采集建议
3.1 GPU、CUDA、PyTorch、COLMAP 这类基础条件怎么确认
不管用哪个 3DGS 代码库,基础环境都绕不开几个部分。
- GPU:NVIDIA 显卡优先,显存建议至少 8GB,做室外大场景最好 12GB 或以上。
- CUDA 和 PyTorch:需要先确认 PyTorch 版本对应的 CUDA 版本,再装对应的编译环境。很多报错不是算法问题,是 PyTorch、CUDA、显卡驱动三者版本不匹配。
- COLMAP:用于从照片做稀疏重建。Linux 下可以用 apt 或源码编译,Windows 下有预编译包,macOS 也能跑,但大批量室外场景建议还是 Linux。
- Python 环境:建议单独建一个 conda 环境,不要和日常开发环境混在一起。
原始标题里没有给出具体代码仓库和工具版本,所以落地时先看项目的 requirements 和环境安装说明,再按自己机器情况调整。不要直接拿最新的 CUDA 版本去跑老代码,优先使用项目推荐的版本组合。
3.2 采集照片时怎么减少干扰物和拍出更利于过滤的数据
即使有自动过滤,采集端做得越规范,后面的成功率越高。
第一,保证相邻照片重叠率不低于 60%。重叠太少,COLMAP 特征匹配会断,位姿容易飘。
第二,同一个区域尽量在不同时间段、不同位置多拍几张。这样背景结构有足够多的支持视角,干扰物在单次拍摄里出现的比例会被稀释。
第三,如果条件允许,把拍摄过程拆成多条扫描线,而不是站在原地转圈。站在原地转圈会导致中心区域视角单一,边缘区域重叠不足。
第四,避免忽明忽暗的自动曝光和强烈反光。照片曝光不一致会直接影响可微渲染的收敛,也会让一致性打分出现误判。
第五,可以按视频抽帧采集。视频抽帧的优点是视角连续,缺点是相邻帧相似度太高,会浪费计算量。建议每隔几帧抽一帧,不要全部塞进去。
3.3 通用 3DGS 项目落地时的目录结构和输入输出
一个标准的 3DGS 实验目录通常长这样:
project/ images/ # 输入照片,jpg 或 png sparse/ # COLMAP 输出,包含相机位姿和稀疏点云 output/ # 训练输出,最终高斯模型 logs/ # 日志和中间结果这里最容易踩的坑是路径和权限。路径里不要带中文和空格,很多 C++ 扩展对路径解析不友好。输出目录要先建好,否则存储满了或者没权限,程序会在运行到一半时悄悄失败。建议每次实验都单独建一个子目录,命名里带上场景名和参数标识,方便回头对比。
4. 单场景实测流程:从照片到干净三维模型的完整步骤
4.1 第一步:COLMAP 稀疏重建
先用 COLMAP 对照片做特征提取、特征匹配、稀疏重建。这一步输出的相机参数和稀疏点云,是整个流程的基础。命令形式大致是:
colmap feature_extractor --database_path project/database.db --image_path project/images colmap exhaustive_matcher --database_path project/database.db colmap mapper --database_path project/database.db --image_path project/images --output_path project/sparse图片数量不多时用 exhaustive_matcher 就行。图片上千张时,建议用 sequential_matcher 或者 vocabulary tree matcher,否则匹配时间会非常长。跑完后用 COLMAP GUI 打开稀疏模型,检查轨迹是否连续、有没有明显漂移。这一步不过关,后面所有过滤都白搭。我一般会在这一步多花 10 分钟,确认没有明显漂移后再进训练环节。
4.2 第二步:跑 3DGS 训练并检查全局模型
接着用 3DGS 原始训练代码或者改进版本做一次完整训练。这一轮训练有两个作用。
一是做一个 baseline,先看干扰物到底在模型里怎么表现。二是为后续 per-view 预测和过滤提供初始的高斯表示和相机参数。
训练时先用默认参数跑一轮。室外场景如果跑出来的 PSNR 很高但视觉上有明显拖影,说明模型把干扰物硬拟合进去了,这是预期中的现象。先确认全局模型存在这个问题,再进入过滤环节,否则你无法判断改进到底有没有效。
4.3 第三步:per-view 预测和一致性打分
这一步是整个方案的核心。具体实现可能因代码库而异,但大致的逻辑是:在全局模型的约束下,对每个视角或视角组重新预测高斯;然后把不同视角预测出的高斯原语转换到统一坐标系;计算每个原语能被多少视角支持、位置偏差有多大;最后用一致性阈值生成一个可信度字段。
实际运行时不需要真的对全部视角做逐像素预测,通常会采样一部分视角做验证。我一般会先取 10 到 20 个视角跑一个快速验证,确认数据能走通,再放开到全量视角。不要一上来就开全量并发,否则显存和内存都会爆炸。
4.4 第四步:生成过滤掩码并重建最终模型
根据一致性打分,把低于阈值的区域标记为干扰物,生成二值掩码或者软权重掩码。然后在下一轮 3DGS 优化中,让这些区域不参与颜色监督,或者降低它们的梯度权重。这样模型就不会再去拟合路人和车辆,最终得到的是没有干扰物的高斯表示。
这里要区分两种做法。一种是直接删除低分高斯原语,简单粗暴,适合干扰物占比小的情况。另一种是在损失函数里加入掩码,让模型在优化过程中自己避开干扰物区域。后者更稳,因为直接删除容易留下空洞,边缘区域会有明显瑕疵。
4.5 结果怎么看:PSNR、SSIM、LPIPS 和目视检查
验证结果不能只看一个指标。
- PSNR:越高越好,但干扰物被擦除后,如果用原图含路人的照片去算 PSNR,分数可能反而下降,因为模型不再去还原路人。
- SSIM:观察结构相似度,对纹理细节更敏感。
- LPIPS:和人眼感知更接近,适合判断“看着干不干净”。
- 目视检查:新视角渲染视频或者多角度截图,重点看原来有路人的区域是否干净、背景是否有空洞、静态物体边缘有没有糊。
原始材料没有给出这套流程的基准数据,所以不建议拿某一篇开源项目的数字直接当验收标准。正确做法是同场景做 A/B 对比:不过滤版本和过滤版本,在去掉干扰物的照片集上算同一组指标。
5. 参数、批量和边界情况
5.1 核心参数怎么理解:视角数量、重叠率、迭代次数、一致性阈值
这类方案里,几个参数直接影响结果。
- 采样视角数量:只取少量视角时速度快,但一致性估计噪声大;取全量视角时更准,但显存和时间成本高。建议先用四分之一到三分之一的视角跑通,再逐步增加。
- 视角重叠率:数据采集时就决定了。重叠率低,背景支持视角不足,过滤会误伤。
- 迭代次数:过滤后重建的迭代次数不要太少。有些项目为了省时间只跑 7000 次,室外大场景往往还不够,建议至少跑到 15000 到 30000 次,并配合测试集看收敛情况。
- 一致性阈值:阈值越高,过滤越激进,越容易误删背景;阈值越低,越保守,干扰物残留越多。没有通用默认值,要拿一个包含已知干扰物的片段去标定。
这里给一个通用排查顺序:先调一致性阈值,再看采样视角数量,最后才处理迭代次数。不要三个参数一起改,否则出问题根本不知道是哪个引起的。
5.2 批量处理多个场景时,要单独设计输出目录和失败重试
如果只是学习,跑一两个场景没问题。如果是项目里要处理几十个场景,批量脚本就要单独设计。
批量任务最常见的坑不是算法,而是输出管理。每个场景的数据格式、照片数量、相机型号可能都不一样,不能假设所有场景都能用同一套参数。建议按场景写一个配置文件,记录场景名、输入图像目录、COLMAP 输出路径、过滤阈值、采样视角数、迭代次数。
脚本按配置文件逐个跑,每个场景单独建输出目录,文件名带上时间戳。跑完一个场景就检查一个输出,不要等全部跑完再回头看。失败时先看日志里是哪个环节报错,是 COLMAP 匹配失败、显存不足,还是过滤阶段没有生成掩码。
5.3 哪些场景收益大,哪些场景收益有限
收益大的场景:景区、街区、园区、校园、展会现场。这些地方有大量路过的人和不固定的临时物体,而且拍摄者通常不能清场。这种场景里,训练无关过滤能省掉的往往是按天计算的人工量。
收益有限的场景:室内封闭场地,基本没有动态干扰物,手工遮罩几分钟就能搞定。或者是一个固定施工围挡占了很大画面比例、且被足够多视角稳定重建出来,它可能因为空间一致性太强而成为漏网之鱼。
还有一类场景要特别小心:纹理极少的大平面。比如一面白墙、一片沙地,本身就没有足够的特征点让 COLMAP 稳定配准,任何过滤方案都很难凭空判断哪里是干扰物。这类场景建议从源头控制,拍摄时增加其他参照物,或者后期人工补充遮罩。
6. 常见问题排查链路
6.1 干扰物没被过滤掉
先别急着怀疑方法不对。按这个顺序排查。
- 看输入数据:这个干扰物出现在多少张图里。如果占了一半以上视角,而且位置稳定,大概率会被当成场景结构。
- 看位姿:COLMAP 稀疏重建是否连续,特别是干扰物附近的背景特征点是否充足。
- 看阈值:一致性阈值是不是设得太低。
- 看采样视角:如果只用了少量视角,而干扰物恰好在这些视角里重复出现,一致性打分也会偏高。
如果以上都没问题,再考虑是不是干扰物和背景深度接近、遮挡关系不明显,导致跨视角投影时很难区分。
6.2 静态物体被误删
误删静态物体的典型原因有三个。
一是背景支持视角不足。某个区域只在少数照片里出现,这些照片里又有干扰物遮挡,模型很难判断遮挡后面的结构是不是稳定的。
二是重复纹理或者相似区域太多。比如一片栏杆、一栋有很多窗户的建筑,跨视角匹配时容易出现对应错位,一致性打分偏低。
三是曝光不一致导致颜色差异过大。同一种材质在不同照片里颜色不同,被打成低分。
处理方式:先降低一致性阈值,再看是否缩小过滤范围,只对特定空间区域做过滤。不要全局一个阈值,更合理的做法是分区域标定。
6.3 显存不够或者速度很慢
Per-view 预测比普通 3DGS 训练要额外消耗资源,因为需要为多个视角维护独立的预测状态。缓解手段:
- 降低输入图片分辨率。室外场景用到 1600 或者 1280 宽度往往够用,不需要原图。
- 减少同时处理的视角数,用分批的方式逐个处理。
- 减少高斯原语数量。3DGS 训练过程中高斯会不断分裂,如果发现显存溢出,可以调低密度化相关参数。
- 显存不够时,优先保证全局 3DGS 训练能跑完,过滤阶段可以放宽对实时性的要求。
速度慢不是 bug。这类方案的时间开销大头通常不在训练,而在 COLMAP 特征匹配和 per-view 一致性计算。如果只是做验证,先用小图、少视角跑通链路,再评估完整流程的耗时。
6.4 环境报错:CUDA 版本、依赖冲突、COLMAP 路径问题
最常见的三个报错来源。
第一,CUDA 版本不匹配。运行训练脚本时报错找不到 CUDA 或者编译失败,先检查 nvidia-smi、nvcc、PyTorch 三者的版本关系。nvidia-smi 显示的驱动版本和 nvcc 显示的 CUDA 版本可以是两回事,PyTorch 里用的是运行时 CUDA。
第二,依赖冲突。3DGS 的老代码依赖特定版本的 submodules,比如 diff-gaussian-rasterization。自己手动改环境时容易破坏编译产物,遇到这类问题建议退回项目推荐的 conda 环境重新装。
第三,路径错误。COLMAP 输出目录不存在、数据库路径写错、图像格式不统一,都会让流程在中途失败。先在命令行单独跑 COLMAP,确认数据库和 sparse 文件夹生成成功,再进下一步。
7. 什么时候该用这个方案,什么时候还是老老实实抠图
7.1 三种常见方案的取舍
面对干扰物,现在主流的选择其实是三种。
手工遮罩。适合干扰物极少、只在一个小区域出现、或者对精度要求极高的项目。优点是可控,缺点是大量人工。如果只需要处理三五张图,手工抠图比任何自动方案都快。
语义分割或目标检测模型。适合物体类别明确、训练数据覆盖充分的场景。比如景区主要路人、街道主要车辆,预训练模型效果通常不错。缺点是一个没见过的新类别出现时容易漏检,而且前处理流程依赖模型版本。
基于几何一致性的自动过滤,也就是 Per-View Gaussian Predictions 这类思路。适合大范围、多场景、拍摄过程不能清场的情况。它不需要标注,不依赖类别清单,但要求相机位姿准确、视角重叠足够。
7.2 从项目成本角度判断
选哪条路,不要只看模型效果,要看总成本。
如果一次性做 20 个社区场景,每个场景几百张照片,有人有车有临时围挡,手工遮罩的成本会非常高。这个场景适合跑自动过滤,因为重复劳动的边际成本是零。
如果只做一栋办公楼的正立面,总共 30 张照片,只有一两个路人,手工遮罩大概 20 分钟就能解决,根本没必要为它部署一套新的前处理流程。
很多团队一开始追求完全自动化,结果花了两周调参,最后发现效果还不如人工处理两小时。我的建议是先算一遍时间账:数据集规模、干扰物覆盖范围、可接受的人工量,再决定要不要引入训练无关过滤。
7.3 落地时最该盯住的三个点
最后留三个判断标准,方便复现时自检。
第一,过滤前后的新视角渲染,在原来有干扰物的区域是否干净,且背景纹理没有被破坏。只看 PSNR 会骗人,要打开渲染视频逐帧看。
第二,同一场景跑两遍,使用相同参数,输出结果是否一致。如果两次结果差异很大,说明流程里有随机性,比如采样视角不稳定,需要固定随机种子。
第三,批量跑几十个场景时,统计每个场景的残留干扰物数量和误删背景面积,不要只看一两个成功案例。
这类工具真正落地时,最该盯住的不是功能列表,而是输入格式、资源占用和失败重试。踩过几次之后你会发现,很多问题不是工具能力不够,而是前置环境和输入材料没有处理干净。