import numpy as np from matplotlib import pyplot as plt from sklearn.neighbors import KDTree from ply import write_ply, read_ply from visu import show_ICP import sys def best_rigid_transform(data, ref): ''' Computes the least-squares best-fit transform that maps corresponding points data to ref. Inputs : data = (d x N) matrix where "N" is the number of points and "d" the dimension ref = (d x N) matrix where "N" is the number of points and "d" the dimension Returns : R = (d x d) rotation matrix T = (d x 1) translation vector Such that R * data + T is aligned on ref ''' # compute centroids centroid_data = np.mean(data, axis=1) centroid_ref = np.mean(ref, axis=1) # ensure centroids are 3x1 centroid_data = centroid_data.reshape(-1, 1) centroid_ref = centroid_ref.reshape(-1, 1) # compute centered PCs data_m = data - centroid_data ref_m = ref - centroid_ref H = data_m @ np.transpose(ref_m) # do SVD U, S, Vt = np.linalg.svd(H) R = Vt.T @ U.T # special reflection case if np.linalg.det(R) < 0: Vt[2,:] *= -1 R = Vt.T @ U.T t = -R @ centroid_data + centroid_ref return R, t def icp_point_to_point(data, ref, max_iter, RMS_threshold): ''' Iterative closest point algorithm with a point to point strategy. Inputs : data = (d x N_data) matrix where "N_data" is the number of points and "d" the dimension ref = (d x N_ref) matrix where "N_ref" is the number of points and "d" the dimension max_iter = stop condition on the number of iterations RMS_threshold = stop condition on the distance Returns : data_aligned = data aligned on reference cloud R_list = list of the (d x d) rotation matrices found at each iteration T_list = list of the (d x 1) translation vectors found at each iteration neighbors_list = At each iteration, you search the nearest neighbors of each data point in the ref cloud and this obtain a (1 x N_data) array of indices. This is the list of those arrays at each iteration ''' # Variable for aligned data data_aligned = np.copy(data) # print(data.shape) # print(ref.shape) # Initiate lists R_list = [] T_list = [] neighbors_list = [] RMS_list = [] ref_tree = KDTree(ref.T) for _ in range(max_iter): # iterate through points in the data neighbors_list.append([]) for point in data_aligned.T: # find the nearest neighbor in the ref neighbor = ref_tree.query(point.reshape(1,-1), return_distance=False)[0][0] neighbors_list[-1].append(neighbor) # compute new R and t ref_prime = ref[:, neighbors_list[-1]] R, t = best_rigid_transform(data, ref_prime) R_list.append(R) T_list.append(t) # update data_aligned data_aligned = R.dot(data) + t # compute RMS distances2 = np.sum(np.power(data_aligned - ref_prime, 2), axis=0) RMS_list.append( np.sqrt(np.mean(distances2)) ) if RMS_list[-1] < RMS_threshold: break return data_aligned, R_list, T_list, neighbors_list, RMS_list if __name__ == '__main__': # Transformation estimation # ************************* # # If statement to skip this part if wanted if False: # Cloud paths bunny_o_path = '../data/bunny_original.ply' bunny_r_path = '../data/bunny_returned.ply' # Load clouds bunny_o_ply = read_ply(bunny_o_path) bunny_r_ply = read_ply(bunny_r_path) bunny_o = np.vstack((bunny_o_ply['x'], bunny_o_ply['y'], bunny_o_ply['z'])) bunny_r = np.vstack((bunny_r_ply['x'], bunny_r_ply['y'], bunny_r_ply['z'])) # Find the best transformation R, T = best_rigid_transform(bunny_r, bunny_o) # Apply the tranformation bunny_r_opt = R.dot(bunny_r) + T # Save cloud write_ply('../bunny_r_opt', [bunny_r_opt.T], ['x', 'y', 'z']) # Compute RMS distances2_before = np.sum(np.power(bunny_r - bunny_o, 2), axis=0) RMS_before = np.sqrt(np.mean(distances2_before)) distances2_after = np.sum(np.power(bunny_r_opt - bunny_o, 2), axis=0) RMS_after = np.sqrt(np.mean(distances2_after)) print('Average RMS between points :') print('Before = {:.3f}'.format(RMS_before)) print('After = {:.3f}'.format(RMS_after)) # Test ICP and visualize # ********************** # # If statement to skip this part if wanted if True: # Cloud paths ref2D_path = '../data/ref2D.ply' data2D_path = '../data/data2D.ply' # Load clouds ref2D_ply = read_ply(ref2D_path) data2D_ply = read_ply(data2D_path) ref2D = np.vstack((ref2D_ply['x'], ref2D_ply['y'])) data2D = np.vstack((data2D_ply['x'], data2D_ply['y'])) # Apply ICP data2D_opt, R_list, T_list, neighbors_list, RMS_list = icp_point_to_point(data2D, ref2D, 10, 1e-4) # Show ICP show_ICP(data2D, ref2D, R_list, T_list, neighbors_list) # Plot RMS plt.plot(RMS_list) plt.show() # If statement to skip this part if wanted if False: # Cloud paths bunny_o_path = '../data/bunny_original.ply' bunny_p_path = '../data/bunny_perturbed.ply' # Load clouds bunny_o_ply = read_ply(bunny_o_path) bunny_p_ply = read_ply(bunny_p_path) bunny_o = np.vstack((bunny_o_ply['x'], bunny_o_ply['y'], bunny_o_ply['z'])) bunny_p = np.vstack((bunny_p_ply['x'], bunny_p_ply['y'], bunny_p_ply['z'])) # Apply ICP bunny_p_opt, R_list, T_list, neighbors_list, RMS_list = icp_point_to_point(bunny_p, bunny_o, 25, 1e-4) # Show ICP show_ICP(bunny_p, bunny_o, R_list, T_list, neighbors_list) # Plot RMS plt.plot(RMS_list) plt.show()