3DGS 算法架构与实现:初始化、密度控制与训练策略
在理解了 3DGS 的核心数学原理后,本文将深入探讨其完整的算法架构与实现细节。3DGS 的成功不仅依赖于优雅的数学表示,更依赖于精心设计的训练策略。
1. 引言
1.1 算法总体框架
3DGS 完整流程包括 SfM 点云初始化、高斯初始化、训练循环(可微分渲染、计算损失、反向传播、参数更新)、密度控制(添加/移除高斯)及最终输出高斯场景。
3D Gaussian Splatting(3DGS)的完整算法架构与实现细节。内容涵盖基于 SfM 的点云初始化策略、自适应密度控制机制(包括克隆、分裂与剪枝)、优化器配置及学习率调度方案。此外,文章还解析了关键实现细节如协方差计算稳定性、深度排序优化及 Tile 分配策略,并提供了常见问题诊断与质量优化建议。通过介绍 Mip-Splatting 等变体改进,帮助开发者理解如何高效训练 3DGS 模型以实现实时渲染与高质量重建。
在理解了 3DGS 的核心数学原理后,本文将深入探讨其完整的算法架构与实现细节。3DGS 的成功不仅依赖于优雅的数学表示,更依赖于精心设计的训练策略。
3DGS 完整流程包括 SfM 点云初始化、高斯初始化、训练循环(可微分渲染、计算损失、反向传播、参数更新)、密度控制(添加/移除高斯)及最终输出高斯场景。
主要步骤:
3DGS 使用 Structure from Motion (SfM) 获取初始点云和相机位姿。
常用 SfM 工具:
SfM 流程:
COLMAP 输出格式:
| 文件 | 内容 |
|---|---|
| cameras.bin/txt | 相机内参 (fx, fy, cx, cy) |
| images.bin/txt | 图像位姿 (四元数 + 平移) |
| points3D.bin/txt | 稀疏点云 (位置 + 颜色) |
每个 SfM 点初始化为一个高斯:
# 初始化参数
for point in sfm_points:
# 位置:直接使用 SfM 点坐标
mu = point.xyz # (3,)
# 缩放:基于邻近点距离
distances = compute_knn_distances(point, k=3)
scale = mean(distances)/2 # (3,) 各向同性
# 旋转:单位四元数(无旋转)
rotation = [1,0,0,0] # (4,)
# 不透明度:初始为 0.1,存储 logit 值
opacity = inverse_sigmoid(0.1)
# 颜色:从 SfM 点颜色初始化 SH 0 阶
sh_dc = RGB2SH(point.color) # (3,)
sh_rest = zeros(45) # 高阶 SH 初始化为 0
| 参数 | 存储形式 | 激活函数 | 原因 |
|---|---|---|---|
| 位置 μ | 原始值 | 无 | 无约束 |
| 缩放 s | log 值 | exp | 保证正值 |
| 旋转 q | 原始值 | 归一化 | 保证单位四元数 |
| 不透明度 α | logit 值 | sigmoid | 约束到 [0,1] |
| SH 系数 | 原始值 | 无 | 无约束 |
当没有 SfM 点云时,可以使用随机初始化:
# 在场景边界框内随机采样
points = uniform_sample(bbox, num_points=100000)
colors = random_colors(num_points)
但随机初始化通常需要更多迭代才能收敛。
SfM 点云的问题:
密度控制目标:
密度控制的关键依据是位置梯度的累积:
$$\bar{g}i = \frac{1}{T}\sum{t=1}^{T} \left|\frac{\partial \mathcal{L}}{\partial \mu'_i}\right|$$
其中 $\mu'_i$ 是高斯中心在图像平面的投影位置。
直觉解释:
触发条件:
操作:复制高斯,沿梯度方向移动
适用场景:欠重建区域,需要更多高斯覆盖
触发条件:
操作:将大高斯分裂为多个小高斯
分裂策略:
def split_gaussian(gaussian, n_split=2):
# 新高斯的位置:沿主轴方向采样
samples = sample_pdf(gaussian.covariance, n_split)
new_positions = gaussian.mu + samples
# 新高斯的缩放:除以分裂系数
new_scale = gaussian.scale / (0.8 * n_split)
# 其他参数:继承原高斯
new_rotation = gaussian.rotation
new_opacity = gaussian.opacity
new_sh = gaussian.sh
return new_gaussians
剪枝策略:
| 条件 | 操作 | 目的 |
|---|---|---|
| $\alpha_i < \epsilon_\alpha$(如 0.005) | 移除 | 删除近乎透明的高斯 |
| 过大的高斯 | 移除 | 防止"飞溅"伪影 |
| 世界空间中过大 | 移除 | 删除异常高斯 |
def prune_gaussians(gaussians, opacity_threshold=0.005):
# 透明度剪枝
mask = gaussians.opacity > opacity_threshold
# 尺度剪枝
max_scale = compute_scene_extent() * 0.1
mask &= gaussians.scale.max(dim=-1) < max_scale
return gaussians[mask]
| 参数 | 默认值 | 含义 |
|---|---|---|
| densify_from_iter | 500 | 开始密度控制的迭代 |
| densify_until_iter | 15000 | 停止密度控制的迭代 |
| densify_grad_threshold | 0.0002 | 梯度阈值 |
| densification_interval | 100 | 密度控制间隔 |
| opacity_reset_interval | 3000 | 不透明度重置间隔 |
| percent_dense | 0.01 | 场景范围比例阈值 |
3DGS 使用 Adam 优化器,不同参数使用不同学习率:
| 参数 | 学习率 | 说明 |
|---|---|---|
| 位置 μ | 0.00016 × extent | 与场景大小相关 |
| 缩放 s | 0.005 | 对数空间 |
| 旋转 q | 0.001 | 四元数空间 |
| 不透明度 α | 0.05 | logit 空间 |
| SH DC(0 阶) | 0.0025 | 基础颜色 |
| SH rest(高阶) | 0.0025 / 20 | 视角相关颜色 |
位置学习率指数衰减:
$$lr_t = lr_0 \cdot \left(\frac{lr_{final}}{lr_0}\right)^{t/T}$$
为避免过早学习高阶 SH 导致的过拟合,3DGS 采用渐进激活策略:
# 球谐函数激活迭代
sh_degree_schedule = {
0: 0, # 从迭代 0 开始使用 0 阶 SH
1000: 1, # 从迭代 1000 开始使用 1 阶 SH
2000: 2, # 从迭代 2000 开始使用 2 阶 SH
3000: 3 # 从迭代 3000 开始使用 3 阶 SH
}
为避免高斯变得过于透明或训练初期的不稳定,周期性重置不透明度:
def reset_opacity(gaussians, target=0.01):
# 将所有高斯的不透明度重置到较低值
new_opacity = min(gaussians.opacity, target)
gaussians.opacity = inverse_sigmoid(new_opacity)
重置时机:每隔 opacity_reset_interval(默认 3000)次迭代
算法:3D Gaussian Splatting 训练
输入:多视角图像集合 {I_i}, 相机参数 {K_i, R_i, t_i}
输出:优化后的高斯集合 G
1. 初始化
- 运行 COLMAP 获取稀疏点云 P 和相机位姿
- 从 P 初始化高斯集合 G
- 初始化优化器
2. 训练循环 (iter = 1 to max_iter)
2.1 随机选择视角
- 随机选取训练图像 I_i
2.2 前向渲染
- 计算协方差矩阵 Σ = RSS^TR^T
- 投影高斯到 2D Σ' = JWΣWᵀJᵀ
- Tile-based 排序
- Alpha 混合得到渲染图像 Î_i
2.3 损失计算
- L = (1-λ)L1(I_i, Î_i) + λ·L_SSIM(I_i, Î_i)
2.4 反向传播
- 计算 ∂L/∂θ
- 累积位置梯度 g_μ
2.5 参数更新
- Adam 优化器更新参数
- 更新学习率
2.6 密度控制 (每 densification_interval 迭代)
- if iter < densify_until_iter:
- 克隆小高斯(高梯度)
- 分裂大高斯(高梯度)
- 剪枝透明高斯
- 重置梯度累积
2.7 不透明度重置 (每 opacity_reset_interval 迭代)
- 重置所有高斯不透明度
3. 返回优化后的高斯集合 G
training:
iterations: 30000
# 学习率
position_lr_init: 0.00016
position_lr_final: 0.0000016
position_lr_delay_mult: 0.01
position_lr_max_steps: 30000
feature_lr: 0.0025
opacity_lr: 0.05
scaling_lr: 0.005
rotation_lr: 0.001
# 损失权重
lambda_dssim: 0.2
# 密度控制
densify_from_iter: 500
densify_until_iter: 15000
densify_grad_threshold: 0.0002
densification_interval: 100
# 不透明度重置
opacity_reset_interval: 3000
# SH 渐进
sh_degree: 3
为避免协方差矩阵奇异,添加小的对角正则化:
def build_covariance(scale, rotation):
S = torch.diag(scale)
R = quaternion_to_matrix(rotation)
# 协方差矩阵
L = R @ S
cov = L @ L.T
# 数值稳定性
cov = cov + 1e-6 * torch.eye(3)
return cov
为减少排序开销,使用基数排序(Radix Sort):
// CUDA 基数排序(简化版)
// 对 (key, value) 对按 key 排序
// key: 深度值的整数表示
// value: 高斯索引
cub::DeviceRadixSort::SortPairs(
d_temp_storage, temp_storage_bytes,
d_keys_in, d_keys_out,
d_values_in, d_values_out,
num_items
);
高斯到 Tile 的分配使用保守光栅化:
def get_tile_rect(gaussian_2d, image_size, tile_size=16):
# 计算 2D 高斯的边界框(3σ范围)
radius = 3 * sqrt(max(eigenvalues(cov_2d)))
# 转换为 Tile 坐标
min_tile = floor((center - radius) / tile_size)
max_tile = ceil((center + radius) / tile_size)
return min_tile, max_tile
梯度累积的高效实现:
# 使用原子操作累积梯度
# 避免为每个高斯存储完整梯度历史
class GradientAccumulator:
def __init__(self, num_gaussians):
self.grad_sum = torch.zeros(num_gaussians)
self.count = torch.zeros(num_gaussians)
def accumulate(self, grad, indices):
# 原子加法
self.grad_sum.index_add_(0, indices, grad.norm(dim=-1))
self.count.index_add_(0, indices, torch.ones_like(indices))
def get_mean(self):
return self.grad_sum / (self.count + 1e-6)
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 高斯数量爆炸 | 密度阈值过低 | 增大 densify_grad_threshold |
| 渲染模糊 | SH 阶数不足 | 增加 SH 阶数或训练更多迭代 |
| 飞溅伪影 | 存在异常大高斯 | 降低缩放学习率,增加剪枝 |
| 训练不收敛 | 学习率过高/过低 | 调整学习率 |
| 孔洞 | 初始点云稀疏 | 使用更密集的 SfM 或增加密度控制强度 |
| 优化目标 | 方法 |
|---|---|
| 细节保持 | 增加训练迭代,降低密度控制阈值 |
| 视角一致性 | 确保 SH 阶数≥2 |
| 边缘清晰 | 增加 SSIM 权重 |
| 减少伪影 | 增加不透明度重置频率 |
解决锯齿和模糊问题:
通过向量量化压缩高斯参数:
| 组件 | 原始大小 | 压缩后 | 压缩比 |
|---|---|---|---|
| 位置 | 12B | 6B | 2x |
| 协方差 | 24B | 8B | 3x |
| 颜色 | 48B | 8B | 6x |
| 总计 | ~236B | ~40B | ~6x |
通过自适应采样减少高斯数量:
def importance_sampling(gaussians, target_count):
# 基于渲染贡献度采样
importance = compute_render_contribution(gaussians)
selected = multinomial_sample(importance, target_count)
return gaussians[selected]
本文详细介绍了 3DGS 的完整算法架构:
| 组件 | 作用 | 关键设计 |
|---|---|---|
| SfM 初始化 | 提供初始点云和相机 | COLMAP |
| 自适应密度控制 | 动态调整高斯数量 | 克隆 + 分裂 + 剪枝 |
| 优化策略 | 参数学习 | Adam+ 学习率调度+SH 渐进 |
| 损失函数 | 监督信号 | L1+SSIM |
| 类别 | 参数 | 推荐值 |
|---|---|---|
| 训练 | 迭代次数 | 30000 |
| 密度 | 梯度阈值 | 0.0002 |
| 损失 | SSIM 权重 | 0.2 |
| 学习率 | 位置初始 | 0.00016 |

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online