"In dynamic 3D environments, accurately updating scene representations over time is crucial for applications in robotics, mixed reality, and embodied AI. As scenes evolve, efficient methods to incorporate changes are needed to maintain up-to-date, high-quality reconstructions without the computational overhead of re-optimizing the entire scene. This paper introduces CL-Splats, which incrementally updates Gaussian splatting-based 3D representations from sparse scene captures. CL-Splats integrates a robust change-detection module that segments updated and static components within the scene, enabling focused, local optimization that avoids unnecessary re-computation. Moreover, CL-Splats supports storing and recovering previous scene states, facilitating temporal segmentation and new scene-analysis applications. Our extensive experiments demonstrate that CL-Splats achieves efficient updates with improved reconstruction quality over the state-of-the-art."
- 病根一句话:场景重建完就会过期,而重训的代价不只是慢,新照片覆盖不到的地方会被优化擦掉,真实场景 3DGS 只剩 11.764 dB,比什么都不做还差。
- 三步改法:DINOv2 特征比对出 2D 变化掩码 → 用大数投票把它抬升成 3D 变化区(新出现的物体再补一次采样)→ 区域外全部冻结,只用局部核优化区域内的高斯。
- 一个容易做错的地方:把损失直接限制在 2D 掩码里行不通。那只基线(3DGS+M)只有 15.127 dB,比原始 3DGS 的 21.993 还低,因为掩码没有三维空间意识。
- 两笔收益:合成场景 PSNR 40.125(CL-NeRF 30.063,稠密重训上界约 42),渲染 223 FPS;5K 迭代 40 秒收敛(CL-NeRF 需 25K 迭代、50 分钟)。历史每步只存 36MB,而存整份重建要 1173MB。
1房间里挪了一件东西,模型怎么办
房间里挪了一件东西,模型怎么办
设想一个已经重建好的房间:用手机绕一圈拍了两百张照片,跑一遍 3DGS,得到了一个能实时浏览的三维场景。过了一周,你又走进这个房间,桌上多了一个杯子,椅子上少了一个抱枕,其他什么都没变。
这时候最自然的做法是把照片重拍一遍、重训一遍。但你会立刻发现两件事:第一,绝大多数像素其实一点没变,为它们重算一遍是纯浪费;第二,重拍很难覆盖到每一个角落,而那些没被拍到的部分,恰恰会在重训中被优化得越来越差。
这就是本文要解决的问题:场景重建完了会"过期",而更新的代价不该是重来一遍。
一句话把这件事说清楚:改动是局部的,优化也该是局部的。
这个判断看似平常,落实起来却卡在一个具体的地方:"局部"是三维空间里的局部,而你能直接看到的只有二维图像。照片上被改动的是一个区域,可那个区域对应到场景里是哪些高斯?如果这一步判错,后面的优化要么漏改(该改的没改)、要么乱改(不该改的被拉坏)。本文真正的工作量,大半都花在"怎么把二维的改动准确地抬到三维"上。
除此之外,论文还顺着"局部"这条线做出了两个新用法,它们的价值不比主结果低。一是并行合并:如果两处改动互不重叠(比如"沙发上加了只玩偶"和"桌上撤了台电脑"),可以分别优化再拼起来,不必等两组照片凑齐。二是历史回放:因为每次只动了局部,历史状态就可以只存"改动清单"而不是整份重建,每步 36MB,而存整份要 1173MB。这两件事之所以成立,全靠"改动是局部的"这一个判断。
后面几章的路线:第 2 章把朴素做法的两笔账算清;第 3 到第 6 章是"找出变化"(2D 检测 → 为什么不能只用 2D → 抬升到 3D 的投票 → 新物体的采样);第 7、8 章是"只在局部优化"(包围球与漂移剪枝、局部核);第 9、10 章分别是质量与速度的账;第 11 章讲那两个新用法;第 12 章独立成节,列十条它做不到的地方。
这一篇要记的只有一句话:改动是局部的,优化也该是局部的。
把它放在这个分区的高斯系列里对一下位置,会更容易记住它改的是哪一层。篇目 04(AAA-Gaussians)改采样率、篇目 07(BBSplat)改图元的纹理、篇目 08(2DGS)改图元形状、篇目 12(StopThePop)改混合顺序、篇目 14(CoMoGaussian)改相机的运动建模:这五篇动的都是"场景怎么被表示、怎么被画出来";而 CL-Splats 动的是时间这一维:场景不变的时候它和 3DGS 没差别,一旦场景变了,它回答的是"该改多少、改了之后能不能退回去"。
还有一个现实动机值得记下:这类"长周期更新"的需求在机器人领域最硬。一台家用机器人今天进了客厅、下周再进,它需要的不是"重新建一遍地图",而是"只把变动的部分更新掉,并且知道自己上次看到的是什么"。原文在引言里给的例子就是这个。它决定了整篇论文的取舍方向:宁可接受"只能处理局部变化"这个限制,也要换来"更新便宜、历史可查"。
2朴素做法的两笔账
把问题写成形式:已知时刻 t−1 的重建 Gt−1,以及时刻 t 新拍的一组图像 It = (It^0, …, It^n),目标是高效地把 Gt−1 更新成 Gt。关键约束:除了当前这份重建,没有别的历史信息可用;而 It 只覆盖发生变化的那一小块,不覆盖整个场景。
在这个设定下,"全量重训"有两个问题,原文把它们分得很清楚。
第一笔是效率账。 重训意味着把 It 当作全新的输入跑一遍 3DGS。可 It 里绝大多数像素描述的是没变的东西,这些信息在 Gt−1 里本来就有,把它们重新学一遍,是纯粹的浪费。更现实的问题是:为了重训,你不得不把所有东西重新拍一遍,这在动态环境里几乎做不到。
第二笔是信息账,而且这一笔更致命。 3DGS 的优化是"用当前这批照片去解释整个场景"。如果照片只覆盖了房间的一角,那么没被拍到的部分就失去了监督信号,优化会顺着损失把它们越改越糊。原文的说法是"会在观测不足的区域擦掉 Gt−1 的部件,导致信息损失"。这不是理论担心,主表里给了确凿的数字:
| 方法 | 合成场景 PSNR ↑ | 真实场景 PSNR ↑ |
|---|---|---|
| 3DGS(在新数据上重训) | 21.993 | 11.764 |
| 3DGS+M(损失限制在 2D 掩码内) | 15.127 | 8.585 |
| GaussianEditor | 19.801 | 24.133 |
| CLNeRF | 26.758 | 24.541 |
| CL-NeRF | 30.063 | 23.268 |
| CL-Splats(本文) | 40.125 | 28.249 |
数据来源:原文 Table 2(CL-Splats 数据集;指标顺序为 PSNR / LPIPS / SSIM / FPS,此处只取 PSNR)。真实场景那一列的 11.764 dB 是整张表最值得盯的数字:它比"什么都不做"还差,原始重建至少是完整场景,而重训之后的模型在没拍到的区域已经崩了。这一行是整篇论文的动机来源:证明"重训"不是慢一点的问题,而是会把已有成果弄坏。
顺带说一句为什么这个设定值得单独研究。它和"动态场景重建"不是一回事。动态重建(比如城市街景里跑动的车)关注的是连续视频里的运动;而这里关注的是间隔很久、张数很少的两次拍摄之间的差异,比如今天和上周、早上和晚上。原文在相关工作里对这条线的定位是:这类"长期演化"的技术比实时重建要少得多,而它恰恰是机器人、混合现实这类应用真正需要的(机器人需要知道"上周桌上那个杯子现在还在不在")。
把这一章收成一句:朴素做法的失败不在精度,而在它把"没被观测"当成了"应该被改"。要避开这一点,就必须先找出"哪里真的变了",这正是下一章的事。
这里要把任务里的"已知与未知"再钉一遍,因为它决定了整篇方法的自由度。已知:时刻 t−1 的完整重建 Gt−1,以及时刻 t 新拍的一组图像 It。未知:新场景长什么样、哪些部分变了。特意不假设的:It 覆盖整个场景(实际只覆盖变动区)、以及能拿到过去任意时刻的数据(只保留 Gt−1)。
最后一条约束最容易被忽略,但它其实是本文与"时序重建"类方法的根本区别。CL-NeRF 那类方法需要按时间戳组织的一系列观测才能训练;而本文的设定是"随时拿到一批新照片就能更新",没有固定的时间序列,也不需要反复回访。这个差别直接决定了后面两个用法的可能性:只有当"每一次更新都是一次独立的局部改动"时,才谈得上"把两处改动并行处理"(第 11 章),也才谈得上"只存改动清单"来回放历史。
再补一句关于评价指标的选取。本文同时报 PSNR、SSIM、LPIPS 与帧率四项,这个组合是有针对性的:PSNR/SSIM 看的是"改完之后对不对",LPIPS 更接近人的观感,而帧率决定它能不能用在交互式系统里。对这类"更新"任务,单看 PSNR 会漏掉一个关键问题:方法有没有把没变的地方改坏。第 9 章那张定性图(图 9-1)用红框与橙框把这两件事分开考核,正是为了补上指标说不清的那部分。
3第一步:找出 2D 上哪里变了
变化检测这件事,直接用像素比是不行的:光照稍变一点、曝光差一档,像素值就全变了,而场景其实没动。所以本文不比重建出来的像素,而是比语义特征。
先要把新照片对齐到旧重建的坐标系里。做法是用 COLMAP 估计 It 的相机位姿 Ct,与 Gt−1 的坐标系保持一致(沿用了 CL-NeRF、CLNeRF 的做法;在变化的场景上跑 COLMAP 的设置细节放在附录)。有了位姿,就可以用旧模型在同一个位姿上渲染出一张图 Î^{t−1}_i,然后把它和新照片 It^i 比对:"旧模型此刻会看到什么"与"你实际拍到了什么",两者的差就是变化。
比对用的是 DINOv2 特征,而不是颜色:
E 是 DINOv2 的特征提取器,输出逐块(per-patch)的特征图。换成特征比对的理由值得说透:DINOv2 的特征是自监督学出来的语义描述子,它对光照、曝光的轻微变化不敏感,但对"这里原本是椅子、现在是空的"这类语义改变非常敏感。这正好对上需求,我们要找的是"东西变了",不是"亮度变了"。
有了两组特征图,逐像素比余弦相似度,低于阈值就判为变化:
M^ti 就是第 i 张图上的二值变化掩码:1 表示这里变了。阈值 τ₁ 是唯一的超参,含义是"特征方向偏离到这个程度就算不同"。
最后一步是把掩码处理好:先缩放回原始图像分辨率,再用一个滑动二次核做一次膨胀:
膨胀的目的有两层。一是填补小孔洞:特征比对难免有噪点,膨胀把零散的孤立像素连成片。二是留出余量:变化的边界往往是渐变的(比如物体边缘、它的阴影),如果掩码卡得太紧,边界附近的高斯就得不到更新,留下接缝。原文后面有一个专门的消融(Table 4)来量这件事,结论是膨胀显著提升了准确率、几乎没有损失召回。这是"宁大勿小"的取舍,原因在第 4 章会说明。
到这一步为止,得到的还只是一个图像平面上的圈。而真正要优化的是三维高斯,把平面上的圈直接当成"三维里的范围"用,会出问题。下一章就把这个问题说清,它是整篇论文里最容易做错的一步。
这四步的先后次序值得记牢,因为它反映了本文对"信息从哪来"的安排:几何信息全部来自旧重建(Gt−1),变化信息全部来自新照片(It),而两者是靠"用旧模型在同样位姿下渲染一张图"来对齐的。这一步"渲染出来对一下"是整条链的枢纽:没有它,新旧两组观察就没有共同坐标系;有了它,剩下的问题就只是"哪里对不上"。
还有一处工程上的细节:DINOv2 是当作固定的特征提取器用的,本文没有微调它(原文的措辞是"利用 DINOv2 的特征提取器")。这对复现很重要:你不需要为每个场景训练一个特征网络,也不需要额外的标注数据,模型里真正被训练的东西只有后面的高斯参数。整条变化检测链路因此是"零训练"的,换场景即用。
4为什么不能只在 2D 掩码里做优化
拿到 2D 掩码之后,最直觉的做法是:把光度损失限制在掩码内的像素上,别的照旧跑 3DGS。这样不用改任何代码结构,看起来既省又对。
本文给出的结论很干脆:这条路走不通,而且原文专门造了一个基线来证明它,他们把这个做法命名为 3DGS+M,做了完整的对照。结果在主表里(上一章那张):合成场景 15.127 dB、真实场景 8.585 dB,双双低于原始 3DGS 的 21.993 与 11.764。也就是说,加了这个"看起来更聪明"的限制之后,效果反而更差。
为什么会这样?原文的解释指向一个根本性的错配:2D 掩码没有三维空间意识(lacks 3D spatial awareness)。
把这个话说具体一点。一张照片上的某个像素,是一条视线穿过整个场景之后加权累积的结果。那条视线上从近到远可能排着几十个高斯,而掩码只告诉你"这个像素变了",没告诉你"是这条视线上的哪一个高斯变了"。于是会出现两类误伤。
一类是"改错了深度"。 假设桌上换了个杯子,掩码圈住了杯子所在的像素。但同一条视线上,杯子后面还有桌面、桌后的墙。它们其实没变。优化时梯度只看像素误差,会把这条视线上的所有高斯都往"能凑出这张新照片"的方向推,于是桌面被推得凹凸不平、墙面出现噪点。变化的投影是二维的,而优化是在三维里进行的,两者的作用范围不一致。
二类是"改漏了该改的"。 反过来,如果新物体的像素在掩码里只占了一小片,那么它对应的高斯可能只有一部分落在掩码内,另一部分落在掩码外的像素上(因为高斯在屏幕上是椭圆的、会跨像素作用)。掩码外的那些就完全没被更新,物体的形状因此修不全。
还有一层更隐蔽的困难:3DGS 的优化是按 tile 分块做的,而不是逐像素的。原文在消融里提到,最终的 3D 掩码"预测出来是成块的(tiles),因为 3DGS 是按 tile 优化的,这会带来轻微的掩码扩张和块状外观"(图 5 那一处)。这句话反过来正好说明了问题的本质:渲染器的最小工作单位是 tile,不是像素;你想在像素级别局部化,底层根本给不了这个粒度。
把这一章收成一句:"哪里变了"必须先在三维里回答一次,二维的圈只能当作线索,不能当作范围。下一章就来做这个抬升。
这一段论证的价值超出了一个方法的成败,它是一条可复用的经验:当一个"局部"约束是从投影空间里提出来的,用到三维优化上时必须重新表述一次。同类错误在别的领域也常见,比如在屏幕空间圈出一块区域,然后期望三维里的某个子集被精确修改;只要投影不是一一对应(而它几乎永远不是),这个跳跃就不成立。判据很简单:问一句"同一个二维位置,在三维里可能对应多少东西" ,如果答案不是"一个",就必须显式地在三维里做选择。
顺带说一句本文选择"先 2D 再 3D"而不是"直接在 3D 里比"的原因。直接在三维里比较需要一套"三维变化描述子",这样的描述子要么依赖昂贵的体素/特征场计算,要么需要真值三维数据做监督。两者在本文的"只有几张新照片"设定下都不可得。先在二维里用成熟的特征提取器找出候选,再用几何一致性把它们筛到三维,是这类约束条件下最现实的路径。
5把变化抬到 3D:多数投票
抬升的思路很朴素:把旧模型里的高斯逐个投影到每一张 2D 掩码上,看它被多少张掩码圈住。被足够多张掩码圈住的高斯,就算变化区的一部分。
这个做法背后的直觉是"一致性投票"。单张照片上的掩码是可能有错的:特征比对会受噪声影响,膨胀又会额外扩大范围。但如果某个高斯真的属于被改动的物体,那么在不同视角的多张照片上,它都应该落在掩码里;反之,一个只是被膨胀误圈进来的静态高斯,往往只在少数几张照片里落进掩码。用"被多少张圈住"来投票,就能把偶发的误判过滤掉。
写成判据:
Ot 是判定出来的三维变化区(一组高斯),proj(g) 表示把高斯 g 投影到第 i 张图上的位置,K 是视图数阈值。原文的措辞是"在超过 K 个视角里落入掩码的高斯被归入变化区"。
这里有一个关键的取舍要说明,因为 K 的取值直接决定成败。K 取得太严会漏掉真正的变化(该改的高斯因为只被少数视角圈到而被留下);K 取得太松则会把误圈进来的静态高斯算作变化区,等于把第 4 章那类误伤又请了回来。原文对这一点给了一组消融(Table 5 的 (b) 行 "All Vote"):要求高斯落在所有掩码里。也就是最严的 K。结果是 PSNR 从 40.833 掉到 35.611,掉了 5.2 dB。原文的解释是这会"留下一些点没被优化,导致重建质量下降"。
"要求被所有视图圈到"听起来更严谨,实际更差,这个反直觉的结果要专门记一笔。原因在于:变化区里的高斯本来就有相当一部分只在部分视角可见(被自己遮挡、被别的物体挡住、或者在画面边缘)。要求"全部视图都圈到",等于把这些高斯全判为静态,于是它们永远得不到修正。投票这件事的本质是"多数同意",而不是"全体一致",前者能容纳噪声与遮挡,后者不能。
还有一点:这一步只解决"哪些已有的高斯要动",不解决"新东西从哪来"。如果改动是加了一个物体,那片区域在 Gt−1 里根本没有高斯,投票自然得不出任何结果。这是下一章的事。
把投票这一步与第 3 章连起来看,会发现它其实是在做一致性检验:第 3 章的掩码是逐张独立算的,每张都可能出错;投票则要求"多个视角共同同意"。这两层加起来的鲁棒性有个很直观的检验方式:如果某张照片的掩码算错了(比如因为反光把整面墙都标成变化),投票会把它压掉,因为那个错误区域里的高斯在别的视角上并不会被圈到。原文没有专门为这种"单张出错"做实验,但从机制上讲它是被覆盖的。
还有一处实现上的讲究值得提一句:投票是在投影上做的,也就是说用的是"高斯中心投到图像上的位置",而不是"高斯的整个椭圆落在掩码里"。这个选择让判据更宽松(对边界处的物体更友好),代价是可能把一些"中心在掩码内、但实际大部分在掩码外"的高斯也判进来。这类边界的松紧取舍,最终靠第 7 章的漂移剪枝与第 4 章的实验来兜底,如果判松了,优化时那些高斯会漂出区域被剪掉;如果判紧了,就是第 12 章那条"漏判没救"的失败模式。
把投票这一步与"检测"这件事的关系再说透一层:它其实是一个用几何一致性做的过滤器。图像的语义检测(DINOv2)告诉你"这里看着不像原来",而几何一致性(多视角投影投票)告诉你"三维里的哪一撮东西造成了这种不像"。两者的分工意味着整条链路的误报可以分两级消化:语义级的误报(光照、反光造成的假变化)由多数票压掉,几何级的边界模糊由第 7 章的球体剪枝兜住。这种"两级过滤"的设计思路,在这类"从有噪声的二维线索反推三维结构"的问题里很有代表性。
6新物体没有高斯可用:递归采样
第 5 章的投票是按"已有高斯"投票的。如果这次变化是删掉或移动一个物体,处理对象是现成的高斯,投票就够了;但如果是新增一个物体,比如桌上多了个杯子。那片区域在旧重建里什么都没有,没有高斯可投。如果不管它,新物体就永远重建不出来。
本文的补法是:在变化区里补采一批点,作为新高斯的出生点。采样并非简单地撒点,还要保证"补得够多、且尽量落在真正需要的地方"。原文把它写成一个递归过程(算法 1),核心是三分支判断:
n 是目标点数,SAMPLE REGION 是"围绕现有区域采样"。这个递归设计的好处是对三类改动一套代码:删除与移动走到第一个分支(区域里本来就有高斯),全新物体走到第二个分支(从零采起),部分新增走到第三个分支(补一批)。注意它并不去区分这次改动属于哪一类。原文特意强调了这一点:"我们总是独立于操作类型来采样新点,从而在处理各种场景变化时保持灵活性。"
初始化的其余部分沿用标准的 SfM 式初始化,也就是新点的颜色、尺度、不透明度等参数都按 3DGS 的常规方式给初值。这一步的工程含义是:整条管线不需要为"新增物体"准备一套特殊逻辑,它只是"多采了一些点"。
这里值得停下来想一下这个设计的边界,因为它后来成了第 12 章的一条。采样点的来源是"旧重建里的高斯 + 掩码约束"。也就是说,新物体的初值是从旧场景里"借"来的位置与颜色。如果新物体和周围环境差别巨大(比如一个全黑的物体出现在白墙上),这些借来的初值会比较差,需要更多次迭代才能收敛。这也解释了为什么本文的收敛速度虽然快(5K 迭代),但"新增物体"仍然是最难的一类改动(原文在结论附近提到:删除最容易、多物体同时改动最难)。
再补一句关于"点数 n"的理解。它设的是一个目标规模:不要比需要的少(否则新物体表达不出来),也不要多到失控(那么多余的高斯会在优化后被稠密化机制再调整)。原文没有在正文里给 n 的敏感性实验,但在附录里给了采样子程序的细节。对复现者来说这是一个需要调的旋钮:场景里新物体的尺度差异很大时,固定的 n 未必合适。这也是第 12 章"无法区分模糊轻重"那条类似的问题(本文的强项是机制清晰,弱项是自适应参数)。
顺带说清一个容易误解的点:采样出来的点只是新高斯的出生位置,并非最终的高斯。真实的颜色、尺度、不透明度、球谐系数都会在后续优化中学出来。所以"从旧场景里借初值"这件事并不会把旧场景的外观强行带过来。它只是给优化一个"从这里开始找"的起手位置。这一点很关键:它解释了为什么新增一个与周围反差很大的物体终究还是能收敛(只是慢一些),而不是永远学不像。
7只在那一团里优化:包围球与漂移剪枝
现在有了三维变化区 Ot,可以开始优化了。但这里还有两个必须解决的问题,原文把它们列得很清楚:一是"优化的三维范围"怎么定义(哪些区域的高斯允许被更新),二是"怎么保证高斯在优化过程中不跑出去"。
第二个问题容易被忽略:3DGS 的优化会移动高斯的位置(还会稠密化、分裂、克隆)。如果你只是"开始时圈定了一批高斯",那它们在优化过程中完全可能漂到区域之外,把没变化的区域改坏,第 4 章讲的那类误伤会以另一种形式回来。
本文用球体来定义这个范围,理由是三条:容易拟合、适合做基于欧氏距离的聚类、判断"一个点在不在里面"很快。球体是这么得到的:先用 HDBSCAN 对变化区的高斯做聚类,做一次离群点检测,再对每个清理过的簇拟合一个球;由于一个物体可能由多个簇组成,所以允许一个变化区对应多个球。
有了球,成员判据就是一个距离比较:
μg 是高斯中心,cs 与 rs 是球 s 的球心与半径,S 是球的集合。这个式子的形式极简,但它把"空间局部化"变成了一次可快速判定的包含测试。这正是原文选球体的原因。
在这个基础上,漂移剪枝的判据也就顺理成章了:
图 7-1 值得多看一遍,因为它把"局部化"这件事的两个阶段都画出来了:范围用球定义(静态的),漂移用剪枝兜住(动态的)。少了任何一个都不行,只定义范围不剪枝,高斯会漂;只剪枝不定义范围,压根不知道该剪谁。
原文还给了一组对照数据来支持"用球体"这个选择:换成有向包围盒(oriented bounding boxes)后性能没有显著差别,但盒子的成员判定要多花三倍 FLOPs,所以球体更划算(Table 5 的 (d) 行:41.717 对 40.833,几乎同性能)。这是工程论文里很典型的一处取舍:数学上更"紧"的包围体(盒子)并不带来收益,反而更贵,因为瓶颈在判定次数而不是包围精度。
把这一章的两个约束与第 4 章的问题重新对一次,会看清整个方法的结构:第 4 章说的那个错配,"二维的圈"与"三维的优化"作用范围不一致,在这里被两个手段一起消掉了。包围球把"允许更新的范围"从投影空间搬回三维空间,于是"改错了深度"(同一条视线上不该动的高斯被推动)这件事不会发生;漂移剪枝则保证这个范围在整个优化过程里始终有效,于是"优化过程中跑出去"这条路也被堵住。两个手段一静一动,缺一个都不完整。
最后记一个容易被跳过的实现细节:球是从变化区的高斯上聚出来的(先 HDBSCAN 聚类、再离群检测、再拟合),所以球的位置天然贴着"实际要改的东西"。这带来一个好处:它不需要任何人工指定区域(不像有些编辑方法要用户画一个框)。代价则是球的正确性完全依赖于第 5 章投票的结论:投票漏了,球就圈不到;这一点在第 12 章的失败案例里会再次出现。
8局部核:只算那一块的像素,梯度却和全场景一样
空间范围约束好了,最后一步是把计算量真正降下来。注意"只优化一部分高斯"不等于"只算一部分像素":如果优化时仍然把整幅图渲染出来、整幅图算损失,那省下的只是参数更新,渲染这块大头没动。
本文设计了一个局部优化核,每一步做三件事:
这三步的顺序很讲究。关键在于第一步是"动态"的:掩码并非一开始算一次就固定,它每一步都要根据当前的高斯位置重新投影生成。这样即使高斯在优化中移动了,它影响的像素也始终跟着走。这正是漂移剪枝(上一章)在几何层面要做的事,在计算层面由动态投影接住。
第二步的设计值得说明它的边界条件。原文强调这种做法"保持了 Ot 在全场景优化下会得到的相同梯度",也就是只省计算、不改数学。这句话是这一章的核心承诺:局部核不是近似,它是把全场景优化中"对 Ot 那部分高斯的梯度"原封不动地算出来,跳过的只是与它们无关的像素与高斯。如果它只是近似,那么所有主结果都要打折;正因为它是精确的,主结果的增益才归因于"局部化"本身。
这张图的价值在于把"局部化的两个约束各管什么"分开显示了。去掉冻结背景(不限制优化范围),画面整体变差、差图大面积亮;去掉剪枝(允许漂移),差图在变化区边缘一圈亮,正好对应漂移者把边界弄脏。两个组件各修一类错,这是消融表(Table 5)里数字差距的视觉对应。
原文还给出了一条具体的效率读数:这个局部核设计"把平均优化时间减少了 60%",并且指出加速的关键正是第一步的动态重投影。它把计算限制在"被变化高斯覆盖的那些 tile"上。注意这里是 tile 而不是像素:第 4 章说过渲染器的最小工作单位是 tile,所以局部核的粒度也是 tile,这既是它的效率来源,也是它没法做到像素级精确的原因。
顺着"梯度与全场景一致"这句话,可以把这个局部核与第 5 章的投票区分开,两者很容易被混为一谈。投票解决的是"谁参与优化"(集合的选取),局部核解决的是"算哪些像素"(计算的裁剪)。前者是方法的一部分,会改变结果(选错就学不对);后者是纯实现优化,理论上不改变结果(原文用"相同梯度"来保证这一点)。把它们分开看,消融表那两行才好读:Table 5 的 (a)/(b) 行改的是前者(质量掉),(c)/(d) 行改的是后者(质量几乎不动、时间变)。
再说明一下"动态"这两个字的分量。如果掩码只在开始时算一次、之后固定,那么随着高斯移动,总会有一些高斯飘到了掩码外(它的贡献像素不再被计算)、另一些静态高斯飘进了掩码内(白算)。动态重投影的作用就是让"谁在被算"始终跟着"谁在动"走。这也是原文说加速的关键在第一步的原因:它不是把图裁小那么简单,而是把裁的那块始终对准真正在变的位置。
把这一章与第 9、10 章的账连起来看,会明白"精确梯度"这个承诺为什么值得专门强调一次。如果局部核只是近似,那么本文相对 3DGS 的质量优势就无法归因,读者会怀疑"是不是因为你只优化了一部分、所以反而避免了过拟合"。原文用"与全场景优化得到相同梯度"这句话把这条质疑提前堵掉了:质量差异只能来自"该不该动这部分高斯"的集合划分,而不是来自梯度的计算方式。这也解释了消融表里为什么专门列出"(c) 去掉局部核"这一行:它的 PSNR 几乎不变(40.812 对 40.833)而时间涨 60%,正好证明了局部核是纯速度项。
9账一:质量
先交代数据。本文用了三套基准:CL-NeRF 的合成集(原属 Mitsuba,作者改用 Mip-NeRF 360 式轨迹做初始重建)、自建的 CL-Splats 数据集(合成部分分三个复杂度层级,每层覆盖加物体、删物体、移物体、以及组合四类场景;真实部分含五个室内外场景),以及 CL-NeRF 数据集。评价指标是 PSNR、SSIM、LPIPS,外加一个帧率。
主表按"合成/真实"两栏给:
| 方法 | 合成 PSNR ↑ | 合成 SSIM ↑ | 合成 LPIPS ↓ | 合成 FPS ↑ | 真实 PSNR ↑ | 真实 SSIM ↑ | 真实 LPIPS ↓ | 真实 FPS ↑ |
|---|---|---|---|---|---|---|---|---|
| 3DGS | 21.993 | 0.838 | 0.189 | 221 | 11.764 | 0.399 | 0.376 | 125 |
| 3DGS+M | 15.127 | 0.737 | 0.303 | 254 | 8.585 | 0.271 | 0.461 | 151 |
| GaussianEditor | 19.801 | 0.871 | 0.197 | 227 | 24.133 | 0.867 | 0.143 | 137 |
| CLNeRF | 26.758 | 0.738 | 0.322 | <1 | 24.541 | 0.658 | 0.373 | <1 |
| CL-NeRF | 30.063 | 0.939 | 0.058 | <1 | 23.268 | 0.725 | 0.290 | <1 |
| CL-Splats(本文) | 40.125 | 0.985 | 0.015 | 223 | 28.249 | 0.930 | 0.065 | 135 |
数据来源:原文 Table 2(图像分辨率 960×540)。这张表要分四层读。
第一层,看"重训"这一行有多糟。 3DGS 与 3DGS+M 就是第 2 章说的两种朴素做法:前者在新数据上重跑 3DGS,后者把损失限制在 2D 掩码里。合成场景 21.993 与 15.127,真实场景 11.764 与 8.585。真实场景那两个数字是这张表的底色。它们说明"照片只覆盖一角"时,重训甚至不如不更新。
第二层,看本文与最近的两个竞争者的差距。 合成场景 40.125 对 CL-NeRF 的 30.063,高 10.06 dB;真实场景 28.249 对 CLNeRF 的 24.541,高 3.71 dB。而在 LPIPS 上差距更大:合成 0.015 对 0.058(近四倍),真实 0.065 对 0.290。
第三层,看帧率那一列,它是本文与 CL-NeRF 系方法的分水岭。 CLNeRF 与 CL-NeRF 的帧率是 "<1"。原文直接用一个小于号表示"完全不可实时";本文是 223(合成)与 135(真实)FPS。这就是"显式表示"与"隐式表示"的根本差别:前者可以随时把场景拼出来渲染,后者必须跑网络。本文在图 1 的标注里把这件事写得更刺激:81/82 FPS 对 0.001 FPS。
第四层,看那个 40.125 离天花板还有多远。 原文做了一个很扎实的对照:对每个合成场景在改动之后再稠密采集一遍并跑 3DGS,平均 PSNR 约 42 dB,这是重建质量的上界。本文用稀疏的局部照片拿到了 40.1 dB,离"拍全了重训"只差约 2 dB;而第二名 CL-NeRF 是 30.1 dB,差了 12 dB。这一条比"比谁高多少 dB"更有说服力,因为它给出了绝对参照系。
第二张表换到 CL-NeRF 数据集,这个数据集有几个刁钻的地方:表面反射复杂、两个场景大面积是白色无纹理的(白房间)、场景被墙和天花板围住(需要更多高斯才能表示)。
| 方法 | PSNR ↑ | SSIM ↑ | LPIPS ↓ |
|---|---|---|---|
| 3DGS | 11.072 | 0.537 | 0.356 |
| CL-NeRF | 27.302 | 0.829 | 0.177 |
| CL-Splats(本文) | 29.984 | 0.839 | 0.156 |
数据来源:原文 Table 3。本文在三个指标上都最好,但注意这一组的差距明显收窄(PSNR 高 2.68 dB,而主表合成集上高 10 dB)。原文给的解释是这些场景对该数据集上的所有方法都更难,而且明确指出"由于底层表示的限制,它不容易扩展到更高细节的场景(例如驾驶数据)":这句是它自己划的能力边界,第 12 章会列进去。
这张图的读法设计得很聪明:用两种颜色的框把"该改的"与"不该动的"分开考核。只列红框(变化区)会让"重训派"看起来也还行;补上橙框(未变化区)之后,3DGS 把墙面、地面弄糊的问题就藏不住了。这也是本文与一般"重建质量对比"论文在读图上的差别:它考核的其实是两件事,改得对不对,和没动的有没有被弄坏。
这张图还解释了为什么本文要自建一套数据集。已有的基准(CL-NeRF 的合成集)场景数少、改动类型单一,用它很难回答"方法在更复杂或规模更大的场景里还成不成立"。自建数据集的设计思路是把"复杂度"单独做成一个维度:三个层级共享同一套改动,于是"同样的局部改动,放到越来越大的场景里,代价与精度怎么变"这件事被单独拉出来观察。对做评测的人来说这是一种值得照搬的组织方式,把一个变量单独做成梯度,而不是把各种场景混在一起报一个平均值。
10账二:速度与收敛
第二笔账更直白,因为"局部化"本来就是冲着效率去的。
第一件事是收敛速度。 原文给了一组很干脆的对照:本文在5K 迭代就拿到了高质量重建,在一块 NVIDIA RTX Quadro 6000 上40 秒跑完;CL-NeRF 需要 25K 迭代、50 分钟,而且最终细节还更少。原文自己算出了倍数:75 倍。
第二件事是消融表里那一列时间,它说明各组件各自省了多少:
| 配置 | PSNR ↑ | SSIM ↑ | LPIPS ↓ | 时间 |
|---|---|---|---|---|
| (a) 不冻结背景 | 20.773 | 0.811 | 0.176 | 8 分钟 |
| (b) 全视图投票(最严) | 35.611 | 0.881 | 0.102 | 5 分钟 |
| (c) 去掉局部核 | 40.812 | 0.978 | 0.018 | 8 分钟 |
| (d) 用矩形包围盒 | 40.717 | 0.979 | 0.018 | 5 分钟 |
| (e) 完整方法 | 40.833 | 0.980 | 0.018 | 5 分钟 |
数据来源:原文 Table 5(自建数据集 Level-2 的测试集)。这张表的价值在于把质量与时间分开看。
(c) 行是"局部核"的消融,也是最值得看的一行。 去掉局部核之后 PSNR 只从 40.833 掉到 40.812(几乎不变),但时间从 5 分钟涨到 8 分钟,多了 60%。这正好对应上一章那条读数:"局部核把平均优化时间减少 60%":它买的是速度,不是质量。如果在只看 PSNR 的表里,这一项会被判为"无用"。
(a) 行是"冻结背景"的消融,它买的是质量。 不冻结背景,PSNR 直接掉到 20.773(掉 20 dB),时间反而更长(8 分钟):这是全场唯一一项"又慢又差"的配置,也再次印证第 4 章的判断:优化的作用范围必须被限制住。
(d) 行是包围体的选择,前面说过:矩形盒与球体性能几乎一样(40.717 对 40.833,0.12 dB 的差在噪声量级),但盒子的判定要三倍 FLOPs。这一行是"性能相当就选便宜的"的典型例证。
第三件事是变化的规模统计。 原文用两张图量了"一次改动到底涉及多少东西":
读这类统计图有一条纪律要记住:它是分布,不是单值。只看平均值很容易得出"改动通常很小、所以局部化收益很大"的乐观结论,而实际体验取决于长尾,最大的一次改动有多大。长尾决定了最坏情况下的耗时,也就决定了这套方法能不能满足实时或近实时的服务承诺。原文给出这两张图,用途正是给"局部化"的收益划出一个经验范围,而不是宣称一个固定倍数。
把两笔账合起来:质量上它在稀疏输入下逼近了"拍全了重训"的天花板(40.1 对约 42 dB),速度上它把收敛压到 5K 迭代、保持了实时帧率(223/135 FPS),而两个基线方法在帧率一栏是"<1"。这是一个相当干净的结果组合:既没有拿速度换质量,也没有反过来。
关于帧率那一栏还值得再说一句,因为它容易被误读。本文的 223/135 FPS 与 3DGS 的 221/125 FPS 基本一样,也就是说"实时"这件事并非本文发明,它继承的是显式表示的天赋;真正被它改变的是更新一次的代价(11 分钟、5K 迭代)与历史能不能查。反过来看 CL-NeRF 那一栏的"<1"也不该被简单当成"它做得差",隐式表示要跑网络才能出图,这是路线本身的代价。所以这张表最该记住的对比,是"两条路线在实时性上的结构性差距",而不是"谁比谁快":一条可以随时出图,一条要跑网络;这类差距不会靠调参追平,只会靠换表示来解决。对做工程的人来说,这句话比任何一组 PSNR 都更能指导选型。
11两个新用法:并行合并与历史回放
主结果之外,本文还有两个用法值得单独看,因为它们是"局部性"这个前提顺出来的直接推论,而且都解决实际问题。
用法一:把互不重叠的改动并行处理再合并。 假设某天有两处改动同时发生:沙发上多了只玩偶、桌上撤了台电脑。如果要求"一次拍全两处",你就得等两组照片凑齐;而如果两处改动在空间上互不重叠,其实可以分别优化再拼起来。
具体做法是:对每处改动各跑一遍本文的方法,得到两份完整重建 G1^t 与 G2^t,以及各自的三维变化区 O1^t、O2^t;然后把 G1^t、G2^t 里对应变化区的部分替换回基准重建 Gt−1,就得到合并后的结果:
这个用法的意义不在省了几次拍摄,而在于它说明了显式表示的独特优势。隐式方法(NeRF 系)把整场景压进一个网络,你想改一部分,改动是弥漫在整个网络权重里的,没法"把这一块抠出来搬走";而显式表示里,改动就是一组高斯加上它们在数组里的下标,"搬"是天然可行的操作。原文对这一点的措辞是:"NeRF 类方法无法合并并发改动,因为它们隐式表示的限制。"
用法二:历史回放。 这一条更实用。假设你每月重建一次,一年后想知道"三月份桌上有没有那个杯子"。朴素做法是每个月存一份完整重建,但场景是百万高斯量级,几个月下来磁盘就满了。
本文的做法是利用共享信息:既然每次只改动了一小块,那就只存"改动清单",三维变化区里的高斯,以及它们在重建数组里的下标。要回放第 i 个时刻的场景,就用最新的 GT 作底,把从 i 到 T 的各次改动反向替换回去。原文给的存储读数很有说服力:
36 对 1173,差了 32 倍。 而且原文强调了一句关键的话:这种回放"不带来任何质量损失"。因为它并未压缩任何数据,只是把原始的高斯原封不动地搬回去。这一条与第 9 章的质量账不完全是一回事:质量账说的是"当前时刻重建得好不好",这一条说的是"过去时刻能不能一模一样地拿回来",后者在很多应用里要求更硬(比如要做变更审计)。
原文还专门解释了为什么既有的时序方法做不好这件事,两条理由都值得记下:其一,容量有限,把所有历史压进一个模型,模型表达不了长期演化;其二,灾难性遗忘,除非反复观测,模型会逐渐忘记之前的状态。这两条合起来说明"只存改动清单"绕开的是"用一个模型承载全部历史"这个结构性难题,并非什么小聪明。
把这两个用法放回"时间这一维"看,本文的定位会更清楚。它同时回答了两个问题:当前场景是什么样(增量更新),以及过去某个时刻是什么样(历史回放)。前者是"往前更新",后者是"往回读"。两者共用一个前提,改动是局部的、可枚举的。如果改动是全局的,"改动清单"这个概念就不存在了,两个用法也会一起失效。这也是为什么第 12 章把"不会处理全局光照变化"放在第二条:它不是一个小缺陷,而是与整个方法的前提正面对撞。
还有一处对比值得记下,它解释了为什么显式表示在这类任务上有结构性优势。用隐式表示做历史回放时,你要么存下每个时刻的网络权重(体积巨大),要么让一个网络学会"条件于时间戳"地输出不同场景(容量有限、会遗忘);而显式表示里,"历史"天然就是一串可以按索引取回的参数,回放只是"把某一段子集换回去"。这属于表示形式本身带来的差异,而非工程上的取巧,也解释了原文为什么能在相关工作表里把"local"和"history"两列都打上勾。
12边界与反例(独立成节,10 条)
前面十一章讲的是它能做什么。这一章只讲它做不到、或要做到什么代价,按"原文自述"与"读表读出来的"分开标注。
1. 上限由基底决定(原文自述)。 原文的 Limitations 第一条就说得很直白:方法"固有地受基底三维表示的重建能力限制"。局部优化只能把已有表示修得更好,改不了它的天花板。
2. 假设改动是局部的,因此不会处理全局光照变化(原文自述)。 如果一整天的光照变了、或者窗帘拉开导致整间屋子的亮度都变了,本文没有对应机制。原文把它列为明确的未来方向:把几何更新与外观更新结合起来。
3. 不容易扩展到高细节场景(原文自述)。 原文在讨论 CL-NeRF 数据集时提到:"由于其底层表示,它不容易扩展到更高层次的细节,例如驾驶数据。" 这一条是能力边界里最硬的一条。
4. 新增物体是采样来的,初值靠"借"(读方法)。 第 6 章的采样点来自旧重建与掩码约束,新物体的初值因此是从周围"借"的。与周围环境差异很大的物体需要更多迭代,原文也提到多物体同时改动是最难的一类。
5. 依赖 COLMAP 位姿(读方法)。 每批新照片都要先用 COLMAP 估计位姿并对齐到旧坐标系。在"场景已经变了"的图像上跑 COLMAP 本身就有难度,原文为此在附录里专门写了设置细节:这意味着管线里有一段对使用者不透明的工程依赖。
6. 掩码的质量直接决定成败(读表 + 原文自述)。 Table 4 的数据是召回 0.942 但准确率只有 0.609:六成的判为变化的地方其实没变。原文特意说明这是有意为之(高召回优先,因为漏判会导致该改的改不到),但这也意味着后面全靠三维投票与漂移剪枝把误判过滤掉。
7. 判错"变化区"就救不回来(读图)。 见下面的图 12-1:如果掩码低估了变化区,局部优化无论怎么跑都补不回来。这是"局部化"的固有代价,范围错了就没有第二次机会。
8. 局部核的粒度是 tile 不是像素(原文自述)。 原文在掩码消融里承认最终的 3D 掩码"预测出来是成块的,因为 3DGS 按 tile 优化,这会带来轻微的掩码扩张和块状外观"。所以"精确到像素的局部性"在实现层面拿不到。
9. 帧率优势主要集中在"更新"这一步(读表)。 主表的 FPS 是渲染帧率,本文与 3DGS 系方法同量级(223 对 221),真正拉开差距的是 CL-NeRF 系的"<1"。换句话说,本文的帧率优势是继承来的,不是发明的。它来自选了显式表示。
10. 真实场景的绝对质量仍有明显余量(读表)。 真实场景 PSNR 28.249,虽然比基线高不少,但离合成场景的 40 还有大段距离。原文自己也说明真实数据的挑战(复杂反射、无纹理区域、稀疏观测)让所有方法的表现都下降。
十条归拢成一句话:它把"局部更新"这件事做得相当完整,代价是这套方法完全建立在"改动确实局限在一小块、而且能被检测出来"这个前提上,前提成立时它又快又准,前提不成立(全局光照变化、掩码漏判、细节极高的场景)它就没有退路。
再把十条翻成一组"要不要上"的判断。该上的场合:机器人/混合现实里"场景基本不变、偶发局部变动"的长周期更新;需要保留历史状态供审计或分析;需要实时浏览(帧率与 3DGS 同量级)。可以先不上的场合:场景每天都在大范围变化;光照条件会整体改变而你需要它跟着变;需要重建的是高细节的大尺度户外场景(如驾驶数据)。一个隐含的选型建议:既然它的上限由基底决定,那么在选用它之前,先用同样的照片跑一遍 3DGS 看看质量够不够:基底不够好,局部优化也救不回来。
最后说一句这篇论文在写作上最见功夫的地方:它把"前提"当成一等公民来处理。整篇文章里,"改动是局部的"这句话被反复兑现:第 3 到第 8 章是把它做成机制,第 11 章是把它推成两个新用法,第 12 章(以及附录里的图 12-1)是把它的代价画出来。对比之下,很多工作在给出方法之后只写一句"我们的方法假设场景变化是局部的",然后就不了了之。 差别在于:前者让读者能判断"我的场景算不算局部变化",后者不能。做技术选型时,能不能自己判断适用边界,往往比方法本身强多少分贝更重要。
顺着这一点,给打算复现或借鉴的人留三条实操建议。第一,先把"变化检测"这一段调稳再上线:本文后面所有机制都建立在"掩码不漏判"这个前提上,而 Table 4 显示原始掩码的准确率只有 0.609:如果你把它直接接到自己的管线上而不做三维投票,结果会比不做还差(3DGS+M 就是例子)。第二,把 N 与 K 这类阈值当成需要按场景调的旋钮:它们影响"变化区判多大",而这个尺度与场景的规模、物体的大小直接相关。第三,先量清楚你的场景里"一次改动涉及多少高斯":第 10 章那两张图说明收益与这个比例直接挂钩,如果改动占场景的比重很大,这套方法的优势会迅速消失。
13贡献块
- ① 一个通用的增量更新框架:面向高斯溅射表示做"持续且局部化"的更新,让场景重建随观测不断修正,而不必推倒重来。
- ② 一套局部高斯优化策略:把优化限制在三维空间的一小块里(包围球 + 漂移剪枝 + 局部核),既保证更新精确,又把它做快。
- ③ 在真实与合成场景上的系统验证:覆盖不同复杂度与不同改动类型,并额外演示批量更新与场景历史恢复两个任务。
- ④ 两套自建数据集:专为动态场景重建评测设计,含合成(三层级 × 四类改动)与真实(五个室内外场景)两部分,并公开。
- 四条串成一条因果链:先看清"局部性"这个前提(①)→ 把局部性做成可执行的机制(②)→ 用覆盖多种改动的数据验证它(③④)。最值得学的是第一步到第二步之间的翻译:"局部"落在实现上就是三个问题,范围怎么定(球)、越界怎么办(剪枝)、算力怎么省(局部核)。
14原文出处块
- 标题
- CL-Splats: Continual Learning of Gaussian Splatting with Local Optimization
- 作者
- Jan Ackermann、Jonas Kulhanek、Shengqu Cai、Haofei Xu、Marc Pollefeys、Gordon Wetzstein、Leonidas J. Guibas、Songyou Peng
- 机构
- ETH Zürich / Stanford University / CTU Prague / Google DeepMind
- 发表
- ICCV 2025 · arXiv:2506.21117v2 [cs.CV],19 页 13 图
- PDF 链接
- arXiv:2506.21117 [cs.CV]
- 许可
- arXiv 非独占分发许可(非 CC):arXiv 页面标注 arXiv.org perpetual, non-exclusive license。本站按分区既有做法逐件登记、逐图署名引用,存在许可残留风险(与篇目 02/03/09–11/13 同类)。