-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathpose_graph_optimization.py
406 lines (312 loc) · 15.7 KB
/
pose_graph_optimization.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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
from utils import read_pair as rp
import numpy as np
from scipy.linalg import cho_solve, cholesky
from utils import downscale as ds
from utils import lukas_kanade as lk
from utils import se3
import warnings
warnings.filterwarnings('ignore')
def pgo(pair_path=None, absolute_poses=None, kf_index=None, loop_closure=False, loop_pairs=None, level=4,
r_p_list=[], s_m_list=[], lambd=0.1):
pose_num = len(absolute_poses)
if loop_closure:
loop_num = len(loop_pairs)
else:
loop_num = 0
loop_ind = 0
b_k = np.zeros(6 * pose_num) # b_k vector
h_k = np.zeros([6 * pose_num, 6 * pose_num]) # H_k matrix
residual = np.zeros([pose_num + loop_num, 6]) # residual vector
relative_pose_list = r_p_list
sigma_matrix_list = s_m_list
for ind in np.arange(pose_num - 1 + loop_num):
if ind >= pose_num - 1:
# add loop closure manually at the end of the trajectory by observing overlap
loop_pair = loop_pairs[loop_ind]
loop_ind += 1
first_pose_ind = loop_pair[0]
second_pose_ind = loop_pair[1]
ref_frame = kf_index[first_pose_ind]
cur_frame = kf_index[second_pose_ind]
first_pose = absolute_poses[first_pose_ind]
second_pose = absolute_poses[second_pose_ind]
else:
first_pose_ind = ind
second_pose_ind = ind + 1
ref_frame = kf_index[first_pose_ind]
cur_frame = kf_index[second_pose_ind]
first_pose = absolute_poses[first_pose_ind]
second_pose = absolute_poses[second_pose_ind]
# calculate the pose difference between keyframes pair
pose_diff = np.linalg.inv(second_pose) @ first_pose
# create relative pose constraint between keyframes pair through direct image alignment in the first pgo iter
if len(relative_pose_list) < ind + 1:
image1, depth1, _ = rp.read_pair(pair_path, ref_frame)
image2, depth2, _ = rp.read_pair(pair_path, cur_frame)
relative_pose, sigma_matrix = create_relative_pose_constraint(image1, depth1, image2, depth2, level,
se3.se3Log(pose_diff))
relative_pose_list.append(relative_pose)
sigma_matrix_list.append(sigma_matrix)
else:
relative_pose = relative_pose_list[ind]
sigma_matrix = sigma_matrix_list[ind]
# convert twist coordinate to 4*4 transformation matrix
relative_pose = se3.se3Exp(relative_pose)
# calculate relative pose residual between this key-frame pair
resid = se3.se3Log(np.linalg.inv(relative_pose) @ pose_diff)
# print(resid)
# stack residual vector
residual[ind + 1] = resid
# calculate jacobian matrix
jacobian = calculatejacobian(pose_num, first_pose, second_pose, relative_pose, first_pose_ind, second_pose_ind,
resid)
# accumulate b_k and h_k for gauss newton step
b_k += jacobian.T @ sigma_matrix @ resid
h_k += jacobian.T @ sigma_matrix @ jacobian
# print('keyframes pair:{}'.format(ind + 1))
# add another term for first pose in b_k and h_k
resid_0 = se3.se3Log(absolute_poses[0])
residual[0] = resid_0
jaco_0 = calculate_jaco_single_frame(pose_num, absolute_poses[0], resid_0)
sigma_matrix = np.identity(6) * 1e6
b_k += jaco_0.T @ sigma_matrix @ resid_0
h_k += jaco_0.T @ sigma_matrix @ jaco_0
residual = residual.reshape(-1)
# update all key frame poses with Levenberg-Marquardt method
# upd = - np.linalg.inv(h_k + lambd * np.diag(np.diag(h_k))) @ b_k
# with gauss newton method
# upd = - np.linalg.inv(h_k) @ b_k
# use cholesky factorization to solve the linear system -h_k * upd = b_k
c = cholesky(h_k)
upd = - cho_solve((c, False), b_k)
upd = upd.reshape(-1, 6)
for jj in range(pose_num):
absolute_poses[jj] = se3.se3Exp(upd[jj]) @ absolute_poses[jj]
residual_after_update = calculate_residual(absolute_poses, relative_pose_list, loop_pairs, loop_num)
return absolute_poses, residual, relative_pose_list, sigma_matrix_list, residual_after_update
def calculate_residual(absolute_poses, relative_poses, loop_pairs, loop_num):
pose_num = len(absolute_poses)
loop_ind = 0
residual = np.zeros([pose_num + loop_num, 6])
for ind in range(pose_num - 1 + loop_num):
if ind >= pose_num - 1:
# add loop closure manually at the end of the trajectory by observing overlap
loop_pair = loop_pairs[loop_ind]
loop_ind += 1
first_pose_ind = loop_pair[0]
second_pose_ind = loop_pair[1]
first_pose = absolute_poses[first_pose_ind]
second_pose = absolute_poses[second_pose_ind]
else:
first_pose_ind = ind
second_pose_ind = ind + 1
first_pose = absolute_poses[first_pose_ind]
second_pose = absolute_poses[second_pose_ind]
# calculate the pose difference between keyframes pair
pose_diff = np.linalg.inv(second_pose) @ first_pose
relative_pose = se3.se3Exp(relative_poses[ind])
residual[ind + 1] = se3.se3Log(np.linalg.inv(relative_pose) @ pose_diff)
residual[0] = se3.se3Log(absolute_poses[0])
residual = residual.reshape(-1)
return residual
def calculate_jaco_single_frame(pose_num, absolute_pose, resid):
jacobian = np.zeros([6, 6 * pose_num])
for j in range(6):
epsVec = np.zeros(6)
eps = 1e-6
epsVec[j] = eps
# multiply pose increment from left onto the absolute poses
first_pose_eps = se3.se3Exp(epsVec) @ absolute_pose
resid_eps = se3.se3Log(first_pose_eps)
# calculate jacobian at first and second pose position
jacobian[:, j] = (resid_eps - resid) / eps
return jacobian
def pgo_with_auto_loop_closure(pair_path=None, absolute_poses=None, kf_index=None, loop_pairs=None, level=4,
r_p_list=[], s_m_list=[], lambd=0.1):
pose_num = len(absolute_poses)
pair_num = len(loop_pairs)
b_k = np.zeros(6 * pose_num) # b_k vector
h_k = np.zeros([6 * pose_num, 6 * pose_num]) # H_k matrix
residual = np.zeros([pair_num + 1, 6]) # residual vector
relative_pose_list = r_p_list
sigma_matrix_list = s_m_list
for i in np.arange(pair_num):
ref_frame = loop_pairs[i][0]
cur_frame = loop_pairs[i][1]
first_pose_ind = np.where(kf_index == ref_frame)[0][0]
second_pose_ind = np.where(kf_index == cur_frame)[0][0]
first_pose = absolute_poses[first_pose_ind]
second_pose = absolute_poses[second_pose_ind]
image1, depth1, _ = rp.read_pair(pair_path, ref_frame)
image2, depth2, _ = rp.read_pair(pair_path, cur_frame)
# calculate the pose difference between keyframes pair
pose_diff = np.linalg.inv(second_pose) @ first_pose
# create relative pose constraint between keyframes pair through direct image alignment in the first pgo iter
if len(relative_pose_list) < i + 1:
image1, depth1, _ = rp.read_pair(pair_path, ref_frame)
image2, depth2, _ = rp.read_pair(pair_path, cur_frame)
relative_pose, sigma_matrix = create_relative_pose_constraint(image1, depth1, image2, depth2, level,
se3.se3Log(pose_diff))
relative_pose_list.append(relative_pose)
sigma_matrix_list.append(sigma_matrix)
else:
relative_pose = relative_pose_list[i]
sigma_matrix = sigma_matrix_list[i]
# convert twist coordinate to 4*4 transformation matrix
relative_pose = se3.se3Exp(relative_pose)
# calculate relative pose residual between this key-frame pair
resid = se3.se3Log(np.linalg.inv(relative_pose) @ pose_diff)
# stack residual vector
residual[i + 1] = resid
# calculate jacobian matrix
jacobian = calculatejacobian(pose_num, first_pose, second_pose, relative_pose, first_pose_ind, second_pose_ind,
resid)
# accumulate b_k and h_k for gauss newton step
b_k += jacobian.T @ sigma_matrix @ resid
h_k += jacobian.T @ sigma_matrix @ jacobian
# print('keyframes pair:{}'.format(i + 1))
# add another term for first pose in b_k and h_k
resid_0 = se3.se3Log(absolute_poses[0])
residual[0] = resid_0
jaco_0 = calculate_jaco_single_frame(pose_num, absolute_poses[0], resid_0)
sigma_matrix = np.identity(6) * 1e6
b_k += jaco_0.T @ sigma_matrix @ resid_0
h_k += jaco_0.T @ sigma_matrix @ jaco_0
# update all key frame poses
# upd = - np.linalg.inv(h_k) @ b_k # use gauss-newton
# use cholesky factorization to solve the linear system H x = b
c = cholesky(h_k + lambd * np.diag(np.diag(h_k))) # use levenberg marquardt
upd = - cho_solve((c, False), b_k)
upd = upd.reshape(-1, 6)
for jj in range(pose_num):
absolute_poses[jj] = se3.se3Exp(upd[jj]) @ absolute_poses[jj]
residual = residual.reshape(-1)
residual_after_update = calculate_residual_with_auto_lc(absolute_poses, relative_pose_list, loop_pairs, kf_index)
return absolute_poses, residual, relative_pose_list, sigma_matrix_list, residual_after_update
def calculate_residual_with_auto_lc(absolute_poses, relative_poses, loop_pairs, kf_index):
pair_num = len(loop_pairs)
residual = np.zeros([pair_num + 1, 6]) # residual vector
for i in np.arange(pair_num):
ref_frame = loop_pairs[i][0]
cur_frame = loop_pairs[i][1]
first_pose_ind = np.where(kf_index == ref_frame)[0][0]
second_pose_ind = np.where(kf_index == cur_frame)[0][0]
first_pose = absolute_poses[first_pose_ind]
second_pose = absolute_poses[second_pose_ind]
# calculate the pose difference between keyframes pair
pose_diff = np.linalg.inv(second_pose) @ first_pose
relative_pose = se3.se3Exp(relative_poses[i])
residual[i + 1] = se3.se3Log(np.linalg.inv(relative_pose) @ pose_diff)
residual[0] = se3.se3Log(absolute_poses[0])
residual = residual.reshape(-1)
return residual
def create_relative_pose_constraint(image1, depth1, image2, depth2, level, initial_rela_pose):
# camera calibration matrix
fx = 520.9 # focal length x
fy = 521.0 # focal length y
cx = 325.1 # optical center x
cy = 249.7 # optical center y
K = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]])
use_hubernorm = 1
norm_param = 0.2
# perform level times down_sampling and display the downscaled image
Id = image1
Dd = depth1
Kd = K
for i in range(level):
Id, Dd, Kd = ds.downscale(Id, Dd, Kd)
Iref = Id
Dref = Dd
Id = image2
Dd = depth2
Kd = K
for i in range(level):
Id, Dd, Kd = ds.downscale(Id, Dd, Kd)
# do direct image alignment between this key-frame pair
xi = initial_rela_pose
relative_pose, sigma_matrix = lk.do_alignment(Iref, Dref, Kd, Id, Dd, xi, norm_param,
use_hubernorm)
return relative_pose, sigma_matrix
def calculatejacobian(pose_num, first_pose, second_pose, relative_pose, first_pose_ind, second_pose_ind, resid):
jacobian = np.zeros([6, 6 * pose_num])
for j in range(6):
epsVec = np.zeros(6)
eps = 1e-6
epsVec[j] = eps
# multiply pose increment from left onto the absolute poses
first_pose_eps = se3.se3Exp(epsVec) @ first_pose
second_pose_eps = se3.se3Exp(epsVec) @ second_pose
# calculate new pose difference after eps pose increment and new relative pose residual
# first key frame, fix second_pose
pose_diff_eps = np.linalg.inv(second_pose) @ first_pose_eps
resid_first_eps = se3.se3Log(np.linalg.inv(relative_pose) @ pose_diff_eps)
# second key frame, fix first pose
pose_diff_eps = np.linalg.inv(second_pose_eps) @ first_pose
resid_second_eps = se3.se3Log(np.linalg.inv(relative_pose) @ pose_diff_eps)
# calculate jacobian at first and second pose position
jacobian[:, 6 * first_pose_ind + j] = (resid_first_eps - resid) / eps
jacobian[:, 6 * second_pose_ind + j] = (resid_second_eps - resid) / eps
return jacobian
def optimise_pose_graph(pair_path=None, pose_path=None, index_path=None, loop_closure=False, level=4):
x = rp.read_absolute_poses(pose_path)
kf_index = rp.read_pose_index(index_path)
# errLast = 1e10
loop_pairs = [[172, 0]] # manually chosen loop closure keyframe pair on level 4
relativ_pose_list = []
sigma_matrix_list = []
lambd = 1e-4
new_x = x.copy()
for i in range(20):
x, residual, relativ_pose_list, sigma_matrix_list, residual_after_update = pgo(pair_path=pair_path,
absolute_poses=new_x.copy(),
kf_index=kf_index,
loop_closure=loop_closure,
loop_pairs=loop_pairs,
level=level,
r_p_list=relativ_pose_list,
s_m_list=sigma_matrix_list,
lambd=lambd)
errLast = np.mean(residual ** 2)
err = np.mean(residual_after_update ** 2)
if err < errLast:
# accept the update and set lambda = lambda/2
print('iter:', i, 'errLast', errLast, 'err:', err, "accepted", 'lambda', lambd)
new_x = x.copy()
lambd = lambd / 2
elif err > errLast:
# reject the update and set lambda = lambda*2
print('iter:', i, 'errLast', errLast, 'err:', err, "rejected", 'lambda', lambd)
lambd = lambd * 2
else:
print('error converges')
break
np.save('traj_{}.npy'.format(i), new_x)
def optimise_pose_graph_with_auto_loop_closure(pair_path=None, pose_path=None, index_path=None, loop_pairs_path=None,
level=4):
x = rp.read_absolute_poses(pose_path)
kf_index = rp.read_pose_index(index_path)
loop_pairs = np.load(loop_pairs_path)
# errLast = 1e10
relativ_pose_list = []
sigma_matrix_list = []
lambd = 1e-4
new_x = x.copy()
for i in range(20):
x, residual, relativ_pose_list, sigma_matrix_list, residual_after_update = pgo_with_auto_loop_closure(
pair_path=pair_path, absolute_poses=new_x.copy(), kf_index=kf_index, loop_pairs=loop_pairs, level=level,
r_p_list=relativ_pose_list, s_m_list=sigma_matrix_list, lambd=lambd)
errLast = np.mean(residual ** 2)
err = np.mean(residual_after_update ** 2)
if err < errLast:
# accept the update and set lambda = lambda/2
print('iter:', i, 'errLast', errLast, 'err:', err, "accepted", 'lambda', lambd)
new_x = x.copy()
lambd = lambd / 2
elif err > errLast:
# reject the update and set lambda = lambda*2
print('iter:', i, 'errLast', errLast, 'err:', err, "rejected", 'lambda', lambd)
lambd = lambd * 2
else:
print('error converges')
break
np.save('traj_{}.npy'.format(i), new_x)