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 # get number of dimensions m = A.shape[1] # translate points to their centroids centroid_A = np.mean(A, axis=0) centroid_B = np.mean(B, axis=0) AA = A - centroid_A BB = B - centroid_B # rotation matrix H = np.dot(AA.T, BB) U, S, Vt = np.linalg.svd(H) R = np.dot(Vt.T, U.T) # special reflection case if np.linalg.det(R) < 0: Vt[m-1,:] *= -1 R = np.dot(Vt.T, U.T) # translation t = centroid_B.T - np.dot(R,centroid_A.T) # Step added for scalar (deprecated) 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 # homogeneous transformation T = np.identity(m+1) T[:m, :m] = R T[:m, m] = t # Step : (Deprecated for Scalar) # T = np.dot(s, 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 # get number of dimensions m = A.shape[1] # make points homogeneous, copy them to maintain the originals 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) # apply the initial pose estimation if init_pose is not None: src = np.dot(init_pose, src) prev_error = 0 for i in range(max_iterations): # # find the nearest neighbors between the current source and destination points # distances, indices = nearest_neighbor(src[:m,:].T, dst[:m,:].T) # # # compute the transformation between the current source and nearest destination points # T,_,_ = best_fit_transform(src[:m,:].T, dst[:m,indices].T) # Step x : just for our T-shape transform, we don'n need this nearest neighbor search distances = np.sum((src[:m, :] - dst[:m, :])**2) T, _, _ = best_fit_transform(src[:m, :].T, dst[:m, :].T) # update the current source src = np.dot(T, src) # check error mean_error = np.mean(distances) if np.abs(prev_error - mean_error) < tolerance: break prev_error = mean_error # calculate final transformation T,_,_ = best_fit_transform(A, src[:m,:].T) return T, distances, i