ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

3DGS三维重建实战:Gaussian Splatting压缩包部署与训练调参指南

3DGS三维重建实战:Gaussian Splatting压缩包部署与训练调参指南

简介:这是一份面向三维重建与深度学习开发者的 3D Gaussian Splatting 完整工程包,适用于学习神经辐射场渲染、实景建模及自主训练部署。压缩包内含以 cpp/hpp/py 为核心的源码、配置文件及 iPhone 实拍重建数据集,并配套预处理、COLMAP 特征提取、训练与可视化脚本,可直接据此复现从照片到可交互三维场景的完整流程。资源共 2000 个文件,主要包含 C++ 头文件与实现、Python 脚本、HTML 文档、着色器文件及多种模型/图像格式,整体约 489MB,目录结构完整,覆盖核心算法实现、依赖库和示例数据。已有 762 人学习下载,特别适合希望在本地运行 3DGS 并基于真实数据开展实验的研究者与实践者,可节省环境配置与数据采集时间,快速进入模型训练与效果调优阶段。

1. gaussian-splatting.zip 值不值得下:一个压缩包里的完整三维重建闭环

第一次把 gaussian-splatting.zip 解开时,我其实是带着怀疑的:网上到处都在说 3DGS 多快多准,但一个压缩包就能装下从数据预处理到可旋转三维场景的完整闭环?跑完一遍之后我确认了两件事:它确实装得下,而且从 COLMAP 稀疏重建到渲染出图,比我在 NeRF 上折腾的时间至少少一个数量级。这份资源适合三类人:想把三维重建快速落地看效果的算法工程师、需要把显式点云接进渲染管线的图形学开发者、以及刚入门想搞懂 3DGS 各模块是什么的研究生。这篇不绕概念,直接按我拆包的顺序讲:原理逻辑、环境配置、训练参数、导出工具,最后是几个让我白等一晚上的坑。

2. 原理定位与环境配置:3DGS 与 NeRF、MVS 的边界在哪里

2.1 为什么是高斯椭球:显式表达为什么比隐式更适合

3DGS 的核心思路,是把一个场景表示成一大堆带 3D 协方差的高斯分布。每个高斯都是一个半透明的椭球,带有位置、旋转、缩放、颜色和不透明度参数。优化过程中,这些椭球通过可微光栅化被投影到图像平面,和参考图像做逐像素的 L1 加 D-SSIM 损失,再反向传播调整参数。它和 NeRF 最大的区别在于:NeRF 是隐式神经场,要沿光线积分采样,每条光线要算几十个采样点;3DGS 是显式地把椭球按深度排序后画到屏幕上,光栅化一遍就把所有椭球的贡献累计完,所以训练和推理都快一个量级。

如果你玩过 MVS,可以从另一边理解这个选择。MVS 要先估计深度图再融合点云,弱纹理区域容易翻车,而且深度估计的误差会一路传导到点云融合。3DGS 直接用一个可微的椭球集合去拟合多视图误差,把"先算深度、再建几何"这个容易出错的中间环节整个跳过。我个人理解,3DGS 最适合"给定一组照片,产出能自由旋转、能直接导出点云的显式三维场景"这类任务。它的产出物不是黑匣子,而是每个高斯的坐标、旋转、颜色都能取出来的结构化数据,这让后续接 Mesh、接点云工具链都变得容易。

这套压缩包里的代码也是按这个思想组织的:Python 管训练流程和参数调度,CUDA 管光栅化内核。训练脚本用 Python 启动,真正耗时的高斯投影和梯度回传是在 CUDA 内核里完成的。理解这条边界很关键,后面排查编译问题时你才知道该去查 Python 层、C++ 层还是 CUDA 层。

2.2 环境配置:Python 版本、CUDA 版本与依赖锁定的顺序

第一步是先建独立 conda 环境。我复现当天没有直接用 base 环境,因为这项目的子模块要编译 CUDA 扩展,在共享环境里装错 PyTorch 版本会污染其他东西。

conda create -n 3dgs python=3.7 -y conda activate 3dgs pip install torch==1.13.1 --extra-index-url https://download.pytorch.org/whl/cu116 pip install -r requirements.txt

