【三维重建】SpotlessSplats:去除瞬态干扰物的三维高斯喷溅(3DGS)
三维高斯喷溅(3DGS)是一种最新的三维重建技术,提供了高效的训练和渲染速度,使其适用于实时应用。然而,目前的方法需要高度控制的环境——没有移动的人或风吹的元素,以及一致的照明——以满足3DGS的视图间一致性假设。这使得重建现实世界的捕获成为问题。我们提出了无斑点斑图,一种利用预训练和通用特征结合鲁棒优化来有效地忽略瞬态干扰物的方法。我们的方法实现了最先进的重建质量。利用神经辐射场(NeRF)和最
代码:https://spotlesssplats.github.io
论文:https://arxiv.org/pdf/2406.20055
来源:DeepMind,多伦多大学,斯坦福大学,西蒙弗雷泽大学
提示:关注B站【方矩实验室】,查看视频讲解
摘要
三维高斯喷溅(3DGS)是一种最新的三维重建技术,提供了高效的训练和渲染速度,使其适用于实时应用。然而,目前的方法需要高度控制的环境——没有移动的人或风吹的元素,以及一致的照明——以满足3DGS的视图间一致性假设。这使得重建现实世界的捕获成为问题。我们提出了无斑点斑图,一种利用预训练和通用特征结合鲁棒优化来有效地忽略瞬态干扰物的方法。我们的方法实现了最先进的重建质量。
一、前言
利用神经辐射场(NeRF)和最近的三维高斯喷溅(3DGS)从二维图像中重建三维场景一直是视觉研究的热点。目前的大多数方法都假设图像同时捕获,完美pose,无噪声。这简化了3D重建任务,但在现实世界中很少存在,移动物体(如人或宠物)、照明变化和其他虚假的光度不一致性会降低性能,限制了应用。
在NeRF训练中,通过基于颜色残差的大小来降低加权或丢弃不一致的观察值,已经纳入了对异常值的鲁棒性。对于3DGS,自适应稠密化引入了颜色残差的variance,当直接应用来自健壮NeRF框架的现有思想时,影响这些瞬态的检测。
SpotlessSplats(SLS),一个用于3DGS的三维场景重建框架,通过无监督检测训练图像中的异常值。我们没有在RGB空间中检测异常值,而是 利用了 text-to-image 模型中更丰富、学习到的特征空间。该特征嵌入的有意义的语义结构,更容易检测结构化干扰物的spatial support ,例如,与单个对象相关联。我们没有使用手动指定的鲁棒kernels 来进行异常值识别[40],而是利用特征空间中的自适应方法来检测异常值 。为此目的,我们在这个框架内考虑了两种方法 。第一种方法是使用局部特征嵌入的非参数化聚类,来寻找结构化异常值的图像区域。第二种方法使用MLP,以无监督的方式训练来预测特征空间中可能与干扰物相关的部分。进一步引入了一种(互补的和通用的)稀疏化策略,与鲁棒优化兼容,提供类似的重建质量,减少2到4倍的飞溅量 ,即使在无干扰的数据集上,显著节省计算和内存。通过对随机捕获的场景[37,40]的具有挑战性的基准的实验,SLS在重建精度方面始终优于竞争方法。
- 一种自适应的、鲁棒的损失,利用 text-to-image的扩散特征,可靠地识别正常的捕获中的瞬态干扰物,消除了对光度误差的过拟合问题。
- 一种新的稀疏化方法与我们的鲁棒损失兼容,显著减少了高斯数量,在不损失保真度的情况下节省了计算和内存
二、相关工作
神经辐射场(NeRF),由于其高质量的重建和新颖的三维场景视图合成而得到了广泛的关注。NeRF将场景表示为与视图相关的发射体。体渲染方程]的吸收-发射部分渲染。随后又进行了多次增强。快速训练和推理[8,28,46,54]、有限或单视图(s) [15,35,55]训练和同时姿态推理[20,22,50]使辐射场更接近实际应用。最近,3D高斯溅射(3DGS)[17]被提出作为一种基于原始的替代nerf,具有显著更快的渲染速度,同时保持高质量。三维高斯分布可以有效地栅格化使用阿尔法混合[59]。这种简化的表示利用了现代GPU硬件来促进实时渲染。3DGS的效率和简单性促使了该领域内的焦点转移,许多NeRF增强被快速移植到3DGS
2.1 NeRF的鲁棒性
最初的NeRF论文对捕获设置做出了强有力的假设:场景需要是完全静态的,并且在整个捕获过程中照明应该保持不变。最近 ,NeRF已经扩展到对违反这些约束的非结构化“in the wild”捕获图像进行训练。两项有影响力的工作,NeRF-W [25]和RobustNeRF [40]解决了瞬态干扰物的问题,都使用光度误差作为指导。NeRF-W [25]建模了一个三维不确定性场渲染到二维异常掩模,降低了高误差像素的损失,以及一个防止退化解的正则化器。NeRF-W [25]还通过学习到的嵌入来建模全局外观,这对于在广泛变化的光照和大气条件下捕获的图像很有用。城市辐射场(URF)[36]和Block-NeRF [47]同样将学习到的外观嵌入应用于大规模重建。HA-NeRF [7]和Cross-Ray[53]模型的2D离群值掩模,而不是3D场,利用CNN或Transformer进行交叉射线相关。
RobustNeRF [40]从鲁棒估计的角度来处理这个问题,使用二值权值由阈值渲染误差确定,并使用模糊核来反映属于干扰物的像素是空间相关的假设。然而,RobustNeRF和NeRF-W变体[7,53]都完全依赖于RGB残差,因此,它们经常用与背景相似的颜色错误地分类瞬态数据;参见图2中的稳健性mask。为了避免这种情况,以前的方法需要仔细调整超参数,即RobustNeRF中的模糊核大小和阈值,以及NeRF-W中的正则化器权重。相反,我们的方法使用文本到图像模型的丰富表示来进行语义离群值建模。这避免了直接的RGB错误监督,因为它依赖于特征空间的相似性来进行聚类。
最近的NeRF On-the-go[37]发布了一个随意捕获的视频数据集。与我们的方法类似,它使用DINOv2 [31]的语义语义特征通过一个小MLP预测离群mask。然而,它也依赖于 structural rendering error的直接监督,导致超出或分割不足;参见图3。NeRF-HuGS [6]结合了来自COLMAP的鲁棒稀疏点云[43]的启发式方法,以及现成的语义分割来去除干扰物。这两种启发式方法在[37]数据集的严重瞬态occlusions下都失败了。
2.2 Precomputed features
使用强大的预计算的视觉特征,如DINO [4,31],已经证明了推广到多个视觉任务的能力。DDPM扩散模型[13,39,45]因其从文本提示[30,34,38,41]中获得的逼真图像生成能力而引起了人们的关注。这些模型的内部特征已被证明是同样强大的,并能够推广到许多领域,如分割和关键点配准。
2.3 Robustness in 3DGS
多个工作解决了对野外捕获数据的3DGS训练。SWAG [10]和GS-W [57]模型外观变化使用学习的全局和局部每个原型外观嵌入。类似地,WE-GS [49]使用一个图像编码器来学习对每个图像的颜色参数的适应。Wild-GS [52]学习了一个用于外观嵌入的空间三平面场。所有这些方法[49,52,57]都采用了像NeRFW [25]这样的干扰物的mask预测方法,通过预测二维干扰物mask来降低高误差渲染像素的权重。SWAG [10]学习每个高斯分布的每幅图像的不透明度,并将具有高不透明度方差的原型表示为瞬态。值得注意的是SWAG [10]和GS-W[57],当将额外的学习瞬态掩模应用于Phototourism scenes场景[44]时,它们比本地/全局外观建模没有或几乎没有改进。SLS专注于具有较长时间瞬变和最小外观变化的临时捕获,这在视频捕获中很常见,如数据集[37]。
三、Background
三维高斯溅射(3DGS)将三维场景表示为三维各向异性高斯 G = G= G={ g i g_i gi}的集合。每个splat g i g_i gi,由一个均值 µ i µ_i µi,一个正半定协方差矩阵 Σ i Σ_i Σi,一个不透明度 α i α_i αi,并由球谐系数 c i c_i ci参数化。
3D场景表示通过栅格化呈现到屏幕空间。我们表示W为透视变换矩阵,三维协方差的投影二维屏幕空间可以近似为: Σ ~ = J W Σ W T J T \tilde{Σ}=JWΣW^TJ^T Σ~=JWΣWTJT,J是投影矩阵的雅可比矩阵,它提供了一个用线性近似非线性投影的过程。
为了确保Σ在整个优化过程中表示协方差(即正半定),协方差矩阵参数化为 Σ = R S S T R T Σ=RSS^TR^T Σ=RSSTRT,其中尺度S=diag (s)与s∈R3,旋转R从一个单位四元数q计算。一旦计算了屏幕空间中的分割位置和协方差,图像形成过程执行体积渲染作为alpapha混合,这反过来需要沿视图方向排序。请注意,与NeRF每次渲染一个像素不同,3DSG以一次向前传递的方式渲染整个图像。
3.1 3DGS的鲁棒优化
与之前的作品[18,26,47]不同,我们没有对瞬态对象类、外观和/或形状进行假设。
我们通过借鉴RobustNeRF来解决这个问题,通过识别输入图像中应该被mask的部分来消除干扰物。该问题简化为预测(无监督)每个训练图像的内部/外部点的maks { M n M_n Mn} n = 1 N ^N_{n=1} n=1N,并通过mask L1损失来优化模型:
其中 I ^ n ( t ) \hat{I}^{(t)}_n I^n(t) 是在训练迭代(t)时的渲染结果。RobustNeRF 通过观察训练过程中的光度不一致来检测瞬态效应;即,具有大的损失值的图像区域。通过用 R n ( t ) = I n − I ^ n ( t ) R^{(t)}_n =I_n - \hat{I}^{(t)}_n Rn(t)=In−I^n(t) 表示残差的图像(轻微滥用符号,因为1范数是沿颜色通道像素执行的),mask计算为:
其中 1 1 1是一个指示函数(为真则返回1,否则为0),ρ是一个广义中位数,τ是一个超参数,控制cut-off percentile;B是一个(标准化)3×3box 滤波器,通过卷积(~)执行 a morphological dilation。直观地说,上面(2)总结的RobustNeRF [40]通过假设内部值/异常值是空间相关的,扩展了trimmed robust estimator[9]。我们发现,直接将[40]的想法应用到3DGS中,即使不受如图2中所示的误导性颜色残余情况的限制,也不能有效地去除异常值。相反,为了适应3DGS的表现和训练过程中的差异,需要进行一些调整(4.2节);
四、method
以上方法中的干扰物mask 是基于新视图的光度误差而建立的。相反,我们建议根据干扰物的语义来识别它们,在训练过程中,识别其的再次出现。我们把语义看作是从一个自监督的二维基础模型(如[48])计算出来的特征映射。从训练图像中去除干扰物的过程,转化为识别可能导致大光度误差的特征子空间的过程。举个例子,一只狗在静态场景中散步。要么在每张图像(4.1.1节),要么或更广泛地说,在数据集中(4.1.2节)中识别“狗”的像素(可能是重建中出现问题的原因),并自动从优化中删除它们。SpotlessSplats旨在减少对局部颜色残差的离群值检测和对颜色误差的过拟合,而强调依赖像素之间的语义特征相似性,也称为“clustering”。
4.1 识别干扰物(distractors)
给定输入图像{ I n I_n In} n = 1 N ^N_{n=1} n=1N,使用 Stable Diffusion提取特征图{ F n F_n Fn} n = 1 N ^N_{n=1} n=1N。这个预处理步骤在训练开始之前执行一次,使用其计算 inlier/outlier masks M ( t ) M^{(t)} M(t);我们删除图像索引n以简化符号,因为训练过程涉及每批一个图像。现在详细介绍检测outliers 的两种不同的方法:
4.1.1 空间聚类(Spatial clustering)
预处理阶段,额外对图像区域进行无监督聚类。与超像素技术[14,21]类似,我们将图像过度分割成C个空间连接组件的固定基数集合;参见“聚类特征”图2。具体说,在特征图 F F F上执行层次聚类[2011年],其中每个像素都连接到它周围的8个像素。将像素p分配给聚类c表示为 C [ c , p ] C[c,p] C[c,p]∈{ 0 , 1 0,1 0,1},并将聚类初始化为每个像素其自身聚类。融合簇间特征方差最少的集群(collapsing those that cause the least amount of inter-cluster feature variance differential before/post collapse)。当C=100集群仍然存在时,集群将终止( Clustering terminates when C=100 clusters remain)。
然后,从公式(2)的mask内部像素的百分比,计算出簇c是一个内部像素的概率:
然后将簇标签传播回像素:
使用 M a g g ( t ) M^{(t)}_{agg} Magg(t),而不是 M ( t ) M^{(t)} M(t),作为 inlier/outlier的mask来训练(1)中的3DGS模型。这个模型配置指定为“SLS-agg”
4.1.2 时空聚类(Spatio-temporal clustering)
第二种方法是训练一个分类器,根据像素的相关特征来决定像素是否应该被(1)优化。为此,我们使用一个带有参数θ的MLP,从像素特征中预测每个像素的inlier概率:
分类器参数
θ
(
t
)
θ^{(t)}
θ(t)与3DGS优化同时更新。H用1×1卷积实现,MLP和3DGS交替优化。MLP分类器损失为:
λ=0.5,U和L是由当前残差的mask计算出的自监督标签:
换句话说,我们只在像素上直接监督分类器,这样我们就可以根据重构残差来确定 inlier
status,否则我们就严重依赖于特征空间中的语义相似性;见图4。为了进一步正则化H,将相似的特征映射到相似的概率,我们通过
L
r
e
g
L_{reg}
Lreg最小化它的 Lipschitz constant[文献23]。
然后使用 M m l p ( t ) M^{(t)}_{mlp} Mmlp(t),而不是 M ( t ) M^{(t)} M(t),作为 inlier/outlier的mask来训练(1)中的3个DGS。我们将此模型配置指定为“SLS-mlp”
4.2 3DGS的鲁棒性优化
直接将任何鲁棒的mask 技术应用于3DGS,会导致mask 过拟合到一个过早的3DGS模型(见4.2.1节),比如基于图像的训练(4.2.2节),或3DGS的密集化策略(见4.2.3节)使得inlier estimator产生偏差。下面我们提出了解决方案。
4.2.1 计划采样来进行预热(Warm up with scheduled sampling)
逐步应用mask很重要,因为初始残差是随机的。如果我们使用学习到的聚类来mask,这是双重正确的,因为MLP在优化的早期不会收敛,并随机预测mask。此外,直接使用 outlier mask 往往导致 quickly overcommit to outliers,防止有价值的错误的反向传播,并从这些区域学习。我们通过将每个像素的mask策略,制定为基于mask的伯努利分布的采样来缓解这种情况:
其中,α是一个阶梯指数调度器( staircase exponential scheduler),从1到0,提供了一个热身。这使得我们仍然可以在我们不确定的区域中稀疏地采样梯度,从而可以更好地分类离群值。
4.2.2 基于图像的训练中的 Trimmed estimators(裁剪估计器)
[40]实现了一个修剪后的估计器,其基本的假设是每个minibatch(平均)包含相同比例的异常值。这个假设在3DGS训练运行中被打破了,其中每个minibatch都是一个完整的图像,而不是从训练图像集中随机抽取的一个像素集。这给实现(2)的广义中值带来了挑战,因为异常值的分布在图像之间是有偏颇的。
我们通过跟踪多个训练批次上的残差量级来解决:将残差的magnitudes离散为B个直方图buckets,宽度等于渲染误差的下界( 1 0 − 3 10^{−3} 10−3)。我们通过对bucket population的有折扣的更新,来升级每次迭代中每个bucket的似然,类似于快速中值滤波方法[32]。这保持了残差分布的移动估计,内存消耗不变,从中我们可以提取出广义中值 ρ ρ ρ作为直方图总体中的 τ τ τ 分位数
4.2.3 对“重置不透明度”的替代
原始GS每M次迭代,会重置所有高斯分布的不透明度。opacity reset处理两个问题:首先,在具有挑战性的数据集中,在相机附近的优化容易积累高斯分布,常被称为floater。这很难处理,因为它迫使相机光线尽早饱和于透光率,因此梯度没有机会流通到场景的遮挡部分。opacity reset降低了所有高斯分布的不透明度,这样梯度就可以沿着整个射线再次流动。第二,opacity reset控制高斯数量。将不透明度重置为一个低值,允许(永远无法恢复到更高不透明度的GS)通过自适应密度控制机制进行修剪。
然而,opacity reset干扰了残差分布跟踪,导致残影在opacity reset 后的迭代中变大。简单的禁用并不能work,因为是优化必须的。根据文献[11], 我们采用基于利用率的修剪(UBP:utilization-based pruning)。我们跟踪渲染的颜色相对于每个高斯 g g g的投影位置 x g x_g xg的梯度 。与3D位置相比,计算关于投影位置的导数,允许一个更少的内存密集型的GPU实现,同时提供了一个与[Bayes’ Rays: Uncertainty quantification in neural radiance fields. CVPR, 2024]中类似的度量。其中,利用率 utilization定义为:
我们在图像全局 (W×H)来平均该指标,在前一组 ∣ N T ( t ) ∣ = 100 |N_T(t)|=100 ∣NT(t)∣=100张图像中,每100步计算一次。当 u g < κ u_g<κ ug<κ, with κ = 1 0 − 8 κ = 10^{−8} κ=10−8时,裁剪高斯。基于利用率的剪枝替换opacity reset,实现了两个原始目标,同时减轻了对残差分布跟踪的干扰。基于利用率的剪枝通过使用更少的高斯原型显著地压缩了场景表示,同时即使在无离群值的场景中也能实现高重建质量;参见第5.2节。它还能有效地处理floater(见图10)。floater的利用率很低,因为他们参与渲染很少的视图。此外,使用(11)中所示的masked derivatives, 可以去除在 warm-up阶段的 any splat that has leaked through the robust mask。
4.2.4 Appearance modeling (外观建模)
.
原始GS假设场景的图像(包括干扰物)在光度上是完全一致的,无法应用于自动曝光和白平衡。SpotlessSplats结合文献[36]的方案,适用于文献[17]的球谐表示的视图依赖的颜色。详细的,共同优化了每个输入摄像机视图的latent
z
n
∈
R
64
z_n∈R^{64}
zn∈R64,并通过MLP将其映射到作用于谐波系数
c
c
c 的线性变换:
其中 ⚪ 是 Hadamard 乘积(矩阵的逐元素相乘),b模型改变了图像的亮度,a提供了表达能力来补偿白平衡。在优化过程中,可训练的参数还包括 θ Q θ_Q θQ和{ z n z_n zn}。这种简化的模型可以有效地防止 z n z_n zn在按图像调整时过度解释干扰物,就像在一个更简单的GLO [NeRF in the Wild]中发生的那样;参见Rematas等人的[ Urban Radiance Fields ]进行分析。
五、实验结果
数据集。我们在随机捕获的 RobustNeRF [40] 和 NeRF on-the-go[37]数据集上评估。RobustNeRF数据集包括四个充满干扰和无干扰训练分割的场景。 ‘Crab’ 和 ‘Yoda’场景具有不同的干扰物,不是在一个休闲视频中捕获的。NeRF on-the-go数据集有6个场景,有三个水平的瞬态干扰物遮挡(低、中、高)和一个单独的干净测试集用于定量比较。
基线。三维高斯喷射方法尚未广泛解决无干扰物重建的问题。现有方法主要关注全局外观变化,如亮度变化[10,19,49],而不是关注为此任务策划的随机捕获数据集。此外,这些方法还没有公开可用的源代码。因此,我们与普通的3DGS方法和稳健的NeRF方法进行了比较。我们比较了最先进的NeRF方法,NeRFon-go[37],NeRF-hugs[6]和RobustNeRF[40],MipNeRF-360
指标。PSNR、SSIM和LPIPS的重建指标(LPIPS指标使用标准化的VGG特征)。NeRF-HuGS [6]报告来自AlexNet特性的LPIPS指标;为了公平比较,我们计算并报告其发布的VGG LPIPS指标。
实施细节。模型都经过了30k次迭代的训练。我们关闭不透明度重置,在第8000步,只重置非扩散球谐系数到0.001。这确保了在MLP训练的早期阶段泄露的任何干扰物都不会被建模为视图依赖效应。我们每500步到15000步,每100步运行UBP。对于MLP训练,我们使用具有0.001学习率的Adam优化器。我们从SD v2.1的第2个上采样层计算图像特征,去噪时间步长为261,和一个空提示符中计算图像特征。Tang等人[48]发现这种配置对分割和关键点匹配任务最有效。我们将degree 20的位置编码拼接作为MLP的输入。
5.1 无干扰物的三维重建
RobustNeRF [40] 和 NeRF on-the-go评估我们的方法。在图5定量地显示了SLS-mlp在RobustNeRF数据集上优于所有稳健的基于nerf的基线。对原始3DGS的改进,性能更接近理想的干净模型,特别是在“Yoda”和“Android”上。定性结果表明,原始3DGS试图将干扰物建模为noisy的floater((‘Yoda’, ‘Statue’))或视角依赖效应(“Android”)或两者的混合物(“Crab”)。NeRF-HuGS [6]使用基于分割的掩模显示s signs of over masking(去除四个场景中的静态部分),或under-mask in challenging sparsely sampled views letting in transient objects(“Crab”)。
图3和图6中,对NeRF on-the-go数据集进行了类似的分析。对于低遮挡场景,来自COLMAP [42]点云的原始3DGS的鲁棒初始化,特别是RANSAC对异常值的拒绝,足以产生良好的重建质量。然而,随着干扰物密度的增加,3DGS重建质量下降,定性结果显示干扰物瞬态泄漏。此外,定性结果显示,NeRF在工作时没有去除训练早期阶段的一些干扰物((‘Patio’, ‘Corner’, ‘mountain’ and ‘Spot’),这显示出与渲染错误过拟合的进一步迹象。这也可以看到在细节的over-masking(‘Patio High’)或更大的结构(“喷泉”)被完全去除。
5.2 基于利用率的剪枝的效果
在我们所有的实验中,使我们提出的基于利用的修剪(UBP)(Sec。4.2.3),将高斯数从4×减少到6×。这种压缩意味着启用UBP的训练时间至少减少了2×,在推理期间减少了3×。图10显示,启用UBP可能会略微降低定量测量值,但在实际应用中,最终的渲染效果更干净,漂浮物更少(例如,图像的左下角)。类似的观察结果表明,PSNR和LPIPS等指标可能不能像渲染的视频那样清晰地完全反映飞蚊群的存在。考虑到高斯数的大幅减少,我们提出UBP作为一种适用于杂乱和干净的数据集的压缩技术。图7显示,在干净的MipNeRF360 [2]数据集上,使用UBP而不是不透明度重置,在保持渲染质量的同时,将高斯数从2×减少到4.5×
5.3 Ablation study
在图8中,我们比较了SLS的性能与其他健壮的掩蔽技术的进展。该进展始于简单地应用一个鲁棒过滤器(2),然后应用SLS-agg,最后在SLS-mlp中使用MLP。我们证明了SLS-agg和SLS-mlp都能够有效地从重建的场景中去除干扰物,同时保持对场景的最大覆盖范围。此外,在图9和图10中,我们减少了我们在架构设计和第4.2节中提出的调整中的选择。图9显示,使用一个MLP而不是一个小的CNN(都大约有30K参数,和两个非线性激活)可以更好地适应微妙的瞬变,如阴影。选择正则化器权重的λ似乎没有什么影响。在凝聚聚类中,更多的聚类通常会得到更好的结果,在100个聚类后收益减少。图10进一步说明了UBP在去除泄漏的干扰物方面的有效性。我们的其他适应能力,GLO,热身阶段和伯努利抽样都显示出了改进。
六、代码讲解
# 1.渲染图像-------------------------------------------------------------------
renders, alphas, info = self.rasterize_splats(
camtoworlds=camtoworlds,
Ks=Ks,
width=width,
height=height,
sh_degree=sh_degree_to_use,
near_plane=cfg.near_plane,
far_plane=cfg.far_plane,
image_ids=image_ids,
render_mode="RGB+ED" if cfg.depth_loss else "RGB",
)
colors, depths = renders[..., 0:3], renders[..., 3:4]
# 2.robust loss(loss_type)---------------------------------------------------
error_per_pixel = torch.abs(colors - pixels) # torch.Size(1, 377, 503, 3)
pred_mask = self.robust_mask( error_per_pixel, self.running_stats["avg_err"]=1)
# 像素误差小于阈值,或邻居中至少一个误差小于阈值,则为1,否则为 0。具体代码为:
def robust_mask(
self, error_per_pixel: torch.Tensor, loss_threshold: float
) -> torch.Tensor:
epsilon = 1e-3
error_per_pixel = error_per_pixel.mean(axis=-1, keepdims=True)
error_per_pixel = error_per_pixel.squeeze(-1).unsqueeze(0)
is_inlier_pixel = (error_per_pixel < loss_threshold).float()
window_size = 3
channel = 1
window = torch.ones((1, 1, window_size, window_size), dtype=torch.float) / (
window_size * window_size
) # 每个像素是否有至少一个邻居,误差小于阈值
if error_per_pixel.is_cuda:
window = window.cuda(error_per_pixel.get_device())
window = window.type_as(error_per_pixel)
has_inlier_neighbors = F.conv2d(
is_inlier_pixel, window, padding=window_size // 2, groups=channel
)
has_inlier_neighbors = (has_inlier_neighbors > 0.5).float()
is_inlier_pixel = ((has_inlier_neighbors + is_inlier_pixel) > epsilon).float()
pred_mask = is_inlier_pixel.squeeze(0).unsqueeze(-1)
return pred_mask
if cfg.semantics:
sf = data["semantics"].to(device) # (1,1280,50,50)
sf = nn.Upsample(
size=(colors.shape[1], colors.shape[2]),
mode="bilinear",
)(sf).squeeze(0) # # (1,1280,377, 503)
pos_enc = get_positional_encodings(
colors.shape[1], colors.shape[2], 20
).permute((2, 0, 1)) # torch.Size([80, 377, 503])
sf = torch.cat([sf, pos_enc], dim=0) # (1,1360, 377, 503)
sf_flat = sf.reshape(sf.shape[0], -1).permute((1, 0)) # (189631, 1360)
self.spotless_module.eval()
pred_mask_up = self.spotless_module(sf_flat) # MLP+sigmoid(1360->1)
pred_mask = pred_mask_up.reshape(
1, colors.shape[1], colors.shape[2], 1
) # torch.Size([1, 377, 503, 1])
# 计算 lower and upper bound masks for spotless mlp loss
lower_mask = self.robust_mask(
error_per_pixel, self.running_stats["lower_err"]
)
upper_mask = self.robust_mask(
error_per_pixel, self.running_stats["upper_err"]
)
alpha = np.exp(cfg.schedule_beta * np.floor((1 + step) / 1.5)) # alpha 值在0到1之间变化,表示当前训练的"温度",用于控制后续的随机采样过程
pred_mask = torch.bernoulli( torch.clip(
alpha + (1 - alpha) * pred_mask.clone().detach(),
min=0.0, max=1.0, ))
rgbloss = (pred_mask.clone().detach() * error_per_pixel).mean()
ssimloss = 1.0 - self.ssim(pixels.permute(0, 3, 1, 2), colors.permute(0, 3, 1, 2))
loss = rgbloss * (1.0 - cfg.ssim_lambda) + ssimloss * cfg.ssim_lambda
if self.mlp_spotless:
self.spotless_module.train()
spot_loss = self.spotless_loss(
pred_mask_up.flatten(), upper_mask.flatten(), lower_mask.flatten()
)
reg = 0.5 * self.spotless_module.get_regularizer()
spot_loss = spot_loss + reg
spot_loss.backward()
# 3.update_running_stats(info) #----------------------
cfg = self.cfg
# normalize grads to [-1, 1] screen space
if cfg.absgrad:
grads = info["means2d"].absgrad.clone()
else:
grads = info["means2d"].grad.clone()
grads[..., 0] *= info["width"] / 2.0 * cfg.batch_size
grads[..., 1] *= info["height"] / 2.0 * cfg.batch_size
self.running_stats["hist_err"] = (
0.95 * self.running_stats["hist_err"] + info["err"]
)
mid_err = torch.sum(self.running_stats["hist_err"]) * cfg.robust_percentile
self.running_stats["avg_err"] = torch.linspace(0, 1, cfg.bin_size + 1)[
torch.where(torch.cumsum(self.running_stats["hist_err"], 0) >= mid_err)[0][
0
]
]
lower_err = torch.sum(self.running_stats["hist_err"]) * cfg.lower_bound
upper_err = torch.sum(self.running_stats["hist_err"]) * cfg.upper_bound
self.running_stats["lower_err"] = torch.linspace(0, 1, cfg.bin_size + 1)[
torch.where(torch.cumsum(self.running_stats["hist_err"], 0) >= lower_err)[
0
][0]
]
self.running_stats["upper_err"] = torch.linspace(0, 1, cfg.bin_size + 1)[
torch.where(torch.cumsum(self.running_stats["hist_err"], 0) >= upper_err)[
0
][0]
]
# 如果iter大于500且为100倍数 step > cfg.refine_start_iter and step % cfg.refine_every == 0
grads = self.running_stats["grad2d"] / self.running_stats[
"count"].clamp_min(1)
# 4.grow GSs------------------------------------------------------------------
is_grad_high = grads >= cfg.grow_grad2d # "高梯度" GS:2D梯度是否大于等于阈值cfg.grow_grad2d
is_small = (
torch.exp(self.splats["scales"]).max(dim=-1).values
<= cfg.grow_scale3d * self.scene_scale
) # 0.01*1.77 # "小尺度" GS:3D尺度self.splats["scales"] 是否小于cfg.grow_scale3d * self.scene_scale
is_dupli = is_grad_high & is_small # 同时满足
n_dupli = is_dupli.sum().item()
self.refine_duplicate(is_dupli) # 对is_dupli标记的GS进行复制扩大,包括复制参数、优化器状态以及一些统计量
is_split = is_grad_high & ~is_small
is_split = torch.cat(
[
is_split,
# new GSs added by duplication will not be split
torch.zeros(n_dupli, device=device, dtype=torch.bool),
]
)
n_split = is_split.sum().item() # 计算那些点要split
self.refine_split(is_split) # 复制了原有GS,并基于原有GS的quad和scale随机扰动生成新的GS
print(
f"Step {step}: {n_dupli} GSs duplicated, {n_split} GSs split. "
f"Now having {len(self.splats['means3d'])} GSs."
)
# 4.prune GSs-------------------------------------------------------------
is_prune = torch.sigmoid(self.splats["opacities"]) < cfg.prune_opa # 0.0005
# 5. optimize
for optimizer in self.optimizers:
optimizer.step()
optimizer.zero_grad(set_to_none=True)
for optimizer in self.pose_optimizers:
optimizer.step()
optimizer.zero_grad(set_to_none=True)
for optimizer in self.app_optimizers:
optimizer.step()
optimizer.zero_grad(set_to_none=True)
for optimizer in self.spotless_optimizers:
optimizer.step()
optimizer.zero_grad(set_to_none=True)
for scheduler in schedulers:
scheduler.step()
# 5.Save the mask image
# 保存模型权重 checkpoint
if step in [i - 1 for i in cfg.save_steps] or step == max_steps - 1: # 在设置的7000或者30k保存一次
mem = torch.cuda.max_memory_allocated() / 1024**3 # 占GPU 4.9GB
stats = {
"mem": mem,
"ellapsed_time": time.time() - global_tic,
"num_GS": len(self.splats["means3d"]),
}
with open(f"{self.stats_dir}/train_step{step:04d}.json", "w") as f:
json.dump(stats, f)
torch.save({
"step": step,
"splats": self.splats.state_dict(),
}, f"{self.ckpt_dir}/ckpt_{step}.pt")
# self.splats.state_dict()共包含5部分:'means3d', 'opacities', 'quats', 'scales', 'sh0', 'shN'
维度分别为(N,3)(N,1)(N,4)(N,3)(N,1,3)(n,15,3)
d
\sqrt{d}
d
1
8
\frac {1}{8}
81
x
ˉ
\bar{x}
xˉ
x
^
\hat{x}
x^
x
~
\tilde{x}
x~
ϵ
\epsilon
ϵ
ϕ
\phi
ϕ
额外知识
1.层次聚类(Agglomerative clustering)
层次聚类顾名思义就是按照某个层次对样本集进行聚类操作,这里的层次实际上指的就是某种距离定义。
层次聚类最终的目的是消减类别的数量,所以在行为上类似于树状图由叶节点逐步向根节点靠近的过程,这种行为过程又被称为“自底向上”。
更通俗的,层次聚类是将初始化的多个类簇看做树节点,每一步迭代,都是将两两相近的类簇合并成一个新的大类簇,如此反复,直至最终只剩一个类簇(根节点)。
与层次聚类相反的是分裂聚类(divisive clustering),又名 DIANA(Divise Analysis),它的行为过程为“自顶向下”。
聚类过程:
1.数据准备;
2.计算数据集中各样本之间的距离(相似度信息);
3.使用 连接函数(linkage function)将样本进行分组形成层次聚类数(分组依据是上一步计算出来的距离信息),距离相近的样本会被链接在一起;
4.决定在什么地方将层次聚类树截断成多个聚类。
2.Lipschitz constant(普希茨常数)
对抗样本:在分类器中,引起错误预测的不可察觉的扰动输入。
一、用Lipschitz常量来度量和控制网络的不稳定性
二、Lipschitz常量与网络鲁棒的关系,从而加强型的鲁棒性
三、平衡网络的表达能力(准确率)和稳定性
定义:
用 K 0 K_0 K0 限制模型的输出值,不会有太极端的变化:。
控制模型的平滑:
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)