简介:这份源码包面向希望低成本自制3D Gaussian Splatting数据集的开发者与研究者,围绕Colmap工具给出从图像采集到格式转换的完整实现思路,帮助解决稀疏重建无相机轨迹、稠密点云孔洞多等常见问题。资源共12个文件,压缩包约379KB,包含3个Python脚本用于重建与格式转换、1个Colmap配置文件、1份Markdown说明文档,以及网页预览与项目配置等辅助文件,结构紧凑便于直接运行与二次修改。目前已有319人学习下载。通过阅读代码与配套说明,读者可掌握数据集核心要求、环境安装、稀疏与稠密重建流程、格式转换要点,并获得设备与效率优化建议及排错思路,为后续3DGS训练与应用打下基础,适合具备一定Python与三维重建基础的中高级用户参考。
1. 从一堆照片到能训 3DGS 的数据集,Colmap 到底卡在哪一步
手里有一组环绕拍摄的照片,想训一个 3D Gaussian Splatting 场景,结果卡在第一步:3DGS 官方仓库要的是一份带相机内外参和稀疏点云的 COLMAP 数据集,而大多数人手上只有一堆 JPG。Colmap 自制 3DGS 数据集这件事,难点从来不是「跑不跑得起来」,而是跑完之后位姿对不对、点云够不够、尺度乱不乱。我见过太多人把automatic_reconstructor一把梭跑完,直接丢给 3DGS 训练,结果要么初始化点云稀得像雾,要么相机轨迹整个翻过来,训练出来的场景糊成一团。这篇笔记就按我实际做数据的顺序,把 Colmap 从装到出数据集、再到喂给 3DGS 之前的自检,一步步拆开讲。适合已经会跑 3DGS 训练、但被数据准备卡住的从业者,也适合想搞清楚 SfM 这条链路到底在干什么的新手。
先说清楚 Colmap 在整条链路里的位置。3DGS 训练需要三样东西:每张图对应的相机位姿(外参)、相机内参、以及一组三维点作为高斯初始化的种子。这三样恰好就是 SfM(Structure from Motion)的输出。Colmap 是目前开源里最稳的 SfM/MVS 工具之一,它把「特征提取 → 特征匹配 → 增量式重建 → 光束法平差」这一整套流程封装成了命令行和 GUI。你要做的不是理解每个数学公式,而是理解每一步的输出长什么样、参数动一下会怎样。自制数据集的核心矛盾在于:照片质量、拍摄方式、Colmap 参数三者互相牵制,任何一环拉胯,最后 3DGS 都会用一张糊图回报你。
2. Colmap 装好之后,先搞懂它到底吐出了什么
2.1 三个目录决定你后面所有操作
Colmap 跑完一个重建,工作目录里会长出这些东西,你必须先认清它们,否则后面 3DGS 报错你都不知道去哪找原因。
workspace/ ├── database.db # 特征和匹配关系的 SQLite 库 ├── sparse/ # 稀疏重建结果,核心中的核心 │ └── 0/ │ ├── cameras.bin # 相机内参(焦距、主点、畸变) │ ├── images.bin # 每张图的位姿(旋转+平移) │ └── points3D.bin # 稀疏三维点云 └── dense/ # 稠密重建结果,3DGS 一般用不到sparse/0/这三个.bin文件就是 3DGS 真正要吃的东西。3DGS 官方脚本convert.py会把它们转成cameras.json、images.json和points3D.ply。很多人以为dense/也要,其实 3DGS 初始化只用稀疏点,稠密点云反而会让显存爆炸,这是第一个容易翻车的地方。
提示:
sparse/下面可能出现0/、1/多个子目录,那是 Colmap 把场景拆成了多个连通分量。3DGS 只认0/,如果主模型不在0/,你得手动指定或重新调匹配参数。
2.2 内参模型选错,后面全白干
Colmap 的cameras.bin里存的是相机内参模型。常见的有SIMPLE_PINHOLE、PINHOLE、SIMPLE_RADIAL、RADIAL、OPENCV等。选哪个不是随便点的,它决定了 Colmap 要优化几个畸变参数。
| 模型名 | 参数个数 | 适用场景 |
|---|---|---|
| SIMPLE_PINHOLE | 3 | 无畸变、焦距已知的合成数据 |
| PINHOLE | 4 | 无畸变、fx/fy 可能不同 |
| SIMPLE_RADIAL | 4 | 手机/普通相机,一个径向畸变 |
| RADIAL | 5 | 畸变稍明显的镜头 |
| OPENCV | 8 | 广角、鱼眼矫正后仍有残余畸变 |
我一般用SIMPLE_RADIAL起步,因为绝大多数手机和消费级相机一个径向畸变参数就够。如果你用的是运动相机或者广角镜头,直接上OPENCV,否则边缘的畸变会让匹配点对不上,重建出来的点云在画面边缘全是飞点。
# 用 SIMPLE_RADIAL 跑特征提取,单相机模式 colmap feature_extractor \ --database_path ./workspace/database.db \ --image_path ./images \ --ImageReader.camera_model SIMPLE_RADIAL \ --ImageReader.single_camera 1--ImageReader.single_camera 1表示所有照片来自同一台相机,共享一套内参。如果你中途换了镜头或者用了不同设备,这个必须设成 0,否则 Colmap 会强行用一套内参去拟合所有图,位姿直接崩。--ImageReader.camera_model就是上面表格里选的模型,改一个词,后面优化自由度就变了。
2.3 特征匹配:穷举还是顺序,差别在时间不在质量
特征提取完,下一步是匹配。Colmap 提供几种匹配器,最常用的是exhaustive_matcher和sequential_matcher。
# 穷举匹配,适合照片数量少(<200张)且顺序乱的情况 colmap exhaustive_matcher \ --database_path ./workspace/database.db # 顺序匹配,适合视频抽帧或环绕拍摄,照片有先后顺序 colmap sequential_matcher \ --database_path ./workspace/database.db \ --SequentialMatching.overlap 10exhaustive_matcher会把每张图和其余所有图比一遍,O(n²) 的复杂度,200 张以内还能忍,上千张就是几个小时起步。sequential_matcher只和前后overlap张比,速度快很多,但前提是你的照片顺序确实是环绕或推进的。我一般拍摄时按顺时针走一圈,文件名用IMG_0001到IMG_0200递增,这样sequential_matcher的overlap设 10 到 15 就够,既快又不会漏匹配。
注意:如果你照片顺序是乱的,比如从不同文件夹拷进来导致文件名乱序,
sequential_matcher会漏掉大量匹配,重建直接失败。这种情况老老实实用exhaustive_matcher。
3. 从匹配到稀疏重建:参数怎么调才不翻车
3.1 增量式重建的入口命令
匹配完成后,进入mapper,也就是增量式重建。这一步是 Colmap 最吃内存、最容易失败的地方。
colmap mapper \ --database_path ./workspace/database.db \ --image_path ./images \ --output_path ./workspace/sparse \ --Mapper.ba_global_function_tolerance 1e-6 \ --Mapper.filter_max_reproj_error 4 \ --Mapper.init_min_tri_angle 4--Mapper.ba_global_function_tolerance是光束法平差的收敛容差,默认 1e-6 已经够严,调大只会让结果更糙。--Mapper.filter_max_reproj_error 4表示重投影误差超过 4 像素的点会被过滤掉,这个值越小点云越干净但越稀,我一般 4 到 8 之间试。--Mapper.init_min_tri_angle 4是初始化时三角测量的最小夹角,太小会导致远距离点深度估计不准,太大又会让初始化失败,4 度是个稳妥起点。
跑完mapper,去sparse/0/看三个文件在不在。如果sparse/是空的,说明重建失败,常见原因是匹配点太少或者内参模型选错。
3.2 用 model_analyzer 先看一眼再往下走
别急着转 3DGS 格式,先用 Colmap 自带的model_analyzer检查一下重建质量。
colmap model_analyzer \ --path ./workspace/sparse/0输出里重点看几个数:Registered images是成功注册的图片数,如果远小于你拍的总数,说明很多图没匹配上;Points是稀疏点数量,几百张图一般能出几万到几十万个点;Mean reprojection error是平均重投影误差,单位像素,低于 1 算不错,高于 2 就要警惕。
我一般会要求注册率在 90% 以上,重投影误差低于 1.5。达不到就回去查匹配参数或者照片质量,而不是硬着头皮往下走。这一步多花五分钟,能省掉后面 3DGS 训练几小时的无效等待。
3.3 把 Colmap 输出转成 3DGS 能吃的格式
3DGS 官方仓库里有个convert.py,专门干这件事。假设你已经把 3DGS 代码拉下来了,在项目根目录执行:
python convert.py \ -s ./workspace \ --skip_matching-s指向你的 Colmap 工作目录,--skip_matching表示跳过重新匹配,直接用已有的sparse/0/。跑完之后会在workspace/下生成cameras.json、images.json和points3D.ply。这三个文件就是 3DGS 训练脚本train.py要读的输入。
提示:
convert.py默认读sparse/0/,如果你的模型在别的子目录,要么改脚本里的路径,要么把0/重命名或软链过去。
4. 自制数据集最容易踩的五个坑
4.1 坑一:照片全是模糊的,Colmap 匹配直接崩
现象:mapper跑完sparse/是空的,或者注册图片数只有个位数。
原因:拍摄时手抖、光线不足导致快门变慢,照片本身模糊,特征点提取不出来。Colmap 的特征提取器对模糊非常敏感,模糊图的 SIFT 特征又少又不可靠。
解决:拍摄时保证光线充足,快门速度至少 1/100 秒以上,能上三脚架就上。已经拍糊的图,Colmap 救不回来,只能重拍。我一般会在导入前用脚本筛一遍,把拉普拉斯方差低于阈值的图直接剔除。
import cv2 import os # 用拉普拉斯方差判断模糊,低于 100 的图直接跳过 def is_blurry(image_path, threshold=100): img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) variance = cv2.Laplacian(img, cv2.CV_64F).var() return variance < threshold for f in os.listdir('./images'): if is_blurry(os.path.join('./images', f)): print(f'模糊,建议剔除: {f}')cv2.Laplacian算的是图像二阶导数的方差,方差越小说明边缘越少、图越糊。阈值 100 是个经验值,手机照片一般能到 200 以上,低于 100 的基本没法用。
4.2 坑二:单相机模式开了,但中途换了设备
现象:重建能跑完,但部分图片的位姿明显错乱,点云在某个区域炸开。
原因:--ImageReader.single_camera 1强制所有图共享一套内参,但如果你中途换了手机或者改了焦距,内参根本不一样,Colmap 强行拟合的结果就是位姿扭曲。
解决:拍摄全程用同一台设备、同一焦距,不要中途变焦。如果确实用了多台设备,把single_camera设成 0,让 Colmap 为每组图单独估计内参。代价是需要的照片更多,否则每组内参都估不准。
4.3 坑三:点云太稀,3DGS 初始化像雾
现象:3DGS 训练能启动,但初始高斯点少得可怜,训练很久场景还是糊的。
原因:Colmap 的稀疏点云本身就不密,如果filter_max_reproj_error设得太小,过滤太狠,点就更少。3DGS 靠这些点初始化高斯的位置和颜色,点太少等于起点太差。
解决:把--Mapper.filter_max_reproj_error从 4 放宽到 8,让更多点通过筛选。另外可以在mapper之后跑一次bundle_adjuster做全局优化,把位姿和点云再精修一轮。
colmap bundle_adjuster \ --input_path ./workspace/sparse/0 \ --output_path ./workspace/sparse/0 \ --BundleAdjustment.refine_principal_point 1--BundleAdjustment.refine_principal_point 1表示同时优化主点,对手机这种主点可能偏移的相机有帮助。跑完再model_analyzer看一遍,点数和误差应该都有改善。
4.4 坑四:照片数量太多,内存直接爆
现象:mapper跑到一半被系统杀掉,或者报std::bad_alloc。
原因:增量式重建要把所有匹配关系读进内存,照片上千张时内存占用轻松超过 32G。
解决:分块重建,或者先用sequential_matcher降低匹配数量。如果必须一次重建,把--Mapper.ba_global_images_ratio调大,减少全局平差的频率。
colmap mapper \ --database_path ./workspace/database.db \ --image_path ./images \ --output_path ./workspace/sparse \ --Mapper.ba_global_images_ratio 1.5 \ --Mapper.ba_global_points_ratio 1.5这两个 ratio 控制的是「新增多少图片或点之后触发一次全局 BA」,调大意味着 BA 次数减少,内存峰值降低,但精度会略降。我一般 500 张以内不动它,超过 800 张才考虑调。
4.5 坑五:转完格式,3DGS 报找不到 points3D.ply
现象:convert.py跑完没报错,但train.py启动时说points3D.ply不存在。
原因:convert.py生成的文件名和路径取决于你的目录结构,有时候它会生成points3D.ply在workspace/根目录,有时候在workspace/sparse/0/下。3DGS 的train.py默认去-s指定的源路径下找。
解决:跑完convert.py后,手动确认三个文件的位置,用find找一下。
find ./workspace -name "points3D.ply" -o -name "cameras.json" -o -name "images.json"找到之后,要么把它们移到train.py期望的路径,要么在train.py里改--source_path指向正确目录。这个坑不涉及算法,纯粹是路径问题,但第一次做的人经常卡在这里。
5. 让数据集更耐训的两个进阶技巧
5.1 用掩码把动态物体和天空剔掉
Colmap 重建时会把所有能匹配上的东西都算进去,包括走动的人、飘动的树叶、天空的云。这些动态物体在 3DGS 训练里会变成一堆漂浮的高斯,严重影响观感。我的做法是在特征提取阶段就加掩码,把天空和明显动态区域涂黑,让 Colmap 不去提取那里的特征。
colmap feature_extractor \ --database_path ./workspace/database.db \ --image_path ./images \ --ImageReader.camera_model SIMPLE_RADIAL \ --ImageReader.single_camera 1 \ --ImageReader.mask_path ./masks--ImageReader.mask_path指向一个和images/同结构的掩码目录,每张图对应一张黑白掩码,黑色区域不提取特征。掩码可以用分割模型批量生成,也可以手动涂几张关键帧。这一步做完,点云会干净很多,3DGS 训练时漂浮物明显减少。
5.2 用 model_aligner 把尺度拉回真实世界
Colmap 重建出来的场景尺度是任意的,3DGS 训练本身不关心绝对尺度,但如果你后续要做测量、要和别的模型对齐,尺度就必须固定。model_aligner可以用已知距离或者地理坐标把模型缩放到真实尺度。
colmap model_aligner \ --input_path ./workspace/sparse/0 \ --output_path ./workspace/sparse/0_aligned \ --ref_images_path ./ref_images.txt \ --ref_is_gps 0 \ --alignment_type ecef--ref_images_path是一个文本文件,每行写着图片名和它对应的真实坐标。--alignment_type ecef表示用笛卡尔坐标对齐。如果你没有真实坐标,也可以简单地在场景里量一段已知距离,用--alignment_type custom加两个点来定尺度。这一步不是必须的,但做过一次之后,后面所有基于这个数据集的实验都可复现,省掉很多「上次那个尺度是多少来着」的后悔药。
我自己现在的习惯是:拍完照片先筛模糊,再跑feature_extractor加掩码,sequential_matcher匹配,mapper重建,model_analyzer检查,bundle_adjuster精修,最后convert.py转格式。这一套走下来,500 张以内的照片,从原始 JPG 到能训的 3DGS 数据集,大概 40 分钟到 1 小时。比起反复重训 3DGS 去猜哪里出了问题,前面这 1 小时花得值。希望帮到你。
本文还有配套的精品资源,点击获取