D2NeRF

repository·main·Indexed 41 days ago

https://github.com/chikayan/d2nerf

A codebase for self-supervised decoupling of dynamic and static objects from monocular video, implemented based on HyperNeRF. It includes tools for training models using Gin configuration, evaluating checkpoints, and processing custom datasets, including Kubric metadata conversion and multi-resolution image resizing.

Tokens
2.4K
Snippets
7
Records
10
Agent score
2%

What's inside D2NeRF

  1. Evaluate and render checkpoints with eval.py

    main

    To plot telemetry to Tensorboard and render checkpoints on the fly during or after training, run the eval.py script using the same arguments as the training command.

    Example Command:

    python eval.py \
        --base_folder $EXPERIMENT_PATH \
        --gin_bindings="data_dir='$DATASET_PATH'" \
        --gin_configs $CONFIG_PATH
    python eval.py \
        --base_folder $EXPERIMENT_PATH \
        --gin_bindings="data_dir='$DATASET_PATH'" \
        --gin_configs $CONFIG_PATH
  2. Train a D2NeRF model

    main

    To start training, you need a dataset and an experiment path. Use the train.py script with Gin configuration bindings to specify the data directory.

    Required Environment Variables/Paths:

    • DATASET_PATH: Path to your unzipped dataset.
    • EXPERIMENT_PATH: Path where you want to save the training results.
    • CONFIG_PATH: Path to a .gin configuration file.

    Example Command:

    export DATASET_PATH=/path/to/dataset
    export EXPERIMENT_PATH=/path/to/save/experiment/to
    export CONFIG_PATH=configs/rl/001.gin
    python train.py \
        --base_folder $EXPERIMENT_PATH \
        --gin_bindings="data_dir='$DATASET_PATH'" \
        --gin_configs $CONFIG_PATH
    export DATASET_PATH=/path/to/dataset
    export EXPERIMENT_PATH=/path/to/save/experiment/to
    export CONFIG_PATH=configs/rl/001.gin
    python train.py \
        --base_folder $EXPERIMENT_PATH \
        --gin_bindings="data_dir='$DATASET_PATH'" \
        --gin_configs $CONFIG_PATH
  3. Install and set up D2NeRF

    main

    D2NeRF requires Python 3.8 or higher. Follow these steps to set up your environment:

    1. Create a Conda environment:
      conda create --name d2nerf python=3.8
    2. Install dependencies:
      pip install -r requirements.txt
    3. Install the appropriate JAX distribution for your hardware (e.g., CUDA support):
      pip install --upgrade "jax[cuda]" -f https://storage.googleapis.com/jax-releases/jax_releases.html
    conda create --name d2nerf python=3.8
    pip install -r requirements.txt
    pip install --upgrade "jax[cuda]" -f https://storage.googleapis.com/jax-releases/jax_releases.html
  4. Configure D2NeRF using Gin

    main

    D2NeRF uses Gin for configuration. The project provides several preset configuration directories:

    • configs/decompose/: Template configurations defining shared configurations for NeRF and HyperNeRF.
    • configs/rl/: Configurations for experiments on real-life scenes.
    • configs/synthetic/: Configurations for experiments on synthetic scenes.

    For detailed documentation on specific configuration parameters, refer to config.py in the repository.

  5. Generate test camera orbits

    main

    To evaluate model performance on unseen views, you can generate a circular orbit of test cameras.

    Steps:

    1. Calculate the center of all training camera positions.
    2. Define a coordinate system (axis_a, axis_b) orthogonal to the vector from the center to the cameras.
    3. Determine a radius for the orbit (e.g., mean_distance * RADIUS_RATIO).
    4. Use hypernerf.camera.Camera.from_json to load a base camera, then update its position using trigonometric functions and use .look_at() to orient it towards the scene center [0,0,0].
    # Generate N_TEST cameras in an orbit
    RADIUS_RATIO = 0.5
    N_TEST = 100
    # ... (axis calculation logic) ...
    
    for i, ang in enumerate(angles):
        new_pos = center + radius * np.cos(ang) * axis_a + radius * np.sin(ang) * axis_b
        
        test_cam = camera_model.Camera.from_json(str(camera_gt_dir / rgb_list[0].stem) + '.json')
        test_cam.position = new_pos
        test_cam.look_at(new_pos, np.array([0,0,0]), np.array([1,0,0]))
    
        with open(str(camera_test_gt_dir / f'{i:06d}.json'), 'w') as f:
            json.dump(test_cam.to_json(), f, indent=2)
  6. Resize Kubric images into multiple scales

    main

    To prepare datasets for multi-resolution training, you can downsample raw RGB and mask images into different scales (e.g., 1x, 2x, 4x, 8x). The process uses cv2.INTER_AREA interpolation to prevent moiré artifacts and ensures image dimensions are divisible by the scale factor using a make_divisible utility.

    Workflow:

    1. Identify the raw image directories (rgb-raw, mask-raw, static-rgb-raw).
    2. Define the target scales (e.g., [1, 2, 4, 8]).
    3. Iterate through images, applying make_divisible to the original size based on the maximum scale, then downsampling and saving to scale-specific subdirectories (e.g., rgb/2x/).
    # Example scale configuration
    image_scales = [1, 2, 4, 8]
    
    # The process involves iterating through raw files and saving to scale-specific folders
    for image_path in Path(tmp_rgb_raw_dir).glob('*.png'):
        image = make_divisible(imageio.imread(image_path), max(image_scales))
        for scale in image_scales:
            save_image(
                rgb_dir / f'{scale}x/{image_path.stem}.png',
                image_to_uint8(downsample_image(image, scale))
            )
  7. Convert Kubric metadata to ground truth camera JSON

    main

    Kubric camera metadata must be converted into a specific JSON format for use in NeRF training. This involves:

    1. Quaternion Conversion: Changing the format from [w, x, y, z] to [x, y, z, w] for compatibility with scipy.spatial.transform.Rotation.
    2. Orientation: Calculating the world-to-camera rotation matrix (the transpose of the rotation matrix derived from the quaternion).
    3. Focal Length: Scaling the focal length based on the image size and sensor width: focal_length * image_size[0] / sensor_width.
    4. Camera Parameters: Setting principal_point (typically image center), skew (0), pixel_aspect_ratio (1), and distortion parameters to zero.
    # Core conversion logic snippet
    quat = k_meta['camera']['quaternions'][i]
    # Convert [w,x,y,z] -> [x,y,z,w]
    quat = [quat[1], quat[2], quat[3], quat[0]]
    rot = R.from_quat(quat)
    matrix = rot.as_matrix()
    
    meta['orientation'] = matrix.T.tolist()
    meta['position'] = k_meta['camera']['positions'][i]
    meta['focal_length'] = k_meta['camera']['focal_length'] * image_size[0] / k_meta['camera']['sensor_width']
    meta['principal_point'] = [image_size[0]/2, image_size[1]/2]
    meta['image_size'] = image_size
  8. Generate scene_gt.json

    main

    The scene_gt.json file defines the spatial bounds and scaling of the scene. It requires:

    • scale: A scaling factor derived from the 3D bounding box of the objects (e.g., 1.0 / sqrt(sum(bbox_diff**2))).
    • center: The 3D center of the scene (often [0, 0, 0]).
    • bbox: The 3D bounding box corners [[min_x, min_y, min_z], [max_x, max_y, max_z]].
    • near and far: The near and far clipping planes for ray sampling.
    # Example scene info generation
    bbox_corners = np.array([[0, 0, 0], [50, 50, 10]])
    scene_scale = 1.0 / np.sqrt(np.sum((bbox_corners[1] - bbox_corners[0]) ** 2))
    scene_center = np.array([0., 0., 0.])
    
    with scene_json_path.open('w') as f:
        json.dump({
            'scale': scene_scale,
            'center': scene_center.tolist(),
            'bbox': bbox_corners.tolist(),
            'near': 0.1,
            'far': 1.0,
        }, f, indent=2)
  9. Generate dataset.json and metadata.json

    main

    The parser generates two essential JSON files for dataset management:

    1. dataset.json: Contains the total count of images, ids (filenames without extensions), and splits for train_ids and val_ids.
    2. metadata.json: Maps each image_id to its corresponding warp_id, appearance_id, and camera_id. For standard datasets, these are often set to the index of the image in the training set.

    For freeze tests, dataset.json is configured such that all images are in the val_ids set, and metadata.json maps each image_id to a specific freeze_frame ID.