ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

3DGS从零实战:CUDA环境搭建、COLMAP预处理与训练踩坑指南

3DGS从零实战:CUDA环境搭建、COLMAP预处理与训练踩坑指南

在图形学和三维重建这个圈子里,2023年底到2024年最绕不开的一个名字就是3DGS(3D Gaussian Splatting)。它用一群带颜色的三维高斯球代替传统网格,配合快速光栅化,把训练速度和渲染画质同时做到了一个离谱的高度。这个系列前面几篇已经把数学原理、差分渲染、球谐函数这些底子打得差不多了,这篇作为04篇,我直接讲实操:怎么从零把环境搭起来,怎么把手里的照片变成一组能实时浏览的3DGS模型,以及训练过程中那些文档里不会写的坑。

这篇内容适合两类人:一类是刚看完论文、想跑通官方代码但被CUDA和依赖搞到怀疑人生的新手;另一类是已经在用NeRF、想对比一下3DGS工作效率的老玩家。如果你是后者,可以直接跳到第三、四章看参数和踩坑部分。

1. 内容整体设计与思路拆解

1.1 为什么3DGS的代码实践比NeRF更“亲民”

相比NeRF那种隐式表达,3DGS最大的优势在于它把整个场景表达为显式的高斯集合,所以训练过程非常直观。你打开TensorBoard,能看到点云从稀疏到稠密、从无颜色到色彩饱满的变化过程,而不是对着一个抽象的体密度场发呆。

代码层面,官方实现(图形学项目名简称INRIA版)结构很清晰,主入口就一个train.py,核心逻辑在scene、gaussian_model、renderer几个模块里。没有复杂的网络结构堆叠,也没有让人头痛的调度器配置。你只需要搞定环境,训练本身几乎就是一行命令的事。

我在搭建过程中最大的体会是:3DGS的环境配置难点不在PyTorch,而在那三个C++/CUDA扩展——diff-gaussian-rasterization、simple-knn、fused-ssim。它们需要和你本机的CUDA、PyTorch版本严格匹配,否则编译期就会给你脸色看。这一点我会在第二章专门展开。

1.2 整套代码流程的关键节点

把官方仓库跑通,本质上就是走通四个环节:环境准备、数据预处理、模型训练、结果导出。数据预处理部分官方默认调用COLMAP做SfM,这一步很多新手觉得“多余”,实际上它决定了后续训练能不能收敛、模型质量能到多高。

我建议一开始不要直接上自己的数据集,先用官方提供的场景数据(比如那个著名的自行车场景)跑通全流程,确认环境没问题、代码能训练出结果,再换成自己的照片。否则一旦出问题,你很难判断是代码bug、环境问题还是数据问题。

整套流程的关键节点我画个表给你,方便对照:

节点作用关键产出常见问题
环境配置编译CUDA扩展可导入的模块CUDA版本不匹配、编译超时
SfM预处理计算相机位姿和稀疏点云sparse/目录COLMAP找不到图像、特征匹配失败
高斯初始化用稀疏点云初始化高斯球3D高斯属性点云太稀疏导致初始化失败
训练优化迭代更新高斯属性checkpoint和点云显存不足、loss不下降、飞点
结果导出生成可交互模型ply文件、渲染视频相机路径设置错误

下面我按这个顺序,一步步带你把代码跑起来。

2. 环境配置全记录——从显卡驱动到CUDA扩展编译

2.1 硬件和驱动的基本盘

3DGS不像大语言模型那样吃显存,但也不是随便一台机器就能跑的。我的主力机是RTX 4090(24GB显存),训练官方默认的自行车场景,大约9分钟左右出到7K迭代,显存占用在8GB左右。如果你用的是12GB显存的卡,也完全够用,只是需要把图像下采样比例稍微调一下。

