欢迎光临
我们一直在努力

Reduced-3DGS 量化模型转标准 3DGS 格式 — 问题排查与解决方案

摘要:本文分析了 reduced-3DGS(基于向量量化的高斯溅射改进版)输出无法被标准 3DGS 可视化工具渲染的问题。通过对比目录结构、PLY 文件头和 codebook.pt 结构,发现主要原因是多元组顶点元素和量化索引格式不兼容。为解决此问题,提供了完整的 Python 转换脚本 convert_to_standard_ply.py,可将 reduced-3DGS 的多元组 PLY 格式(支持量化/未量化、float32/half-float)转换为标准单元素 PLY 格式,并给出了具体的使用方法和验证步骤。

目录

  • 问题描述
  • 排查过程
  • 原因分析
  • 解决方案:转换脚本
  • 质量影响分析
  • 技术要点
  • 相关代码位置

  • 1. 问题描述

    本工程(reduced-3dgs)是原版 gaussian-splatting 的改进版本,引入了向量量化(Vector Quantization, VQ)来压缩 Gaussian 参数。通过 conda 环境 gaussian_splatting_reduced 运行 train.py 得到的结果无法被标准 3DGS 在线可视化工具正常渲染,而原版工程的输出可以。

    工程输出路径可视化
    原版 gaussian-splatting output/b52cc067-d/ ✅ 正常
    reduced-3dgs output/6ddc4bf3-6/ ❌ 失败

    2. 排查过程

    2.1 目录结构对比

    # 原版 3DGS 输出
    output/b52cc067-d/
    ├── cameras.json
    ├── cfg_args
    ├── exposure.json
    ├── input.ply
    ├── events.out.tfevents.*
    └── point_cloud/
    └── iteration_7000/
    └── point_cloud.ply ← 803 MB,标准格式
    Reduced-3DGS 输出
    output/6ddc4bf3-6/
    ├── cameras.json
    ├── cfg_args
    ├── input.ply
    ├── codebook.pt ← 318 MB,码本(额外文件)
    └── point_cloud/
    ├── iteration_7000/
    │ └── point_cloud.ply ← 736 MB
    └── iteration_30000/
    ├── point_cloud.ply ← 1.34 GB(未量化,多元组)
    ├── point_cloud_quantised.ply ← 386 MB(量化版)
    └── point_cloud_quantised_half.ply ← 352 MB(量化半精度版)

    关键差异:

    • reduced-3dgs 多了 codebook.pt(码本文件)
    • reduced-3dgs 的 PLY 文件多了 _quantised、_quantised_half 后缀
    • reduced-3dgs 没有 exposure.json

    2.2 PLY 文件头对比

    原版 3DGS — 标准格式

    ply
    format binary_little_endian 1.0
    element vertex 3241453 ← 单一顶点元素
    property float x
    property float y
    property float z
    property float nx ← 有法线
    property float ny
    property float nz
    property float f_dc_0
    property float f_dc_1
    property float f_dc_2
    property float f_rest_0 ← 45 个 f_rest (sh_degree=3)

    property float f_rest_44
    property float opacity
    property float scale_0
    property float scale_1
    property float scale_2
    property float rot_0
    property float rot_1
    property float rot_2
    property float rot_3
    end_header

    Reduced-3DGS — 多元组 + 量化格式

    ply
    format binary_little_endian 1.0
    element vertex_0 0 ← 分组 0: 0 个高斯 (sh_degree=0)
    property float x

    property float rot_3
    element vertex_1 0 ← 分组 1: 0 个高斯 (sh_degree=1)
    property float x

    property float f_rest_0 ← 9 个 f_rest

    property float f_rest_8

    property float rot_3
    element vertex_2 0 ← 分组 2: 0 个高斯 (sh_degree=2)
    … ← 24 个 f_rest
    element vertex_3 5677385 ← 分组 3: 5,677,385 个高斯 (sh_degree=3)
    … ← 45 个 f_rest
    element codebook_centers 256 ← 码本: 256 个聚类中心
    property float features_dc ← DC 颜色码本
    property float features_rest_0 ← 15 个 SH 系数码本

    property float features_rest_14
    property float opacity ← 不透明度码本
    property float scaling ← 缩放码本
    property float rotation_re ← 旋转实部码本
    property float rotation_im ← 旋转虚部码本
    end_header

    2.3 codebook.pt 结构

    {
    'features_dc': Codebook(ids, centers), # ids: (P, 1, 3), centers: (256,)
    'features_rest_0': Codebook(ids, centers), # ids: (P_1+, 1, 1), centers: (256,)

    'features_rest_14': Codebook(ids, centers),
    'opacity': Codebook(ids, centers), # ids: (P, 1), centers: (256,)
    'scaling': Codebook(ids, centers), # ids: (P, 3), centers: (256,)
    'rotation_re': Codebook(ids, centers), # ids: (P, 1), centers: (256,)
    'rotation_im': Codebook(ids, centers), # ids: (P, 3), centers: (256,)
    }

    每个 Codebook 包含:

    • ids:每个高斯参数的码本索引(0-255),uint8
    • centers:256 个聚类中心值,float32

    3. 原因分析

    3.1 格式差异总览

    维度原版 3DGSReduced-3DGS
    顶点元素 单个 element vertex 多个 element vertex_N(按 SH degree 分组)
    法线 ✅ nx, ny, nz ❌ 不存在
    SH 系数 统一 45 个 f_rest 每组不同数量(0/9/24/45),按 SH 带宽分配
    参数编码 直接存储 float32 值 量化版存储 uint8 码本索引
    附加数据 codebook_centers 元素 + 外部 codebook.pt

    3.2 为什么标准工具无法渲染

  • 多元组结构不兼容:标准 3DGS 查看器(SuperSplat、INRIA viewer、antimatter15 等)要求 PLY 文件只有一个 element vertex。reduced PLY 有多个 vertex_N 元素加上 codebook_centers,解析器无法识别。
  • 量化索引而非实际参数:量化版的 PLY 中 f_dc_*、f_rest_*、opacity、scale_*、rot_* 存储的是 uint8 码本索引(0-255),而非实际的 float 参数值。必须用码本做查表解码才能还原。
  • Variable SH bands:不同高斯被分配到不同 SH degree 组,贡献度小的高斯用更少的 SH 系数,这是原版没有的优化。
  • 3.3 编解码流程

    保存流程(save_ply, quantised=True):
    原始参数 (float32)
    ↓ k-means 聚类 (generate_codebook)
    码本 centers (256,) + ids (uint8 indices)
    ↓ save_ply
    PLY: vertex_N 存 ids, codebook_centers 存 centers
    加载流程(load_ply, quantised=True):
    PLY: vertex_N (ids) + codebook_centers
    ↓ _parse_vertex_group: 查表解码
    原始参数 (float32)

    GaussianModel 渲染


    4. 解决方案:转换脚本

    4.1 脚本概述

    脚本 convert_to_standard_ply.py 位于项目根目录,用于将 reduced-3DGS 的多元组 PLY 格式转换为标准 3DGS 单元素格式。脚本自动处理量化/未量化、float32/half-float 格式,解析多元组结构并执行码本查表解码(如需要),最终输出标准 62 属性 PLY 文件。

    4.2 完整代码

    #!/usr/bin/env python3
    """
    convert_to_standard_ply.py
    将 reduced-3DGS 的多元组 PLY 转换为标准 3DGS 格式。
    支持量化/未量化、float32/half-float 格式。
    """
    import argparse
    import numpy as np
    from plyfile import PlyData, PlyElement

    def parse_args():
    parser = argparse.ArgumentParser(
    description='Convert reduced-3DGS PLY to standard 3DGS format'
    )
    parser.add_argument(
    '-i', '–input',
    required=True,
    help='输入 PLY 文件路径(reduced-3DGS 格式)'
    )
    parser.add_argument(
    '-o', '–output',
    required=True,
    help='输出 PLY 文件路径(标准 3DGS 格式)'
    )
    return parser.parse_args()

    def load_reduced_ply(ply_path):
    """加载 reduced-3DGS PLY 文件,返回顶点组和码本"""
    ply = PlyData.read(ply_path)

    # 提取顶点组
    vertex_groups = []
    for element in ply.elements:
    if element.name.startswith('vertex_'):
    sh_degree = int(element.name.split('_')[1])
    vertex_groups.append((sh_degree, element))

    # 提取码本
    codebook = None
    for element in ply.elements:
    if element.name == 'codebook_centers':
    codebook = element
    break

    return vertex_groups, codebook, ply

    def decode_quantised_data(vertex_groups, codebook):
    """对量化数据进行解码"""
    if codebook is None:
    return vertex_groups

    # 提取码本中心
    codebook_data = {}
    for prop in codebook.properties:
    codebook_data[prop.name] = codebook[prop.name]

    # 解码每个顶点组
    decoded_groups = []
    for sh_degree, group in vertex_groups:
    decoded_group = {}
    for prop in group.properties:
    if prop.name in codebook_data:
    # 码本查表解码
    indices = group[prop.name].astype(np.uint8)
    decoded_group[prop.name] = codebook_data[prop.name][indices]
    else:
    decoded_group[prop.name] = group[prop.name]

    decoded_groups.append((sh_degree, decoded_group))

    return decoded_groups

    def merge_vertex_groups(vertex_groups):
    """合并所有 SH degree 组为单一顶点数组"""
    all_vertices = []

    for sh_degree, group in vertex_groups:
    num_vertices = len(next(iter(group.values())))

    # 为每个顶点创建标准 62 属性数组
    for i in range(num_vertices):
    vertex = {}

    # 位置 (x, y, z)
    vertex['x'] = group['x'][i] if 'x' in group else 0.0
    vertex['y'] = group['y'][i] if 'y' in group else 0.0
    vertex['z'] = group['z'][i] if 'z' in group else 0.0

    # 法线 (nx, ny, nz) – reduced-3DGS 没有法线,填充 0
    vertex['nx'] = 0.0
    vertex['ny'] = 0.0
    vertex['nz'] = 0.0

    # DC 颜色系数 (f_dc_0, f_dc_1, f_dc_2)
    for j in range(3):
    key = f'f_dc_{j}'
    vertex[key] = group[key][i] if key in group else 0.0

    # SH 系数 (f_rest_0..f_rest_44)
    # reduced-3DGS 根据 SH degree 使用不同数量的系数
    max_sh_coeffs = 45 # SH degree=3 时的最大系数数
    for j in range(max_sh_coeffs):
    key = f'f_rest_{j}'
    if key in group:
    vertex[key] = group[key][i]
    else:
    vertex[key] = 0.0 # 零填充

    # 不透明度
    vertex['opacity'] = group['opacity'][i] if 'opacity' in group else 0.0

    # 缩放 (scale_0, scale_1, scale_2)
    for j in range(3):
    key = f'scale_{j}'
    vertex[key] = group[key][i] if key in group else 0.0

    # 旋转四元数 (rot_0, rot_1, rot_2, rot_3)
    for j in range(4):
    key = f'rot_{j}'
    vertex[key] = group[key][i] if key in group else (1.0 if j == 0 else 0.0)

    all_vertices.append(vertex)

    return all_vertices

    def create_standard_ply(vertices, output_path):
    """创建标准 3DGS PLY 文件"""
    # 定义标准 62 属性
    dtype = [
    ('x', 'f4'), ('y', 'f4'), ('z', 'f4'),
    ('nx', 'f4'), ('ny', 'f4'), ('nz', 'f4')
    ]

    # DC 颜色系数
    for i in range(3):
    dtype.append((f'f_dc_{i}', 'f4'))

    # SH 系数
    for i in range(45):
    dtype.append((f'f_rest_{i}', 'f4'))

    # 其他参数
    dtype.extend([
    ('opacity', 'f4'),
    ('scale_0', 'f4'), ('scale_1', 'f4'), ('scale_2', 'f4'),
    ('rot_0', 'f4'), ('rot_1', 'f4'), ('rot_2', 'f4'), ('rot_3', 'f4')
    ])

    # 创建结构化数组
    structured_array = np.zeros(len(vertices), dtype=dtype)

    for i, vertex in enumerate(vertices):
    for key in vertex:
    structured_array[i][key] = vertex[key]

    # 创建 PLY 元素
    vertex_element = PlyElement.describe(structured_array, 'vertex')

    # 写入文件
    PlyData([vertex_element], text=False).write(output_path)

    print(f"Done! Standard PLY with {len(vertices)} vertices written.")
    print(f"File size: {len(vertices) * 62 * 4 / (1024*1024):.1f} MB (estimated)")

    def main():
    args = parse_args()

    print(f"Reading: {args.input}")

    # 加载 reduced PLY
    vertex_groups, codebook, ply = load_reduced_ply(args.input)

    # 检测格式
    is_quantised = codebook is not None
    is_half_float = False

    if is_quantised:
    print("Quantised: True")
    # 检查是否为半精度
    if 'features_dc' in codebook.dtype.names:
    sample = codebook['features_dc'][0]
    if sample.dtype == np.float16 or (isinstance(sample, np.ndarray) and sample.dtype == np.float16):
    is_half_float = True
    print("Half-float: True")
    else:
    print("Half-float: False")
    else:
    print("Quantised: False")
    print("Half-float: False")

    # 打印顶点组信息
    print(f"Vertex groups: {[(sh, len(g)) for sh, g in vertex_groups]}")
    if is_quantised:
    print(f"Codebook: present")
    print(f"Codebook entries: {len(codebook)}")

    total_gaussians = sum(len(g) for _, g in vertex_groups)
    print(f"Total gaussians: {total_gaussians}")

    # 解码量化数据(如果需要)
    if is_quantised:
    vertex_groups = decode_quantised_data(vertex_groups, codebook)

    # 合并顶点组
    all_vertices = merge_vertex_groups(vertex_groups)

    # 创建标准 PLY
    print(f"Writing: {args.output}")
    create_standard_ply(all_vertices, args.output)

    if __name__ == '__main__':
    main()

    4.3 使用方法

    安装依赖:

    pip install plyfile numpy

    基本命令:

    python convert_to_standard_ply.py -i [输入文件] -o [输出文件]

    参数说明:

    • -i, –input: 输入 PLY 文件路径(reduced-3DGS 格式)
    • -o, –output: 输出 PLY 文件路径(标准 3DGS 格式)

    4.4 使用示例

    示例 1:转换量化版 PLY

    python convert_to_standard_ply.py \\
    -i output/6ddc4bf3-6/point_cloud/iteration_30000/point_cloud_quantised.ply \\
    -o output/6ddc4bf3-6/point_cloud/iteration_30000/point_cloud_standard.ply

    示例 2:转换未量化版 PLY

    python convert_to_standard_ply.py \\
    -i output/6ddc4bf3-6/point_cloud/iteration_30000/point_cloud.ply \\
    -o output/6ddc4bf3-6/point_cloud/iteration_30000/point_cloud_standard.ply

    示例 3:转换半精度量化版 PLY

    python convert_to_standard_ply.py \\
    -i output/6ddc4bf3-6/point_cloud/iteration_30000/point_cloud_quantised_half.ply \\
    -o output/6ddc4bf3-6/point_cloud/iteration_30000/point_cloud_standard.ply

    4.5 转换验证

    转换完成后,将生成的 point_cloud_standard.ply 拖入以下任一在线可视化工具验证:

    • SuperSplat: Cleaning Up…
    • INRIA 官方 Viewer: 3D Gaussian Splatting for Real-Time Radiance Field Rendering
    • gsplat.tech: Home – PlatGS

    4.6 转换验证结果

    === 原版 3DGS PLY ===
    Element: vertex, count: 3,241,453
    Properties (62): x, y, z, nx, ny, nz, f_dc_0..2, f_rest_0..44,
    opacity, scale_0..2, rot_0..3
    === 转换后 PLY ===
    Element: vertex, count: 5,677,385 ← 更多高斯(VQ 允许更高密度)
    Properties (62): 完全匹配 ✅
    Properties match: True ✅

    结果可视化示例:

    赞(0)
    未经允许不得转载:171主机测评 » Reduced-3DGS 量化模型转标准 3DGS 格式 — 问题排查与解决方案
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址