"""
ICP method in python by Clay Fannigan. Improvement by Max Bazik. This version
originally had scaling, added by Alvin Wan. I have removed the scalilng but
kept some changes that allow for differently-sized point clouds to be aligned.
This code was originally licensed under the Apache License version 2.0.
"""
import numpy as np
from sklearn.neighbors import NearestNeighbors
[docs]
def nearest_neighbor(src, dst):
'''
Find the nearest (Euclidean) neighbor in dst for each point in src
Input:
src: Nx3 array of points
dst: Nx3 array of points
Output:
distances: Euclidean distances of the nearest neighbor
indices: dst indices of the nearest neighbor
'''
neigh = NearestNeighbors(n_neighbors=1)
neigh.fit(dst)
distances, indices = neigh.kneighbors(src, return_distance=True)
return distances.ravel(), indices.ravel()
[docs]
def icp(A, B, init_pose=None, max_iterations=100, tolerance=1e-10):
'''
The Iterative Closest Point method
Input:
A: Nx3 numpy array of source 3D points
B: Nx3 numpy array of destination 3D point
init_pose: 4x4 homogeneous transformation
max_iterations: exit algorithm after max_iterations
tolerance: convergence criteria
Output:
T: final homogeneous transformation
distances: Euclidean distances (errors) of the nearest neighbor
'''
# make points homogeneous, copy them so as to maintain the originals
src = np.ones((4,A.shape[0]))
dst = np.ones((4,B.shape[0]))
src[0:3,:] = np.copy(A.T)
dst[0:3,:] = np.copy(B.T)
# apply the initial pose estimation
if init_pose is not None:
src = np.dot(init_pose, src)
prev_error = 0
try:
for i in range(max_iterations):
# find the nearest neighbours between the current source and destination points
distances, indices = nearest_neighbor(src[0:3,:].T, dst[0:3,:].T)
# compute the transformation between the current source and nearest destination points
T, _, _ = best_fit_transform(src[0:3,:].T, dst[0:3,indices].T)
# update the current source
src = T.dot(src)
# check error
mean_error = np.sum(distances) / distances.size
if abs(prev_error-mean_error) < tolerance:
break
prev_error = mean_error
# calculate final transformation
T, _, _ = best_fit_transform(A, src[0:3,:].T)
return T, distances
except ValueError as e:
print(e)
return np.eye(4), 1, np.array([np.inf])
except np.linalg.linalg.LinAlgError as e:
print(e)
return np.eye(4), 1, np.array([np.inf])