3D Scene Densification Strategy

Studies how clone, split, prune, reset, relocation, and sampling policies affect novel-view scene reconstruction.

Vision & Generationgsplat
cv-3dgs-densification

Description

3D Gaussian Splatting Densification Strategy

Objective

Design a densification strategy for 3D Gaussian Splatting (3DGS) that improves novel view synthesis quality on real-world scenes under a fixed training and rendering pipeline.

Background

3D Gaussian Splatting (Kerbl et al., SIGGRAPH 2023) represents scenes as collections of anisotropic 3D Gaussians optimized via differentiable rasterization. A central component of training is the densification strategy, which controls how Gaussians are added, split, pruned, or otherwise reorganized during optimization. Common operations include:

  • Clone small Gaussians in under-reconstructed regions.
  • Split large Gaussians into smaller ones to recover finer detail.
  • Prune transparent or oversized Gaussians.
  • Reset opacities periodically to encourage pruning of redundant Gaussians.

Recent work proposes various refinements:

  • AbsGS (Ye et al., arXiv:2404.10484) — homodirectional view-space gradient using the absolute value of per-pixel sub-gradients to overcome over-reconstruction caused by gradient cancellation.
  • Mini-Splatting (Fang & Wang, arXiv:2403.14166) — blur-aware splitting and importance-weighted stochastic sampling for Gaussian count control.
  • 3DGS-MCMC (Kheradmand et al., NeurIPS 2024 Spotlight, arXiv:2404.09591) — treats densification as Markov-Chain Monte Carlo sampling, replacing cloning with a relocation step that preserves the sampled distribution.
  • Taming-3DGS (Mallick et al., SIGGRAPH Asia 2024, arXiv:2406.15643) — budgeted per-step densification controlled by maximum gradient blending.
  • EDC: Efficient Density Control (Deng et al., arXiv:2411.10133) — long-axis splitting with explicit child-Gaussian opacity control plus recovery-aware pruning.

Implementation Contract

Implement a CustomStrategy class in custom_strategy.py. The strategy controls the full lifecycle of Gaussians during training via two hooks called by the training loop:

@dataclass
class CustomStrategy(Strategy):
    def initialize_state(self, scene_scale: float = 1.0) -> Dict[str, Any]:
        # Initialize running statistics for the strategy.
        ...

    def step_pre_backward(self, params, optimizers, state, step, info):
        # Called BEFORE loss.backward(). Use to retain gradients.
        ...

    def step_post_backward(self, params, optimizers, state, step, info, packed=False):
        # Called AFTER loss.backward() and optimizer.step().
        # Implement densification / pruning logic here.
        ...

Available Operations (gsplat.strategy.ops)

  • duplicate(params, optimizers, state, mask) — clone selected Gaussians.
  • split(params, optimizers, state, mask) — split selected Gaussians (sample 2 new positions from the covariance).
  • remove(params, optimizers, state, mask) — remove selected Gaussians.
  • reset_opa(params, optimizers, state, value) — reset all opacities to a value.
  • relocate(params, optimizers, state, mask, binoms, min_opacity) — relocate dead Gaussians on top of live ones.
  • sample_add(params, optimizers, state, n, binoms, min_opacity) — add new Gaussians sampled from the opacity distribution.
  • inject_noise_to_position(params, optimizers, state, scaler) — perturb positions with Gaussian noise.

Available Information

The info dict passed in by the rasterizer contains:

  • means2d — 2D projected means (with .grad after backward).
  • width, height — image dimensions.
  • n_cameras — number of cameras in the batch.
  • radii — screen-space radii per Gaussian.
  • gaussian_ids — which Gaussians are visible.

The params dict contains:

  • means[N, 3] positions.
  • scales[N, 3] log-scales (use torch.exp(...) for actual scales).
  • quats[N, 4] rotation quaternions.
  • opacities[N] logit-opacities (use torch.sigmoid(...) for actual opacities).
  • sh0, shN — spherical-harmonic colour coefficients.

