-
Notifications
You must be signed in to change notification settings - Fork 21
/
Copy pathRANSAC.py
156 lines (138 loc) · 5.39 KB
/
RANSAC.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
"""
RANSAC algorithm for point cloud registration.
"""
import open3d as o3d
import numpy as np
import copy
from scipy import spatial
import random
import math
import os
import sys
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.append(BASE_DIR)
sys.path.append(os.path.join(BASE_DIR, '../lib'))
from configs import FLAGS
def compute_transformation(source, target):
""" Kabsch Algorithm """
# Normalization
number = len(source)
# the centroid of source points
cs = np.zeros((3, 1))
# the centroid of target points
ct = copy.deepcopy(cs)
cs[0] = np.mean(source[:][0])
cs[1] = np.mean(source[:][1])
cs[2] = np.mean(source[:][2])
ct[0] = np.mean(target[:][0])
cs[1] = np.mean(target[:][1])
cs[2] = np.mean(target[:][2])
# covariance matrix
cov = np.zeros((3, 3))
# translate the centroids of both models to the origin of the coordinate system (0,0,0)
# subtract from each point coordinates the coordinates of its corresponding centroid
for i in range(number):
sources = source[i].reshape(-1, 1) - cs
targets = target[i].reshape(-1, 1) - ct
cov = cov + np.dot(sources, np.transpose(targets))
# SVD (singular values decomposition)
u, w, v = np.linalg.svd(cov)
# rotation matrix
R = np.dot(u, np.transpose(v))
# Transformation vector
T = ct - np.dot(R, cs)
return R, T
# compute the transformed points from source to target based on the R/T found in Kabsch Algorithm
def _transform(source, R, T):
points = []
for point in source:
points.append(np.dot(R, point.reshape(-1, 1) + T))
return points
# compute the root mean square error between source and target
def compute_rmse(source, target, R, T):
rmse = 0
number = len(target)
points = _transform(source, R, T)
for i in range(number):
error = target[i].reshape(-1, 1) - points[i]
rmse = rmse + math.sqrt(error[0] ** 2 + error[1] ** 2 + error[2] ** 2)
return rmse
def draw_registrations(source, target, transformation=None, recolor=False):
source_temp = copy.deepcopy(source)
target_temp = copy.deepcopy(target)
if recolor: # recolor the points
source_temp.paint_uniform_color([1, 0.706, 0])
target_temp.paint_uniform_color([0, 0.651, 0.929])
if transformation is not None: # transforma source to targets
source_temp.transform(transformation)
o3d.visualization.draw_geometries([source_temp, target_temp])
def pc2array(pointcloud):
return np.asarray(pointcloud.points)
def registration_RANSAC(source, target, source_feature, target_feature, ransac_n=3, max_iteration=100000,
max_validation=100):
# the intention of RANSAC is to get the optimal transformation between the source and target point cloud
global opt_rmse, opt_T, opt_R
s = pc2array(source) # (4760,3)
t = pc2array(target)
# source features (33,4760)
sf = np.transpose(source_feature.data)
tf = np.transpose(target_feature.data)
# create a KD tree
tree = spatial.KDTree(tf)
corres_stock = tree.query(sf)[1]
for i in range(max_iteration):
# take ransac_n points randomly
idx = [random.randint(0, s.shape[0] - 1) for j in range(ransac_n)]
corres_idx = corres_stock[idx]
source_point = s[idx, ...]
target_point = t[corres_idx, ...]
# estimate transformation
# use Kabsch Algorithm
R, T = compute_transformation(source_point, target_point)
# calculate rmse for all points
source_point = s
target_point = t[corres_stock, ...]
rmse = compute_rmse(source_point, target_point, R, T)
# compare rmse and optimal rmse and then store the smaller one as optimal values
if not i:
opt_rmse = rmse
opt_R = R
opt_T = T
else:
if rmse < opt_rmse:
opt_rmse = rmse
opt_R = R
opt_T = T
return opt_R, opt_T
# this is to get the fpfh features, just call the library
def get_fpfh(cp, voxel_size=0.05):
# voxel_size is used for downsampling
cp = cp.voxel_down_sample(voxel_size)
cp.estimate_normals()
return cp, o3d.pipelines.registration.compute_fpfh_feature(cp, o3d.geometry.KDTreeSearchParamHybrid(radius=5, max_nn=100))
def ransac(source_file, target_file):
"""
ransac algorithm
:param source_file: source point cloud (type: str)
:param target_file: target point cloud (type: str)
:return:
"""
source = o3d.io.read_point_cloud(source_file)
target = o3d.io.read_point_cloud(target_file)
# if we want to use RANSAC registration, get_fpfh features should be acquired firstly
r1, f1 = get_fpfh(source)
# print(r1, f1)
r2, f2 = get_fpfh(target)
# print(r2, f2)
R, T = registration_RANSAC(r1, r2, f1, f2)
# transformation matrix is formed by R, T based on np.hstack and np.vstack(corporate two matrices by rows)
# Notice we need add the last row [0 0 0 1] to make it homogeneous
transformation = np.vstack((np.hstack((np.float64(R), np.float64(T))), np.array([0, 0, 0, 1])))
print("transform matrix: \n", transformation)
# draw_registrations(r1, r2, transformation, True)
if __name__ == "__main__":
print("********** RANSAC **********")
print("source file: ", FLAGS.s_file)
print("target file: ", FLAGS.t_file)
ransac(FLAGS.s_file, FLAGS.t_file)
print("***************************")