显存不够的替代方案是降低输入图像分辨率。比如把图像缩放参数从默认的1改成2或4,显存占用会成倍下降,但细节还原度也会肉眼可见地变差。我的建议是:8GB显存跑720p左右输入,12GB跑1080p,24GB直接原图。这个经验值适用于大多数中小场景。

驱动方面,注意你的NVIDIA驱动版本必须支持你打算安装的CUDA版本。最简单的方法是用nvidia-smi看一下驱动支持的最高CUDA版本号,然后往下兼容安装即可。

2.2 创建干净的conda环境和CUDA选型

我推荐用conda管理环境,别直接在系统Python里折腾。创建命令如下:

conda create -n 3dgs python=3.8 -y conda activate 3dgs

Python版本建议选3.8或3.9,实测3.10也能跑,但某些C++扩展的编译脚本对3.10的支持略有瑕疵,没必要冒这个险。

接下来安装PyTorch。这一步是整个环境配置的基石,千万要看清楚自己的CUDA版本。我机器上装的是CUDA 11.8,对应的PyTorch安装命令是:

pip install torch==2.0.0+cu118 torchvision==0.15.0+cu118 --extra-index-url https://download.pytorch.org/whl/cu118

这里有一个非常关键的对应关系,我直接给你结论:PyTorch的CUDA版本和你要编译的CUDA扩展的版本尽量保持一致。比如你用cu118的PyTorch去编译依赖CUDA 12构建的扩展,轻则报错,重则运行时出诡异的内存错误。

注意:如果你拿到的是新版本GPU(比如Ada架构),也不要盲目装CUDA 12,先看你的PyTorch支持什么。很多时候CUDA 11.8反而更稳,因为官方验证过。

2.3 克隆仓库与子模块

官方代码仓库里用了submodule来管理三个关键扩展。克隆时必须加--recursive参数,否则扩展目录是空的:

git clone --recursive https://github.com/graphdeco-inria/gaussian-splatting.git cd gaussian-splatting

如果之前忘了加--recursive,进目录后手动补一下:

git submodule update --init --recursive

然后安装Python侧的依赖:

pip install -r requirements.txt

requirements里主要是tensorboard、plyfile、tqdm这类常规库,不会有太大问题。真正的大头在后面的扩展编译。

2.4 编译diff-gaussian-rasterization等三个扩展

这是整个环境配置过程里最大的一道坎。官方在README里建议直接用pip install,但我实测下来,直接pip install扩展目录经常因为环境和编译参数问题失败。

我最终能稳定跑通的方法是分别编译三个子模块:

# 编译diff-gaussian-rasterization pip install submodules/diff-gaussian-rasterization # 编译simple-knn pip install submodules/simple-knn # 编译fused-ssim pip install submodules/fused-ssim

编译过程会调用nvcc,所以你需要确保nvcc在PATH里。验证方式:

nvcc --version

如果提示找不到nvcc,说明CUDA Toolkit没装或者没有写入环境变量。我踩过这个坑:系统里明明有CUDA,Python侧也能用,但因为没把/usr/local/cuda/bin加到PATH,导致编译时找不到nvcc。

编译过程通常需要几分钟到十几分钟。如果编译中途报错,绝大部分情况是CUDA和PyTorch版本不匹配,或者GCC版本太新(GCC 12以上容易出兼容问题)。我的建议是装GCC 9或10,用conda装最省事:

conda install gcc=9 gxx=9 -c conda-forge

编译成功的标志是import xxxx不报错。在环境里跑一下:

python -c "import diff_gaussian_rasterization; print('ok')"

如果这行输出ok,整个环境配置基本就大功告成了。

2.5 一个更省事的方案:Docker镜像

如果你不想在宿主机上折腾依赖,官方提供了Dockerfile。这个方案对那些实验机器多、环境容易搞脏的人特别友好。我自己在服务器上就是这样用的,一条命令拉起环境,不会影响别的项目。

docker build -t 3dgs . docker run -it --gpus all -v /your/data:/data 3dgs bash

