1+ """
2+ ICP method in python by Clay Fannigan. Improvement by Max Bazik. This version
3+ originally had scaling, added by Alvin Wan. I have removed the scalilng but
4+ kept some changes that allow for differently-sized point clouds to be aligned.
5+ This code was originally licensed under the Apache License version 2.0.
6+ """
7+
8+ import numpy as np
9+ from sklearn .neighbors import NearestNeighbors
10+
11+ def best_fit_transform (A , B ):
12+ '''
13+ Calculates the least-squares best-fit transform between corresponding 3D points A->B
14+ Input:
15+ A: Nx3 numpy array of corresponding 3D points
16+ B: Nx3 numpy array of corresponding 3D points
17+ Returns:
18+ T: 4x4 homogeneous transformation matrix
19+ R: 3x3 rotation matrix
20+ t: 3x1 column vector for translation
21+ s: 3x1 column vector for scaling
22+ '''
23+
24+ assert len (A ) == len (B )
25+
26+ # translate points to their centroids
27+ centroid_A = np .mean (A , axis = 0 )
28+ centroid_B = np .mean (B , axis = 0 )
29+ AA = A - centroid_A
30+ BB = B - centroid_B
31+
32+ # rotation matrix
33+ H = np .dot (AA .T , BB )
34+ U , S , Vt = np .linalg .svd (H )
35+ R = np .dot (Vt .T , U .T )
36+
37+ # special reflection case
38+ if np .linalg .det (R ) < 0 :
39+ Vt [2 ,:] *= - 1
40+ R = np .dot (Vt .T , U .T )
41+
42+ # translation
43+ t = centroid_B .T - np .dot (R , centroid_A .T )
44+
45+ # homogeneous transformation
46+ T = np .identity (4 )
47+ T [0 :3 , 0 :3 ] = R
48+ T [0 :3 , 3 ] = t
49+
50+ return T , R , t
51+
52+ def nearest_neighbor (src , dst ):
53+ '''
54+ Find the nearest (Euclidean) neighbor in dst for each point in src
55+ Input:
56+ src: Nx3 array of points
57+ dst: Nx3 array of points
58+ Output:
59+ distances: Euclidean distances of the nearest neighbor
60+ indices: dst indices of the nearest neighbor
61+ '''
62+
63+ neigh = NearestNeighbors (n_neighbors = 1 )
64+ neigh .fit (dst )
65+ distances , indices = neigh .kneighbors (src , return_distance = True )
66+ return distances .ravel (), indices .ravel ()
67+
68+ def icp (A , B , init_pose = None , max_iterations = 100 , tolerance = 1e-10 ):
69+ '''
70+ The Iterative Closest Point method
71+ Input:
72+ A: Nx3 numpy array of source 3D points
73+ B: Nx3 numpy array of destination 3D point
74+ init_pose: 4x4 homogeneous transformation
75+ max_iterations: exit algorithm after max_iterations
76+ tolerance: convergence criteria
77+ Output:
78+ T: final homogeneous transformation
79+ distances: Euclidean distances (errors) of the nearest neighbor
80+ '''
81+
82+ # make points homogeneous, copy them so as to maintain the originals
83+ src = np .ones ((4 ,A .shape [0 ]))
84+ dst = np .ones ((4 ,B .shape [0 ]))
85+ src [0 :3 ,:] = np .copy (A .T )
86+ dst [0 :3 ,:] = np .copy (B .T )
87+
88+ # apply the initial pose estimation
89+ if init_pose is not None :
90+ src = np .dot (init_pose , src )
91+
92+ prev_error = 0
93+
94+ try :
95+
96+ for i in range (max_iterations ):
97+ # find the nearest neighbours between the current source and destination points
98+ distances , indices = nearest_neighbor (src [0 :3 ,:].T , dst [0 :3 ,:].T )
99+
100+ # compute the transformation between the current source and nearest destination points
101+ T , _ , _ = best_fit_transform (src [0 :3 ,:].T , dst [0 :3 ,indices ].T )
102+
103+ # update the current source
104+ src = T .dot (src )
105+
106+ # check error
107+ mean_error = np .sum (distances ) / distances .size
108+ if abs (prev_error - mean_error ) < tolerance :
109+ break
110+ prev_error = mean_error
111+
112+ # calculate final transformation
113+ T , _ , _ = best_fit_transform (A , src [0 :3 ,:].T )
114+
115+ return T , distances
116+ except ValueError as e :
117+ print (e )
118+ return np .eye (4 ), 1 , np .array ([np .inf ])
119+ except np .linalg .linalg .LinAlgError as e :
120+ print (e )
121+ return np .eye (4 ), 1 , np .array ([np .inf ])
0 commit comments