MVA-2021 / npm3d / hw4_reconstruction / code / reconstruction.py
reconstruction.py
Raw
#
#
#      0===========================================================0
#      |              TP4 Surface Reconstruction                   |
#      0===========================================================0
#
#
# ------------------------------------------------------------------------------------------
#
#      Jean-Emmanuel DESCHAUD - 02/02/2018
#


# Import numpy package and name it "np"
import numpy as np

# Import functions from scikit-learn
from sklearn.neighbors import KDTree

# Import functions to read and write ply files
from ply import write_ply, read_ply

# Import time package
import time

from skimage import measure

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection

import trimesh


# Hoppe surface reconstruction
def compute_hoppe(points,normals,scalar_field,grid_resolution,min_grid,size_voxel):

    # create the grid
    x_ = np.arange(grid_resolution + 1)
    y_ = x_.copy()
    z_ = x_.copy()
    x, y, z = np.meshgrid(x_, y_, z_, indexing='ij')
    grid = np.stack([x, y, z])
    grid = grid.reshape((3,len(x)**3)).T # reshape grid as Nx3 (necessary for tree search)
    grid = min_grid + size_voxel * grid

    # create kd tree to query more efficiently
    tree = KDTree(points)
    neighbors = tree.query(grid, return_distance=False)
    neighbors = neighbors.flatten()

    # compute hoppe function
    grid = np.sum(normals[neighbors]*(grid - points[neighbors]), axis=1)
    grid = grid.reshape((len(x), len(x), len(x))) # reshape grid as (res,res,res)

    scalar_field[:] = grid[:]
    return
    

# IMLS surface reconstruction
def compute_imls(points,normals,scalar_field,grid_resolution,min_grid,size_voxel,knn):

    # create the grid
    x_ = np.arange(grid_resolution + 1)
    y_ = x_.copy()
    z_ = x_.copy()
    x, y, z = np.meshgrid(x_, y_, z_, indexing='ij')
    grid = np.stack([x, y, z])
    grid = grid.reshape((3,len(x)**3)).T # reshape grid as Nx3 (necessary for tree search)
    grid = min_grid + size_voxel * grid

    # create kd tree to query more efficiently
    tree = KDTree(points)
    neighbors = tree.query(grid, k=knn, return_distance=False)

    # compute imls function
    h = 0.001
    diff = grid[:,None,:] - points[neighbors]
    theta = np.exp(-np.linalg.norm(diff, axis=2)**2/h**2)
    theta = np.clip(theta, 1e-8, np.inf)
    num = np.sum( np.sum(normals[neighbors] * diff, axis=2) * theta, axis=1 )
    denom = np.sum(theta, axis = 1)
    grid = num/denom
    grid = grid.reshape((len(x), len(x), len(x))) # reshape grid as (res,res,res)

    scalar_field[:] = grid[:]
    return
    

if __name__ == '__main__':

    t0 = time.time()
    
    # Path of the file
    file_path = '../data/bunny_normals.ply'

    # Load point cloud
    data = read_ply(file_path)

    # Concatenate data
    points = np.vstack((data['x'], data['y'], data['z'])).T
    normals = np.vstack((data['nx'], data['ny'], data['nz'])).T

	# Compute the min and max of the data points
    min_grid = np.amin(points, axis=0)
    max_grid = np.amax(points, axis=0)
				
	# Increase the bounding box of data points by decreasing min_grid and inscreasing max_grid
    min_grid = min_grid - 0.10*(max_grid-min_grid)
    max_grid = max_grid + 0.10*(max_grid-min_grid)

	# grid_resolution is the number of voxels in the grid in x, y, z axis
    grid_resolution = 100
    size_voxel = np.array([(max_grid[0]-min_grid[0])/grid_resolution,(max_grid[1]-min_grid[1])/grid_resolution,(max_grid[2]-min_grid[2])/grid_resolution])
	
	# Create a volume grid to compute the scalar field for surface reconstruction
    scalar_field = np.zeros((grid_resolution+1,grid_resolution+1,grid_resolution+1),dtype = np.float32)

	# Compute the scalar field in the grid
    compute_hoppe(points,normals,scalar_field,grid_resolution,min_grid,size_voxel)
    # compute_imls(points,normals,scalar_field,grid_resolution,min_grid,size_voxel,30)

	# Compute the mesh from the scalar field based on marching cubes algorithm
    verts, faces, normals_tri, values_tri = measure.marching_cubes_lewiner(scalar_field, level=0.0, spacing=(size_voxel[0],size_voxel[1],size_voxel[2]))
	
    # Export the mesh in ply using trimesh lib
    mesh = trimesh.Trimesh(vertices = verts, faces = faces)
    mesh.export(file_obj='../bunny_mesh_hoppe_30.ply', file_type='ply')
	
    print("Total time for surface reconstruction : ", time.time()-t0)