这个系列写到第四篇,前几篇把 3D Gaussian Splatting(后面统称 3DGS)的原理、渲染方程、优化策略都过了一遍,理论框架应该已经清楚了。但说实话,理论看得再多,不如自己亲手把一串代码跑通来得实在——尤其是第一次用自己的数据集训练出场景的那一刻,那种成就感是读十篇论文都换不来的。
这篇就讲实操。我会按照“环境配置、数据准备、训练调参、问题排查”这条主线走,把从零开始跑 3DGS 的完整链路拆开揉碎。读者最好已经具备基础的 PyTorch 使用经验,懂一点点相机标定和 COLMAP 更好,但如果没接触过也没关系,文中涉及的关键概念我会用直白的话交代清楚。目标很简单:让你在看完这篇之后,能用自己的图片跑出一个像模像样的 3D 场景。
1. 环境配置:先把“地基”打牢
1.1 硬件与系统选择
3DGS 训练是典型的 GPU 密集型负载,对显存和算力都有硬性要求。官方代码在 CUDA 上有大量自定义算子,所以显卡基本限定 N 卡。我自己的主力机器是一张 24G 显存的卡,训练常见的室内场景很从容;如果你手上是 8G 或 10G 显存的卡,也能跑,只是批次尺寸、图像分辨率、迭代轮数这些参数要适当压缩,后面我会专门说怎么调。
系统方面优先选 Linux。Windows 虽然在官方仓库里有社区支持,但编译原生算子和依赖库的时候容易踩坑,比如 Visual Studio 版本不兼容、C++ 运行库缺失这类问题,排查起来相当费劲。如果你是在 Windows 上学习,建议直接装 WSL 2,或者干脆用 Docker 镜像,能省掉大量导入导出的折腾。我个人实测下来,Ubuntu 20.04 和 22.04 都很稳,内核版本对这些科学计算框架的兼容性最好。
1.2 创建虚拟环境与安装 PyTorch
3DGS 官方实现基于 PyTorch,版本和 CUDA 版本需要严格匹配。我也见过有人用 CUDA 12 系列跑通旧版 PyTorch 的,但那都是靠环境变量强行绕过去的,新手不建议模仿。我的建议是先装好驱动,再用nvidia-smi确认支持的 CUDA 版本,然后据此选择 PyTorch 安装命令。
拿我现在这台机器举例,驱动支持 CUDA 11.8,所以我用的是 PyTorch 2.0.1 + CUDA 11.8 的组合。创建环境这一步很基础,但值得专门写一下:
conda create -n 3dgs python=3.8 conda activate 3dgs pip install torch==2.0.1 torchvision==0.15.2 --index-url https://download.pytorch.org/whl/cu118这里有个小提示:Python 版本不必追求最新。3DGS 官方仓库写的是 Python 3.7 到 3.9 区间兼容最好,Python 3.10 以上有时会遇到一些老依赖的编译问题。我用 3.8 是因为它和后续要装的 tiny-cuda-nn 扩展包兼容性最稳,买不了吃亏买不了上当。
1.3 安装核心子模块
3DGS 仓库里的核心代码其实分好几块,训练脚本是一个大框架,但真正“吃”算力的是几个自定义算子。安装这些算子时,PyTorch 的版本、CUDA 工具链、gcc 编译器必须“三点一线”,否则必然报错。
git clone https://github.com/graphdeco-inria/gaussian-splatting.git cd gaussian-splatting pip install submodules/diff-gaussian-rasterization pip install submodules/simple-knn pip install plyfile pip install tqdmdiff-gaussian-rasterization 是 3DGS 的核心光栅化器,它把高斯点云渲染成 2D 图像的过程全部写成了 CUDA 算子。这一步编译时间比较长,需要耐心等。如果你之前的 CUDA 环境变量没配好,这里大概率会出现nvcc not found或者gcc: error: unrecognized command line option之类的问题。排查思路通常是检查/usr/local/cuda/bin是否在 PATH 里。
另外两个依赖可能会被忽视:fused-ssim和tiny-cuda-nn。前者用来算 SSIM 指标,后者是官方推荐的快速 MLP 实现。如果你只是训练场景而不做蒸馏或转网格,这两个不是必装的;但如果你打算跑官方的 render 脚本做渲染质量评估,建议还是装一下:
pip install submodules/fused-ssim pip install submodules/tiny-cuda-nn我最初偷懒没装 fused-ssim,结果渲染阶段每次算 SSIM 都会 fallback 到 CPU 实现,速度慢得让人怀疑机器是不是坏了。后来老老实实装完,同一组数据的评估速度快了快 20 倍。
提示:安装顺序要讲究。先装 PyTorch,再装子模块,最后装评估依赖。如果先装了子模块再换 PyTorch 版本,那抱歉,得全部重来。这个坑我替你们踩过了。
2. 数据集准备:从手机照片到 COLMAP 点云
2.1 图像采集技巧
3DGS 和 NeRF 一样,输入是“一组带相机位姿的图像”。相机位姿怎么来?最常见的方式就是 COLMAP,一个开源的 SfM 工具。但 COLMAP 也不是万能的,你采集的图片质量直接影响重建效果。
我个人的采集经验总结成几个字:多角度、低曝光、慢移动。具体说,拍摄时尽量围绕目标物体走一圈甚至两圈,每两到三度拍一张,保证相邻帧之间有足够的视觉重叠。不要在大光比环境下拍,比如正午阳光直射的室外场景,阴影区域很容易让特征点匹配崩掉。另外就是避免运动模糊,尤其是手机拍摄时,快门速度不够时手稍微抖一下,整张图就废了。
图片数量上,单物体建议 80 到 150 张,大场景可以到 300 张以上。但也不是越多越好。我之前试过一个 500 张图的室内场景,图片之间有大量重复区域,COLMAP 跑完产生了很多冗余相机位姿,后续训练显存直接爆掉,最后只保留 200 张效果反而更好。
2.2 数据目录结构与 COLMAP 命令
官方训练脚本默认从特定目录读取数据,目录结构长这样:
data/ └── MyScene/ ├── images/ │ ├── 0001.jpg │ ├── 0002.jpg │ └── ... └── sparse/ └── 0/ ├── cameras.bin ├── images.bin └── points3D.bin其中sparse/0这个目录就是 COLMAP 的输出。如果你已经有现成的 COLMAP 工程(比如之前做 NeRF 用过),可以直接把结果复制过来;没有的话,就手动跑一遍 COLMAP。官方仓库提供了脚本convert.py,可以自动完成特征提取、匹配、稀疏重建这几个阶段。但我更推荐一步步自己执行命令,这样出问题了知道卡在哪:
colmap feature_extractor --database_path database.db --image_path images colmap exhaustive_matcher --database_path database.db colmap mapper --database_path database.db --image_path images --output_path sparse特征提取阶段,--ImageReader.single_camera 1这个参数要留意。如果你所有照片是用同一台手机拍的,务必把这个 flag 打开,表示所有图像共用同一个相机内参;否则 COLMAP 会认为每张照片来自不同的相机,重建结果会非常差。我第一次跑就忘了这个 flag,出来的稀疏点云稀疏得像散沙,训练出来的场景糊成一团。
2.3 检查点云质量
COLMAP 跑完以后,不要急着训练。先用可视化工具加载sparse/0的点云,比如 COLMAP 自带的 GUI,或者第三方工具,快速检查两个指标:点云密度和相机轨迹连续性。
理想的稀疏点云应该是均匀覆盖整个场景表面,而不是只集中在某几个纹理特别丰富的角落。相机轨迹应该是平滑的曲线,如果有明显的跳变,说明某些图像的位姿估计错了,需要检查对应图像的采集质量。我见过一个案例:某开发者的数据集里有一张严重过曝的照片,导致 COLMAP 把它的位姿估计偏了一大截,训练出来的场景在某个视角下出现重影。当时排查了很久,最后就是靠可视化相机轨迹才发现的。
数据质量是 3DGS 训练的“天花板”。算法再先进,也不可能从一张模糊的图片里恢复出清晰的细节。宁可在采集阶段多花 20 分钟反复确认,也不要等训练完再后悔。
3. 模型训练:跑通官方流程
3.1 训练脚本全参数解读
环境配好、数据集就位之后,就可以执行训练命令了:
python train.py -s data/MyScene -m output/MyScene-s指定源数据目录,-m指定输出目录。官方脚本会自动读取数据目录下sparse/0的文件,在输出目录下生成点云、日志和模型文件。
但如果你是第一次跑,我建议先跑一个“迷你版”测试:把图片分辨率降下来(比如缩到原来的四分之一),同时把迭代轮数从默认的 30000 改成 3000。这样整个流程几分钟就能跑完,能快速验证环境是否有问题。确认没问题后再跑完整版本,能省大量调试时间。
训练过程会打印很多信息,新手看到一堆指标可能会慌。其实只需要关注这几个:
- 当前迭代轮数(iteration)
- 当前 PSNR 值(越高越好,一般数值在 10 到 35 之间浮动)
- 高斯点数量(不断增长,后期趋于平稳)
3.2 关键训练参数与调优思路
官方默认的超参数已经经过调优,用默认值跑大多数场景都能得到不错的结果。但遇到特殊情况时,你需要懂得改什么、为什么改。
学习率:3DGS 对位置、旋转、缩放、颜色分别设置了不同的学习率,在arguments/__init__.py里可以看到。其中位置的学习率初始值设为 0.00016,且每迭代 1000 次乘以 0.998 进行衰减。这个衰减策略很关键:前期快速收敛,后期精细调整。如果你发现训练后期 PSNR 曲线还在稳步上升但很缓慢,可以考虑增大衰减系数,让模型在最后几千轮“冲一冲”;反过来,如果曲线震荡明显,说明学习率太大了,衰减要更激进一些。
迭代轮数:默认 30000,对一个 200 张图的场景来说是够用的。我试过把轮数增加一倍到 60000,PSNR 确实能再涨 0.5 到 1 个 dB,但训练时间也翻倍了。对于追求时间和效果平衡的朋友,30000 是首选;如果你用的是短视频抽帧生成的密集数据集,60000 可能会带来边际收益。
densification 策略:这是 3DGS 区别于 NeRF 的核心机制。简单说,训练过程中算法会不断地把稀疏的高斯点“分裂”成更多点,以贴合场景的几何细节。默认设置下,从第 500 次迭代开始每 100 轮做一次 densify,到了第 15000 轮停止 densify,但继续优化属性。--densify_grad_threshold这个参数控制分裂的触发条件,默认 0.0002。小场景可以适当降低到 0.0001,让更多点参与分裂,提升细节;大场景则应提高,避免点数量爆炸导致显存溢出。
损失函数:3DGS 的损失函数是 L1 损失和 D-SSIM 损失的加权和,默认权重为0.8 * L1 + 0.2 * (1 - SSIM)。L1 保证像素级的颜色一致性,SSIM 保证结构相似性。两个损失达到平衡才能得到视觉上好看的重建结果。如果你用的是有大量明亮高光表面的场景(比如瓷器),可以考虑把 D-SSIM 的权重调高一点,比如 0.3,这样重建出的高光会更自然。
3.3 训练过程的观察与分析
训练开始后,输出目录下会生成一个point_cloud/文件夹,每迭代一定轮数会保存一个 PLY 点云快照。我建议养成一个习惯:每隔几千轮就用渲染脚本对关键帧看一眼中间结果。这样做的好处是,一旦发现问题可以尽早止损,不用等到 30000 轮结束才发现结果崩了。
官方提供了一条评估命令:
python render.py -s data/MyScene -m output/MyScene它会根据训练好的模型渲染所有训练视角下的图像,并计算 PSNR、SSIM、LPIPS 这三项指标。这三个数值就是学界常用的量化评估标准。PSNR 越高越好(通常在 25 到 35 之间),SSIM 越接近 1 越好,LPIPS 越低越好。我的一个经验参考值:室内小物体场景 PSNR 在 30 以上算及格,室外大场景 27 以上就值得庆祝了。
写到这里我特别想强调一个容易被忽略的点:评估用的视角和训练用的视角不能完全一样。官方render.py默认渲染训练视角,这只能反映“过拟合程度”。如果条件允许,额外采集一组未参与训练的视角图像作为测试集,才能真正评估泛化能力。
4. 常见问题与排查实录
4.1 编译阶段的报错:nvidia-smi 没问题但编译失败
很多初学者在pip install submodules/diff-gaussian-rasterization这一步就被卡住了。最常见的报错是:
RuntimeError: No CUDA GPUs are available或者
gcc: fatal error: cannot find 'crtbegin.o'前者大概率是 CUDA_HOME 没设对。检查一下:
echo $CUDA_HOME如果是空的,请确保你在安装 CUDA 工具包时选择了“添加到 PATH”选项;如果没选,手动补上:
export CUDA_HOME=/usr/local/cuda export PATH=/usr/local/cuda/bin:$PATH export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH后者则多半是 gcc 版本过新。Ubuntu 22.04 自带的 gcc 11 在某些旧版 CUDA 工具链下会有兼容问题,我曾经遇到过crtbegin.o找不到,后来换成 gcc 9 就顺利编译了。
4.2 训练到一半显存溢出
训练过程中最打击士气的事,就是迭代到一半突然跳出CUDA out of memory。我一开始也觉得是显存不够大,后来才发现很多时候是因为“高斯点数量增长失控”导致的。
3DGS 在 densify 阶段,点数量是指数级增长的。如果你采集的场景有大量平坦的无纹理区域,算法会在这些区域反复分裂高斯点,很快点数量就会超过百万,显存也就跟着爆了。最简单的处理方式有两个:一是降低输入图像分辨率,比如从 1080p 降到 720p,显存占用能减少约一半;二是调高--densify_grad_threshold,减少分裂的触发频率。
还有一种做法是直接限制最大点数量,可以修改gaussian_model.py中的densify_and_update逻辑,在分裂后对点云做一次 Max 数量的截断。不过这是改动算法内部逻辑的活儿,不建议新手一开始就尝试。
4.3 训练结果出现“雾状”伪影
如果你训练出来的场景在边角区域或者背景部分有类似雾的漂浮物,大概率是“漂浮高斯”没被剔除干净。3DGS 算法里有一个prune机制,会剔除不透明度过低或者体积过大的高斯点,但它的触发频率和阈值可能不够激进。
我遇到过一次黑暗背景下拍摄的场景,训练跑完后背景区域出现大片漂浮物。排查下来发现是因为背景区域特征点太少,COLMAP 在这块区域的稀疏点云几乎是空的,3DGS 不得不凭空生成很多高斯点去“凑数”,而这些点没有明确的视觉约束,就形成了雾状伪影。
应对策略有三个方向:
- 采集时尽量让背景有纹理,不放纯色墙面;
- 训练时调低
--opacity_cutoff的初始值,让不透明度低的高斯点被更早地剔除; - 适当增加迭代轮数,因为有些漂浮点在后期会被逐渐压下去。
4.4 PSNR 一直很低,但训练曲线显示收敛了
这种情况最让人抓狂。训练看起来一切正常,损失函数在下降,PSNR 却始终在 15 左右徘徊。我排查这类问题的经验是:先去看输入图像本身。如果你用的是直接采集的手机照片,检查一下是不是有大量重复帧或者高度相似的图像。比如你站在原地拍一个物体拍了一分钟,期间基本没移动,那么这 40 多张图片在 COLMAP 的视角中几乎等价,会严重干扰位姿估计,最终导致重建质量极差。
处理方式是先做一次关键帧筛选,把视觉重叠度太高的帧去掉。简单的做法是用工具计算相邻帧的视觉相似度,或者直接隔几帧抽一帧,保证帧间的位移足够明显。
提示:3DGS 不是“图越多越好”,而是要“图够看越好”。理想情况下,场景中的每个表面至少被 3 个视角覆盖,但也不要超过 10 个。覆盖率太高,训练会趋向过拟合稀疏视角,反而损害泛化能力。
4.5 训练时 GPU 利用率忽高忽低
我跑的第一次训练,发现 GPU 利用率在 20% 到 90% 之间抽搐,一开始以为代码有 bug。后来一看top,发现是数据加载环节在拖后腿。原因是官方代码默认用 CPU 加载图像并做预处理,如果图像太大或者磁盘读取速度慢,GPU 就会饿得“打嗝”。
解决办法很直接:提高--data_device参数指向的加载单元、把图像缩到合适尺寸(如 800x600 左右),或者直接用 SSD 存放数据集。如果你有足够大的内存,也可以把数据集完全 cache 到内存里,训练会丝滑很多。
5. 自定义场景的进阶玩法
5.1 多场景通用训练技巧
当你完成第一个场景训练,开始处理第二个、第三个场景时,会发现不同场景之间的最优参数差异其实不小。我建议建立一套自己的“场景诊断流程”:先用低分辨率快速跑 3000 轮,观察点云增长曲线和初步 PSNR,再据此调整分辨率、densify 阈值和迭代轮数。
这个过程有点像做菜先试菜:大火快炒一小份,尝一下咸淡,再决定整锅菜怎么调味。我经历过的最理想的一次流程,是用低分辨率跑了个 5000 轮的小样,发现场景中有大量细长结构(比如电线、栏杆),于是把初始高斯点的缩放因子调小了一层,最终完整训练的清晰度明显优于第一次。
5.2 导出与可视化
训练完成后,你会拿到一个.ply文件,里面存了几十万甚至上百万个高斯点的属性。想快速查看效果,不需要再跑渲染脚本,直接用一些开源可视化工具加载 PLY 文件即可。校里面的几个工具我用下来体验不错,在浏览器里就能拖拽视角,方便快速检查。
如果想把 3DGS 场景导出到游戏引擎或 Web 端展示,可以考虑用官方仓库里的convert.py把模型转换成更轻量的格式,或者用社区贡献的 mesh 提取工具把点云转成三角网格。这一步对后续应用部署很重要。
5.3 与深度学习的结合方向
3DGS 这两年能从一篇论文火成工具链,核心在于它是显式表达,天然适合和深度学习流程结合。做完基础训练后,你可以尝试几个扩展方向:比如用 3DGS 做新视角合成之外,把几何信息提取出来,做体积测量;或者把多个场景的高斯点合并,做场景编辑。这些都是在官方代码基础之上加几行代码就能入门的,远比从零训练一个 NeRF 要容易得多。
我个人对 3DGS 的期待是它正在成为“三维视觉领域的扩散模型”——一旦基础场景表达统一了,上游的采集、下游的渲染都能快速迭代。作为从业者,现在掌握这套工具,属于正好踩在技术拐点上。
6. 最后的几点个人体会
3DGS 看起来“无脑跑就行”,但真正把效果做好,拼的是对每个步骤背后逻辑的把握。环境配置的每一次报错,都是因为版本链断裂;训练的每一次崩坏,都是因为数据链路有洞;参数调整的每一次犹豫,都是因为不知道默认值为什么长成这样。所以这篇写了这么多“为什么”,就是想帮各位把链条上的每一环都补上。
实操中我最大的感悟是:在执行代码之前,先花时间理解数据。图像分辨率、光照一致性、运动模糊、COLMAP 点云密度,这四个要素几乎决定了最终效果的上限。训练脚本里的参数只是帮你逼近这个上限,而不是帮你突破它。
另外一个想特别叮嘱的是:不要只盯着 PSNR 一个指标。在真实场景中,LPIPS 反映的感知质量往往比 PSNR 更符合人眼观感。有时候 PSNR 提升了 0.5,但 LPIPS 却变差了,这说明模型在过度拟合锐利的颜色细节,而牺牲了整体的结构质感。多指标交叉验证,才能判断一个场景“到底行不行”。
3DGS 是一个迭代速度飞快的领域,从基础跑通到自定义场景再到融合深度学习,每一步都有大量细碎的技巧等着去踩坑、去积累。希望这篇能帮你在第一步上少走些弯路,后续的路就得靠各位自己动手去探索了。还是那句话:跑通一遍,胜过读十遍论文。