容器里默认环境已经配好,直接train.py就行。我遇到的小问题是容器内访问宿主机数据要处理好挂载路径,尤其注意别把带中文的路径挂进去,COLMAP对中文路径的支持非常差。

3. 数据准备与预处理——这一步决定你模型的天花板

3.1 COLMAP的安装与调用

3DGS官方代码默认使用COLMAP做SfM,目的是从一堆照片里恢复相机内外参和稀疏点云。注意,COLMAP不是Python库,而是一个独立的C++程序。你需要单独安装它,并让命令行里能直接敲colmap这个命令。

Ubuntu下的安装方式:

sudo apt install colmap

如果你用Windows,就直接去COLMAP官网下载预编译的exe,然后把可执行文件所在目录加入PATH。Windows下实测COLMAP的调用偶尔会有路径解析问题,建议所有路径全英文无空格。

3DGS仓库里有个convert.py脚本,它会自动调用COLMAP处理图像:

python convert.py -s data/bicycle

这个脚本做的事情很简单:对data/bicycle/images下的所有图片做特征提取、匹配、稀疏重建,最后把结果写到data/bicycle/sparse目录。训练时,代码从sparse目录读取相机参数和初始点云。

如果你在Windows下遇到convert.py调用COLMAP失败,多数情况是spawn了子进程但找不到colmap命令。我的处理方式是在convert.py里把COLMAP路径写死为绝对路径:

COLMAP_PATH = "C:/path/to/colmap.exe"

3.2 数据集的目录结构说明

官方推荐的数据集结构是这样的:

data/ └── bicycle/ ├── images/ │ ├── 00001.jpg │ ├── 00002.jpg │ └── ... ├── sparse/ │ ├── 0/ │ │ ├── cameras.bin │ │ ├── images.bin │ │ └── points3D.bin └── ...

images目录存放所有输入照片,sparse目录是COLMAP的输出。注意,训练时3DGS只读取sparse目录里的相机参数和点云,图像本身是根据sparse里的对应关系从images目录读取的。两者缺一不可。

如果你用的是别人的数据集(比如Mip-NeRF 360提供的数据),通常会带colmap输出目录,那就不需要再跑convert.py了。这时候你需要手动建立目录结构,把官方expect的sparse内容放进去。

3.3 用自己的相机照片建数据集的实操建议

这一块是很多人第一次上手最容易翻车的地方。我来说说照片采集的要点:

拍摄数量:我建议一个中小型场景拍150~300张照片。太少(比如50张)会导致稀疏重建不稳定,相机位姿算不准;太多(比如超过1000张)会让COLMAP跑上几个小时,训练也慢。官方场景一般在200张左右。

重叠率:相邻照片之间至少要有60%以上的视觉重叠。别像拍风景一样每隔几米拍一张,那会让特征匹配断掉。正确的做法是围绕目标物体或区域,环扫一圈,每隔一小段距离拍一张,角度变化平缓。

光照条件:尽量在均匀光照下拍摄,避免强阴影和过曝。3DGS对高光很敏感,玻璃瓶、金属表面这种高反射物体会在训练后出现一层“薄雾”,实际上是球谐函数无法表达镜面反射的硬伤。

运动模糊:手持拍摄时注意快门速度。模糊的照片会导致COLMAP特征点定位不准,进而影响整个相机位姿估计。我有个朋友用手机拍了一组,很多照片在走动时按下快门,结果COLMAP只有一半图片匹配上了,最后效果惨不忍睹。

3.4 图像预处理:分辨率与格式的取舍

输入图像的尺寸直接影响显存和训练速度。一个取巧的做法是先用低分辨率跑通流程,确认数据没问题后再用全分辨率做最终训练。我在数据准备阶段会先跑一个快速测试,把图片缩小到800px,COLMAP跑一遍,确认能出模型再放大。