这段命令的逻辑是:先用 Python 3.7 隔离出一个干净环境,再装带 CUDA 11.6 配套版本的 PyTorch,最后装 requirements 里声明的依赖。PyTorch 版本选择非常关键,因为后面要编译的 CUDA 光栅化扩展依赖 torch 的 C++ ABI,torch 版本和 nvcc 版本不一致时,最容易在 import 扩展时报缺符号或找不到 .so 的错误。requirements 里一般是 numpy、opencv-python、tqdm、tensorboard 这类基础依赖,不会引入太激进的新包。

装完依赖后要确认 nvcc 和 torch 指向同一个 CUDA 版本。我一般会在终端分别跑 nvcc --version 和 python -c "import torch; print(torch.version.cuda)",两个显示一致才继续。如果你发现 torch 是 cu116 而 nvcc 是 cu121,别急着继续,先统一版本,不然后面编译子模块时会有得折腾。Windows 环境下还要额外注意:编译需要 Visual Studio 的 C++ 生成工具,且 VS 版本要和 CUDA 支持列表对得上,否则 cmake 那一步会直接失败,报的错误还经常让你以为是 CUDA 没装好。

显卡驱动方面,我的经验是驱动不要太新也不要太旧,NVIDIA 官方驱动保持一年内的版本通常都没问题。显存小于 8G 的卡也能跑,但需要提前把训练分辨率降一档,这个后面训练章节会具体说。

2.3 目录结构:解压后最该先读的三个文件

打开压缩包后,不建议直接跑命令,先花五分钟看目录结构。常见布局是:Python 训练脚本和渲染脚本在根目录,diff-gaussian-rasterization 这类子模块以 submodule 形式存在,data 和 output 目录留空等你填,README 里写了依赖和启动顺序。

我最先读的三个文件是:README 里的环境要求、requirements.txt、以及子模块目录下的 setup.py。README 是作者对这套流程的完整描述,训练和渲染的启动命令、注意事项都写在里面;requirements 决定了 pip 装什么;setup.py 会告诉你编译扩展时它期望的 CUDA 架构。如果你只有一个普通游戏卡但 setup.py 里写的是 sm_86,编译时间会变长,但一般不影响最终使用。搞清楚这三份文件,后面每一步命令的输入输出你心里都有数。

数据目录建议保持仓库默认结构:data/ 放拍摄图片和 COLMAP 结果,output/ 放训练输出。这套资源里的默认参数几乎都按这两条路径假设来写。我见过有人把数据放到自定义路径,导致后面 convert.py 和 train.py 的 -s 参数要跟着改一串,报错时还得回头排查路径,平白多一层麻烦。

3. 数据预处理与训练调参:COLMAP 到高斯集合的关键链路

3.1 convert.py 与 COLMAP 稀疏重建:为什么只靠图片还不够

训练前必须先解决"相机在哪、空间点在哪"的问题。3DGS 的初始高斯集合来自 COLMAP 的稀疏重建结果,而不是从零随机生成。这一步由 convert.py 完成,它会自动扫描场景目录并调用 COLMAP 做重建。

python convert.py -s data/cat

这个脚本会在 data/cat 下找图像文件,然后依次执行特征提取、特征匹配、稀疏重建,最后把结果写到 sparse/0 目录下。-s 指定场景目录。输出里有三个关键二进制文件:cameras.bin、images.bin、points3D.bin,分别记录相机内外参、图像位姿和三维点云。train.py 就是从这些文件里的三维点初始化高斯集合的。

理解这步逻辑有个关键点:3DGS 不是从纯噪声开始的,它把 COLMAP 的点云作为初始椭球位置。这个设计很聪明,因为一个大致正确的几何骨架能让优化少走很多弯路。反过来也意味着,如果 COLMAP 重建质量差,后面训练怎么调参都补不回来。我一般会在跑 convert.py 之前先人工筛一遍图片,把模糊、过曝、重复率太高的帧删掉。COLMAP 对图片质量很敏感,一组 300 张图里混进几十张模糊帧,sparse 重建出来的点云会稀稀拉拉,后面画面上全是漂浮物。

拍摄时保证相邻帧之间至少有 40% 的重叠,特征匹配数才够。手机视频抽帧的话,每秒留 2 到 3 帧比较合适,抽得太狠,帧间重叠不够,容易把一组好好的照片拆成好几段对不上的片段。这里我有个习惯:convert.py 跑完会刷新 sparse/0 目录,我会用 COLMAP 的 GUI 打开看一眼点云,如果点云明显分成好几块、互相错位,说明拍摄路径闭环没做好,先回去补拍比硬训更快。

3.2 训练启动:迭代数、学习率与 densification 策略

