当前位置: 首页 > news >正文

3D Gaussian Splatting:渲染流程


1. 主程序入口 (if __name__ == "__main__")


程序从主入口开始执行:

 pos = torch.load('trained_gaussians/kitchen/pos_7000.pt').cuda()
    opacity_raw = torch.load('trained_gaussians/kitchen/opacity_raw_7000.pt').cuda()
    f_dc = torch.load('trained_gaussians/kitchen/f_dc_7000.pt').cuda()
    f_rest = torch.load('trained_gaussians/kitchen/f_rest_7000.pt').cuda()
    scale_raw = torch.load('trained_gaussians/kitchen/scale_raw_7000.pt').cuda()
    q_raw = torch.load('trained_gaussians/kitchen/q_rot_7000.pt').cuda()

    cam_parameters = np.load('out_colmap/kitchen/cam_meta.npy', allow_pickle=True).item()
    orbit_c2ws = torch.load('camera_trajectories/kitchen_orbit.pt').cuda()

如果不能用cuda则可以用cpu来实现:

    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    
    pos = torch.load('trained_gaussians/kitchen/pos_7000.pt', map_location=device)
    opacity_raw = torch.load('trained_gaussians/kitchen/opacity_raw_7000.pt', map_location=device)
    f_dc = torch.load('trained_gaussians/kitchen/f_dc_7000.pt', map_location=device)
    f_rest = torch.load('trained_gaussians/kitchen/f_rest_7000.pt', map_location=device)
    scale_raw = torch.load('trained_gaussians/kitchen/scale_raw_7000.pt', map_location=device)
    q_raw = torch.load('trained_gaussians/kitchen/q_rot_7000.pt', map_location=device)

    cam_parameters = np.load('out_colmap/kitchen/cam_meta.npy', allow_pickle=True).item()
    orbit_c2ws = torch.load('camera_trajectories/kitchen_orbit.pt', map_location=device)

加载预训练的3D高斯参数和相机轨迹数据:

pos: 3D高斯点的位置坐标
opacity_raw: 原始透明度参数
f_dc: 球谐函数直流分量系数
f_rest: 球谐函数交流分量系数
scale_raw: 原始尺度参数
q_raw: 四元数旋转参数
cam_parameters: 相机内参
orbit_c2ws: 相机轨迹变换矩阵


sigma = build_sigma_from_params(scale_raw, q_raw)
根据尺度和旋转参数构建3D高斯的协方差矩阵。

2. 渲染循环


with torch.no_grad():
    for i, c2w_i in tqdm(enumerate(orbit_c2ws)):
对每个相机位姿进行渲染,使用tqdm显示进度条,torch.no_grad()禁用梯度计算以提高性能。

3. 相机参数设置


c2w = c2w_i
H = cam_parameters['height'] // 2
W = cam_parameters['width'] // 2
H_src = cam_parameters['height']
W_src = cam_parameters['width']
fx, fy = cam_parameters['fx'], cam_parameters['fy']
cx, cy = W_src / 2, H_src / 2
fx, fy, cx, cy = scale_intrinsics(H, W, H_src, W_src, fx, fy, cx, cy)
设置当前帧的相机参数,包括图像尺寸和相机内参,并根据目标分辨率调整内参。

4. 颜色计算


color = evaluate_sh(f_dc, f_rest, pos, c2w)
调用evaluate_sh函数计算每个3D高斯点在当前相机视角下的颜色。这个函数根据观察方向计算球谐函数基函数,然后与系数相乘得到最终颜色。

球谐函数(Spherical Harmonics)在3DGS中的作用


1. 视点相关着色(View-dependent Shading)
在传统的点云渲染中,每个点通常只有一个固定颜色。但真实世界中的物体表面会根据观察角度呈现不同的颜色(比如镜面反射、法线变化等)。球谐函数允许我们表示这种与视点相关的着色效果。

