ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

3DGS实战全流程:从三维高斯泼溅原理解析到自制数据集训练

3DGS实战全流程:从三维高斯泼溅原理解析到自制数据集训练 第一次看到3DGS渲染出来的街景时我先是愣了几秒然后反复拖动观察窗口确认自己没看错。那些以往用NeRF要等上好几分钟才能算出来的视角变化在3DGS里几乎变成了鼠标拖到哪、画面就实时跟到哪。更夸张的是训练一个自己的小场景从照片到能自由浏览的模型竟然能压缩到半小时上下。作为一个从传统MVS和神经辐射场一路摸过来的业余研究者这种“看到结果”的冲击比读十篇论文都来得直接。这篇东西就是我复盘整个学习过程后的记录包括怎么理解三维高斯泼溅的核心思路、怎么把官方代码跑通、怎么用手机拍一段视频就做出自己的3DGS数据集以及训练路上真正让我翻车的那些细节。如果你正准备入坑3DGS或者刚从NeRF转过来这些踩坑记录应该能帮你省下不少时间。1. 初见3DGS为什么它对NeRF的体验级颠覆那么大1.1 我对NeRF的忍耐阈值在聊3DGS之前得先说说我原来在NeRF上的状态。NeRF的思路是拿一个多层感知机去拟合一个连续辐射场输入一个三维坐标和一个视角方向输出颜色和体密度然后用体渲染公式沿着光线积分出像素值。这个思路本身非常优雅也是我在很长一段时间里觉得“新视角合成就该这么做”的标准答案。但真正用过的人都知道NeRF在工程上特别磨人训练快则几十分钟、慢则几小时渲染一张图又要逐光线采样GPU再好也得几百毫秒起步分辨率一往上提采样点数翻倍时间也跟着翻倍。所以我一开始对“从照片重建场景”这件事的预期是默认为“离线渲染没问题实时想都不用想”。1.2 3DGS用一堆三维高斯“拼”场景的本质3DGS的全称是3D Gaussian Splatting核心思想其实很朴素场景不用隐式网络来表示而是显式地用一大堆三维高斯分布去拼。你可以想象成把场景的前景和背景都掰碎成几十万上百万个微小的彩色果冻团每个果冻团有自己的中心位置、大小、长短轴朝向、颜色和透明度。训练的过程就是不断调整这些果冻团的形状、颜色和透明度让它们从某个视角投影到屏幕上时正好能还原出真实照片的样子。这和NeRF最大的区别在于NeRF是“隐式”的你必须拿着坐标去网络里查询3DGS是“显式”的渲染时只需要把这堆三维高斯投影到二维图像平面上再按透明度从前往后做Alpha混合。由于每个像素上只处理少数几个有贡献的高斯GPU又能对这种批量投影和排序做高度并行化所以渲染速度直接冲到了实时级别。第一次跑官方Demo时我拖到哪儿画面跟到哪儿那种顺畅感说实话非常强烈。1.3 训练变快以后整个研究心态都不一样了更让我意外的是3DGS把训练速度也拉上来了。官方实现里一个常规的街区场景在高端显卡上十几分钟到半小时就能train完迭代三万次左右。我自己的2060Super虽然慢一些但也能在四五十分钟内结束。相比NeRF时代动不动要等几个小时、还要反复调网络结构的日子这种“有问题马上改、改完马上看结果”的循环效率提升太多了。这直接改变了我的工作习惯以前是小心翼翼调参跑通一次现在是可以大胆试错同一个序列光照稍微不对拍摄角度差一点重拍重跑就是了。这种“可试错性”才是3DGS真正让我觉得它是工程上可用的方向的原因。2. 复现官方代码的环境与仓库结构最容易被拖住的一段2.1 环境版本组合的坑学习3DGS第一步自然是把官方仓库跑起来。这里最劝退的不是代码逻辑而是环境。官方仓库是graphdeco-inria/gaussian-splatting基于PyTorch里面有两个需要自己编译的C/CUDA子模块diff-gaussian-rasterization和simple-knn。也就是说你的本地环境必须同时满足PyTorch版本、CUDA版本、编译器版本、甚至操作系统的多重匹配。我一开始图省事直接用了PyTorch 2.0加CUDA 12.1结果在编译diff-gaussian-rasterization的时候就报错一堆模板实例化的错误翻来覆去找不到原因。后来退回官方推荐的组合PyTorch 1.13.1搭配CUDA 11.7用Windows上装好的Visual Studio 2019的cl.exe来编译几分钟就过了。如果你是Linux相对省心一些但也要注意gcc版本不能太新太新的编译器有时候会跟CUDA Toolkit内置的NVCC不合拍。这里给一个我觉得最稳的环境清单组件推荐配置备注操作系统Ubuntu 20.04或Windows 10/11Windows需要Visual Studio 2019/2022 C工具链显卡驱动对应CUDA 11.7以上驱动只要比CUDA新就行CUDA11.7或11.8不建议直奔12.x兼容性坑多PyTorch1.13.1对应CUDA 11.7官方测试版本COLMAP3.9及以上需要加入PATH环境变量2.2 仓库里几个文件的定义把环境搞定后先别急着跑train.py花半小时把仓库结构浏览一遍收益很大。官方代码的逻辑其实非常直白。train.py是训练入口负责加载数据集、初始化高斯模型、执行优化循环gaussian_renderer/负责把一组三维高斯属性变成图像是每次迭代都会调用的核心模块scene/里面做了数据读取和场景类定义特别是dataset_readers.py告诉你怎么把COLMAP生成的sparse结果转换成高斯模型需要的输入格式utils/下有一些工具函数比如图像保存、相机参数转换两个submodules就是前面说的需要编译的光栅化器和高斯KNN初始化。我当初一个很大的误区是以为要自己写什么核心模块实际上你只要把train.py的流程梳理清楚后续做数据预处理和调参就很有方向。举个例子train.py拿到一个数据集的source_path之后如果发现里面没有sparse文件夹会自动调用COLMAP做特征提取和匹配再用mapper重建稀疏点云如果有现成的sparse就直接读。这意味着你想用自己的图片或视频做数据集唯一要准备的其实是“输入图片”和“让图片能稳定地被COLMAP重建出相机位姿”。2.3 训练命令与输出文件命名跑通的命令很简单如果你有一个照片目录D:\data\my_scene里面放着一堆jpg或png那么命令是python train.py -s D:\data\my_scene -m D:\output\my_scene-s是场景路径-m是输出模型路径。训练过程中会每若干步保存一次点云文件格式是ply里面记录每个三维高斯的位置、尺度、旋转、颜色球谐系数和不透明度。最终模型可以直接用官方的viewer打开也可以导出成其它格式给三维软件用。我第一次看到点云文件和渲染结果对应上的时候才真正理解“场景被显式化”是什么意思——原来真的就是把千万个小椭球存进一个文件里。3. 深读论文容易被绕晕的几个关键机制3.1 三维高斯怎么表示椭球并保持可微论文里每个三维高斯由一个均值和一个协方差矩阵来定义。均值就是椭球的中心位置协方差矩阵决定椭球的形状、大小和朝向。但协方差矩阵本身要满足半正定不能直接当成普通参数丢给优化器去梯度下降。论文的做法是把协方差分解成旋转矩阵R和缩放矩阵S的乘积训练时优化的是四元数q和三维缩放向量s再用公式构造出协方差。这样既保证了物理合理性又能让梯度正常回传。我当时看这里总觉得“不过是个参数化技巧”但真到自己做数据集时才发现这个参数化直接影响了优化稳定性。如果直接用协方差矩阵元素作为参数学优化过程中很容易出现非半正定的情况高斯就会变成一堆乱七八糟的形状渲染结果立刻飘起来。所以读这篇论文理解“优化参数”和“实际使用参数”的区别很关键。3.2 投影时协方差变换带来的现实意义渲染时要看的是世界坐标系里的三维高斯投影到图像坐标系后的二维高斯足迹。论文用透视投影的仿射近似来计算二维协方差先对投影变换求雅可比矩阵J再配合视角变换的旋转矩阵W把三维协方差转成二维协方差Σ J W Σ W^T J^T。这里有一个很反直觉的地方三维高斯投影到平面上其实并不是严格的高斯分布但论文故意做了近似把非线性的透视投影在切点附近当作线性变换来处理。这么做的好处是整个过程仍然高度可微且数值计算很稳定。实际试下来只要相机在看场景的中心区域附近这种近似效果完全够用只有靠近画面边缘时会有轻微误差但和高斯的位置、尺度优化相比几乎可以忽略。这个近似是3DGS能保持实时渲染的一个重要前提。3.3 自适应密度控制里的复制与分割高斯数量的增加和删减是另一个核心机制。训练不是一开始就有几百万个高斯的而是从COLMAP稀疏点云初始化来得几千到几万个高斯然后在迭代过程中动态增删。官方实现里有一个关键逻辑每100次迭代统计一次所有高斯位置梯度的累积值如果某个高斯在多个视角里都有较大梯度说明这个区域的表示不够细腻需要细化反之如果它透明度长期偏低且对成像几乎没贡献就删掉。细化的具体做法有两种如果高斯很小就原地复制一个并稍微扰动方向如果高斯很大就切成两个较小的高斯。为什么要区分这两种情况我的理解是小高斯表示一个局部密集细节复制后两个高斯可以慢慢分化出不同方向的特征大高斯表示覆盖大范围的大块结构直接拆成两半比复制更能有效提高细节表达能力。这个机制让我想到四叉树它就是动态地让模型把“产能”分配到图像差异最明显的地方。3.4 球谐函数与分块光栅化颜色这部分3DGS没有直接存RGB而是存球谐函数的系数。低阶球谐已经能表达大部分漫反射和轻微视角相关色变训练时默认往往用三阶即10个系数。视角变化时颜色会按视角方向做线性组合。这点在户外场景尤其重要因为树叶、水面、天空这种地方的反射是明显随视角变化的如果只存固定RGB很多角度看起来会像贴了张纸。光栅化则是性能的关键。按论文的说法如果对每个像素都遍历所有高斯计算的复杂度会高到没法用。官方做法是先把图像分成16×16像素的tile对每个tile找出可能会覆盖到的高斯按深度排序再对tile内像素做混合。这一步用CUDA实现得很彻底配合显式化的场景表示才实现了大家看到的实时渲染。理解了这个设计你在调分辨率、调tile大小的时候就不会瞎调了。4. 用自己的设备制作3DGS数据集从拍摄到COLMAP的完整链路4.1 手机拍摄的基本原则热搜里“3dgs自己制作数据集”是我觉得最实用的方向。很多新手都是拿别人现成的数据集跑通但真正用起来还是要能自己拍。我第一次就是用手机拍小区楼下的一小片花坛。手机拍摄最重要的原则有三条第一场景必须是静态的人、车、晃动的树叶都会让重建崩掉第二相邻画面要有足够重叠一般不低于70%简单说就是每张照片都要和上一张能找到足够多的共同参照物第三曝光、白平衡要固定不要让相机自动调整否则同一场景在不同角度下的亮度差会让COLMAP匹配困难。很多人上来就围着物体转一整圈结果画面运动太快匹配率很低。正确做法是像拍全景一样缓慢移动保证每张照片之间是“小步前进”。我的习惯是绕着目标物体做多层次环绕加俯仰确保物体顶部、底部都有视角覆盖因为3DGS对没有拍到的区域会直接糊成一片或者生成一团漂移的东西。4.2 从视频截帧代替连拍用手机连拍几十上百张其实也可以但连拍时手稍微一抖就容易模糊而且大量相似照片会让COLMAP匹配冗余。我后来换成拍视频再截帧走一圈录一分钟的4K视频然后用FFmpeg按每秒2到3帧的密度抽帧。这样操作起来很轻松画面天然连续重叠率也高。命令大致是ffmpeg -i input.mp4 -vf fps2 -q:v 2 frames/frame_%04d.jpg你要注意抽帧密度。抽得太密比如每秒10帧相邻画面几乎一样COLMAP会匹配出大量重复观测反而拖慢速度甚至在BA捆绑调整时因为基线过短而出现漂移。我试下来4K视频每秒抽2到3帧跑一个30秒的环绕视频一次性得到60到90张图数量和相机分布都比较理想。4.3 COLMAP建立sparse模型的完整命令得到图片后建议先手动整理一下把模糊的、过度曝光的、没拍到主体直接删掉。之后COLMAP就可以上场了。我在命令行里用的是一组操作colmap feature_extractor --database_path database.db --image_path images --ImageReader.single_camera 1 colmap exhaustive_matcher --database_path database.db mkdir sparse colmap mapper --database_path database.db --image_path images --output_path sparsefeature_extractor负责提取每张图的SIFT特征exhaustive_matcher做两两特征匹配mapter生成稀疏重建结果。如果你的图片数量在100张以内exhaustive匹配完全没问题要是几百张以上建议改成sequential_matcher或vocab_tree_matcher否则匹配时间会爆炸。mapper跑完后sparse目录下会生成0这个子目录里面放着cameras.bin、images.bin、points3D.bin和对应的一些文本文件这就是3DGS官方代码需要的相机位姿和初始点云。有一点要提醒COLMAP的mapper偶尔会因为初始化失败直接输出一个空的0目录。这种情况下我一般会回退到手动指定初始相机对的策略用GUI打开COLMAP在“Model”里先选两张质量好、重叠高的图作为seed再继续迭代重建。实操中这个操作能救很大一部分“拍得没问题但重建出不来”的情况。4.4 数据筛选与常见质量问题数据筛选我是吃过亏的。第一次拍完我急着导入直接开train结果训练出来的模型从某些角度能看到“半个花坛飘在空中”。后来一查是因为我拍的时候有几帧包含了树影摇动COLMAP在这些帧上估计出的位姿明显有偏差。解决办法也很粗暴逐张浏览抽帧结果把有动态物体、严重运动模糊、或者画面里出现自己影子的照片直接删掉。宁可少几张不要烂帧。另外如果拍摄场景远处有大片天空或者纯色墙这类低纹理区域COLMAP在那些方向的位姿约束会很弱会导致整个模型出现倾斜或背景缺失。可以适度保留一些边缘物体比如远处的楼、树作为背景参考让相机位姿之间有更强的几何约束。等重建完再在数据准备阶段把多余的背景裁掉都行。5. 训练迭代中常见的翻车现场和修复思路5.1 损失曲线能告诉你什么训练启动后控制台会每隔几步打印一次loss主要是L1损失和SSIM加权损失。我习惯在前一千次迭代盯着曲线看。正常情况下loss会快速下降然后趋于平缓如果loss在某个阶段突然反弹或者下降速度慢得离谱多半不是参数问题而是输入数据出了问题——最常见的就是某些照片位姿算歪了那些“歪视角”在优化时会持续给梯度拧着整个场景。有一次我的loss死活降不下去最后把COLMAP重建结果在GUI里看了一眼发现近一半相机位姿堆在了半空中真是哭笑不得。5.2 满天飞的火星伪影训练到后期我最常遇到的一个奇怪现象是场景中间出现大量细长条状的高斯从中心向四周发散官方社区里管这叫“splatter”或“floaters”。我分析下来有两个主要原因一是初始点云里本身存在错误点在COLMAP阶段就位置漂移了训练时模型为了弥补这些错误点生成了细长高斯二是某个区域图片覆盖不足模型只能用少量高斯去硬凑视角变化最后高斯就被拉成长条。我后来用的有效手段包括在训练前把COLMAP稀疏点云里面“明显是孤点”的点删掉以及训练时开启过大的稠密化阈值变化对应的参数。如果只是轻微floaters其实渲染时基本看不出来但要是严重到穿帮就得回头修数据而不是硬调渲染参数。这算是“数据决定上限参数只是调节下限”的典型案例。5.3 显存不足与分辨率取舍3DGS对显存的要求比NeRF还要敏感因为光栅化阶段要在显存里保留每个tile的高斯排序和中间结果。我自己在2060Super 8GB上训练1920×1280左右的图非常吃力经常跑着跑着就OOM。后来我降低到1600×1067并把batch大小保持默认就稳定了很多。如果你显卡只有8GB建议直接缩到1500×1000以内或者用较少的迭代数量。反正渲染阶段质量可以靠高分辨率输出训练阶段低分辨率反而能让优化更快更稳。5.4 是否“照片越多效果越好”这是一个很典型的错觉。照片多确实能提供更多视角信息但同时也对位姿精度要求更高。如果一百张图和六十张图拍摄范围差不多多出的四十张大多是重复视角COLMAP跑出来的位姿冗余且可能引入累积误差。我的经验是每段环绕视频截帧控制在60到120张覆盖范围广且均匀比堆数量更有用。质量优先宁缺毋滥。6. 让我真正接受3DGS的落地场景和技术边界6.1 静态场景和产品展示表现最好3DGS最好的使用场景目前看还是静态物体和静态环境的数字化。比如一个街角、一尊雕塑、一套家具、一个手办只要环境光照稳定、没有太多反光通常能得到很高的视觉质量。渲染速度快带来的最直接收益是产品展示三维扫描后直接放进Web浏览器里用鼠标拖一拖就能看各个角度配合WebGL插件一秒能出几十帧甚至上百帧这就是绝对优势。6.2 动态场景与镜面物体的局限但3DGS并不是万能药。它默认假设场景是静态的遇到动态物体就没有统一解法。虽然有分支在做动态三维高斯泼溅但工程复杂度一下子高了不少。另外玻璃、水面、镜面这类高度视角依赖的材质如果球谐阶数不够或者拍摄角度覆盖不密出来的效果经常是“半透明裂纹”或者“变幻扭曲”。别指望纯静态方法能解决透明折射问题这是场景建模领域的经典难题3DGS只是把渲染提速了并没有从原理上改变这个物理限制。6.3 工作流整合与下一步是什么把3DGS纳入我自己的三维内容生产流程后最显著的变化是“拍照建模”的门槛真的落到了普通人可操作的水平。过去做一套虚拟博物馆级别的场景要么用激光扫描仪要么用倾斜摄影计算平台现在一台手机加一台带独立显卡的电脑半天之内就能产出可交互的高质量模型。把这些ply文件转成其他网格格式后还能进传统三维软件里做二次编辑。我现在在尝试的方向是把它和小型数字孪生项目结合用多段围绕拍摄的方式把办公区还原出来导入到自研的浏览器展示框架里替代原来的图片热区引导。坦白说3DGS的出现让“低成本场景重建”和“实时渲染”第一次能同时成立后续的实用化空间还很大。最后再说一点个人体会。比起直接下别人打好包的模型我反而更建议你认真走一遍“拍素材—COLMAP—训练—调优”的完整流程。因为只有当你亲手把一台手机的普通视频变成可以在屏幕上自由游走的三维场景时你才会真正理解那些论文里的公式到底各自承担了什么角色。3DGS的火爆不是某个单一算法的胜利而是整套工程链路一起成熟的体现。对这行来说能亲手走通一次比读十篇综述都管用。
返回列表