Fixed Pipeline

The following are FIXED across all strategies and must not be changed:

  • Renderer: gsplat CUDA rasterizer.
  • Optimizer: AdamW with per-parameter learning rates.
  • Photometric loss: 0.8 * L1 + 0.2 * SSIM per training step.
  • Training: 30,000 steps per scene.
  • SH degree: 3 (increased gradually during training).

Baselines

BaselineDescription
absgradgsplat DefaultStrategy with the AbsGS absolute-gradient criterion (Ye et al., arXiv:2404.10484).
tamingTaming-3DGS budgeted densification with max-grad blending (Mallick et al., arXiv:2406.15643), combined with the AbsGS gradient and the revised opacity formula.
edcTaming densification combined with EDC long-axis splitting and recovery-aware pruning (Deng et al., arXiv:2411.10133).

Evaluation

Evaluation uses Mip-NeRF 360 scenes (Barron et al., 2022) with every 8th image held out for testing. Metrics:

MetricDirectionDescription
PSNRhigher is betterPeak signal-to-noise ratio (primary metric).
SSIMhigher is betterStructural similarity.
LPIPSlower is betterLearned perceptual similarity.

Scoring uses per-scene PSNR. The contribution should be a transferable densification rule, not a change to the renderer, photometric loss, optimizer, dataset, or evaluation protocol.

Code

custom_strategy.py
EditableRead-only
1"""Custom densification strategy for 3D Gaussian Splatting.
2
3This file defines the CustomStrategy class that controls how Gaussians
4are added, split, and pruned during per-scene optimization.
5"""
6
7from dataclasses import dataclass
8from typing import Any, Dict, Tuple, Union
9
10import math
11import torch
12from gsplat.strategy.base import Strategy
13from gsplat.strategy.ops import (
14 duplicate, split, remove, reset_opa,
15 relocate, sample_add, inject_noise_to_position,

Method Summary

Auto-summarized from each method's code by an LLM reviewer — not the model's original output. Browse via the picker below; the Code section is independent.
Baselines
Agents
Claude Opus 4.6·Pseudocodehigh

Opacity-Gated, Annealed Densification

AbsGrad + (avg, max)-blended growth signal with an annealed threshold and an opacity gate; uses revised-opacity split.

1. for each step, accumulate giL/μi2Dg_i \leftarrow |\partial L / \partial \mu^{2D}_i| (AbsGS) → grad2d, grad2d_max
2. every refine_every steps after warmup:
3. combinedi=0.65gi+0.35maxgi\mathrm{combined}_i = 0.65\,\overline{g}_i + 0.35\,\max g_i
4. τgrow_grad2d(1.30.5progress)\tau \leftarrow \mathrm{grow\_grad2d}\,(1.3 - 0.5\cdot \mathrm{progress}) // annealed coarse→fine
5. high =(combined>τ)(σ(oi)>0.05)= (\mathrm{combined} > \tau) \wedge (\sigma(o_i) > 0.05) // opacity gate
6. duplicate small ∧ high; split large ∧ high with revised_opacity=True
7. prune σ(oi)<0.005\sigma(o_i) < 0.005 or oversized scale>0.1s\mathrm{scale} > 0.1\cdot s
8. reset opacity to 2prune_opa2\,\mathrm{prune\_opa} every 3000 steps; stop refining at 19k
Δ vs. baselineAdds two pieces on top of the Taming/AbsGS template: (i) a linear time-annealed growth threshold τ(t) = grow_grad2d·(1.3 − 0.5·progress), and (ii) an opacity gate σ(o) > 0.05 that suppresses growth on barely-visible Gaussians; everything else (avg/max blend, revised_opacity split, prune rules) inherits from Taming.
grow_grad2d=5e-4avg_weight=0.65max_weight=0.35opacity_gate=0.05anneal range=[1.3, 0.8] × baserefine_stop_iter=19000reset_every=3000prune_opa=0.005Recovers Taming-3DGS at progress=0.6, opacity gate off.

Results