Skip to content

Commit 1ae2ae1

Browse files
committed
Restrict injected noise
1 parent 5ac0c86 commit 1ae2ae1

2 files changed

Lines changed: 45 additions & 15 deletions

File tree

src/gui/gui.jl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,15 @@ function handle_ui!(gui::GSGUI; frame_time)
755755

756756
CImGui.EndTable()
757757

758+
if gui.trainer.strategy isa MCMCStrategy
759+
strategy = gui.trainer.strategy
760+
max_cap_ref = Ref{Int32}(strategy.max_cap)
761+
CImGui.PushItemWidth(-100)
762+
if CImGui.InputInt("Max Gaussians", max_cap_ref, 100_000, 500_000)
763+
strategy.max_cap = max(length(gui.gaussians), Int(max_cap_ref[]))
764+
end
765+
end
766+
758767
image_filenames = gui.trainer.dataset.train_image_filenames
759768
CImGui.Text("Camera view:")
760769
CImGui.PushItemWidth(-1)

src/mcmc.jl

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
Densification from "3D Gaussian Splatting as Markov Chain Monte Carlo"
33
(as implemented in LichtFeld Studio): instead of heuristic clone/split/prune
44
& opacity resets, the number of Gaussians only grows (up to `max_cap`) and
5-
dead Gaussians (opacity ≤ `min_opacity`) are relocated onto alive ones with
6-
an opacity/scale correction (Eq. 9) that preserves the render.
5+
dead Gaussians (opacity ≤ `min_opacity` or scale above `max_scale`·extent)
6+
are relocated onto alive ones with an opacity/scale correction (Eq. 9) that
7+
preserves the render.
78
Position noise scaled by each Gaussian's covariance & opacity keeps the chain
89
exploring, while opacity & scale L1 regularization (see [`regularization_loss`](@ref))
910
provides the pressure that produces dead Gaussians to recycle.
@@ -14,6 +15,7 @@ mutable struct MCMCStrategy <: AbstractStrategy
1415

1516
max_cap::Int
1617
min_opacity::Float32
18+
max_scale::Float32 # Relative to the scene extent.
1719
start_refine::Int
1820
stop_refine::Int
1921
refine_every::Int
@@ -27,6 +29,7 @@ end
2729
function MCMCStrategy(;
2830
max_cap::Int = 2_000_000,
2931
min_opacity::Float32 = 0.005f0,
32+
max_scale::Float32 = 0.1f0,
3033
start_refine::Int = 500,
3134
stop_refine::Int = 25_000,
3235
refine_every::Int = 100,
@@ -38,7 +41,7 @@ function MCMCStrategy(;
3841
)
3942
MCMCStrategy(
4043
mcmc_binom_coefficients(n_max),
41-
max_cap, min_opacity, start_refine, stop_refine, refine_every,
44+
max_cap, min_opacity, max_scale, start_refine, stop_refine, refine_every,
4245
grow_factor, noise_lr, opacity_reg, scale_reg, n_max)
4346
end
4447

@@ -70,22 +73,30 @@ function post_train_step!(
7073
step % strategy.refine_every == 0
7174
if refining
7275
GPUArrays.unsafe_free!(cache)
73-
relocate_gaussians!(strategy, gs, optimizers)
76+
relocate_gaussians!(strategy, gs, optimizers; extent)
7477
add_gaussians!(strategy, gs, optimizers)
7578
end
76-
inject_noise!(strategy, gs, optimizers.points.lr)
79+
inject_noise!(strategy, gs, optimizers.points.lr; extent)
7780
return
7881
end
7982

8083
"""
81-
Move dead Gaussians (opacity ≤ `min_opacity`) onto alive ones sampled with
82-
probability ∝ opacity, correcting the target's opacity/scale (Eq. 9) so the
83-
render is preserved. Adam moments of every touched Gaussian are reset.
84+
Move dead Gaussians (opacity ≤ `min_opacity` or scale above `max_scale`·extent)
85+
onto alive ones sampled with probability ∝ opacity, correcting the target's
86+
opacity/scale (Eq. 9) so the render is preserved.
87+
Adam moments of every touched Gaussian are reset.
8488
"""
85-
function relocate_gaussians!(strategy::MCMCStrategy, gs::GaussianModel, optimizers)
89+
function relocate_gaussians!(
90+
strategy::MCMCStrategy, gs::GaussianModel, optimizers; extent::Float32,
91+
)
8692
o = Array(reshape(NU.sigmoid.(gs.opacities), :))
87-
dead = findall((strategy.min_opacity), o)
88-
alive = findall(>(strategy.min_opacity), o)
93+
# Oversized Gaussians join the dead set: MCMC has no other pruning
94+
# mechanism, so without this runaways survive until their opacity dies.
95+
log_max_scale = log(strategy.max_scale * extent)
96+
s_max = Array(vec(maximum(gs.scales; dims=1)))
97+
is_dead = @. (o strategy.min_opacity) | (s_max > log_max_scale)
98+
dead = findall(is_dead)
99+
alive = findall(!, is_dead)
89100
(isempty(dead) || isempty(alive)) && return 0
90101

91102
ids = multinomial_sample(o[alive], length(dead))
@@ -228,22 +239,29 @@ end
228239
"""
229240
Perturb positions with noise `∝ Σ·ξ`, gated to near-dead Gaussians by a steep
230241
opacity sigmoid & scaled by the (decaying) position learning rate.
242+
Each kick is capped at a fraction of the scene extent: `‖Σ·ξ‖ ∝ scale²`
243+
would otherwise teleport big near-dead Gaussians across the scene every step.
231244
"""
232-
function inject_noise!(strategy::MCMCStrategy, gs::GaussianModel, points_lr::Float32)
245+
function inject_noise!(
246+
strategy::MCMCStrategy, gs::GaussianModel, points_lr::Float32; extent::Float32,
247+
)
233248
n = length(gs)
234249
n == 0 && return
235250
isotropic = size(gs.scales, 1) == 1
251+
# Half the relocation size threshold: kicks stay small relative
252+
# to the biggest Gaussian allowed to survive relocation.
253+
max_kick = 0.5f0 * strategy.max_scale * extent
236254
_inject_noise!(get_backend(gs))(
237255
reinterpret(SVector{3, Float32}, gs.points),
238256
gs.opacities,
239257
isotropic ? gs.scales : reinterpret(SVector{3, Float32}, gs.scales),
240258
reinterpret(SVector{4, Float32}, gs.rotations),
241-
points_lr * strategy.noise_lr; ndrange=n)
259+
points_lr * strategy.noise_lr, max_kick; ndrange=n)
242260
return
243261
end
244262

245263
@kernel cpu=false inbounds=true function _inject_noise!(
246-
points, opacities, scales, rotations, lr::Float32,
264+
points, opacities, scales, rotations, lr::Float32, max_kick::Float32,
247265
)
248266
i = @index(Global)
249267
ξ = SVector{3, Float32}(randn(Float32), randn(Float32), randn(Float32))
@@ -257,5 +275,8 @@ end
257275
op = NU.sigmoid(opacities[i])
258276
# Cap the exponent: for opaque Gaussians `exp` overflows to `Inf`.
259277
factor = lr / (1f0 + exp(min(100f0 * op - 0.5f0, 80f0)))
260-
points[i] = points[i] .+ factor .* Σξ
278+
Δ = factor .* Σξ
279+
l = norm(Δ)
280+
l > max_kick &&= Δ .* (max_kick / l))
281+
points[i] = points[i] .+ Δ
261282
end

0 commit comments

Comments
 (0)