nvdiffrec icon indicating copy to clipboard operation
nvdiffrec copied to clipboard

Calculating Chamfer Loss

Open JiyouSeo opened this issue 2 years ago • 2 comments

Hello, I was working to calculate Chamfer Loss results on NeRF realistic synthetic dataset. And In your paper, Table 8, You compared chamfer loss result to the meshs produced by PhySG. Can you share the code or library you used when you calculate chamfer loss?

JiyouSeo avatar Aug 17 '22 04:08 JiyouSeo

Hello,

We used the https://github.com/otaheri/chamfer_distance package for chamfer distance, and trimesh to load meshes & sample point clouds. We used 2.5M points for the results in the paper, but you can reduce that value to improve performance when experimenting. I had to make local changes to the chamfer_distance package to make it run (on Windows), but unfortunately cannot share the modifications here. Optionally you can use Kaolin which has a similar chamfer function.

It's a good idea to sanity check the meshes in some 3D modeling program. Different projects use different coordinate system conventions leading to mismatch in rotation/scaling.

import argparse

import torch
import numpy as np

# pip install trimesh[all]
import trimesh

# https://github.com/otaheri/chamfer_distance
from chamfer_distance import ChamferDistance

def as_mesh(scene_or_mesh):
    if isinstance(scene_or_mesh, trimesh.Scene):
        assert len(scene_or_mesh.geometry) > 0
        mesh = trimesh.util.concatenate(
            tuple(trimesh.Trimesh(vertices=g.vertices, faces=g.faces)
                for g in scene_or_mesh.geometry.values()))
    else:
        assert isinstance(scene_or_mesh, trimesh.Trimesh)
        mesh = scene_or_mesh
    return mesh

def sample_mesh(m, n):
    vpos, _ = trimesh.sample.sample_surface(m, n)
    return torch.tensor(vpos, dtype=torch.float32, device="cuda")

if __name__ == "__main__":
    chamfer_dist = ChamferDistance()

    parser = argparse.ArgumentParser(description='Chamfer loss')
    parser.add_argument('-n', type=int, default=2500000)
    parser.add_argument('mesh', type=str)
    parser.add_argument('ref', type=str)
    FLAGS = parser.parse_args()

    mesh = as_mesh(trimesh.load(FLAGS.mesh))
    ref = as_mesh(trimesh.load(FLAGS.ref))

    # Make sure l=1.0 maps to 1/10th of the AABB. https://arxiv.org/pdf/1612.00603.pdf
    scale = 10.0 / np.amax(np.amax(ref.vertices, axis=0) - np.amin(ref.vertices, axis=0))
    ref.vertices = ref.vertices * scale
    mesh.vertices = mesh.vertices * scale

    # Sample mesh surfaces
    vpos_mesh = sample_mesh(mesh, FLAGS.n)
    vpos_ref = sample_mesh(ref, FLAGS.n)

    dist1, dist2 = chamfer_dist(vpos_mesh[None, ...], vpos_ref[None, ...])
    loss = (torch.mean(dist1) + torch.mean(dist2)).item()
    
    print("[%7d tris]: %1.5f" % (mesh.faces.shape[0], loss))

JHnvidia avatar Aug 17 '22 08:08 JHnvidia

Thank you for kindly replying !

JiyouSeo avatar Aug 18 '22 07:08 JiyouSeo

Hello, thanks for your work! I was wondering how we can obtain the reference mesh ref = as_mesh(trimesh.load(FLAGS.ref)) from the NeRF synthetic dataset, as I did not find that information from the paper.

weihan1 avatar Feb 06 '24 16:02 weihan1

Hi @weihan1,

The NeRF paper released their data files: https://drive.google.com/drive/folders/128yBriW1IG_3NJ5Rp7APSTZsJqdJdfc1

You can find the synthetic NeRF scenes in the blend_files.zip file, and you'll manually have to export them as .obj files from blender if you want to load them using the script.

JHnvidia avatar Feb 07 '24 10:02 JHnvidia