2. 直流分量(DC Component)和交流分量(AC Components)
直流分量 (f_dc)
对应球谐函数的第0阶基函数(Y0)
表示与视角无关的基础颜色
是一个常数项,所有方向上的贡献相同
相当于传统渲染中的"漫反射颜色"
交流分量 (f_rest)
对应球谐函数的第1、2、3阶基函数(Y1-Y15)
表示与视角相关的颜色变化
捕捉光照和观察角度引起的颜色变化
总共15个系数(3个颜色通道 × 15个基函数 = 45个参数)
3. 球谐函数基函数
代码中的 evaluate_sh 函数计算了16个球谐基函数:
Y0 = torch.full_like(x, SH_C0)  # 常数项(直流分量)
Y1 = - SH_C1_y * y              # 1阶基函数
Y2 = SH_C1_z * z
Y3 = - SH_C1_x * x
Y4-Y15 = ...                    # 2阶和3阶基函数
4. 颜色计算过程
最终颜色通过以下方式计算:
# 构建球谐系数张量
sh[:, 0] = f_dc                 # 直流分量
sh[:, 1:, 0] = f_rest[:, :15]   # R通道的交流分量
sh[:, 1:, 1] = f_rest[:, 15:30] # G通道的交流分量
sh[:, 1:, 2] = f_rest[:, 30:45] # B通道的交流分量

# 计算最终颜色
color = torch.sigmoid((sh * Y.unsqueeze(2)).sum(dim=1))
这相当于:

R = f_dc_r * Y0 + f_rest_r1 * Y1 + f_rest_r2 * Y2 + ... + f_rest_r15 * Y15
G = f_dc_g * Y0 + f_rest_g1 * Y1 + f_rest_g2 * Y2 + ... + f_rest_g15 * Y15
B = f_dc_b * Y0 + f_rest_b1 * Y1 + f_rest_b2 * Y2 + ... + f_rest_b15 * Y15

优势:
紧凑表示:用少量系数(48个)表示复杂的视点相关着色
旋转不变性:球谐函数在旋转时有良好的数学性质
高效计算:比存储每个方向的颜色值更高效
平滑过渡:自然地插值视角变化引起的效果

5. 核心渲染


img = render(pos, color, opacity_raw, sigma, c2w, H, W, fx, fy, cx, cy)
调用核心渲染函数,传入所有必要参数生成最终图像。

核心渲染函数详解
第一步:点投影和筛选
uv, x, y, z = project_points(pos, c2w, fx, fy, cx, cy)
in_guard = (uv[:, 0] > -pix_guard) & (uv[:, 0] < W + pix_guard) & (
    uv[:, 1] > -pix_guard) & (uv[:, 1] < H + pix_guard) & (z > near) & (z < far)
将所有3D点投影到2D图像平面,并筛选在视锥体和图像边界内的点。

第二步:协方差矩阵投影
# Project the covariance
Rcw = c2w[:3, :3]
Rwc = Rcw.t()
invz = 1 / z.clamp_min(1e-6)
invz2 = invz * invz
J = torch.zeros((pos.shape[0], 2, 3), device=pos.device, dtype=pos.dtype)
J[:, 0, 0] = fx * invz
J[:, 1, 1] = fy * invz
J[:, 0, 2] = -fx * x * invz2
J[:, 1, 2] = -fy * y * invz2
tmp = Rwc.unsqueeze(0) @ sigma @ Rwc.t().unsqueeze(0)  # Eq. 5
sigma_camera = J @ tmp @ J.transpose(1, 2)
将3D协方差矩阵投影到2D图像平面,得到2D高斯的协方差矩阵。这里使用了雅可比矩阵J进行变换。

第三步:正定性处理和排序
# Ensure positive definiteness
evals, evecs = torch.linalg.eigh(sigma_camera)
evals = torch.clamp(evals, min=1e-6, max=1e4)
sigma_camera = evecs @ torch.diag_embed(evals) @ evecs.transpose(1, 2)

# Global depth sorting
order = torch.argsort(z, descending=False)
确保投影后的协方差矩阵是正定的,并按深度对点进行排序(从近到远)。

第四步:瓦片划分(Tiling)
# Tiling
major_variance = evals[:, 1].clamp_min(1e-12).clamp_max(1e4)  # [N]
radius = torch.ceil(3.0 * torch.sqrt(major_variance)).to(torch.int64)
umin = torch.floor(u - radius).to(torch.int64)
umax = torch.floor(u + radius).to(torch.int64)
vmin = torch.floor(v - radius).to(torch.int64)
vmax = torch.floor(v + radius).to(torch.int64)
计算每个2D高斯的影响半径,并确定其在图像上的轴对齐包围盒(AABB)。


