2DGS数据集处理实战:COLMAP与自定义数据预处理全解析
引言:从图像到三维场景的桥梁
在计算机视觉领域,将二维图像序列转换为三维场景表示是一个核心挑战。2D Gaussian Splatting(2DGS)作为SIGGRAPH'24的最新研究成果,为这一问题提供了高效且精确的解决方案。本文将深入探讨2DGS数据处理流程,重点解析COLMAP相机姿态估计与自定义数据预处理的关键技术,帮助读者掌握从原始图像到训练数据的完整转换过程。
读完本文后,您将能够:
- 理解2DGS数据处理的整体流程与核心组件
- 熟练使用COLMAP进行相机标定与稀疏重建
- 掌握自定义数据集的格式转换与预处理方法
- 解决数据处理中常见的坐标转换、图像畸变等问题
- 优化数据质量以提升2DGS模型训练效果
2DGS数据处理全景图
2DGS的数据处理流程涉及多个关键步骤,从原始图像采集到最终可用于训练的数据集,每个环节都对最终重建质量有着重要影响。以下是2DGS数据处理的整体框架:
核心数据处理模块
2DGS项目中负责数据处理的核心模块主要集中在scene/目录下,包括:
- COLMAP数据加载器:scene/colmap_loader.py
- 数据集读取器:scene/dataset_readers.py
- 相机模型:scene/cameras.py
- 高斯模型:scene/gaussian_model.py
这些模块协同工作,完成从COLMAP输出文件到2DGS训练数据的转换。其中,colmap_loader.py负责解析COLMAP生成的相机参数和三维点云,dataset_readers.py则将这些数据转换为2DGS所需的格式,并进行数据集划分。
COLMAP相机标定与三维重建
COLMAP(COLMAP - Structure-from-Motion and Multi-View Stereo)是一个广泛使用的开源三维重建工具,它能够从多张二维图像中估计相机姿态并重建场景的三维结构。在2DGS中,COLMAP的输出结果是数据处理的重要输入。
COLMAP支持的相机模型
COLMAP支持多种相机模型,2DGS通过scene/colmap_loader.py中的CAMERA_MODELS字典定义了支持的相机模型及其参数数量:
CAMERA_MODELS = {
CameraModel(model_id=0, model_name="SIMPLE_PINHOLE", num_params=3),
CameraModel(model_id=1, model_name="PINHOLE", num_params=4),
CameraModel(model_id=2, model_name="SIMPLE_RADIAL", num_params=4),
CameraModel(model_id=3, model_name="RADIAL", num_params=5),
CameraModel(model_id=4, model_name="OPENCV", num_params=8),
CameraModel(model_id=5, model_name="OPENCV_FISHEYE", num_params=8),
CameraModel(model_id=6, model_name="FULL_OPENCV", num_params=12),
CameraModel(model_id=7, model_name="FOV", num_params=5),
CameraModel(model_id=8, model_name="SIMPLE_RADIAL_FISHEYE", num_params=4),
CameraModel(model_id=9, model_name="RADIAL_FISHEYE", num_params=5),
CameraModel(model_id=10, model_name="THIN_PRISM_FISHEYE", num_params=12)
}
在2DGS中,主要支持PINHOLE和SIMPLE_PINHOLE两种相机模型,这两种模型假设图像已经经过畸变校正。
COLMAP数据格式解析
COLMAP输出两种格式的数据:文本格式和二进制格式。2DGS的scene/colmap_loader.py提供了读取这两种格式的函数:
- 文本格式读取:
read_extrinsics_text()、read_intrinsics_text()、read_points3D_text() - 二进制格式读取:
read_extrinsics_binary()、read_intrinsics_binary()、read_points3D_binary()
以相机内参读取为例,文本格式的相机内参文件(cameras.txt)格式如下:
# Camera list with one line of data per camera:
# CAMERA_ID, MODEL, WIDTH, HEIGHT, PARAMS[]
# Number of cameras: 2
1 PINHOLE 1920 1080 1500 1500 960 540
2 PINHOLE 1920 1080 1500 1500 960 540
对应的解析代码在read_intrinsics_text()函数中实现:
def read_intrinsics_text(path):
"""
Taken from https://github.com/colmap/colmap/blob/dev/scripts/python/read_write_model.py
"""
cameras = {}
with open(path, "r") as fid:
while True:
line = fid.readline()
if not line:
break
line = line.strip()
if len(line) > 0 and line[0] != "#":
elems = line.split()
camera_id = int(elems[0])
model = elems[1]
assert model == "PINHOLE", "While the loader support other types, the rest of the code assumes PINHOLE"
width = int(elems[2])
height = int(elems[3])
params = np.array(tuple(map(float, elems[4:])))
cameras[camera_id] = Camera(id=camera_id, model=model,
width=width, height=height,
params=params)
return cameras
四元数到旋转矩阵的转换
COLMAP使用四元数(Quaternion)表示相机旋转,而2DGS在内部使用旋转矩阵(Rotation Matrix)。scene/colmap_loader.py中的qvec2rotmat()函数实现了这一转换:
def qvec2rotmat(qvec):
return np.array([
[1 - 2 * qvec[2]**2 - 2 * qvec[3]**2,
2 * qvec[1] * qvec[2] - 2 * qvec[0] * qvec[3],
2 * qvec[3] * qvec[1] + 2 * qvec[0] * qvec[2]],
[2 * qvec[1] * qvec[2] + 2 * qvec[0] * qvec[3],
1 - 2 * qvec[1]**2 - 2 * qvec[3]**2,
2 * qvec[2] * qvec[3] - 2 * qvec[0] * qvec[1]],
[2 * qvec[3] * qvec[1] - 2 * qvec[0] * qvec[2],
2 * qvec[2] * qvec[3] + 2 * qvec[0] * qvec[1],
1 - 2 * qvec[1]**2 - 2 * qvec[2]**2]])
这一转换对于正确理解相机姿态至关重要,因为四元数和旋转矩阵在不同的计算场景中各有优势:四元数适合插值和避免万向锁问题,而旋转矩阵则便于进行坐标变换计算。
COLMAP到2DGS的工作流
使用COLMAP处理图像并将结果导入2DGS的完整流程如下:
- 图像准备:收集同一场景的多张图像,确保足够的重叠区域
- 特征提取与匹配:COLMAP自动提取图像特征并进行匹配
- 相机姿态估计:通过运动恢复结构(SfM)估计相机内外参数
- 稀疏重建:生成场景的稀疏点云
- 数据导入2DGS:使用scene/colmap_loader.py读取COLMAP输出
相机参数与坐标转换
相机参数的正确解析和坐标系统的统一是数据处理中的关键环节。不同的软件和库可能采用不同的坐标系统定义,需要进行适当的转换才能确保数据的一致性。
相机内参解析
相机内参(Intrinsic Parameters)描述了相机的光学特性,包括焦距、主点坐标等。对于PINHOLE相机模型,2DGS通过以下代码解析内参:
if intr.model=="SIMPLE_PINHOLE":
focal_length_x = intr.params[0]
FovY = focal2fov(focal_length_x, height)
FovX = focal2fov(focal_length_x, width)
elif intr.model=="PINHOLE":
focal_length_x = intr.params[0]
focal_length_y = intr.params[1]
FovY = focal2fov(focal_length_y, height)
FovX = focal2fov(focal_length_x, width)
其中,focal2fov()函数将焦距转换为视场角(Field of View):
def focal2fov(focal, pixels):
return 2 * math.atan(pixels / (2 * focal))
视场角是相机光学系统的重要参数,它决定了相机能够捕捉的场景范围。在2DGS中,视场角用于正确计算高斯分布的投影。
坐标系统转换
不同的三维重建和渲染系统可能采用不同的坐标系统定义。COLMAP和2DGS在坐标系统上存在差异,需要进行适当的转换。
COLMAP采用的是计算机视觉中常用的坐标系统:
- X轴:向右
- Y轴:向下
- Z轴:向前
而在一些图形学系统(如OpenGL、Blender)中,坐标系统通常为:
- X轴:向右
- Y轴:向上
- Z轴:向后
2DGS在处理Nerf合成数据集时,需要进行坐标系统的转换:
# NeRF 'transform_matrix' is a camera-to-world transform
c2w = np.array(frame["transform_matrix"])
# change from OpenGL/Blender camera axes (Y up, Z back) to COLMAP (Y down, Z forward)
c2w[:3, 1:3] *= -1
这一转换确保了不同来源的数据能够在统一的坐标系统下进行处理,避免了因坐标定义不同而导致的模型错误。
相机姿态表示
相机姿态可以用多种方式表示,2DGS中主要使用旋转矩阵(R)和平移向量(T)。相机外参(Extrinsic Parameters)描述了相机在世界坐标系中的位置和朝向。
在scene/dataset_readers.py中,相机信息被封装为CameraInfo类:
class CameraInfo(NamedTuple):
uid: int
R: np.array
T: np.array
FovY: np.array
FovX: np.array
image: np.array
image_path: str
image_name: str
width: int
height: int
其中,R是旋转矩阵,T是平移向量,共同描述了相机在世界坐标系中的姿态。
点云数据处理
点云(Point Cloud)是三维场景的一种离散表示,由大量三维点组成,每个点通常包含三维坐标和颜色信息。在2DGS中,点云数据作为初始的三维场景表示,为高斯分布的初始化提供基础。
COLMAP点云格式
COLMAP输出的点云有两种格式:文本格式(points3D.txt)和二进制格式(points3D.bin)。2DGS提供了读取这两种格式的函数:
- 文本格式读取:
read_points3D_text() - 二进制格式读取:
read_points3D_binary()
以二进制格式读取为例:
def read_points3D_binary(path_to_model_file):
with open(path_to_model_file, "rb") as fid:
num_points = read_next_bytes(fid, 8, "Q")[0]
xyzs = np.empty((num_points, 3))
rgbs = np.empty((num_points, 3))
errors = np.empty((num_points, 1))
for p_id in range(num_points):
binary_point_line_properties = read_next_bytes(
fid, num_bytes=43, format_char_sequence="QdddBBBd")
xyz = np.array(binary_point_line_properties[1:4])
rgb = np.array(binary_point_line_properties[4:7])
error = np.array(binary_point_line_properties[7])
track_length = read_next_bytes(
fid, num_bytes=8, format_char_sequence="Q")[0]
track_elems = read_next_bytes(
fid, num_bytes=8*track_length,
format_char_sequence="ii"*track_length)
xyzs[p_id] = xyz
rgbs[p_id] = rgb
errors[p_id] = error
return xyzs, rgbs, errors
PLY格式转换
为了方便点云的可视化和后续处理,2DGS将COLMAP点云转换为PLY(Polygon File Format)格式:
def storePly(path, xyz, rgb):
# Define the dtype for the structured array
dtype = [('x', 'f4'), ('y', 'f4'), ('z', 'f4'),
('nx', 'f4'), ('ny', 'f4'), ('nz', 'f4'),
('red', 'u1'), ('green', 'u1'), ('blue', 'u1')]
normals = np.zeros_like(xyz)
elements = np.empty(xyz.shape[0], dtype=dtype)
attributes = np.concatenate((xyz, normals, rgb), axis=1)
elements[:] = list(map(tuple, attributes))
# Create the PlyData object and write to file
vertex_element = PlyElement.describe(elements, 'vertex')
ply_data = PlyData([vertex_element])
ply_data.write(path)
PLY格式是一种通用的三维模型格式,支持点云、网格等多种表示形式,便于使用MeshLab、CloudCompare等软件进行可视化和编辑。
点云统计与过滤
在将点云用于2DGS训练之前,通常需要进行统计分析和过滤,去除噪声点和离群点。虽然2DGS的代码中没有直接提供这些功能,但可以使用Python的点云处理库(如Open3D)进行扩展:
# 示例:使用Open3D进行点云过滤
import open3d as o3d
pcd = o3d.io.read_point_cloud("sparse/0/points3D.ply")
# 统计离群点移除
cl, ind = pcd.remove_statistical_outlier(nb_neighbors=20, std_ratio=2.0)
filtered_pcd = pcd.select_by_index(ind)
o3d.io.write_point_cloud("sparse/0/filtered_points3D.ply", filtered_pcd)
这一步骤对于提高模型训练质量非常重要,因为噪声点会导致高斯分布的错误初始化,影响最终的渲染效果。
自定义数据集预处理
除了使用COLMAP处理的数据集外,2DGS还支持自定义数据集的导入。这需要将自定义数据转换为2DGS能够识别的格式,并确保坐标系统和相机参数的一致性。
自定义数据集格式要求
2DGS支持类似NeRF的自定义数据集格式,主要包含:
- 图像文件:场景的二维图像
- 变换文件:描述相机姿态和内参的JSON文件
变换文件(如transforms_train.json)的格式如下:
{
"camera_angle_x": 0.6911112070083618,
"frames": [
{
"file_path": "./train/r_0",
"transform_matrix": [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0]
]
},
// 更多帧...
]
}
数据加载与坐标转换
2DGS提供了readCamerasFromTransforms()函数来读取自定义数据集:
def readCamerasFromTransforms(path, transformsfile, white_background, extension=".png"):
cam_infos = []
with open(os.path.join(path, transformsfile)) as json_file:
contents = json.load(json_file)
fovx = contents["camera_angle_x"]
frames = contents["frames"]
for idx, frame in enumerate(frames):
cam_name = os.path.join(path, frame["file_path"] + extension)
# NeRF 'transform_matrix' is a camera-to-world transform
c2w = np.array(frame["transform_matrix"])
# change from OpenGL/Blender camera axes (Y up, Z back) to COLMAP (Y down, Z forward)
c2w[:3, 1:3] *= -1
# get the world-to-camera transform and set R, T
w2c = np.linalg.inv(c2w)
R = np.transpose(w2c[:3,:3]) # R is stored transposed due to 'glm' in CUDA code
T = w2c[:3, 3]
# 图像加载和处理...
注意这里的坐标转换:NeRF使用的是OpenGL/Blender坐标系统(Y轴向上,Z轴向后),而2DGS采用COLMAP的坐标系统(Y轴向下,Z轴向前),因此需要对变换矩阵进行调整。
数据集划分与归一化
为了进行模型训练和评估,需要将数据集划分为训练集和测试集。2DGS提供了灵活的划分方式:
if eval:
train_cam_infos = [c for idx, c in enumerate(cam_infos) if idx % llffhold != 0]
test_cam_infos = [c for idx, c in enumerate(cam_infos) if idx % llffhold == 0]
else:
train_cam_infos = cam_infos
test_cam_infos = []
此外,为了提高训练稳定性,2DGS还对相机坐标进行归一化处理,将场景中心平移到原点并缩放到单位球内:
def getNerfppNorm(cam_info):
def get_center_and_diag(cam_centers):
cam_centers = np.hstack(cam_centers)
avg_cam_center = np.mean(cam_centers, axis=1, keepdims=True)
center = avg_cam_center
dist = np.linalg.norm(cam_centers - center, axis=0, keepdims=True)
diagonal = np.max(dist)
return center.flatten(), diagonal
cam_centers = []
for cam in cam_info:
W2C = getWorld2View2(cam.R, cam.T)
C2W = np.linalg.inv(W2C)
cam_centers.append(C2W[:3, 3:4])
center, diagonal = get_center_and_diag(cam_centers)
radius = diagonal * 1.1
translate = -center
return {"translate": translate, "radius": radius}
这一归一化步骤对于优化器的收敛和模型的泛化能力至关重要。
数据处理常见问题与解决方案
在数据处理过程中,可能会遇到各种问题,如坐标转换错误、图像格式不兼容、相机参数缺失等。以下是一些常见问题的解决方案:
坐标系统混淆
问题:导入自定义数据集后,渲染结果出现严重扭曲或相机位置异常。
原因:不同软件使用的坐标系统不同,导致相机姿态计算错误。
解决方案:确保正确进行坐标系统转换,特别是Y轴方向和Z轴方向的调整:
# 将OpenGL/Blender坐标转换为COLMAP坐标
c2w[:3, 1:3] *= -1 # Y轴和Z轴反转
图像畸变未校正
问题:渲染结果边缘出现明显的畸变。
原因:使用了带有畸变的图像,但未进行畸变校正。
解决方案:
- 使用COLMAP对图像进行畸变校正
- 在自定义数据集中确保提供的是校正后的图像
- 对于鱼眼相机等特殊镜头,使用对应的相机模型
点云质量低下
问题:稀疏点云过于稀疏或包含大量噪声点,导致高斯初始化效果差。
解决方案:
- 增加输入图像数量,提高特征匹配质量
- 使用COLMAP的图像对准功能优化相机姿态
- 对点云进行后处理,如统计离群点移除
- 考虑使用MVS(Multi-View Stereo)生成稠密点云
相机参数不完整
问题:缺少相机内参或外参,导致数据加载失败。
解决方案:
- 确保COLMAP处理成功完成,生成完整的输出文件
- 对于自定义数据集,检查transforms.json文件是否包含所有必要参数
- 使用默认参数进行估计,如假设主点位于图像中心
# 示例:假设主点位于图像中心
if principal_point is None:
cx = width / 2
cy = height / 2
数据预处理优化策略
为了提高2DGS模型的训练效果和渲染质量,可以采用以下数据预处理优化策略:
图像分辨率调整
过高的图像分辨率会增加计算负担,而过低的分辨率则会丢失细节。根据场景复杂度选择合适的分辨率:
# 示例:调整图像分辨率
image = image.resize((new_width, new_height), Image.LANCZOS)
图像增强
适当的图像增强可以提高模型的泛化能力:
# 示例:简单的图像增强
from PIL import ImageEnhance
enhancer = ImageEnhance.Brightness(image)
image = enhancer.enhance(1.2) # 增加亮度
相机参数精细化
精确的相机参数对于三维重建至关重要,可以通过以下方法优化:
- 使用棋盘格标定板精确标定相机内参
- 考虑相机的径向和切向畸变参数
- 对于多相机系统,单独标定每个相机
点云密度优化
点云的密度和分布直接影响高斯初始化的质量:
- 确保图像覆盖场景的各个角度
- 增加图像数量以提高点云密度
- 使用稠密重建方法生成更密集的点云
总结与展望
数据处理是2DGS技术 pipeline 中的关键环节,直接影响最终的重建质量和渲染效果。本文详细介绍了2DGS数据处理的完整流程,包括COLMAP相机标定、点云处理、坐标转换和自定义数据集导入等核心内容。
通过掌握这些技术,读者可以:
- 熟练使用COLMAP进行相机姿态估计和稀疏重建
- 理解并处理不同坐标系统之间的转换
- 准备高质量的2DGS训练数据
- 解决数据处理中常见的技术难题
未来,随着2DGS技术的不断发展,数据处理流程可能会更加自动化和高效化。例如,集成实时相机标定、自动图像质量评估、智能点云优化等功能,进一步降低数据准备的门槛,提高重建质量。
对于希望深入研究2DGS数据处理的读者,建议重点关注以下方向:
- 不同相机模型对2DGS重建质量的影响
- 点云密度与高斯数量的关系优化
- 动态场景的数据处理策略
- 多模态数据(如RGB-D)的融合方法
通过不断优化数据处理流程,2DGS技术将在三维重建、增强现实、虚拟现实等领域发挥更大的作用。
附录:数据集处理工具清单
COLMAP相关工具
- COLMAP官方文档:https://colmap.github.io/
- COLMAP Python API:提供更灵活的数据处理接口
点云处理工具
- Open3D:开源点云处理库,支持多种滤波和特征提取算法
- CloudCompare:可视化和编辑点云数据的强大工具
图像预处理工具
- OpenCV:计算机视觉库,提供图像畸变校正、特征提取等功能
- PIL/Pillow:Python图像处理库,用于图像格式转换和基本操作
2DGS数据处理脚本
- 数据格式转换:convert.py
- 模型训练:train.py
- 场景渲染:render.py
- 可视化工具:view.py
掌握这些工具将帮助您更高效地进行2DGS数据集的处理和优化,为后续的模型训练和场景重建奠定坚实基础。
希望本文能够帮助您深入理解2DGS的数据处理流程。如果您有任何问题或建议,欢迎在项目GitHub仓库提交issue或PR。感谢您的阅读!
如果觉得本文对您有帮助,请点赞、收藏并关注项目更新,以便获取更多2DGS相关的技术分享。
下一期预告:《2DGS模型训练全解析:从参数调优到训练加速》
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



