There are different models that represent 3d shapes. One of them, common in medical imagging, material science, deep learning of 3d objects, is the voxel model. Voxel data is defined as a binary 3d array. The positions of value 1 point out how the 3d shape is distributed in space. The elements of a voxel data set are called voxels. They are 3d equivalent to 2d pixels.
For deep learning purposes a 3d mesh (3d polygonization/triangularization) of an object is transformed into a voxel data set. Such a process is called voxelization. The voxelization does not render the voxels, it only generates a database of voxel data.
The term voxelization was coined by A Kaufmann and E Shimony in 3D scan-conversion algorithms for voxel-based graphics..
There is an executable 3D mesh voxelizer program, called binvox
, https://www.patrickmin.com/binvox/,
that converts a Wavefront obj
, off
, ply
or stl
3d mesh to a file with the extension binvox.
The command:
binvox mesh3d.ext -d 64
converts a 3d mesh file having one of the above extensions (obj, off, ply, stl) into a voxel data set that converted to a numpy array has the shape (64, 64, 64). To get all options for binvox run it without without any argument.
Due to a simple compression, a binvox file format has a reduced size in comparison to its array version.
Binvox files can be read in Python, via binvox_rw
, https://github.com/dimatura/binvox-rw-py.
In this notebook we illustrate how a binvox file is read and converted to a binary array, and the corresponding voxel data is rendered placing at some position in space a cube for each voxel. Extracting the vertices of all cubes, and retaining a single copy of each vertex, we get the vertices of a voxel data meshing. Then fixing a a triangulation of the template cube we can extract the triangles corresponding to the unique vertex list, for all cubes. With these data we represent the voxel model as a Plotly.mesh3d.
import numpy as np
import plotly.graph_objs as go
import binvox_rw
filename= 'chair.binvox' #https://github.com/empet/Datasets/tree/master/binvoxf
with open(filename, 'rb') as f:
model = binvox_rw.read_as_3d_array(f)
voxels = model.data.astype(np.uint8)
print(voxels.shape)
(32, 32, 32)
def cube_points(position3d):
#position3d is either a 3-list or an array of shape(3,)
# where a unit cube defined below is translated
#define an array of shape(8, 3) as a template for a 3d cube ;
#each row, cube[k], defines the coordinates of a cube vertex
cube = np.array([[0, 0, 0],
[1, 0, 0],
[1, 1, 0],
[0, 1, 0],
[0, 0, 1],
[1, 0, 1],
[1, 1, 1],
[0, 1, 1]], dtype=float) + np.asarray(position3d)
#the last sum translates the template cube(i.e. its representative points)
# on the direction OP, with P of coords position3d
return cube
def triangulate_cube_faces(positions):
# positions is an array of shape (N, 3) containing all cube (voxel) positions of an object
#This function sets up all voxels, extract their defining vertices as arrays of shape (3,)
# and reduces their number, by determining the array of unique vertices
# the voxel faces are triangularized and from their faces one extracts the lists of indices I, J, K
#to define a mesh3d;
positions = np.asarray(positions)
if positions.shape[1] != 3:
raise ValueError("Wrong shape for positions of cubes in your data")
all_cubes = [cube_points(pos) for pos in positions]
p, q, r = np.array(all_cubes).shape
vertices, ixr = np.unique(np.array(all_cubes).reshape(p*q, r), return_inverse=True, axis=0)
I = []
J = []
K = []
# each triplei (i, j, k) defines a face/triangle
for k in range(len(all_cubes)):
I.extend(np.take(ixr, [8*k, 8*k+2, 8*k+4, 8*k+6,8*k+5, 8*k+2, 8*k+4, 8*k+3, 8*k+6, 8*k+3, 8*k+4, 8*k+1]))
J.extend(np.take(ixr, [8*k+1, 8*k+3, 8*k+5, 8*k+7, 8*k+1, 8*k+6, 8*k, 8*k+7, 8*k+2, 8*k+7, 8*k+5, 8*k]))
K.extend(np.take(ixr, [8*k+2, 8*k, 8*k+6, 8*k+4, 8*k+2, 8*k+5, 8*k+3, 8*k+4, 8*k+3, 8*k+6, 8*k+1, 8*k+4]))
return vertices, I, J, K
Extract positions where the voxels values are equal to 1. At each position will be translated a template cube.
m, n, p = voxels.shape
x, y, z = np.indices((m, n, p))
positions = np.c_[x[voxels==1], y[voxels==1], z[voxels==1]]
positions.shape
(1002, 3)
vertices, I, J, K = triangulate_cube_faces(positions)
X, Y, Z = vertices.T
mesh3d = go.Mesh3d(x=X, y=Y, z=Z, i=I, j=J, k=K, flatshading=True, color="#ce6a6b")
layout = go.Layout(width=650,
height=750,
title_text='Voxel data visualization',
title_x=0.5)
fig = go.Figure(data=[mesh3d], layout=layout)
fig.layout.scene.update(xaxis_showticklabels=False, xaxis_ticks='', xaxis_title='',
yaxis_showticklabels=False, yaxis_ticks='',yaxis_title='',
zaxis_showticklabels=False, zaxis_ticks='', zaxis_title='',
camera_eye_x=1.4, camera_eye_y=-2.5, camera_eye_z=1);
fig.data[0].update(lighting=dict(
ambient=0.5,
diffuse=1,
fresnel=4,
specular=0.5,
roughness=0.5));
from plotly.offline import download_plotlyjs, init_notebook_mode, iplot
init_notebook_mode(connected=True)
iplot(fig)
Meshes of 3d shapes, for voxelization and voxel model rendering, can be downloaded from Princeton University, where there is a large scale dataset created for experiments that led to the paper 3D ShapeNets: A Deep Representation for Volumetric Shape Modeling.
Here https://github.com/empet/Datasets/tree/master/binvoxf can be downloaded three binvox files to experiment further with this method of voxel rendering.