图片格式方面,官方支持.jpg和.png。JPEG体积小但压缩有损,PNG无损但文件大。我的经验是日常拍摄的JPG完全够用,但如果你做的是高精度的物体建模(比如文物数字化),建议用PNG或者高质量JPEG(压缩质量95以上)。

注意:图像命名建议统一为数字递增格式(00001.jpg,00002.jpg...)。虽然COLMAP本身不要求,但3DGS在加载图片时是按文件名排序的,如果混了中文名或者特殊字符,可能在训练时报错找不到图片。

4. 训练实操——从命令行到参数调优

4.1 train.py的使用与核心参数

环境配好、数据也处理完了,训练这一步理论上是很轻松的。核心命令就一行:

python train.py -s data/bicycle -m output/bicycle

-s指定数据集目录,-m指定输出目录。训练默认跑30K迭代,如果是官方bicycle数据集,在4090上大约需要30到40分钟。跑完后输出目录里会得到point_cloud/iteration_30000这个子目录,里面就是最终的3DGS模型(一个.ply文件)。

如果你想快速验证流程,可以把迭代数调小:

python train.py -s data/bicycle -m output/bicycle-test --iterations 7000

官方其实还提供了--test_iterations参数,可以在指定的迭代步数上做评估输出。比如:

python train.py -s data/bicycle -m output/bicycle --test_iterations 7000 30000 --save_iterations 7000 30000

这样会在7000步和30000步都保存一次模型,方便你对比不同迭代量下的效果差异。

4.2 训练过程中发生了什么——读懂内部机制

如果你打开TensorBoard看训练过程,会发现3DGS的训练分成几个阶段。前几百步主要是对初始高斯球进行位置和颜色调整,粗糙地把场景大的结构撑起来。之后每隔一定步数会做一次densify(稠密化),把细节区域的高斯球密度提上去,同时修剪掉那些透明度太低或位置太远的“飞点”。

densification是在固定迭代步数触发的,默认从第500步开始,到第15000步结束。这期间模型会周期性地新增高斯球,数量可能从几万个涨到几十万甚至上百万。如果你的场景很复杂,高斯球数量会随着训练持续上涨,这时显存占用会逐渐增加。

我在实际训练中发现的一个经验是:球谐阶数(sh_degree,默认3)不要一上来就调低。虽然SH阶数越高,颜色表达越丰富,但训练前期如果只让低阶球谐优化,会出现色彩过渡不自然的问题。官方默认设置已经很合理,新手不要乱动这个参数。

4.3 训练输出与模型文件说明

训练结束后,输出目录里主要包含:

output/bicycle/ ├── point_cloud/ │ └── iteration_30000/ │ ├── point_cloud.ply │ └── ... ├── test/ │ └── ... ├── train/ │ └── ... ├── cameras.json └── ...

point_cloud.ply就是最终的3DGS模型文件,可以用官方提供的SIBR viewer打开,也可以配合一些Web端查看器直接拖拽浏览。

test目录里是模型在测试视角上的渲染结果和GT对比,train目录里则是训练集上的。train的PSNR通常会略高于test,这很正常,说明模型在训练集上过拟合了,但如果两者差距过大(比如超过3dB),就要考虑是不是训练太久或者场景本身过小。

cameras.json保存的是评估用相机的参数。如果你想自己渲染一段绕场景旋转的视频,可以修改这个文件里的相机轨迹,然后用render.py重新渲染。这里有个小技巧:绕场景旋转的相机半径不要设太大,否则相机跑到模型外太空,渲染出来一片黑。

4.4 用render.py导出视频

训练完模型后,你可能想生成一段展示视频,或者导出某个自定义视角的静态图。官方提供了render.py:

python render.py -m output/bicycle --skip_train

这会用默认的相机路径渲染视频帧集,分布在test目录下。如果你想生成真正的.mp4视频,需要自己调用ffmpeg把帧序列合成。我在实际操作中会直接这么干:

