Register a Surface with ICP#
Download Python source code | Download Jupyter notebook
Recover the rigid transform between two surfaces with the iterative closest
point implementation behind pyvista.DataSetFilters.align().
import numpy as np
import pyvista as pv
from pyvista import examples
Load a reference surface#
The download_action_figure() scan is an
asymmetric reference mesh.
A camera position that frames the figure clearly.
cpos = pv.CameraPosition(
position=(150, -250, 150),
focal_point=(0, 0, 35),
viewup=(0, 0, 1),
)
Transform a copy away from the reference#
The transformed copy stands in for an incoming scan that needs to be registered back onto the reference.
offset = np.array(target.length) * 0.4
transform = pv.Transform().rotate_x(25).rotate_z(-35).translate((offset, -offset, offset))
source = target.transform(transform, inplace=False)
pl = pv.Plotter()
pl.add_mesh(target, color='lightgray', opacity=0.6)
pl.add_mesh(source, color='tomato', opacity=0.8)
pl.camera_position = cpos
pl.show()

Recover the rigid transform#
pyvista.DataSetFilters.align() runs ICP and returns both the aligned
mesh and the recovered transform matrix.
aligned, matrix = source.align(target, return_matrix=True)
_, closest_points = target.find_closest_cell(aligned.points, return_closest_point=True)
aligned['distance_to_target'] = np.linalg.norm(aligned.points - closest_points, axis=1)
pl = pv.Plotter(shape=(1, 2))
pl.subplot(0, 0)
pl.add_mesh(target, color='lightgray', opacity=0.6)
pl.add_mesh(source, color='tomato', opacity=0.8)
pl.camera_position = cpos
pl.subplot(0, 1)
pl.add_mesh(target, color='lightgray', opacity=0.4)
pl.add_mesh(aligned, scalars='distance_to_target', cmap='viridis')
pl.camera_position = cpos
pl.link_views()
pl.show()

Inspect the recovered transform#
The returned matrix maps the transformed copy back onto the reference.
array([[ 0.819, -0.574, -0. , -93.122],
[ 0.52 , 0.742, 0.423, -13.376],
[ -0.242, -0.346, 0.906, -67.538],
[ 0. , 0. , 0. , 1. ]])
Measure the residual distances#
A successful registration leaves a small point-to-surface residual.
(np.float64(1.5467277336856347e-05), np.float64(4.892452988130443e-05))
Total running time of the script: (0 minutes 1.399 seconds)