Skip to content

Tomoya-Matsubara/msvs

Repository files navigation

MSVS: Multi-Shell Viewpoint Sampling for Comprehensive Evaluation of 3D Watermarking

MSVS Overview

ICASSP 2026
Project Page

Tomoya Matsubara, Lingfeng Yao, Chenpei Huang, Miao Pan, Hideo Saito

This repository is the official implementation of the paper "MSVS: Multi-Shell Viewpoint Sampling for Comprehensive Evaluation of 3D Watermarking", accepted at ICASSP 2026.

Abstract

Reliable copyright protection for 3D assets requires watermark verification across arbitrary viewpoints. However, existing evaluations rely on dataset splits or ad-hoc camera samplings that overlook failure cases. We introduce Multi-Shell Viewpoint Sampling (MSVS), which ensures uniform, distance-aware coverage via concentric, visibility-bounded shells and spherical sampling. MSVS exposes significantly low bit accuracy even on high-quality renderings that an adversary would prefer. Based on this evaluation standard, we further propose a greedy subsampling strategy that selects training views guided by a locality-aware kernel. For 3D-GSW, greedy subsampling improves MSVS bit accuracy by +0.020 on the Blender dataset and +0.056 on the Stanford-ORB dataset over random selection, and the gains persist under common image attacks. MSVS establishes a comprehensive benchmark for 3D watermark evaluation, while greedy subsampling provides an efficient strategy to enhance watermark protection.

Table of Contents
  1. About the Project
  2. Getting Started
    1. Prerequisites
    2. Installation
  3. Evaluation Pipeline
    1. Render the Scene with Gaussian Splatting
    2. Compute the Bit Accuracy of the Rendered Images
    3. (Optional) Fast Version
  4. Support for Non-Gaussian Splatting Methods
  5. Subsampling
  6. Random Subsampling
  7. Aggregate Results Across Multiple Scenes
  8. Example Usage

1. About the Project

MSVS is a comprehensive evaluation algorithm for 3D watermarking methods based on bit-string embedding into 3D assets.

Unlike existing evaluation methods that rely on dataset splits or ad-hoc camera samplings, MSVS samples viewpoints uniformly in 3D space, which helps to evaluate the watermarking robustness more comprehensively.

2. Getting Started

2.1. Prerequisites

  • uv: Python package manager
  • CUDA 11.8

Note

This code has been tested with CUDA 11.8. For other versions, please modify the tool.uv.index in pyproject.toml accordingly.

2.2. Installation

  1. Clone this repository:

    git clone https://github.com/Tomoya-Matsubara/msvs.git
    cd msvs
  2. Install the required packages using uv:

    uv sync

Alternatively, you can add MSVS to an existing uv environment with:

uv add git+https://github.com/Tomoya-Matsubara/msvs

3. Evaluation Pipeline

Following the original 3DGS implementation, the evaluation pipeline consists of the following steps:

  1. Render the scene with Gaussian splatting.
  2. Compute the bit accuracy of the rendered images.

3.1. Render the Scene with Gaussian Splatting

Scenes can be rendered using the render_and_save_scene_images_with_gaussians() function provided in the render_gaussians module:

import pathlib

import torch

from msvs.render import render_gaussians
from msvs.sampling import point_sampler

# TODO: Replace with actual paths
DATASET_DIRECTORY: pathlib.Path = ...
"""Path to the dataset directory (e.g., dataset/nerf_synthetic/chair)."""
MODEL_DIRECTORY: pathlib.Path = ...
"""Path to the model directory (e.g., gaussian_models/nerf_synthetic/chair)."""
SAVE_DIRECTORY: pathlib.Path = ...
"""Path to the directory where rendered images will be saved (e.g., outputs)."""


device = torch.device("cuda")
sampling_config = point_sampler.MultiShellSampleConfig(
    seed=0, device=device, auto_radius=True
)
render_gaussians.render_and_save_scene_images_with_gaussians(
    dataset_directory=DATASET_DIRECTORY,
    model_directory=MODEL_DIRECTORY,
    save_directory=SAVE_DIRECTORY,
    scale_spec=1,
    load_iteration=7000,
    evaluate=True,
    debug=False,
    compensate_exposure=False,
    resolution_scales=[1.0],
    inverse_depth_directory_name=None,
    device=device,
    background_color="black",
    compute_3d_covariances_python=False,
    convert_shs_python=False,
    anti_aliasing=False,
    separate_sh=False,
    sample_config=sampling_config,
)

This function renders the scene using the specified Gaussian splatting parameters and saves the output images to the designated directory. By setting sampling_config, an additional dataset split named sampled is created, allowing for more exhaustive coverage of the scene during rendering and evaluation.

3.2. Compute the Bit Accuracy of the Rendered Images

Once the scene has been rendered, the next step is to compute the bit accuracy of the rendered images.

This can be done using the evaluate_and_save_model_metrics() function in the metric module:

import torch

from msvs import metrics


def compute_bit_accuracy(image: torch.Tensor) -> float:
    # Define your bit accuracy computation logic here
    bit_accuracy = ...
    return bit_accuracy

metrics.evaluate_and_save_model_metrics(
    model_output_directory=SAVE_DIRECTORY,
    compute_bit_accuracy=compute_bit_accuracy,
)

This function computes the bit accuracy of the rendered images and saves the evaluation metrics to the specified directory.

Note

Because watermark decoders (architectures, parameters, and I/O formats) vary by method, you need to implement compute_bit_accuracy() accordingly.