数据处理完,开始训练。官方推荐的单卡训练命令不复杂:

python train.py -s data/cat -m output/cat --iterations 30000

-s 指定场景目录,-m 指定模型输出目录,--iterations 指定总迭代数。3DGS 的训练会经历一个密集化阶段:每隔一定步数,检查每个高斯的梯度累积,梯度大的位置会触发分裂或克隆,让高斯数量从几千个增长到几十万甚至更多。这个机制由 --densification_interval 控制,默认大约每 100 次迭代检查一次。

比较常改的参数还有 --test_iterations,决定每隔多少轮在验证集上算一次 PSNR;--save_iterations,决定哪些时间点保存中间模型。默认总迭代数 30000 对多数场景够用,前提是输入图片数量在 100 到 300 张这个量级。如果你的场景非常大,比如上千张图,我会把 --iterations 加到 50000 以上,同时把位置学习率初始值调小一点,避免后期在局部最优附近来回震荡。

还有一个容易被忽略的细节是损失函数。3DGS 用 L1 加 D-SSIM 的组合,D-SSIM 权重默认 0.2。这个组合让彩色边缘更锐利,但也意味着如果你的照片有大量弱纹理墙面,D-SSIM 会把墙面磨得过平。遇到这种场景,我会稍微把 D-SSIM 权重降到 0.1,让 L1 主导几何恢复,墙面细节反而会好一些。这些参数都写在训练脚本的 cfg 里,改之前先备份一份默认值,方便对照。

3.3 输出目录:中间产物、tensorboard 与收敛判断

训练开始后,output/cat 下会出现 checkpoint、cfg_args、point_cloud 等目录。point_cloud/iter_30000 里保存的是每个迭代节点的完整模型状态,包含每个高斯的位置、协方差、颜色和 SH 系数。checkpoint 里存的是优化器状态,用于中断后恢复训练,不是最终模型。这点我见过不少人搞混,以为 checkpoint 就是输出模型,拿它去渲染结果却发现没有点云。

tensorboard 日志也在输出目录里,训练时可以通过 tensorboard --logdir output/cat 实时看损失曲线和 PSNR。我习惯不只盯 PSNR,而是把高斯数量变化曲线也打开。如果数量过早饱和,说明 densification 阶段没起到应有的作用,场景细节可能丢失;如果数量一直涨到几百万还不收敛,说明场景初始化有问题或者训练过拟合了。这两个迹象比单纯看 loss 数值更有参考价值。

有时候训练中途断了,不需要从头开始。train.py 加 --start_checkpoint 参数指向 checkpoint 文件就能恢复。我一般会在每次长时间训练前确认 checkpoint 开关是打开的,不然跑两小时断电就全没了,那一刻真的会想砸键盘。

4. 渲染与导出:把三维结果接进外部工具链

4.1 render.py 的常用参数与视角控制

训练完成,验证效果最直接的方式是用自带的渲染脚本:

python render.py -m output/cat -s data/cat

这个脚本读取 output/cat 里保存的模型,用训练时的相机位姿重新渲染图像,结果写到 output/cat/test/ours_30000/ 下。默认会同时渲染训练集视角和验证集视角,方便对比。如果你只想渲染最后一次迭代的模型,可以在命令里加 --iteration 指定。渲染脚本不参与损失计算,它只是把已训练好的椭球集合投影到图像空间,所以速度很快,一张图基本就是毫秒级。

渲染的原理可以简单理解为:把所有高斯按深度排序,从前到后做 alpha 混合。这一步在 CUDA 内核里是并行处理的,排序算法对最终质量影响很大。如果你在渲染时看到物体边缘有毛刺,多半不是模型问题,而是视角选择到了高斯密度不足的区域,换个角度就正常了。

关于视角控制有个技巧:想把场景绕一圈给同事看,不一定要改代码。这套资源里的二进制查看器可以在 GUI 里加载模型自由旋转视角,交互式渲染速度比 render.py 出图还顺滑,适合做效果演示。我个人习惯先跑 render.py 确认数值指标正常,再用查看器做展示。因为查看器是即时渲染,视角一晃容易忽略画面质量问题,而脚本渲染可以固定视角、多帧对比。

4.2 点云导出与格式转换:PLY 里的属性到底指什么

3DGS 训练产物本身是一堆高斯参数,不是标准点云格式。要接进 CloudCompare、MeshLab 或者自己的 Python 工具链,需要先导出:

