|
import numpy as np |
|
from sklearn.neighbors import NearestNeighbors |
|
|
|
|
|
def best_fit_transform(A, B): |
|
''' |
|
Calculates the least-squares best-fit transform that maps corresponding points A to B in m spatial dimensions |
|
Input: |
|
A: Nxm numpy array of corresponding points |
|
B: Nxm numpy array of corresponding points |
|
Returns: |
|
T: (m+1)x(m+1) homogeneous transformation matrix that maps A on to B |
|
R: mxm rotation matrix |
|
t: mx1 translation vector |
|
''' |
|
|
|
assert A.shape == B.shape |
|
|
|
|
|
m = A.shape[1] |
|
|
|
|
|
centroid_A = np.mean(A, axis=0) |
|
centroid_B = np.mean(B, axis=0) |
|
AA = A - centroid_A |
|
BB = B - centroid_B |
|
|
|
|
|
H = np.dot(AA.T, BB) |
|
U, S, Vt = np.linalg.svd(H) |
|
R = np.dot(Vt.T, U.T) |
|
|
|
|
|
if np.linalg.det(R) < 0: |
|
Vt[m-1,:] *= -1 |
|
R = np.dot(Vt.T, U.T) |
|
|
|
|
|
t = centroid_B.T - np.dot(R,centroid_A.T) |
|
|
|
|
|
p_deno = np.sum(AA**2, axis=0) |
|
y_nume = np.sum(BB**2, axis=0) |
|
s = np.identity(m+1) |
|
s[:m, :m] = s[:m, :m] * (y_nume / p_deno) ** 0.25 |
|
|
|
|
|
T = np.identity(m+1) |
|
T[:m, :m] = R |
|
T[:m, m] = t |
|
|
|
|
|
|
|
|
|
return T, R, t |
|
|
|
|
|
def nearest_neighbor(src, dst): |
|
''' |
|
Find the nearest (Euclidean) neighbor in dst for each point in src |
|
Input: |
|
src: Nxm array of points |
|
dst: Nxm array of points |
|
Output: |
|
distances: Euclidean distances of the nearest neighbor |
|
indices: dst indices of the nearest neighbor |
|
''' |
|
|
|
assert src.shape == dst.shape |
|
|
|
neigh = NearestNeighbors(n_neighbors=1) |
|
neigh.fit(dst) |
|
distances, indices = neigh.kneighbors(src, return_distance=True) |
|
return distances.ravel(), indices.ravel() |
|
|
|
|
|
def icp(A, B, init_pose=None, max_iterations=50, tolerance=0.0001): |
|
''' |
|
The Iterative Closest Point method: finds best-fit transform that maps points A on to points B |
|
Input: |
|
A: Nxm numpy array of source mD points |
|
B: Nxm numpy array of destination mD point |
|
init_pose: (m+1)x(m+1) homogeneous transformation |
|
max_iterations: exit algorithm after max_iterations |
|
tolerance: convergence criteria |
|
Output: |
|
T: final homogeneous transformation that maps A on to B |
|
distances: Euclidean distances (errors) of the nearest neighbor |
|
i: number of iterations to converge |
|
''' |
|
|
|
assert A.shape == B.shape |
|
|
|
|
|
m = A.shape[1] |
|
|
|
|
|
src = np.ones((m+1,A.shape[0])) |
|
dst = np.ones((m+1,B.shape[0])) |
|
src[:m,:] = np.copy(A.T) |
|
dst[:m,:] = np.copy(B.T) |
|
|
|
|
|
if init_pose is not None: |
|
src = np.dot(init_pose, src) |
|
|
|
prev_error = 0 |
|
|
|
for i in range(max_iterations): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
distances = np.sum((src[:m, :] - dst[:m, :])**2) |
|
T, _, _ = best_fit_transform(src[:m, :].T, dst[:m, :].T) |
|
|
|
|
|
src = np.dot(T, src) |
|
|
|
|
|
mean_error = np.mean(distances) |
|
if np.abs(prev_error - mean_error) < tolerance: |
|
break |
|
prev_error = mean_error |
|
|
|
|
|
T,_,_ = best_fit_transform(A, src[:m,:].T) |
|
|
|
return T, distances, i |
|
|