ARTICLE DETAIL

资讯详情

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

2DGS从高斯场到三角网格:三维重建几何精度实战指南

2DGS从高斯场到三角网格:三维重建几何精度实战指南 去年我一直在折腾三维重建的落地项目用 3D Gaussian Splatting3DGS重建了一堆实景场景视觉质量确实惊艳但一到交付就卡住了——客户要的是能直接进 Blender、CAD、游戏引擎的三角网格而不是一颗颗高斯椭球。对着点云抠表面、跑泊松重建折腾出来的网格要么细节全丢要么表面坑坑洼洼。后来换了 2DGS2D Gaussian Splatting这套方案才算是把好看和可编辑这两件事真正打通了。这篇文章就基于我自己的踩坑过程把 2DGS 从高斯场生成三角网格的原理、完整实现路径和那些文档里不会写的实操细节一次讲清楚。适合刚接触 3D 高斯重建、想让重建结果具备几何精度的读者。1. 为什么非要从高斯变成网格1.1 3DGS 的视觉质量与几何模糊先说清楚问题的原点。3DGS 的核心思路是用海量三维高斯椭球去拟合场景的颜色和密度每个高斯有中心坐标、协方差矩阵体现椭球的形状和旋转、不透明度、球谐系数。渲染时按像素遍历这些高斯做 alpha 合成就能得到和 NeRF 质量相当甚至更好的图像。顺带衍生出高斯模糊、高斯泼溅这些话题里反复出现的东西很多人误以为高斯泼溅就是某个滤镜效果其实它指的是这种以高斯函数为基元的可微渲染管线。但 3DGS 有个致命问题它不在乎表面在哪。每个高斯都是实心的体积椭球在空间中互相重叠、穿插。重建出来的几何是一片模糊的云。如果直接把深度图转点云再跑一遍点云重建表面经常会出现表面穿透、噪声点悬空、法向混乱这些问题。Chamfer Distance 指标在 3DGS 上通常明显差于 NeuS 这类专门做表面的方法。本质上就是3DGS 优化的是图像看起来像不像而不是几何对不对。1.2 三角网格在工程交付中的不可替代性为什么一定要转成三角网格因为下游工业链路几乎全部围绕网格展开传统渲染管线Blender、Maya、UE、Unity以 mesh 为基础支持 UV 贴图、法线贴图、LOD 和物理引擎碰撞体。物理仿真刚体碰撞、流体边界需要明确的封闭表面高斯场无法定义碰撞边界。CAD/CAM 和 3D 打印需要流形网格必须保证拓扑正确、无洞、无自交。增强现实、数字孪生项目里网格比高斯场更适合做轻量化、语义标注和后续编辑。因此从高斯到网格不是一个可选项而是把重建结果转化为生产力的必经工序。而 2DGS 的意义在于它在算法层面就为这个工序铺好了路。2. 2DGS 到底改了什么2.1 从椭球到平面几何基元的本质革命2DGS 的论文题目是《2D Gaussian Splatting for Geometrically Accurate Radiance Fields》核心一句话把 3DGS 的体积高斯椭球替换为 2D 平面高斯也就是一个带厚度为零的圆盘学术上叫 2D surfel。这个改动看起来简单但解决了根本问题。3DGS 的高斯在三维空间有体积重建的表面是这些体积高斯互相叠加后的等值面没有明确的几何定义。而 2DGS 的每个基元本身就是场景表面的一块小面片它的方向就是面片的法向。优化过程中这些面片会自然贴合到物体表面而不是悬浮在空间中。表格对比一下两者维度3DGS2DGS基元三维高斯椭球有体积二维平面高斯圆盘零厚度深度属性沿射线积分获得视点相关射线与平面求交单值深度法向不明确需后期估算由平面方向直接定义表面几何模糊、体积化明确、薄壳化网格提取点云 泊松质量难保证深度图 法向 泊松质量高2.2 2DGS 的数学框架ray-splat intersection 与闭合正态积分2DGS 的渲染核心分三步射线与平面求交、局部坐标变换、闭合形式的高斯积分。每个 2D Gaussian 由中心点p_k、两个切向量t_u和t_v定义平面朝向、缩放因子s_u、s_v以及一个旋转四元数R构成。它的世界坐标参数化是x(u, v) p_k s_u * r_u * u s_v * r_v * v其中r_u、r_v是旋转后的平面基向量u、v是局部平面坐标。训练时每条像素射线r(t) o t*d与这个无限大平面求交得到一个交点t然后在局部坐标下计算该交点到高斯中心的距离再套二维高斯函数求不透明度贡献。这样每个 splat 的深度是唯一的、视点一致的而不是像 3DGS 那样沿射线积分一片模糊。渲染公式里那个很关键的二维高斯积分G(x) exp(-0.5 * (u^2 v^2))由于是二维正态分布在像素覆盖范围内可以解析求解不需要蒙特卡洛采样渲染速度还可以保持在实时级别。这一点保证了 2DGS 能用和 3DGS 差不多的训练成本拿到带明确表面属性的辐射场。2.3 深度与法向为什么这两个输出物如此干净2DGS 在训练完成后我们可以利用训练的视点渲染两种图深度图每个像素记录射线与最近 splat 的相交深度和法向图每个 splat 面片的朝向。因为 splat 本身是平面法向是解析可得的不需要像 3DGS 那样读取深度再算梯度去估法向。这里的深度图是视点无关的吗严格说不是每个 splat 的法向和位置不随视点变化但你从不同角度看同一个 splat渲染出来的深度值仍会因为你采样到了不同部分而略有差异。不过和 3DGS 那种体积交叠产生的深度不确定性相比2DGS 的深度图已经干净太多了——边缘锐利、几何连续、没有模糊的过渡带。这就是后续能直接拿来生成点云和网格的底气。3. 从优化后的高斯场到三角网格完整提取链路3.1 第一步渲染深度图与法向图还原加密点云网上流传的 2DGS 网格提取脚本render.py跑的是这样一个流程用训练好的模型遍历训练集视点为每个视角渲染深度图depth和法向图normal把深度图反投影成相机系下的三维点再按原图颜色染色得到带颜色的稠密点云。基于我自己的实操这一步有几个细节直接决定点云质量渲染视点应选用训练集视点因为训练视角下的深度一致性最好泛化视角可能出现 splat 没有覆盖到的空洞。代码里可设置resolution参数默认可能是 1即原始分辨率。建议保持不低于原始分辨率的一半否则点云密度不足后续泊松重建会撕裂薄板结构。如果想要法向一致请在导出点云时直接使用训练得到的 splat 法向不要用点云库临时估算。2DGS 输出的法向方向需要做一次全局翻转对齐确保所有法向指向相机方向。实际项目里我用一段 Python 脚本配合原版仓库的接口实现from gs import GaussianSplattingRenderer model GaussianSplattingRenderer.load_from_checkpoint(model.ckpt) for view in train_views: depth, normal, color model.render(view) points unproject(depth, view.intrinsics, view.pose) save_ply(points, color, normal, fpc/{view.name}.ply)3.2 第二步深度二义性处理与点密度平衡这是整个流程里最容易被忽略的一环。2DGS 的深度图虽然干净但直接反投影出的点云和真实表面之间仍有系统性误差主要来自两个地方一是 splat 之间的透明度和遮挡。优化过程中前景 splat 会吸收大部分不透明度但背景 splat 在遮挡边界仍会泄漏少量贡献导致边缘像素的深度值偏向背景。这个现象在细小结构上尤其明显比如栏杆、树叶、电线。处理办法是设置一个小的深度阈值比如把 alpha 值小于 0.3 的像素直接丢弃再做连通域滤波。二是点密度不均匀。2DGS 中的 splat 是根据图像梯度分布自适应生成的细节区域的 splat 密度高平坦区域密度低。直接拿全部像素反投影结果是平坦墙面点密度反而比复杂花纹区域低。泊松重建对点密度不敏感但对空洞敏感。如果某区域根本没有点重建就会产生一个大洞。解决方式通常是在反投影时按像素棋盘格采样保留每个像素的深度但只取 1/2 或 1/4 的像素降低冗余。对平坦区域做轻微随机扰动制造微小噪声让泊松重建有足够的厚度信号。这个细节直接关系到薄板结构的成败。如果你直接把全部深度点丢进泊松重建通常得到的是一个个鼓包而不是清晰的表面。3.3 第三步法向修正与融合法向贴图渲染出来后还需要做一次全局融合。2DGS 每个 splat 的法向是局部最优解但不同 splat 之间的法向可能互相矛盾——相邻两个 splat 都贴合表面但一个朝外一个朝内。这在重建表面时会造成波浪状起伏需要用一个滤波步骤把法向场平滑化。可选的方案有用 Open3D 的estimate_normals对点云重新估计法向传入半径参数后它会做局部 PCA对噪声点有一定鲁棒性。将 splat 法向和估计法向做加权平均权重取决于该点的深度置信度和 alpha 值。我习惯的管道是先用 splat 法向做一次倒角距离较近的初步筛选然后用 Open3D 重估法向最后再把重估法向投影回 splat 法向的方向邻域确保不丢失薄片的朝向。import open3d as o3d pcd o3d.io.read_point_cloud(dense.ply) pcd.estimate_normals(search_paramo3d.geometry.KDTreeSearchParamHybrid(radius0.01, max_nn30)) pcd.orient_normals_consistent_tangent_plane(10)3.4 第四步Screened Poisson 表面重建及参数选型做完以上三步点云已经具备足够好的几何质量和完整的法向接下来用 Screened Poisson 重建生成三角网格。Poisson 重建的思路是把点云当作表面上的采样点用这些点的位置和法向信息构建一个指示函数然后提取指示函数为 0 的等值面。2DGS 的薄壳点云非常适合这套算法因为点云已经非常贴近真实表面几乎没有穿透过大的噪声点。关键参数的经验值depth参数控制八叉树深度默认 8。点云密度越高这个值可以越大。我通常在 9 和 10 之间选太大会在噪声区生成细小尖刺太小会丢掉薄板。linear_fit设为 True可以降低边界误差。重建后用remove_low_confidence接口按标量置信度删除低置信三角形。这一步骤做完输出的是封闭的三角网格。如果场景本身是单物体的扫描基本一次成型。4. 完整跑通从训练到网格输出的可复现流程4.1 环境配置与数据准备先说环境。原版 2DGS 仓库基于 PyTorch 和可微光栅化扩展我是在 CUDA 11.8、PyTorch 2.0、Python 3.10 下跑的。安装命令git clone https://github.com/hbb1/2d-gaussian-splatting cd 2d-gaussian-splatting pip install -r requirements.txt python setup.py install数据准备用 COLMAP 导出稀疏点云和相机位姿或者直接用convert.py脚本处理视频帧它会自动跑 COLMAP 并生成训练所需的格式。4.2 训练命令与关键参数训练时用官方脚本python train.py -s /path/to/dataset -m /path/to/output -r 1关键参数说明-r是图像分辨率缩放设置 1 表示全分辨率细节保留最好。迭代次数默认是 30,000 步实测 7,000 步后几何已经稳定但颜色细节还会缓慢提升。想要快速验证跑 7,000 步足够。2DGS 训练时的密度控制和 3DGS 类似每隔一定迭代做一次增删高斯。这个机制对网格提取是双刃剑后面会细说。4.3 网格提取执行步骤训练结束后执行python render.py -m /path/to/output -s /path/to/dataset --skip_train --skip_test python export.py -m /path/to/outputexport.py流程如下对每个训练视角渲染深度图与法向图。用相机位姿把深度图反投影为世界坐标点云并记录每个点的颜色和法向。将所有视角的点云合并在一个 PLY 文件里可能做一次体素降采样。调用 Open3D 的create_from_point_cloud_poisson生成网格模型。实际跑下来一个 1000 帧的小场景训练约 20 分钟导出点云只需几分钟网格生成大概 30 秒。整个过程不需要写任何额外代码官方脚本已经足够。4.4 实测网格质量什么场景表现好、什么场景翻车我用数据对比一下场景3DGS 点云泊松重建2DGS 网格平滑墙面表面起伏严重有波浪伪影平面平滑棱角清晰薄板物体叶子、纸张严重破洞几乎无法重建完整保留厚度可控复杂雕塑细节丢失凹槽被填充细节保留明显倒角距离下降远景背景噪声点超多需要大量手工清理深度一致基本不需要清理直观感受是2DGS 网格在 Chamfer Distance 上相比 3DGS 有数量级的提升F1 分数阈值 1cm 内能提升 10 到 20 个百分点。但注意这个方法在室外大场景上的表现不如室内物体扫描——远景区域 splat 稀疏深度不确定性仍存在网格会有较大空洞。5. 网格提取过程的坑与排查经验5.1 深度图边缘咬合问题内外景分离这是最容易出现的坑。2DGS 优化场景时背景和前景的 splat 之间存在大量透明度叠加导致深度图在物体边缘会粘住一部分背景深度。具体表现是网格物体边缘有一圈薄薄的飞边。解决办法在渲染深度图时打开近远裁剪把过远的背景深度直接截断。对于单个物体扫描使用 mask 约束训练让背景不参与优化。2DGS 支持输入 mask 目录训练时忽略 mask 为 0 的区域。如果已经训练完用 alpha 消融遍历每个 splat把不透明度低于阈值的剔除后再渲染深度图。我通常用后一种方案因为现实项目里经常没有干净 mask改训练工程量大。5.2 点云厚度失控物体变膨胀2DGS 的 splat 是零厚度平面但优化器在拟合半透明、高光、模糊区域时会把 splat 略微倾斜放置导致渲染出的表面在局部呈微小锯齿状。反投影后点云厚度会从理论的 0 变成几毫米。厚度过大的后果是泊松重建出来的表面比真实物体大一圈也就是膨胀。排查时先看你的点云在切平面方向的投影方差如果明显超过了场景平均深度精度说明 splat 在局部倾斜过大。处理办法是加大深度图反投影时的去噪力度或者把 splat 的缩放因子限制在一个阈值内。拥有精确法向之后这一步的容错度还行但需要针对性调整。5.3 面片法向反向内外反转2DGS 训练时每个 splat 的法向是自由优化的没有朝向约束。render.py 输出的 normal 图可能存在部分区域法向指向内部的情况。泊松重建对法向方向极其敏感如果大量法向指向内部等值面就会被夹在中间生成结果或者反转或者双重壳。排查手段用open3d.geometry.PointCloud.orient_normals_consistent_tangent_plane强制法向一致。对于包含多个连通体的场景需要分别处理每个连通体。5.4 细长结构断裂与孤立碎片细长结构是网格提取的重灾区。比如电线、栏杆、树枝。原因是 splat 在这些区域尺寸很小、数量少视点稍有变动就会出现断层。前端渲染时深度图断裂点云不连续。针对这个提高采样分辨率resolution2让细结构有更多像素支撑。降低泊松重建的depth参数让插值更平滑。在点云上做最邻近补点用 KDTree 找到断裂两端补上插值点。实际效果看补点这招对细长结构最有效而且几乎无副作用。5.5 大平面场景的波浪伪影和 3DGS 一样2DGS 在大平面墙面、地面上容易出现低频起伏。因为 splat 在平面上的覆盖冗余不足优化器用少量高斯硬扛整个平面导致渲染深度图上出现波浪。这问题在 2DGS 中比 3DGS 轻因为平面 splat 贴合表面方向能把深度误差控在很小范围但对平坦墙面这种大尺度平面仍要用网格后处理加一次拉普拉斯平滑或者限制 splat 的 gaussian scale 上限让平面被更多小 splat 细密覆盖。前者简单后者要改训练配置再重训一遍适合对平面精度要求极高的项目。6. 从 2DGS 还能延伸出哪些玩法6.1 位姿修正与动态场景重建2DGS 目前主要应用在静态场景。如果你拍的是动态变化的场景人走动、门开合静态高斯场会糊成一片。2024 年底开始有工作把 2DGS 推广到动态场景核心思路是给每个 splat 增加时间相关的形变场让平面圆盘随时间移动旋转。这样不仅能重建动态几何还能从动态高斯场中提取随时间变化的网格序列。配合四维神经场可以输出动态网格动画。6.2 3D Gaussian 与 2DGS 的混合表示有人尝试把 3DGS 的大场景重建能力和 2DGS 的几何精度结合远景用 3DGS 但压低透明度权重近景用 2DGS 面片这样大面积背景不至于出现破洞近处精细物体又能保持精确表面。这个混合框架在 NeRF 领域有先例例如混合体素在 2DGS 上做有没有收益取决于场景尺度。6.3 面向编辑的重建网格提取之后下一步就是网格编辑。因为 2DGS 输出的网格几何准确可以直接做 UV 展开或者参数化。这些是传统 mesh 管线的能力高斯场做不到。算是一个很务实的落地方向用 NeRF/Gaussian 做捕捉用 2DGS 出几何然后用传统 mesh 工具箱做资产生产。别忘了先跑一个快速验证如果你想快速验证整套流程而不想训练一整天找一个小物体、20 到 50 张图保持低迭代3000 步导出的网格质量已经足够看出这套方案的几何优势。再往后试着在这些网格上跑一次光照烘焙你会第一次直观感受到从模糊高斯场到实打实可用模型的转变。2DGS 不是万能的它在重建几何上的进步却是实打实的。从我的体验看这套方法的性价比极高尤其对做室内场景、物体数字化、XR 资产生产的团队来说值得替换掉原来的 3DGS泊松重建管线。最后说个小技巧导出网格时记得检查法向标量场文件如果发现整体翻转直接乘 -1 再做泊松重建很多表面塌陷问题其实只是方向反了。
返回列表