3DGS 训练全流程记录
(NVIDIA P104-100)
RealityScan 2.2 手机摄影测量 → 导出 CSV / COLMAP 格式 → 3D Gaussian Splatting(3DGS)建模。完整记录为什么 NerfStudio 路线在 P104 上走不通、如何搭建原版 3DGS(INRIA)环境,以及从数据准备到训练验证的完整操作教程与实测结果。
1. 前言
项目背景:RealityScan 2.2 手机摄影测量 → 导出 CSV / COLMAP 格式 → 3D Gaussian Splatting(3DGS)建模。本文涵盖:为什么 NerfStudio 路线在 P104 上走不通、如何搭建原版 3DGS(INRIA)环境、从数据准备到训练验证的完整操作教程,以及 P104 实测结果。
配套文档:《RealityScan2.2-NerfStudio-CSV调试记录》(数据转换阶段排错)。
配套脚本:fix_rc_csv.py、colmap_txt2bin.py、prepare_gs_data.py、build_all.bat、build_dgr.bat。
1.1 数据链路
RealityScan 2.2 对齐(手机照片 16 张)
├─ 导出 3.csv → 相机位姿/内参/畸变(Internal/External camera parameters)
├─ 导出 images/ → COLMAP 配套图像(00000.png,已重命名)
└─ 导出 sparse/0/ → COLMAP 稀疏重建(文本格式 txt)
本流程最终产物:3DGS 训练好的高斯点云(point_cloud.ply)+ 渲染图像 1.2 环境
| 项目 | 值 |
|---|---|
| GPU | NVIDIA P104-100(8 GB,Pascal 架构 sm_61) |
| 驱动 / CUDA | 582.53 / 系统 CUDA Toolkit 11.8(nvcc 11.8.89) |
| 编译器 | Visual Studio 2022 Community(MSVC 14.44) |
| Python | 3.10.11 |
| torch / torchvision | 2.4.1+cu118 / 0.19.1+cu118 |
| 训练框架 | gaussian-splatting(INRIA,main + diff-gaussian-rasterization dr_aa 分支) |
| 训练数据 | 16 张,0.5x 分辨率(≈2250×1000) |
1.3 文档结构
- 第 2 章:问题(为什么 NerfStudio 路线受阻、训练环节全部问题)
- 第 3 章:解决思路与方案选型
- 第 4 章:逐步操作教程(可直接照做)
- 第 5 章:P104 实测训练结果
- 第 6 章:后续操作;第 7 章:踩坑清单;第 8 章:附录
2. 问题
2.1 核心问题:NerfStudio 的 splatfacto 无法在 P104 上运行
3D Gaussian Splatting 的核心是一个自定义 CUDA 光栅化器,NerfStudio 1.1.5 的 splatfacto 使用开源库 gsplat。排查确认:
- gsplat 源码构建配置明确注释:
build against architectures >= 7.0 (required by cooperative_groups::labeled_partition in gsplat); cooperative_groups::labeled_partition是 Volta(sm_70)引入的独立线程调度特性,Pascal(sm_60/61)硬件上不存在;- 官方预编译 wheel 仅含
sm_70/75/80/86/90的 cubin,cuobjdump -elf gsplat/csrc.pyd可验证;即使自行编译,代码级依赖也无法绕过。
结论:splatfacto 需要 GPU 架构 ≥ sm_70(如 T4、V100、RTX 20 系+);P104-100(sm_61)只能换方案。
2.2 训练环节遇到的问题(概览)
即使换用原版 3DGS,训练环境搭建也是一串连锁问题(详见第 7 章踩坑清单):
| # | 问题 | 一句话原因 |
|---|---|---|
| 1 | torch.cuda.is_available()=False | 装的是 CPU 版 torch |
| 2 | gsplat no kernel image available | 见 2.1,硬件不支持 |
| 3 | CUDA version (11.8) mismatches PyTorch (12.4) | torch 的 CUDA 版本与系统 nvcc 不一致 |
| 4 | STL1002: expected CUDA 12.4 or newer | MSVC 14.44 的新 STL 拒绝旧 CUDA |
| 5 | unsupported Microsoft Visual Studio version | CUDA 11.8 不认 MSVC 14.44 |
| 6 | cv2 _ARRAY_API not found | numpy 被误升到 2.x |
| 7 | colmap images.bin 解析乱码 | qvec/tvec 误用 float32,应为 float64 |
| 8 | antialiasing 参数报错 | rasterizer 分支与主仓库代码不匹配 |
| 9 | 路径反斜杠丢失 | bash 转义问题 |
3. 解决方法(方案选型)
| 方案 | 可行性 | 结论 |
|---|---|---|
| splatfacto(NerfStudio)+ P104 | 硬件不支持 sm_61 | ❌ |
| splatfacto + 云 GPU(T4 等) | 可行,但需要额外资源 | 备选 |
| 原版 3DGS(INRIA gaussian-splatting) | CUDA 光栅化代码兼容 Pascal,社区在 GTX 10 系上大量验证 | ✅ 采用 |
选择原版 3DGS 的理由:
- 其
diff-gaussian-rasterization光栅化器编写于 2023 年(CUDA 11 时代),对 Pascal 支持良好; - 我们的数据已经具备原版 3DGS 需要的 COLMAP 格式(RealityScan 导出),无需重新做特征匹配/重建;
- 稀疏点云(23513 点)可直接作为高斯初始化,训练更快更稳。
3.1 整体思路
环境对齐(torch cu118 ↔ nvcc 11.8, numpy<2)
→ 获取源码(main + submodules,dr_aa 分支 + glm)
→ 编译三个 CUDA 扩展(MSVC + nvcc,绕过双向版本检查)
→ 数据准备(colmap txt→bin;图像与内参 0.5x 缩放)
→ 训练(7000 步验证)→ 渲染验证(PSNR) 4. 具体操作教程
4.0 环境检查
# 显卡与架构
nvidia-smi --query-gpu=name,compute_cap,memory.total --format=csv
# 期望输出:NVIDIA P104-100, 6.1, 8192 MiB
# CUDA 工具链(nvcc 必须存在)
nvcc --version
# Python 依赖状态
python -c "import torch; print(torch.__version__, torch.cuda.is_available())"
python -c "import numpy; print(numpy.__version__)"
# MSVC 工具链(确认 cl.exe 存在即可,路径在 VS 安装目录)
ls "/c/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/" 4.1 Python 环境对齐
关键原则:torch 的 CUDA 版本必须与 nvcc 版本一致,否则 torch cpp_extension 直接拒绝编译。
# 系统 nvcc 是 11.8 → 安装 cu118 版 torch(必须用 PyTorch 官方源,默认源装的是 CPU 版!)
python -m pip install torch==2.4.1 torchvision==0.19.1 \
--index-url https://download.pytorch.org/whl/cu118
# numpy 必须 <2(open-cv 等二进制扩展依赖 numpy 1.x)
python -m pip install "numpy==1.26.4"
# 验证
python -c "import torch; print(torch.__version__, torch.cuda.is_available())" # 2.4.1+cu118 True
python -c "import numpy; print(numpy.__version__)" # 1.26.4 ⚠️
pip install --force-reinstall会从默认源连带升级依赖(numpy→2.x、torch→CPU 版),曾两次踩坑。装完后务必复核版本。
4.2 获取源码
GitHub 直连(尤其 git 协议)在本环境极不稳定,推荐 ghfast.top 镜像下载 zip:
cd /d/temp
# 主仓库(main 分支)
curl -L --retry 10 -o gs.zip "https://ghfast.top/https://github.com/graphdeco-inria/gaussian-splatting/archive/refs/heads/main.zip"
unzip -q gs.zip && mv gaussian-splatting-main gaussian-splatting
# rasterizer:必须用 dr_aa 分支(主仓库代码调用 antialiasing 参数,main 分支无此接口)
curl -L --retry 10 -o dgr.zip "https://ghfast.top/https://github.com/graphdeco-inria/diff-gaussian-rasterization/archive/refs/heads/dr_aa.zip"
unzip -q dgr.zip
mkdir -p gaussian-splatting/submodules/diff-gaussian-rasterization
mv diff-gaussian-rasterization-dr_aa/* gaussian-splatting/submodules/diff-gaussian-rasterization/
# simple-knn 在 INRIA GitLab(不在 GitHub!)
curl -L --retry 10 -o sknn.zip "https://gitlab.inria.fr/bkerbl/simple-knn/-/archive/main/simple-knn-main.zip"
unzip -q sknn.zip
mkdir -p gaussian-splatting/submodules/simple-knn
mv simple-knn-main/* gaussian-splatting/submodules/simple-knn/
# fused-ssim(metrics 用)
curl -L --retry 10 -o fssim.zip "https://ghfast.top/https://github.com/rahul-goel/fused-ssim/archive/refs/heads/main.zip"
unzip -q fssim.zip
mkdir -p gaussian-splatting/submodules/fused-ssim
mv fused-ssim-main/* gaussian-splatting/submodules/fused-ssim/
# glm(rasterizer 的数学库依赖)
curl -L --retry 10 -o glm.zip "https://ghfast.top/https://github.com/g-truc/glm/archive/refs/tags/1.0.1.zip"
unzip -q glm.zip
mkdir -p gaussian-splatting/submodules/diff-gaussian-rasterization/third_party
cp -r glm-1.0.1/* gaussian-splatting/submodules/diff-gaussian-rasterization/third_party/glm/ 4.3 编译三个 CUDA 扩展
编译前置条件(已在 4.0/4.1 满足):vcvars 环境、nvcc 11.8、torch cu118、numpy<2。
修改三个 setup.py:在 extra_compile_args["nvcc"] 列表开头追加两个参数(MSVC 14.44 过新、CUDA 11.8 过旧,分别绕过两侧版本检查):
"-allow-unsupported-compiler", "-D_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH" 已修改好的脚本见 readme/build_all.bat(对应三个 submodule 的 setup.py 均已加参数):
@echo off
call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" >nul 2>&1
set PYTHONIOENCODING=utf-8
set DISTUTILS_USE_SDK=1
cd /d D:\temp\gaussian-splatting\submodules\diff-gaussian-rasterization
python setup.py install
cd /d D:\temp\gaussian-splatting\submodules\simple-knn
python setup.py install
cd /d D:\temp\gaussian-splatting\submodules\fused-ssim
python setup.py install 执行与验证:
cd /d/temp/ReasonixChat/3/readme && cmd //c build_all.bat
cd /d/temp/gaussian-splatting
python -c "from diff_gaussian_rasterization import GaussianRasterizationSettings, GaussianRasterizer; print('dgr OK')"
python -c "import simple_knn; print('simple_knn OK')"
python -c "import fused_ssim; print('fused_ssim OK')" 编译时间:三个扩展合计约 5~10 分钟(单核 ninja 编译)。
4.4 数据准备
RealityScan 导出的 sparse/0 是 colmap 文本格式,而原版 3DGS 只读二进制格式,需要转换:
# 1) txt → bin(16 相机 / 16 图像 / 23513 点)
cd /d/temp/ReasonixChat/3
python readme/colmap_txt2bin.py
# 2) 图像与内参同步缩放到 0.5x(适配 8GB 显存与训练速度)
# 产物:D:\temp\ReasonixChat\3\gs_data(images/ + sparse/0/*.bin)
python readme/prepare_gs_data.py 0.5 colmap_txt2bin.py 要点:
cameras.bin:[id(int32), model(int32), w(int64), h(int64), params(double...)],PINHOLE=1 共 4 个参数(fx, fy, cx, cy);images.bin:qvec/tvec 是 float64(double),这是最易写错的地方(colmap 官方ReadImagesBinary用 8 字节 double);随后是camera_id(int32)、name(ASCII+\0)、num_points(uint64)、(x,y double, point3D_id int64)...;points3D.bin:[id(int64), xyz(3×double), rgb(3×uint8), error(double), track_len(int64), (image_id int32, point2D_idx int32)...]。
prepare_gs_data.py 要点:3DGS 读取图像原始分辨率,没有内置缩放;缩放图像的同时必须按同一比例缩放 fx/fy/cx/cy 与 w/h,否则相机几何错乱。
4.5 训练
cd /d/temp/gaussian-splatting
PYTHONIOENCODING=utf-8 python train.py \
-s "D:/temp/ReasonixChat/3/gs_data" \
-m "D:/temp/ReasonixChat/3/gs_output" \
--iterations 7000 | 参数 | 含义 |
|---|---|
-s | 数据目录(含 images/ 与 sparse/0/),路径用正斜杠(bash 下反斜杠会被吞) |
-m | 输出目录(模型、checkpoint、日志) |
--iterations | 迭代数,默认 30000;验证流程先用 7000 |
--eval | 可选,按 1/8 划分测试集并输出测试指标 |
训练日志节选(首次运行会提示把 points3D.bin 转成 points3D.ply,属正常):
Loading Training Cameras
Number of points at initialisation : 23513
Training progress: 100%|██████████| 7000/7000 [11:21<00:00, 9.49it/s, Loss=0.0155550]
[ITER 7000] Evaluating train: L1 0.009710725769400597 PSNR 36.59599494934082
[ITER 7000] Saving Gaussians
Training complete. 4.6 渲染验证
cd /d/temp/gaussian-splatting
PYTHONIOENCODING=utf-8 python render.py \
-m "D:/temp/ReasonixChat/3/gs_output" \
-s "D:/temp/ReasonixChat/3/gs_data" \
--iteration 7000 输出:gs_output/train/ours_7000/{renders,gt}/ 与 gs_output/test/ours_7000/。

用脚本对比渲染图与 GT 计算 PSNR:
import numpy as np
from PIL import Image
from pathlib import Path
r = Path("D:/temp/ReasonixChat/3/gs_output/train/ours_7000")
psnrs = []
for p in sorted((r / "renders").glob("*.png")):
gt = np.asarray(Image.open(r / "gt" / p.name).convert("RGB"), dtype=np.float32) / 255.0
pred = np.asarray(Image.open(p).convert("RGB"), dtype=np.float32) / 255.0
mse = np.mean((pred - gt) ** 2)
psnrs.append(10 * np.log10(1.0 / (mse + 1e-12)))
print(f"PSNR: mean={np.mean(psnrs):.2f} min={np.min(psnrs):.2f} max={np.max(psnrs):.2f}") 5. P104 实测训练结果
5.1 训练数据
| 项目 | 值 |
|---|---|
| 图像 | 16 张,0.5x ≈ 2250×1000(原始 4500×2000) |
| 稀疏点初始化 | 23513 点 |
| 迭代数 | 7000 |
| 训练耗时 | 11 分 21 秒(约 9.5 it/s) |
| Loss 收敛 | 0.1606 → 0.0156 |
| 训练集评估 | PSNR 36.6 dB,L1 0.0097 |
5.2 渲染验证(16 帧,与输入图像对比)
| 指标 | 数值 |
|---|---|
| 平均 PSNR | 37.24 dB |
| 最低 / 最高 | 30.17 / 39.92 dB |
说明:此为训练集视角的渲染对比(验证流程),数值已属良好水平;要评估泛化能力需
--eval划分测试集。

5.3 产物
gs_output/
├── point_cloud/iteration_7000/point_cloud.ply ← 训练好的高斯泼溅点云(核心产物)
├── input.ply ← 稀疏点初始化
├── cameras.json / exposure.json ← 相机与曝光参数
├── train/ours_7000/{renders,gt}/ ← 渲染图与真值
├── test/ours_7000/
└── events.out.tfevents.* ← TensorBoard 日志 6. 后续操作
6.1 完整训练(推荐,约 50 分钟)
cd /d/temp/gaussian-splatting
PYTHONIOENCODING=utf-8 python train.py \
-s "D:/temp/ReasonixChat/3/gs_data" \
-m "D:/temp/ReasonixChat/3/gs_output_full" \
--iterations 30000 产物:gs_output_full/point_cloud/iteration_30000/point_cloud.ply。
6.2 查看与导出
- SIBR viewer(官方实时查看器,需另行编译,见仓库
SIBR_viewers/); - 直接渲染视频:
render.py支持--skip_train/--skip_test与自定义轨迹; - 点云 ply 可用 CloudCompare / MeshLab 打开检查质量。
6.3 换用更合适的 GPU(如需要 NerfStudio 生态)
gsplat 要求 sm_70+;租用 T4/RTX 20 系+ 后,output/transforms.json 已就绪,直接:
PYTHONIOENCODING=utf-8 ns-train splatfacto --data output --output-dir runs \
--experiment-name myscan --max-num-iterations 30000 --vis tensorboard \
nerfstudio-data --downscale-factor 2 7. 踩坑清单(按出现顺序)
| # | 现象 | 原因 | 处理 |
|---|---|---|---|
| 1 | torch.cuda.is_available()=False | 默认源装了 CPU 版 torch | 用 PyTorch 官方源装 cu118/cu124 wheel |
| 2 | gsplat no kernel image is available for execution on the device | gsplat 不支持 sm_61(见 2.1) | 换原版 3DGS |
| 3 | DLL load failed while importing gsplat.csrc | torch 版本与 gsplat 预编译 wheel(pt24)不匹配 | 此路已弃用(gsplat 不再需要);若坚持,需 torch 降到与 wheel 一致的版本 |
| 4 | The detected CUDA version (11.8) mismatches ... PyTorch (12.4) | torch 的 CUDA 与 nvcc 不一致 | torch 换 cu118 与 nvcc 11.8 对齐 |
| 5 | unsupported Microsoft Visual Studio version | CUDA 11.8 的 host_config.h 拒绝 MSVC 14.44 | nvcc 加 -allow-unsupported-compiler |
| 6 | STL1002: Unexpected compiler version, expected CUDA 12.4 or newer | MSVC 14.44 新 STL 反向拒绝旧 CUDA | nvcc 加 -D_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH |
| 7 | cv2 _ARRAY_API not found / numpy.core.multiarray failed to import | numpy 被 pip 连带升到 2.x | pip install "numpy==1.26.4" |
| 8 | glm/glm.hpp: No such file or directory | rasterizer 的 third_party/glm 子依赖缺失 | 下载 glm 放入 third_party/glm/ |
| 9 | colmap 二进制读取 UnicodeDecodeError | images.bin 的 qvec/tvec 误用 float32 | 修正为 float64(<4d/<3d) |
| 10 | GaussianRasterizationSettings ... unexpected keyword 'antialiasing' | rasterizer 用了 main 分支,主仓库代码需要 dr_aa | 换 dr_aa 分支重新编译 |
| 11 | Could not recognize scene type | 路径反斜杠被 bash 吞掉,sparse 检测失败 | 传参用正斜杠 D:/temp/... |
| 12 | DISTUTILS_USE_SDK 警告导致编译中止 | torch cpp_extension 要求显式声明 SDK 环境 | bat 中 set DISTUTILS_USE_SDK=1 |
| 13 | --force-reinstall 后 torch 变 CPU 版 / numpy 变 2.x | pip 从默认源重装依赖 | 用 --no-deps 或装完复核版本 |
8. 附录
8.1 文件清单
| 文件/目录 | 说明 |
|---|---|
readme/3DGS训练全流程记录.md | 本文档 |
readme/RealityScan2.2-NerfStudio-CSV调试记录.md | 数据转换阶段排错文档 |
readme/fix_rc_csv.py | RealityScan 2.2 CSV 列名映射(含 --zero-k4) |
readme/colmap_txt2bin.py | colmap 文本 → 二进制转换 |
readme/prepare_gs_data.py | 图像+内参同步缩放,生成 3DGS 数据目录 |
readme/build_all.bat | 一键编译三个 CUDA 扩展 |
readme/build_dgr.bat | 单独编译 rasterizer |
gs_data/ | 3DGS 输入数据(0.5x) |
gs_output/ | 训练产物(7000 步) |
output/ | NerfStudio 转换产物(transforms.json,备用) |
8.2 原理补充
为什么原版 3DGS 支持 Pascal 而 gsplat 不支持?
3D Gaussian Splatting 的光栅化核心是按 tile 组织高斯并做并行排序/混色。原版 diff-gaussian-rasterization(2023)使用经典的 warp 级同步(__syncwarp / shared memory)实现,这些原语 Pascal 就有;而 gsplat 引入 cooperative_groups::labeled_partition(Volta 独立线程调度)实现更高效的动态并行,代价是放弃 Pascal。这是"新库更快但更挑硬件"的典型取舍。
RealityScan 的 COLMAP 坐标系说明
- CSV 导出:
(E, N, U)右手系(x 东、y 北、z 上/alt); - COLMAP 导出:
(E, -U, N)右手系(相机中心 =(x, -alt, y)),两者差一个固定旋转,经 PnP 重投影验证几何一致; - 原版 3DGS 直接消费 COLMAP 姿态,无需关心绝对坐标系(训练时自行归一化)。
8.3 参考链接
- gaussian-splatting 官方仓库:https://github.com/graphdeco-inria/gaussian-splatting
- gsplat 仓库(含 arch >= 7.0 说明):https://github.com/nerfstudio-project/gsplat
- simple-knn(GitLab):https://gitlab.inria.fr/bkerbl/simple-knn
- PyTorch 官方 wheel 源:https://download.pytorch.org/whl/cu118
- ghfast.top 加速镜像:https://ghfast.top/
Happy Reconstructing! 🎉