# Tile index for each AABB
umin_tile = (umin // T).to(torch.int64)  # [N]
umax_tile = (umax // T).to(torch.int64)  # [N]
vmin_tile = (vmin // T).to(torch.int64)  # [N]
vmax_tile = (vmax // T).to(torch.int64)  # [N]
将图像划分为T×T大小的瓦片,并计算每个高斯影响的瓦片范围。

第五步:瓦片-高斯映射
nb_tiles_per_gaussian = n_u * n_v  # [N]
gaussian_ids = torch.repeat_interleave(
    torch.arange(nb_gaussians, device=pos.device, dtype=torch.int64),
    nb_tiles_per_gaussian)  # [0, 0, 0, 0, 1 ...]
建立瓦片和高斯点之间的映射关系,优化渲染性能。

第六步:瓦片排序
idx_z_order = torch.arange(nb_gaussians, device=pos.device, dtype=torch.int64)
M = nb_gaussians + 1
comp = flat_tile_id * M + idx_z_order[gaussian_ids]
comp_sorted, perm = torch.sort(comp)
gaussian_ids = gaussian_ids[perm]
tile_ids_1d = torch.div(comp_sorted, M, rounding_mode='floor')
按瓦片ID和深度对高斯点进行排序,确保正确的渲染顺序。

第七步:逐瓦片渲染
for tile_id, s0, s1 in zip(unique_tile_ids.tolist(), start.tolist(), end.tolist()):
遍历每个瓦片,只处理影响当前瓦片的高斯点。


du = px_u.unsqueeze(0) - gaussian_i_u.unsqueeze(-1)  # [N, T * T]
dv = px_v.unsqueeze(0) - gaussian_i_v.unsqueeze(-1)  # [N, T * T]
A11 = gaussian_i_inverse_covariance[:, 0, 0].unsqueeze(-1)  # [N, 1]
A12 = gaussian_i_inverse_covariance[:, 0, 1].unsqueeze(-1)
A22 = gaussian_i_inverse_covariance[:, 1, 1].unsqueeze(-1)
q = A11 * du * du + 2 * A12 * du * dv + A22 * dv * dv   # [N, T * T]
计算瓦片内每个像素到高斯中心的距离(二次型形式)。


inside = q <= chi_square_clip
g = torch.exp(-0.5 * torch.clamp(q, max=chi_square_clip))  # [N, T * T]
g = torch.where(inside, g, torch.zeros_like(g))
alpha_i = (gaussian_i_opacity.unsqueeze(-1) * g).clamp_max(alpha_max)  # [N, T * T]
alpha_i = torch.where(alpha_i >= alpha_cutoff, alpha_i, torch.zeros_like(alpha_i))
计算每个像素受高斯影响的alpha值,使用指数衰减函数和截断。


T_i = torch.cumprod(one_minus_alpha_i, dim=0)
T_i = torch.concatenate([
    torch.ones((1, alpha_i.shape[-1]), device=pos.device, dtype=pos.dtype),
    T_i[:-1]], dim=0)
alive = (T_i > 1e-4).float()
w = alpha_i * T_i * alive  # [N, T * T]
使用累积透射率公式计算最终权重,实现alpha混合。


final_image[pixel_idx_1d] = (w.unsqueeze(-1) * gaussian_i_color.unsqueeze(1)).sum(dim=0)
将加权颜色值累加到最终图像中。

第八步:输出结果
return final_image.reshape((H, W, 3)).clamp(0, 1)
将一维图像数组重塑为二维,并将值限制在[0,1]范围内。


Image.fromarray((img.cpu().detach().numpy() * 255).astype(np.uint8)).save(f'novel_views/frame_{i:04d}.png')
将渲染结果保存为PNG图像文件。

3DGS 训练过程产生的数据


这些 .pt 文件是通过 3D Gaussian Splatting 训练过程生成的,具体来源于:

1. 训练输入数据
训练 3DGS 模型需要以下输入:

图像集合: 同一场景的多视角照片
相机参数: 每张照片对应的相机位姿和内参
稀疏点云: 通过 Structure-from-Motion (SfM) 方法(如 COLMAP)生成的初始点云
2. 训练输出数据(即 .pt 文件内容)
位置数据 (pos_7000.pt)
# 来源于:
# 1. 初始SfM点云中的3D点位置
# 2. 训练过程中优化得到的最终位置
pos = torch.tensor([[x1, y1, z1],     # 第1个高斯点的3D坐标
                    [x2, y2, z2],     # 第2个高斯点的3D坐标
                    ...,
                    [xn, yn, zn]])    # 第n个高斯点的3D坐标
颜色数据 (f_dc_7000.pt 和 f_rest_7000.pt)

# 来源于:
# 1. 从输入图像中采样初始颜色
# 2. 训练过程中优化得到的球谐函数系数
f_dc = torch.tensor([[r1, g1, b1],    # 第1个点的直流颜色分量
                     [r2, g2, b2],    # 第2个点的直流颜色分量
                     ...])

f_rest = torch.tensor([[c1_1, c1_2, ..., c1_45],  # 第1个点的交流分量(球谐系数)
                       [c2_1, c2_2, ..., c2_45],  # 第2个点的交流分量
                       ...])
透明度数据 (opacity_raw_7000.pt)

# 来源于:
# 1. 训练过程中学习到的每个点的不透明度
# 2. 控制点对最终图像的贡献程度
opacity_raw = torch.tensor([o1, o2, ..., on])  # 每个点的原始透明度参数
尺度和旋转数据 (scale_raw_7000.pt 和 q_rot_7000.pt)

# 来源于:
# 1. 初始点云的分布特征
# 2. 训练过程中优化得到的协方差矩阵参数
scale_raw = torch.tensor([[sx1, sy1, sz1],    # 第1个点的尺度参数
                          [sx2, sy2, sz2],    # 第2个点的尺度参数
                          ...])

q_rot = torch.tensor([[qx1, qy1, qz1, qw1],   # 第1个点的四元数旋转
                      [qx2, qy2, qz2, qw2],   # 第2个点的四元数旋转
                      ...])

具体场景示例
以代码中的 "kitchen" 场景为例:

输入: 厨房场景的多张照片(可能几十到几百张)
处理: 使用 COLMAP 生成稀疏重建
训练: 3DGS 训练算法迭代优化约 7000 步(文件名中的 7000 表示训练步数)
输出: 生成这 6 个 .pt 文件,完整描述厨房场景的 3D 高斯表示

本文参考知乎:https://zhuanlan.zhihu.com/p/1969141577483030927

http://www.dtcms.com/a/577123.html

相关文章:

  • 云原生LVS+Keepalived高可用方案(二)
  • IBM VO 面试经验分享|一场更像“聊天”的正式考核
  • 用单位的服务器做网站关键词优化网站
  • C语言基础开发入门系列(八)C语言指针的理解与实战
  • 如何绑定网站域名解析电商网站模板html
  • 【机器视觉通用检测框架】基于VS2019 C#+VisionPro9.0开发的视觉框架软件,全套源码,开箱即用
  • 智慧交通管理新范式 基于深度学习的城市交通车型识别AI监控系统 车型识别 停车场车型分类系统 高速路车型识别算法
  • pnpm 安装依赖后 仍然启动报的问题
  • 【河北政务服务网-注册_登录安全分析报告】
  • 深入理解 package.json:前端项目的 “身份证“
  • 【辽宁政务服务网-注册_登录安全分析报告】
  • 免费正能量励志网站建设网站要多久到账
  • 新乡市红旗区建设局网站网页制作素材网有哪些
  • 用车申请车辆管理小程序开发
  • 单片机.RS485
  • 从单模型到多域自由转换:StarGAN的公式与多域图像生成魔法
  • 人工智能在全球多领域的应用潜力及当前技术面临的挑战
  • Python处理json数据
  • go开发规范指引
  • 期权交易中的希腊字母:风险管理的多维指南
  • C++---关键字constexpr
  • 广州购物网站公司地址广州网站建设
  • 手术机器人多传感器数据融合 × 深度学习前沿研究精要(2024-2025)
  • 火山引擎升级AI云原生套件:AgentKit、ServingKit、TrainingKit全链路加速AI应用落地
  • Git命令速查手册
  • 随机链表的复制 (带random的链表深度拷贝)| C语言实现
  • 大仓库推到GitHub大踩坑-Git LFS从安装到使用
  • 宁夏制作网站公司网站仿静态和静态的区别
  • 【App开发】02:Android Studio项目环境设置
  • 初识MYSQL —— 复合查询