python export_pointcloud.py -m output/cat -o cat_cloud.ply --voxel_size 0.01

这里 -m 指定模型目录,-o 指定输出 PLY 文件,--voxel_size 是体素下采样尺寸。导出时系统会遍历模型里的每个高斯,把位置作为点坐标,把不透明度和颜色写到属性里,最后按 voxel_size 做一次抽稀。voxel_size 设置得越小,保留的点越多,文件越大。默认值对可视化够用,但如果你要做 Mesh 重建前的点云输入,建议适当降低,保留更多密度信息。

导出后的 PLY 可以直接用 Open3D 读取。常见做法是先估计法线再走泊松重建:

import open3d as o3d pcd = o3d.io.read_point_cloud("cat_cloud.ply") pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.02, max_nn=30)) o3d.io.write_point_cloud("cat_cloud_normals.ply", pcd)

这段代码的逻辑是:读入点云后,在半径 0.02 的邻域内找最近 30 个点估计法线方向,把带法线的点云写回。radius 要根据场景尺度调,室内小场景 0.01 到 0.02 够用,大场景航拍数据要放大到 0.1 以上,否则每个点的邻域里没有足够邻居,法线方向会乱。

4.3 工具链边界:GLB、Mesh 与实时渲染的取舍

拿到 PLY 后,有人会问能不能直接转成 Mesh 或纹理模型。这里要提醒:3DGS 导出的点云是带属性的点,不像传统扫描点云那样有明确的法线方向。直接做泊松重建前,最好先做法线估计,否则表面会出现大量空洞。这个流程可以跑通,但质量上限取决于训练时高斯数量的密度。如果高斯数量本身不够,Mesh 表面会有空洞,这是正常的,补洞也要在训练阶段加数据,而不是后期硬修。

至于预览工具,CloudCompare 打开 PLY 没问题,MeshLab 也行。但要注意:PLY 里的颜色属性如果是以 SH 系数形式保存的,部分工具会显示不出正确颜色。export_pointcloud.py 导出时一般会把颜色转成 RGB 属性,所以多数场景不会遇到问题。如果打开后发现颜色不对,先检查导出参数是否开启了颜色转换,不要盲目改点云数据。

还有一种常见需求是导出带纹理的 Mesh 到游戏引擎里。3DGS 原生的高斯渲染流程和传统 Mesh 渲染管线不同,转换工具通常是先做泊松重建,再把原图反投影生成纹理贴图。这个流程受图片对齐精度影响很大,角度偏差超过几度,贴图就会发虚。我在实际项目中经常是两条腿走路:需要实时交互演示时用原始高斯渲染,需要对接上下游管道时导出点云做二次处理,不要指望一个格式满足所有场景。

5. 避坑清单:训练翻车的 5 个常见原因与排查方法

5.1 子模块编译失败:CUDA 版本对不齐

现象:执行子模块的编译脚本时报 CUDA 版本错误,或者训练启动时 import 报找不到 .so 文件。

原因:nvcc 的版本和 PyTorch 编译时的 CUDA 版本不一致。PyTorch 1.13.1 对应 cu116,但如果系统 nvcc 默认指向 cu121,编译出来的扩展在加载时就会因为运行时库版本不匹配而失败。

解决:先把 nvcc --version 和 python -c "import torch; print(torch.version.cuda)" 两行输出并排对比,不一致就重装。以后换环境时先固定 torch 版本再配 nvcc,顺序不能反过来。

5.2 显存 OOM 与分辨率选择

现象:训练刚开始几步就报 CUDA out of memory。

原因:默认初始高斯数量和图片分辨率对显存要求偏高,尤其当输入图片是 4000 像素以上时,光栅化和梯度计算会瞬间吃满显存。

解决:在 train.py 里加 --resolution 参数,把图片宽高缩到原始尺寸的一半或再低一档。显存小于 8G 的卡建议老老实实降分辨率,PSNR 损失一点,换来的是训练不中断。同时把 submodule 里的立体分辨率参数也对应调整,两个地方不匹配会报形状错误。

5.3 渲染结果出现漂浮物或黑色噪点

现象:渲染画面里出现大块黑色区域或半透明漂浮物,看起来像场景在漏气。

原因:COLMAP 稀疏重建点云里混入了错误的匹配点。这些离群点位置不合理,初始化成高斯后没有被剔除机制清理干净,训练结束后就成了漂浮物。