ffmpeg -framerate 30 -i output/bicycle/test/ours_30000/renders/%05d.png -c:v libx264 -crf 18 output/bicycle/result.mp4

这里crf值18是视觉无损的画质,一般分享够用了。如果你想更小的文件,可以调到23,肉眼基本看不出区别。

5. 常见问题与排查技巧实录

5.1 编译扩展时提示CUDA版本不匹配

这个坑几乎人人都会遇到。集齐了三类症状:undefined symbol、gcc: error: unrecognized command-line option、nvcc fatal。原因就是编译扩展用的CUDA和PyTorch运行时用的CUDA版本不一致。

我的排查思路是这样的:先查PyTorch绑定的是什么CUDA:

python -c "import torch; print(torch.version.cuda)"

再看nvcc版本:

nvcc --version

两者如果不一致,就用conda装一个和PyTorch一致的CUDA Toolkit,并在编译前把PATH和LD_LIBRARY_PATH指过去:

export PATH=/usr/local/cuda-11.8/bin:$PATH export LD_LIBRARY_PATH=/usr/local/cuda-11.8/lib64:$LD_LIBRARY_PATH

conda安装CUDA Toolkit的方式是:

conda install -c nvidia cuda-toolkit=11.8

5.2 训练出来一片黑或者全是噪点

模型输出全黑,通常有两种情况:一是相机参数错误,二是初始点云崩塌。如果你用的是自己的数据集,建议检查COLMAP的稀疏重建结果——用COLMAP GUI打开sparse/0目录,看一眼点云是否正常分布。如果点云像一层纸片一样摊开,说明SfM失败了,照片之间没有足够的特征匹配。

全黑还有一种可能是图像加载路径出错了,模型实际上没有读到训练图像。这种情况在Windows上比较常见,原因是convert.py生成的相机参数里的图像路径分隔符是反斜杠,而代码在Linux风格下解析时出错。解决办法是数据路径全部用英文,并尽量在Linux环境跑训练。

噪点过多(重建表面有一层砂砾感)则通常是densify的阈值设置问题。你可以检查训练后期的高斯球数量,如果涨到了几百万个还止不住,大概率是场景光照变化太大,模型只能用大量小高斯球去硬凑。处理方法在数据端:换一个光照更均匀的时间重新拍摄。

5.3 显存不足怎么办

对于8GB或更小显存的卡,推荐把图像分辨率降下来。train.py里有几个参数可以控制:

python train.py -s data/bicycle -m output/bicycle --resolution 2

这里的--resolution是指图像下采样的倍数,2表示原图宽高各缩小一半。实测1080p输入在8GB卡上设置resolution=2后,显存占用可以降到5GB左右,能跑,但渲染效果明显下降。另一个办法是减少迭代过程中的densify次数,但对新手来说,最简单还是降低分辨率。

如果在训练中途显存突然暴增,大概率是densify触发时高斯球数量暴涨。这时候可以试试把--densify_grad_threshold调高一点,阈值越高,新增的高斯球越少。默认值是0.0002,你可以先试0.0005。注意这个值只影响训练后半段,早期结构建立阶段不受影响。

5.4 COLMAP阶段照片匹配失败

具体表现是convert.py跑了好几个小时,稀疏重建出来只有几十个相机位姿,点云稀稀拉拉。排查思路先看数据:照片是否模糊、是否有大面积重复纹理(比如白墙)、是否有玻璃等透明物体。另外,照片的分辨率太低也会导致特征点数量不足。

我的建议是:先用官方bicycle数据集跑通全流程,确认环境OK。如果COLMAP对你的图片匹配失败,就用GUI打开看看哪些图片被估出了位姿,把匹配不上的图片删掉或者补拍。补拍时注意和相邻照片保持相似视角,别跳太远。

