cv-3dgs-densification
Description
3D Gaussian Splatting Densification Strategy Design
Objective
Design a densification strategy for 3D Gaussian Splatting (3DGS) that achieves the best novel view synthesis quality on real-world scenes.
Background
3D Gaussian Splatting represents scenes as collections of 3D Gaussians optimized via differentiable rendering. A critical component is the densification strategy, which controls how Gaussians are added, split, and pruned during optimization:
- Clone: Duplicate small Gaussians in under-reconstructed regions
- Split: Divide large Gaussians into smaller ones for finer detail
- Prune: Remove transparent or oversized Gaussians
- Reset: Periodically reset opacities to encourage pruning of unneeded Gaussians
Recent work has proposed various improvements:
- AbsGS: Uses absolute gradients instead of average for better detail recovery
- Mini-Splatting: Blur-aware forced splitting + importance-based pruning
- MCMC 3DGS: Treats densification as Markov Chain Monte Carlo sampling
- New Split (Cao et al.): Mathematically consistent Gaussian splitting
Task
Implement a CustomStrategy class in custom_strategy.py. Your strategy controls
the full lifecycle of Gaussians during training via two hooks:
Editable Region
@dataclass
class CustomStrategy(Strategy):
def initialize_state(self, scene_scale: float = 1.0) -> Dict[str, Any]:
# Initialize running statistics for your 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().
# This is where you implement densification logic.
...
Available Operations (from gsplat.strategy.ops)
duplicate(params, optimizers, state, mask)— Clone selected Gaussianssplit(params, optimizers, state, mask)— Split selected Gaussians (sample 2 new positions from covariance)remove(params, optimizers, state, mask)— Remove selected Gaussiansreset_opa(params, optimizers, state, value)— Reset all opacities to a valuerelocate(params, optimizers, state, mask, binoms, min_opacity)— Teleport dead Gaussians to live onessample_add(params, optimizers, state, n, binoms, min_opacity)— Add new Gaussians sampled from opacity distributioninject_noise_to_position(params, optimizers, state, scaler)— Perturb positions
Available Information
The info dict from rasterization contains:
means2d: 2D projected means (with.gradafter backward)width,height: Image dimensionsn_cameras: Number of cameras in batchradii: Screen-space radii per Gaussiangaussian_ids: Which Gaussians are visible
The params dict contains Gaussian parameters:
means: [N, 3] positionsscales: [N, 3] log-scalesquats: [N, 4] rotation quaternionsopacities: [N] logit-opacities (usetorch.sigmoid()for actual opacity)sh0,shN: Spherical harmonic coefficients for color
Architecture (Fixed)
- Renderer: gsplat CUDA rasterizer
- Optimizer: AdamW with per-parameter learning rates
- Loss: 0.8 × L1 + 0.2 × SSIM (fixed)
- Training: 30,000 steps per scene
- SH degree: 3 (increased gradually)
Evaluation
Novel view synthesis quality on held-out test views:
| Metric | Direction | Description |
|---|---|---|
| PSNR | higher is better | Peak signal-to-noise ratio (primary metric) |
| SSIM | higher is better | Structural similarity index |
| LPIPS | lower is better | Learned perceptual similarity (AlexNet) |
Evaluated on 4 Mip-NeRF 360 scenes (garden, bicycle, bonsai, stump) with every 8th image held out for testing.
Baselines
| Name | Strategy | Description |
|---|---|---|
| default | Clone/Split/Prune | Original 3DGS: gradient-threshold densification with periodic opacity reset |
| mcmc | Relocate/Add/Noise | MCMC sampling: teleport dead Gaussians, add new ones, inject position noise |
| absgrad | AbsGS variant | Like default but uses absolute gradients for better fine detail recovery |
| taming | Combined | AbsGS + Taming-3DGS max-grad blend + New Split (revised_opacity) |
Reference Baseline PSNRs (MipNeRF360)
| baseline | garden | bicycle | bonsai | stump |
|---|---|---|---|---|
default | 29.677 | 26.903 | 33.079 | 27.761 |
mcmc | 29.130 | 26.029 | 32.929 | 25.609 |
absgrad | 29.745 | 27.026 | 33.051 | 27.850 |
taming | 30.044 | 27.141 | 33.353 | 27.794 |
Your goal: beat the best baseline per-scene. taming leads on 3 of 4 scenes
(garden / bicycle / bonsai); absgrad still leads stump by a narrow margin.
Code
1"""Custom densification strategy for 3D Gaussian Splatting.23This file defines the CustomStrategy class that controls how Gaussians4are added, split, and pruned during per-scene optimization.5"""67from dataclasses import dataclass8from typing import Any, Dict, Tuple, Union910import math11import torch12from gsplat.strategy.base import Strategy13from gsplat.strategy.ops import (14duplicate, split, remove, reset_opa,15relocate, sample_add, inject_noise_to_position,
Results
| Model | Type | best psnr ↑ | ssim ↑ | lpips ↓ |
|---|---|---|---|---|
| absgrad | baseline | - | - | - |
| default | baseline | - | - | - |
| mcmc | baseline | - | - | - |
| taming | baseline | - | - | - |
| claude-opus-4.6 | vanilla | - | - | - |
| deepseek-reasoner | vanilla | - | - | - |
| gemini-3.1-pro-preview | vanilla | 29.936 | 0.838 | 0.082 |
| qwen3.6-plus | vanilla | 26.907 | 0.536 | 0.478 |
| claude-opus-4.6 | agent | - | - | - |
| deepseek-reasoner | agent | - | - | - |
| gemini-3.1-pro-preview | agent | - | - | - |
| qwen3.6-plus | agent | - | - | - |