解决:检查 sparse/0 目录里的重建质量,最简单的方法是导出 points3D.ply 用 CloudCompare 看一遍,把明显离群的噪点位置记下来。然后在 convert.py 阶段提高特征匹配的置信度阈值,或者直接删掉这些点再重新训练。还有一个辅助手段是调低初始不透明度阈值,让优化更快地把异常高斯清掉。

5.4 loss 下降但 PSNR 不涨

现象:训练 loss 一直在降,但验证集 PSNR 停留在较低水平不动。

原因:过密化阈值设置太激进,高斯数量膨胀后优化器进入了局部最优;或者验证集和训练集来自同一批数据的高度重叠视角,指标虚高但没有实质泛化能力。

解决:把 densification 的梯度阈值调大一档,减少无意义的高斯分裂。验证集单独留出 10 到 15 张不参与训练的视角,指标只看验证集才有参考意义。如果验证集 PSNR 一直上不去,先检查 COLMAP 重建是不是丢了部分相机,而不是调学习率。

5.5 渲染视频抖动的排查

现象:用导出工具做绕场景动画时,画面出现明显闪烁和抖动。

原因:保存的中间模型间隔太近,相邻迭代节点的模型差异被放大,视频里就成了高频闪烁。或者模型本身没收敛就导出,椭球位置还在小幅振荡。

解决:导出动画时不要用 iter_1000、iter_2000 这类密集保存的节点,直接用最后一次迭代的模型。如果必须做动画,把保存节点间隔拉到 3000 以上。导出前先看一眼 PSNR 曲线确认已经进入平台期,未收敛就导出,怎么改都会闪。

注意:上面五条是我复现时遇到的高频问题。遇到报错不要先怀疑资源包有问题,先按版本、输入数据、输出日志三层线索排查,大多数问题都出在环境和数据准备这两层。

6. 进阶技巧:跑通默认配置后,建议先改这四个参数

默认配置跑通只是起点。同样的资源包,四个参数改一改,效果差距很明显。我列了一个对照表,适合你按自己的数据规模做取舍:

参数默认值调整建议适用场景
--iterations3000050000-70000大范围室外或航拍
位置学习率峰值0.00160.0008场景范围大、点云初始位置偏差大
--densification_interval100150高斯数量过早膨胀时
导出 voxel_size默认0.02 展示 / 0.005 重建按下游用途定

第一个是迭代总数与学习率衰减点的配合。默认 30000 次迭代,室内小场景够用,航拍或大范围室外场景明显不够。把 --iterations 提到 50000 到 70000 的同时,把位置学习率最大值从 0.0016 调低到 0.0008,让优化前期别太快冲过头。位置学习率是 3DGS 里影响最大的超参数之一,调太大会让高斯位置振荡,调太小则场景收敛慢,我一般按数据量级做指数式调整。

第二个是过密化间隔与阈值的搭配。默认每 100 次迭代检查一次梯度,阈值过低会造成高斯数量爆炸。当最终 PSNR 过了 30 但模型文件明显偏大时,把 --densification_interval 调到 150,同时把梯度阈值小幅上调。这样做的效果是减少重复冗余的高斯,文件体积下降,渲染速度反而更快。注意不要连续调两次大间隔,否则场景细节会明显变糊。

第三个是验证集策略。默认从训练数据里留出一部分做验证,但如果场景光照变化大,验证结果会失真。我的做法是单独留出 10 到 15 张不同角度、不同时刻的照片,手动指定验证集路径,而不是让脚本随机抽。这样验证 PSNR 更能代表真实泛化能力,尤其是室外阳光下和阴影交界处的表现。

第四个是导出时的体素尺寸。默认导出参数偏保守,点云文件大但质量提升不明显。我按用途定参数:最终只是交流展示,voxel_size 设 0.02 就够;如果要喂给后续 Mesh 重建流程,降到 0.005 以下,保留更多细节,代价是文件从几十 MB 涨到数百 MB。这个选择取决于下游消费方接收多大文件,而不是越大越好。

这几个参数没有标准答案,数据量和硬件决定取舍。从那以后,我每次拿到一个新的三维重建项目,都会强制自己花十分钟把三件事过一遍:确认环境版本、查看输入数据质量、设定独立验证集。看起来都是小事,但能省掉后面排查的大把时间。这份 gaussian-splatting.zip 里的脚本和示例数据足够你完整走一遍流程,把压缩包解开、从 convert.py 开始跑,比到处找零碎教程顺手得多。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进