简介这份源码包面向希望低成本自制3D Gaussian Splatting数据集的开发者与三维重建学习者围绕Colmap预处理流程提供可运行的项目代码帮助解决稀疏重建无相机轨迹、稠密点云孔洞多等常见问题。包内共12个文件以5张jpg示例图片、3个py脚本、1个md说明文档为主另含html页面、gitignore与inscode配置压缩包约379KB结构轻量便于快速上手。其中Python脚本分别对应Colmap配置、稠密重建与格式转换等关键环节md文档梳理了整体流程与注意事项图片可用于验证输入数据规范。已有314人学习适合具备一定三维视觉基础、需要从图像采集到格式转换完整跑通3DGS数据集构建流程的读者参考也可作为后续训练与应用的数据准备起点。1. 从一堆手机照片到能训 3DGS 的数据集中间到底缺了什么你拍了段环绕视频抽帧得到几百张图兴冲冲丢进 3DGS 训练脚本结果要么直接报错要么训出来一团糊。问题八成不在 3DGS 本身而在数据没有经过 SfM 处理——相机位姿是空的稀疏点云是空的3DGS 根本不知道每张图是从哪个角度拍的。Colmap 自制 3DGS 数据集这件事核心就是用 Colmap 把无序图像变成带相机内外参和稀疏点云的规范数据集再喂给 3DGS 训练。它解决的是「有图但没法训」的断层问题适合手里有拍摄素材、想跑通 3DGS 全流程的工程师和研究者。整套流程不复杂但参数和顺序错一步就翻车下面按我实际跑通的路径拆开讲。2. Colmap 跑通 SfM从图像到相机位姿的最小闭环2.1 为什么 3DGS 必须先过 Colmap 这一关3DGS 的训练本质是优化一堆三维高斯椭球的位置、协方差、颜色和透明度而监督信号来自「把高斯投影到每个相机视角后渲染出的图像」与真实图像的差异。这意味着每个训练视角必须知道相机在哪、朝哪看、焦距多少。Colmap 做的就是从多视角图像中同时估计相机位姿和场景稀疏结构输出 cameras.bin、images.bin、points3D.bin 三件套。没有这三件套3DGS 的投影环节就是黑匣子梯度传不回去。常见做法是先用 Colmap 做一遍完整的 SfM拿到稀疏重建结果再转成 3DGS 需要的格式。有人会问能不能跳过 Colmap 直接用 COLMAP-free 的方案可以但那是另一条技术路线稳定性和可控性目前不如 Colmap 成熟。对于自制数据集Colmap 仍然是首选。2.2 图像采集与预处理的三个硬指标在打开 Colmap 之前素材本身要过关。我一般按三个指标检查重叠度相邻帧之间至少 60% 画面重叠环绕拍摄时每转 10 到 15 度一张一圈 24 到 36 张起步。重叠不够特征匹配直接断链。清晰度运动模糊是 SfM 的头号杀手。快门速度不够就补光或降速糊掉的帧宁可删掉也不要留着凑数。纹理纯白墙面、镜面、大面积天空这些弱纹理区域Colmap 提不出特征点。拍摄时尽量让画面里有报纸、木纹、绿植这类有纹理的参照物。预处理阶段用 ffmpeg 抽帧命令如下# 从视频按每秒 2 帧抽图输出到 images 目录 ffmpeg -i input.mp4 -vf fps2 -q:v 2 images/frame_%04d.jpgfps2表示每秒抽 2 帧环绕 30 秒的视频大约得到 60 张图够用。-q:v 2控制 JPEG 质量数值越小质量越高2 到 5 之间比较合适。抽完帧后建议人工过一遍把模糊和重复的删掉这一步花五分钟后面省半小时排错。2.3 Colmap 命令行完整流程与参数逐项说明Colmap 有 GUI 和命令行两套入口。GUI 适合调试命令行适合批量复现。我一般先用 GUI 跑通一遍确认参数再固化成脚本。下面是命令行的完整流程# 1. 特征提取 colmap feature_extractor \ --database_path ./database.db \ --image_path ./images \ --ImageReader.camera_model OPENCV \ --ImageReader.single_camera 1 \ --SiftExtraction.max_image_size 2000 # 2. 特征匹配 colmap exhaustive_matcher \ --database_path ./database.db \ --SiftMatching.guided_matching 1 # 3. 稀疏重建 mkdir sparse colmap mapper \ --database_path ./database.db \ --image_path ./images \ --output_path ./sparse # 4. 去畸变导出 colmap image_undistorter \ --image_path ./images \ --input_path ./sparse/0 \ --output_path ./dense \ --output_type COLMAP逐项说明关键参数。--ImageReader.camera_model OPENCV适用于大多数手机和单反如果用的是鱼眼镜头就换成 OPENCV_FISHEYE。--ImageReader.single_camera 1表示所有图来自同一台相机、内参一致手机拍摄基本都设 1。--SiftExtraction.max_image_size 2000把长边压到 2000 像素太大显存吃不住太小特征点不够2000 是经验值。--SiftMatching.guided_matching 1开启引导匹配能提升弱纹理区域的匹配数代价是慢一些。稀疏重建完成后sparse/0目录下会出现 cameras.bin、images.bin、points3D.bin。用colmap model_analyzer可以查看重建统计colmap model_analyzer --path ./sparse/0重点看两个数注册图像数和平均重投影误差。注册图像数应该接近输入总数如果只有一半说明匹配或重建出了问题。平均重投影误差在 0.5 到 1.0 像素之间算正常超过 2 像素说明位姿不准后面 3DGS 训出来会糊。2.4 判断重建是否合格的四个检查点跑完不要急着转格式先做四个检查检查项合格标准不合格时的处理注册图像比例大于 90%降低匹配阈值或补拍弱纹理区域平均重投影误差小于 1.0 像素剔除误差大的图像后重跑稀疏点数量与场景复杂度匹配增加图像或提高特征提取数量轨迹连续性相机位置平滑无跳变检查是否有重复纹理导致误匹配在 GUI 里打开稀疏重建结果按Render Render camera可以看到相机轨迹。轨迹应该是平滑的环绕或推进如果出现突然跳变说明某些帧的位姿估计错了需要把这些帧找出来删掉重跑。3. 把 Colmap 输出转成 3DGS 能吃的格式3.1 3DGS 到底需要哪些文件3DGS 官方实现读取的数据集目录结构是这样的dataset/ images/ frame_0001.jpg frame_0002.jpg ... sparse/ 0/ cameras.bin images.bin points3D.bin注意这里有个容易搞混的点3DGS 读的是sparse/0下的二进制文件不是去畸变后的dense目录。很多人跑完image_undistorter后把 dense 目录指给 3DGS结果报错找不到相机参数。正确做法是把sparse/0整个目录拷到数据集根目录下图像用原始未去畸变的版本。3.2 转换脚本与目录组织如果 Colmap 输出已经是标准格式其实不需要额外转换脚本直接组织目录即可。但实际项目中经常需要把图像重命名、把多个子场景合并、或者把 Colmap 的文本格式转成二进制格式。下面这个脚本做三件事把图像按顺序重命名、拷贝 sparse 目录、生成一个简单的校验文件。import os import shutil import struct def organize_dataset(src_images, src_sparse, dst_root): 将 Colmap 输出组织成 3DGS 可读的数据集结构 src_images: 原始图像目录 src_sparse: Colmap 稀疏重建目录包含 cameras.bin 等 dst_root: 目标数据集根目录 img_dst os.path.join(dst_root, images) sparse_dst os.path.join(dst_root, sparse, 0) os.makedirs(img_dst, exist_okTrue) os.makedirs(sparse_dst, exist_okTrue) # 拷贝并重命名图像 exts (.jpg, .jpeg, .png) files sorted([f for f in os.listdir(src_images) if f.lower().endswith(exts)]) for idx, fname in enumerate(files): new_name fframe_{idx:04d}{os.path.splitext(fname)[1]} shutil.copy2(os.path.join(src_images, fname), os.path.join(img_dst, new_name)) # 拷贝稀疏重建文件 for f in [cameras.bin, images.bin, points3D.bin]: src os.path.join(src_sparse, f) if os.path.exists(src): shutil.copy2(src, os.path.join(sparse_dst, f)) else: print(f警告缺少 {f}) # 校验 images.bin 中的图像名是否与重命名后一致 # 注意如果重命名了图像images.bin 里的名字会对不上 # 所以更稳妥的做法是不重命名或者用 colmap 的 model_converter 同步 print(f完成{len(files)} 张图像已组织到 {dst_root}) if __name__ __main__: organize_dataset(./images, ./sparse/0, ./dataset_3dgs)这里有个血泪教训不要随意重命名图像。Colmap 的images.bin里存的是原始文件名重命名后 3DGS 按名字去索引图像会找不到。如果确实需要重命名要么在 Colmap 之前就统一命名要么用colmap model_converter把二进制转成文本手动改完再转回去。我一般选择在抽帧阶段就用frame_%04d.jpg的格式后面全程不动文件名。3.3 用 model_converter 做格式互转与校验Colmap 的二进制格式读写快但不可读文本格式方便检查但体积大。model_converter可以在两者之间转换# 二进制转文本方便查看相机参数 colmap model_converter \ --input_path ./sparse/0 \ --output_path ./sparse_txt \ --output_type TXT # 文本转二进制用于最终训练 colmap model_converter \ --input_path ./sparse_txt \ --output_path ./sparse/0 \ --output_type BIN转成文本后cameras.txt里能看到每台相机的内参images.txt里能看到每张图的位姿四元数和平移向量。检查images.txt时注意如果某张图的四元数全是 0 或者平移向量异常大说明这张图的位姿估计失败了应该从数据集中剔除。3.4 数据集划分与分辨率对齐3DGS 训练通常需要留出测试视角来评估渲染质量。常见做法是按 8:1:1 划分训练、测试、验证集但 3DGS 官方脚本默认用llffhold参数每隔 8 张取一张做测试。如果自己划分需要在数据集目录下放一个split.json或者按 3DGS 的约定命名。分辨率方面3DGS 训练时会自动按图像长边缩放但 Colmap 的特征提取分辨率要和训练分辨率匹配。如果 Colmap 用 2000 像素提取特征而 3DGS 训练时缩到 800位姿精度是够的但稀疏点的尺度会偏。我一般让 Colmap 和 3DGS 用同一套图像不做额外缩放显存不够就减图像数量而不是降分辨率。4. 避坑与排查Colmap 自制 3DGS 数据集最常见的五个翻车点4.1 重建只注册了一半图像现象model_analyzer显示注册图像数只有输入的一半甚至更少稀疏点云稀稀拉拉。原因最常见的是图像重叠度不够或者场景中有大面积弱纹理区域导致特征匹配断裂。另一个容易被忽略的原因是single_camera设置错误——如果图像来自不同相机或不同焦距设成 1 会导致内参估计混乱。解决先检查图像序列把模糊和重复的删掉。然后在feature_extractor阶段把--SiftExtraction.max_image_size提到 2400 或 3200增加特征点数量。如果还是不行改用sequential_matcher代替exhaustive_matcher因为环绕拍摄的图像顺序本身就是连续的顺序匹配更稳。4.2 重投影误差过大导致 3DGS 训出来糊现象Colmap 重建看起来正常但 3DGS 训练几百轮后渲染结果仍然模糊PSNR 上不去。原因平均重投影误差超过 2 像素说明相机位姿不够准。3DGS 对位姿误差很敏感位姿偏一点高斯投影就偏一片。解决在 Colmap 里用--Mapper.ba_global_function_tolerance收紧 BA 的收敛容差默认是 1e-6可以试 1e-7。另外在image_undistorter之后用colmap bundle_adjuster做一次全局 BA 精化colmap bundle_adjuster \ --input_path ./sparse/0 \ --output_path ./sparse/0_refined \ --BundleAdjustment.max_num_iterations 100跑完后再看重投影误差通常能降到 0.5 像素以下。4.3 图像文件名不匹配导致 3DGS 报 FileNotFoundError现象3DGS 训练脚本启动后报错说找不到某张图像。原因Colmap 的images.bin里存的是原始文件名如果中间用脚本重命名过图像或者抽帧时的命名和 Colmap 读取时的命名不一致就会对不上。解决统一命名规范。我习惯在抽帧阶段就用frame_%04d.jpgColmap 的image_path指向这个目录后面所有步骤都不再改名。如果已经改了名用model_converter转成文本手动同步images.txt里的文件名再转回二进制。4.4 显存不足导致训练中断现象3DGS 训练到一半报 CUDA out of memory。原因图像分辨率太高或者稀疏点太多导致高斯数量爆炸。解决在 3DGS 的训练配置里降低resolution参数或者用--densify_grad_threshold提高致密化门槛减少高斯数量。另一个办法是在 Colmap 阶段就用--SiftExtraction.max_image_size限制图像尺寸从源头控制数据量。4.5 稀疏点云尺度异常导致相机轨迹错乱现象在 3DGS 的 viewer 里看相机轨迹发现相机位置分布在一个异常大或异常小的范围内。原因Colmap 重建的尺度是不确定的如果没有做尺度对齐不同次重建的尺度可能差几个数量级。解决3DGS 内部会自动做归一化一般不需要手动处理。但如果发现轨迹明显错乱检查images.bin里的平移向量是否在合理范围。如果某张图的平移向量是其他图的几百倍说明这张图的位姿估计错了删掉重跑。5. 进阶技巧用 Colmap 的文本模型做精细控制和批量验证跑通基本流程后真正拉开差距的是对 Colmap 中间结果的控制能力。我一般会在稀疏重建完成后把模型转成文本格式用脚本做一轮自动校验把异常图像筛掉再重跑。这个习惯帮我省了很多次「训到一半发现数据有问题」的后悔药。具体做法是先用model_converter把sparse/0转成 TXT然后读images.txt计算每张图的重投影误差和轨迹平滑度。下面这段脚本做的是轨迹平滑度检查——相邻帧的相机中心距离如果突变超过阈值就标记为可疑帧。import numpy as np def quat_to_rot(q): 四元数转旋转矩阵q 格式为 (w, x, y, z) w, x, y, z q return np.array([ [1-2*(y*yz*z), 2*(x*y-w*z), 2*(x*zw*y)], [2*(x*yw*z), 1-2*(x*xz*z), 2*(y*z-w*x)], [2*(x*z-w*y), 2*(y*zw*x), 1-2*(x*xy*y)] ]) def check_trajectory(images_txt_path, jump_threshold0.5): 检查相机轨迹平滑度 images_txt_path: Colmap 文本格式的 images.txt jump_threshold: 相邻帧距离突变的阈值相对于平均距离的倍数 centers [] names [] with open(images_txt_path, r) as f: for line in f: if line.startswith(#) or len(line.strip()) 0: continue parts line.strip().split() if len(parts) 10: continue # images.txt 格式IMAGE_ID QW QX QY QZ TX TY TZ CAMERA_ID NAME q list(map(float, parts[1:5])) t np.array(list(map(float, parts[5:8]))) rot quat_to_rot(q) center -rot.T t # 相机中心 -R^T * t centers.append(center) names.append(parts[9]) centers np.array(centers) dists np.linalg.norm(np.diff(centers, axis0), axis1) mean_dist np.mean(dists) suspicious [] for i, d in enumerate(dists): if d jump_threshold * mean_dist * 3: suspicious.append((names[i], names[i1], d)) print(f平均帧间距{mean_dist:.4f}) print(f可疑跳变{len(suspicious)} 处) for a, b, d in suspicious: print(f {a} - {b}: {d:.4f}) return suspicious if __name__ __main__: check_trajectory(./sparse_txt/images.txt)这段脚本的核心逻辑是从images.txt里读出每张图的四元数和平移向量算出相机中心坐标然后看相邻帧之间的距离是否突变。jump_threshold设 0.5 表示超过平均间距 1.5 倍就标记。实际用的时候可以先把阈值调大跑一遍看分布再收紧。另一个进阶技巧是分块重建再合并。如果场景很大一次性跑 Colmap 容易内存不够可以按拍摄顺序切成几段分别重建再用colmap model_merger合并。合并的前提是相邻段之间有足够的重叠图像否则合并会失败。我一般会在切分处多留 5 到 10 张重叠帧。最后说一个验证数据集质量的小技巧在 3DGS 训练启动后先看前 100 轮的 loss 曲线。如果 loss 在前 100 轮内降到 0.05 以下说明位姿和稀疏点质量不错如果 loss 震荡或者下降很慢大概率是 Colmap 阶段出了问题别急着调 3DGS 的超参回去检查重投影误差和注册图像比例。这个习惯让我少走了很多弯路希望帮到你。本文还有配套的精品资源点击获取