2D高斯泼溅实战:5分钟搞定几何表面重建(附代码对比3DGS)

📅 发布时间:2026/7/11 7:33:06 👁️ 浏览次数:
2D高斯泼溅实战:5分钟搞定几何表面重建(附代码对比3DGS)
2D高斯泼溅实战从理论到代码5分钟掌握几何表面重建新范式去年3D高斯泼溅3DGS在神经渲染领域掀起了一场革命它用一组可学习的3D高斯椭球体来表征场景实现了令人惊艳的实时渲染效果。然而不少开发者在实际应用中发现3DGS在几何表面重建上存在一个“硬伤”——多视图不一致性。简单来说同一个物体表面从不同视角看3DGS重建出的几何形状可能对不上。这就像用一堆松散的棉花糖去拼一个精确的石膏雕像虽然外观看起来差不多但内在的几何结构却不够扎实。今年三月一篇名为《2D Gaussian Splatting for Geometrically Accurate Radiance Fields》的论文带来了一个更“扁平”但更“精准”的解决方案2D高斯泼溅2DGS。它不再使用3D椭球而是用一组定向的2D高斯圆盘来建模场景表面。这个看似简单的维度缩减却从根本上解决了视图一致性问题让几何重建的精度上了一个新台阶。如果你正在寻找一个既能快速上手又能产出高质量几何表面的新工具那么2DGS很可能就是你需要的。这篇文章将带你绕过繁琐的数学推导直击核心原理并通过清晰的代码对比让你在5分钟内理解2DGS的独特优势与实现要点。1. 核心思想从“棉花糖”到“薄煎饼”的范式转变要理解2DGS我们得先看看3DGS的“阿喀琉斯之踵”。3DGS将一个3D高斯椭球投影到2D图像平面时其投影过程依赖于一个仿射近似。这个近似在椭球中心附近是准确的但远离中心时误差会增大。更重要的是对于同一条光线对应图像中的一个像素它与3D高斯的相交平面会随着相机视角的改变而改变。这就导致了从不同角度看同一个表面点可能被映射到不同的3D高斯上从而产生几何上的不一致。2DGS的巧妙之处在于它直接放弃了3D体积表示转而采用2D表面元素Surfels。每个基元不再是一个椭球而是一个带有法线方向的椭圆形圆盘。这个圆盘本身就定义了一个局部切平面。当相机光线与这个2D圆盘相交时交点在3D空间中是唯一且确定的与视角无关。这就好比我们用一堆薄薄的、有方向的煎饼去贴合物体表面无论从哪个角度看煎饼的位置和朝向都是固定的从而保证了几何的一致性。提示你可以把3DGS想象成用一团团蓬松的云朵3D高斯去填充一个物体的体积而2DGS则是用一片片有厚度的贴纸2D高斯圆盘去覆盖物体的表面。后者更贴合“表面重建”的本质。这种转变带来了几个直接的好处视图一致性几何从根本上解决了3DGS的多视图不一致问题。显式法线2D圆盘自带法线方向为表面重建和光照计算提供了天然支持。更薄的表示更贴合物体表面的薄特性避免了3D体积表示的内在冲突。下面的表格直观对比了3DGS与2DGS在核心建模思想上的差异特性3D高斯泼溅 (3DGS)2D高斯泼溅 (2DGS)基本基元3D高斯椭球体2D定向高斯圆盘 (Surfel)几何表示体积辐射场表面辐射场视图一致性弱依赖仿射近似强基于精确射线-圆盘求交表面法线隐式需从密度场估算显式是基元的固有属性重建侧重点新视角合成 (NVS) 质量与速度几何精度与表面质量2. 模型构建2D高斯圆盘的数学描述理解了核心思想我们来看看2DGS如何用数学语言描述一个“高斯煎饼”。每个2D高斯基元由以下参数定义中心点pk: 圆盘在3D空间中的位置。切向量tu,tv: 定义圆盘所在平面的两个正交方向。它们的叉积tw tu × tv自然给出了圆盘的法线方向n。缩放su,sv: 控制圆盘在tu和tv方向上的大小即椭圆的半轴长。不透明度α和颜色c: 与3DGS类似控制外观。这些参数可以组合成一个齐次变换矩阵H它将局部2D坐标(u, v)映射到世界坐标系中的3D点P(u, v)import torch def construct_homogeneous_matrix(pk, tu, tv, su, sv): 构建从2D局部坐标系到3D世界坐标系的齐次变换矩阵 H。 Args: pk: 中心点形状 (3,) tu, tv: 切向量形状 (3,) su, sv: 缩放因子标量 Returns: H: 齐次变换矩阵形状 (4, 4) # 计算法线此处仅用于理解实际参数化中tw由tu和tv叉积得到 tw torch.cross(tu, tv) # 构建旋转矩阵 R [tu, tv, tw] R torch.stack([tu, tv, tw], dim1) # 形状 (3, 3) # 构建缩放矩阵 S注意最后一个分量为0将3D空间“压扁”到2D平面 S torch.diag(torch.tensor([su, sv, 0.0])) RS R S # 构建齐次变换矩阵 H H torch.eye(4) H[:3, :3] RS H[:3, 3] pk return H # 示例定义一个位于(1,1,1)在XY平面上的圆盘 pk torch.tensor([1.0, 1.0, 1.0]) tu torch.tensor([1.0, 0.0, 0.0]) # X轴方向 tv torch.tensor([0.0, 1.0, 0.0]) # Y轴方向 su, sv 2.0, 3.0 H construct_homogeneous_matrix(pk, tu, tv, su, sv) print(变换矩阵 H:\n, H) # 将局部坐标 (0.5, 0.5) 变换到世界坐标 u_local torch.tensor([0.5, 0.5, 0.0, 1.0]) # 注意z分量为0w分量为1 P_world H u_local print(局部点 (0.5, 0.5) 对应的世界坐标:, P_world[:3])这段代码展示了2DGS基元的参数化方式。关键点在于缩放矩阵S的最后一个对角线元素为0这确保了所有变换后的点都位于由tu和tv张成的2D平面上。在局部(u, v)坐标系中2D高斯函数的值由标准高斯公式给出G(u) exp(-(u² v²)/2)。3. 泼溅与渲染精确的射线-圆盘求交渲染是2DGS与3DGS差异最大的环节。3DGS采用仿射投影近似而2DGS的核心是精确的射线-圆盘求交Ray-Splat Intersection。对于图像上的一个像素x (x, y)我们发出对应的相机射线。2DGS需要找到这条射线与每个2D高斯圆盘的交点。论文采用了一个非常巧妙的解法用三个平面的交点来唯一确定这个交点。定义射线为两个平面的交线像素射线可以表示为x平面hx (-1, 0, 0, x)和y平面hy (0, -1, 0, y)的交线。这两个平面在齐次坐标下表示。将平面变换到圆盘局部坐标系利用之前定义的变换矩阵H和相机视图变换矩阵W将x平面和y平面变换到2D圆盘的局部(u, v)坐标系中。这里有一个数学技巧变换一个平面等价于用变换矩阵的逆转置来变换其平面参数。因此我们无需计算逆矩阵(WH)^{-1}而是使用转置(WH)^T提高了数值稳定性。# 假设 W 是相机视图变换矩阵 W ... # 形状 (4, 4) M W H # 世界到屏幕的变换 # 变换平面使用转置而非求逆 hx torch.tensor([-1.0, 0.0, 0.0, x]) hy torch.tensor([0.0, -1.0, 0.0, y]) hu M.T hx hv M.T hy求解交点交点必须同时位于变换后的x平面、y平面以及2D圆盘平面在局部坐标系中即为w1的平面对应点(u, v, 1, 1)。这导出了一个线性方程组可以直接解析求解出交点的局部坐标(u, v)# 根据论文公式 (7) 求解交点 (u, v) # hu^i 表示四维向量 hu 的第 i 个分量 denominator hu[1]*hv[2] - hu[2]*hv[1] u (hu[2]*hv[4] - hu[4]*hv[2]) / denominator v (hu[4]*hv[1] - hu[1]*hv[4]) / denominator得到(u, v)后即可代入2D高斯函数G(u)计算该像素处此圆盘的贡献值并通过公式P H (u, v, 1, 1)^T得到交点的3D世界坐标和深度z。处理退化情况当从非常倾斜的角度观察时2D圆盘在屏幕空间中可能退化成一条线导致上述求交公式失效。2DGS引入了一个低通滤波器作为保护措施最终用于渲染的高斯值Ĝ(x)取精确求交计算的值G(u(x))与一个以投影中心c为中心、固定半径σ的屏幕空间高斯滤波器的最大值。这确保了即使圆盘几乎侧对相机也不会在渲染中完全消失。光栅化与Alpha混合与3DGS类似2DGS也采用基于瓦片Tile-based的、从前到后的Alpha混合进行光栅化。每个像素的最终颜色是所有与该像素射线相交的2D圆盘按深度排序后混合的结果c(x) Σ ci * αi * Ĝi(u(x)) * Π (1 - αj * Ĝj(u(x)))。4. 训练与正则化让表面更干净、更光滑仅靠渲染颜色与真实图像之间的光度损失如L1 SSIM来优化2DGS虽然能获得不错的视觉效果但重建出的几何表面往往带有噪声不够平整。为此论文引入了两个关键的正则化项深度失真损失Depth Distortion Loss问题在体渲染中一条光线上可能有多个半透明的2D圆盘重叠。如果这些圆盘的深度值相差很大但权重不透明度相近会导致渲染的深度模糊不清几何表面“发虚”。解决方案惩罚同一条光线上所有相交圆盘对之间的深度差异权重由它们的不透明度乘积决定。公式为L_d Σ_i Σ_j ω_i ω_j |z_i - z_j|。这促使优化器将高权重的圆盘即对最终颜色贡献大的圆盘在深度上聚集到一起从而形成一个更清晰、更薄的表面。法线一致性损失Normal Consistency Loss问题2D圆盘有自己的法线由tu × tv给出但如果这些法线与实际的物体表面法线不一致重建出的表面在局部看来会是崎岖不平的。解决方案将渲染出的法线图由2D圆盘法线加权混合得到与从渲染深度图通过梯度计算得到的几何法线进行对齐。公式为L_n Σ_i ω_i (1 - n_i · N)其中N是深度梯度估计的法线。这确保了2D圆盘的法线方向与它们所代表的几何表面的真实朝向一致从而产生更光滑、更合理的几何。最终的损失函数是这三者的加权和L L_color λ_d * L_depth λ_n * L_normal论文通过实验给出了推荐的权重对于有界场景如物体λ_d 1000对于无界场景如360度场景λ_d 100λ_n通常设为0.05。5. 代码实战与3DGS的核心差异一览理解了原理我们通过对比官方3DGS代码与2DGS代码的关键片段来直观感受两者的实现差异。我们将聚焦于基元定义、光栅化入口和损失计算这三个核心环节。差异一基元数据结构3DGS存储的是3D椭球的中心、旋转四元数、3D缩放和不透明度。# 3DGS 基元参数示例 (简化) class Gaussian3D: def __init__(self): self.position torch.zeros(3) # 3D中心 self.rotation torch.zeros(4) # 旋转四元数 self.scale torch.zeros(3) # 3D缩放 self.opacity torch.tensor(1.0) self.sh_coeffs ... # 球谐系数表示颜色2DGS存储的是2D圆盘的中心、两个切向量、2D缩放和不透明度。法线是隐式计算的。# 2DGS 基元参数示例 (简化) class Gaussian2D: def __init__(self): self.position torch.zeros(3) # 3D中心 self.tangent_u torch.zeros(3) # 切向量 u self.tangent_v torch.zeros(3) # 切向量 v self.scale torch.zeros(2) # 2D缩放 (su, sv) self.opacity torch.tensor(1.0) self.sh_coeffs ... # 球谐系数表示颜色 # 法线由切向量叉积得到 property def normal(self): return torch.cross(self.tangent_u, self.tangent_v)差异二光栅化调用两者都使用CUDA加速的光栅化内核但内核函数和输入参数有本质不同。# 3DGS 光栅化调用 (概览) # 输入3D高斯参数位置、旋转、缩放等相机参数 # 过程内部进行仿射投影近似计算2D协方差矩阵然后进行alpha混合 rendered_image, depth, radii rasterize_gaussians_3d( means_3d, rotations, scales, opacities, sh_coeffs, camera_matrix, ... ) # 2DGS 光栅化调用 (概览) # 输入2D高斯参数位置、切向量、缩放等相机参数 # 过程基于射线-圆盘求交计算精确的交点和高斯值然后进行alpha混合 rendered_image, depth, normal, weights rasterize_surfels_2d( means_3d, tangent_us, tangent_vs, scales_2d, opacities, sh_coeffs, camera_matrix, ... )注意2DGS的光栅化内核除了返回颜色和深度还会返回法线图normal和每个交点的混合权重weights这些正是计算正则化损失所必需的。差异三损失函数计算3DGS的损失通常只有光度损失。# 3DGS 损失计算 (简化) loss_color (1.0 - ssim_weight) * l1_loss ssim_weight * (1.0 - ssim(image, gt_image)) total_loss loss_color2DGS则需要额外计算两个正则化损失。# 2DGS 损失计算 (简化参考论文和代码实现思路) loss_color (1.0 - ssim_weight) * l1_loss ssim_weight * (1.0 - ssim(image, gt_image)) # 深度失真损失基于光栅化返回的权重和深度 # 假设 weights 形状为 (N_rays, K) depth 形状为 (N_rays, K) loss_depth compute_depth_distortion_loss(weights, depth) # 实现公式(10) # 法线一致性损失基于光栅化返回的权重、法线和深度图梯度 # normal_render 是渲染的法线图 normal_geom 是从深度图梯度计算的法线 loss_normal compute_normal_consistency_loss(weights, normal_render, normal_geom) # 实现公式(12) # 总损失 lambda_d 1000.0 # 有界场景 lambda_n 0.05 total_loss loss_color lambda_d * loss_depth lambda_n * loss_normal快速上手2DGS官方代码库hbb1/2d-gaussian-splatting提供了清晰的训练和测试流程。其数据准备格式与3DGS完全兼容使用COLMAP或NeRF Synthetic数据集这大大降低了迁移成本。# 1. 环境配置与3DGS类似 git clone --recursive https://github.com/hbb1/2d-gaussian-splatting.git conda env create -f environment.yml conda activate surfel_splatting # 2. 训练一个场景 python train.py -s path_to_your_dataset # 3. 提取网格2DGS的强项 # 有界场景网格提取 python render.py -m path_to_trained_model -s path_to_dataset --skip_test --skip_train # 无界场景网格提取新功能 python render.py -m path_to_trained_model -s path_to_dataset --unbounded --skip_test --skip_train --mesh_res 1024与3DGS相比2DGS训练命令的主要区别在于可以通过--lambda_normal和--lambda_distortion来调节两个正则化项的权重。对于无界大场景建议使用--depth_ratio 0均值深度来减少“圆盘锯齿”伪影。6. 总结与展望2DGS的定位与选择经过以上剖析我们可以清晰地看到2DGS的定位它是一个专为高精度几何表面重建而设计的高斯泼溅变体。它在以下场景中具有明显优势需要高质量网格输出的项目2DGS重建的表面更干净、更薄法线信息准确直接TSDF融合就能得到出色的网格无需像3DGS那样依赖额外的后处理如SDF蒸馏。对几何一致性要求高的应用如AR/VR中的 occlusion handling遮挡处理、物理仿真等视图一致的几何至关重要。表面细节丰富的物体重建2DGS对薄结构如树叶、铁丝网和光滑表面的重建效果更好。当然3DGS并非被取代。它在新视角合成的渲染速度、视觉质量和训练效率上依然非常出色特别是对于外观复杂但几何要求不极致的场景。社区中也有许多工作如SuGaR、GaussianPro致力于为3DGS添加表面约束使其几何质量向2DGS靠拢。那么如何选择如果你的首要目标是实时渲染惊艳的视觉效果且对网格质量要求不高3DGS仍是首选。如果你的项目严重依赖精确的几何表面如逆向工程、数字孪生或者你需要一个“开箱即用”的高质量网格生成方案那么2DGS是更合适的技术路径。不妨将2DGS视为你工具箱里的一把更锋利的解剖刀当任务需要精确的几何解剖时它比3DGS这把“瑞士军刀”更专业。从代码实现的角度看2DGS的核心创新在于用精确的射线-平面求交替代了近似的仿射投影并用表面法线一致性和深度聚集这两个正则化项来“雕琢”几何。这种设计使得其代码在光栅化内核和损失计算部分与3DGS有显著不同但整体的工程框架和数据流是相似的。对于已经熟悉3DGS的开发者转向2DGS的学习曲线并不陡峭。我在几个自采数据集上测试2DGS时发现它的训练初期收敛速度似乎比3DGS稍慢这可能是因为表面约束更强优化空间更受限。但一旦收敛其几何质量的提升是肉眼可见的尤其是在物体边缘和光滑曲面区域。另一个实际注意点是由于引入了额外的正则化损失训练时需要更仔细地调整学习率和损失权重特别是lambda_distortion对于场景尺度差异较大的情况可能需要微调。