这里还有一个小技巧:COLMAP匹配失败时,可以尝试调整convert.py里特征提取的参数,比如增大--SiftExtraction.max_num_features到10000。有时候图片纹理稀疏,默认的8000个特征点不够用。

5.5 训练时loss不下降、指标停滞

如果你看到TensorBoard里PSNR指标训练到一定程度就纹丝不动,先别急着调参。先看训练集和测试集指标的差值,如果两者同步停滞,说明模型表达能力到上限了,多半是数据本身的问题(照片不够、场景太大)。如果训练集还在涨、测试集不动,那是在过拟合,可以提前停止训练。

我遇到过一种更隐蔽的情况:某些场景的墙面反射、水面波动导致图像之间光度不一致。3DGS假设场景是静态的,遇到雨、雾、闪烁的灯光会出现“糊成一团”的现象。这种情况任何参数调优都没用,只能在数据采集时避开了。

6. 应用场景延伸与进阶建议

6.1 3DGS能做什么实际项目

跑通官方代码之后,你可以把3DGS用在很多实际方向上:文物保护里的器物3D重建、电商场景的商品展示、室内装修设计的效果预览、自动驾驶仿真中的道路场景重建。每个方向需要的功夫不一样,但核心模型训练方法是一致的。

物体级重建相对简单,几十张照片就够,模型小、训练快。场景级重建(比如整条街道)对相机位姿精度要求极高,COLMAP很容易在长走廊、无纹理墙面区域丢失尺度信息。这种场景建议分块重建再合并,别指望一次跑通。

6.2 从单个场景到多场景和自定义模型

3DGS目前没有像NeRF那样成熟的预训练/多场景泛化范式,基本还是一个场景练一个模型。但社区里已经有各种改进版本:有做实时流式渲染的,有压缩高斯球数量降低推理成本的,有把3DGS和骨骼动画结合的。如果你有心做科研,代码实践跑通之后,读一读这些改进版本的源码会比重新啃论文高效得多。

我个人觉得3DGS和传统网格模型最大的区别,在于它把“模型”变成了可微的数据结构。这意味着你不仅可以做渲染,还能把3DGS模型直接作为优化对象,比如做体积编辑、动态场景重建甚至三维内容生成。这个方向目前还是蓝海,代码基础打牢了,后面能玩的花样非常多。

6.3 训练完模型的导出与轻量化

如果你想把3DGS模型部署到Web端,需要处理一下点云文件大小。训练完的PLY文件动辄几百MB,浏览器加载很吃力。目前社区的做法主要是做高斯球剪枝和量化,原理很简单:把不透明度过低的球删掉,把位置、颜色、旋转、缩放的数值从float32量化为float16或int8。

我自己试过一个方案:先用官方代码训练出模型,再用现成的剪枝脚本把高斯球从100万剪到30万,渲染质量肉眼几乎无差别,文件体积减小了60%多。如果你做产品原型,这一步几乎是必须的。

7. 最后一个建议

整个3DGS代码实践流程走下来,我最想跟新手说的一句话是:不要被环境配置劝退。那个编译扩展的坎,每个做图形学的人都跨过,跨过去之后剩下的都是水到渠成。我见过有人因为diff-gaussian-rasterization编译失败就放弃了整个方向,其实换个GCC版本或者CUDA版本就能解决。

如果你正在卡环境,我的建议是:关掉多余的浏览器页面,把本文第二章的步骤一行一行照着敲,遇到报错先复制报错信息去搜索引擎看,别急着自己瞎试。这套流程我已经在不同机器上跑了至少五遍,踩过所有能踩的坑,照着来基本一次过。

如果你已经跑通了bicycle场景,下一步不妨用自己的手机拍一组照片试试。选一个光线充足、物体纹理丰富的桌面场景,拍一百张左右,跑一轮低分辨率测试,再跑全分辨率。第一次看到自己拍的照片变成可旋转的三维模型的那一刻,你会觉得所有折腾都值了。

返回列表