3.3. (Optional) Fast Version

The two steps presented above will save rendered images as well as evaluation metrics to the specified directory for easier inspection and debugging. However, in most cases, the involvement of disk I/O can slow down the evaluation process.

If you prefer a faster evaluation without saving intermediate images to disk, you can use the evaluate_and_save_model_metrics_fast() function in the metrics_fast module:

import pathlib

import torch

from msvs import metrics_fast
from msvs.sampling import point_sampler

# TODO: Replace with actual paths
DATASET_DIRECTORY: pathlib.Path = ...
"""Path to the dataset directory (e.g., dataset/nerf_synthetic/chair)."""
MODEL_DIRECTORY: pathlib.Path = ...
"""Path to the model directory (e.g., gaussian_models/nerf_synthetic/chair)."""
SAVE_DIRECTORY: pathlib.Path = ...
"""Path to the directory where evaluation metrics will be saved (e.g., outputs)."""


def compute_bit_accuracy(image: torch.Tensor) -> float:
    # Define your bit accuracy computation logic here
    bit_accuracy = ...
    return bit_accuracy


device = torch.device("cuda")
sampling_config = point_sampler.MultiShellSampleConfig(
    seed=0, device=device, auto_radius=True
)

metrics_fast.evaluate_and_save_model_metrics_fast(
    dataset_directory=DATASET_DIRECTORY,
    model_directory=MODEL_DIRECTORY,
    save_directory=SAVE_DIRECTORY,
    scale_spec=1,
    load_iteration=7000,
    compute_bit_accuracy=compute_bit_accuracy,
    evaluate=True,
    debug=False,
    compensate_exposure=False,
    resolution_scales=[1.0],
    inverse_depth_directory_name=None,
    device=device,
    background_color="black",
    compute_3d_covariances_python=False,
    convert_shs_python=False,
    anti_aliasing=False,
    separate_sh=False,
    sample_config=sampling_config,
    save_sampled_cameras=False,
)

4. Support for Non-Gaussian Splatting Methods

The current implementation targets Gaussian splatting, but it can be extended to other neural rendering techniques (e.g., NeRF, mip-NeRF) by subclassing the following base classes:

  • renderer_base.RendererBase: Base class for all renderer implementations.
  • scene.Scene: Base class for all scene representations.

After providing concrete subclasses, you can render scenes using render_and_save_scene_images() from the render module:

from msvs import scene
from msvs.render import render, renderer_base


# Your concrete implementations must subclass the base classes and
# fulfill their contracts.
class MyMethodRenderer(renderer_base.RendererBase):
    ...
    # Implement all required abstract methods defined by RendererBase.


class MyMethodScene(scene.Scene):
    ...
    # Scene is not an abstract class. Just make sure to implement member variables.

renderer: renderer_base.RendererBase = MyMethodRenderer()
target_scene: scene.Scene = MyMethodScene()

render.render_and_save_scene_images(
    renderer=renderer, target_scene=target_scene, save_directory=SAVE_DIRECTORY
)

Bit accuracy computation follows the same process described above.

5. Subsampling

In addition to the evaluation pipeline, this repository provides utilities for subsampling cameras to augment the training set and efficiently improve watermark coverage.

Once the sampled split is prepared, call subsample_cameras() to choose a subset of cameras for training.

from msvs.sampling import subsampler


subsampler.subsample_cameras(
    sampled_directory=SAVE_DIRECTORY / "sampled",
    dataset_directory=DATASET_DIRECTORY,
    scale_spec=1,
    num_subsample=200
)

The subsampled cameras can be loaded using load_subsampled_cameras() in the camera_loader module:

from msvs.sampling import camera_loader


loaded_cameras = camera_loader.load_subsampled_cameras(
    sampled_directory=SAVE_DIRECTORY / "sampled",
    is_nerf_synthetic=True,
    device=torch.device("cuda"),
)

6. Random Subsampling

As a simple baseline, random subsampling is also supported. To randomly select a subset of cameras from the sampled split, use the random_load_sampled_cameras() function in the camera_loader module:

from msvs.sampling import camera_loader


loaded_cameras = camera_loader.random_load_sampled_cameras(
    sampled_directory=SAVE_DIRECTORY / "sampled",
    num_sample=200,
    is_nerf_synthetic=True,
    device=torch.device("cuda"),
    seed=42,
)

7. Aggregate Results Across Multiple Scenes

Sometimes, a dataset consists of multiple scenes (e.g., the NeRF synthetic dataset). In such cases, you may want to aggregate evaluation metrics across all scenes.

Suppose you have evaluated multiple scenes and stored their results in a common directory structure like this:

results/
├── chair/
│   ├── sampled/
│   │   └── result.json
│   ├── test
│   │   └── result.json
│   └── train
│       └── result.json
├── drums
│   └── ... (similar structure as chair)
...
└── ship
    └── ...

Then, you can use the aggregate_dataset_results() function in the results_utils module to aggregate the metrics:

from msvs.utils import results_utils


dataset_results_directory = ...
results_utils.aggregate_dataset_results(dataset_results_directory)

This function reads the JSON files from each split of each scene, computes the average metrics, and saves the aggregated results in the same results directory.

8. Example Usage

For more information on how to use this repository, please refer to the examples/ directory, which contains an example script for MSVS evaluation.

About

[IEEE ICASSP 2026] Official implementation of the paper "MSVS: Multi-Shell Viewpoint Sampling for Comprehensive Evaluation of 3D Watermarking"

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors