/
refine_homographies.py
executable file
·347 lines (266 loc) · 10.1 KB
/
refine_homographies.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
#! /usr/bin/python
import os.path
import numpy as np
import cPickle as pickle
from collections import OrderedDict
from scipy.optimize import root
from projective_math import SqExpWeightingFunction
from camera_math import estimate_intrinsics_noskew_assume_cxy
from camera_math import estimate_intrinsics_noskew
from camera_math import get_extrinsics_from_homography
from camera_math import matrix_to_xyzrph, matrix_to_intrinsics
from camera_math import xyzrph_to_matrix, intrinsics_to_matrix
from tupletypes import WorldImageHomographyInfo
#--------------------------------------
class HomographyModel(object):
#--------------------------------------
"""
Encapsulation of the data in `WorldImageHomographyInfo`
and methods that act on that data
Members:
--------
`hinfo`: `WorldImageHomographyInfo` object
`itag`: intrinsics tag
`etag`: extrinsics tag
"""
def __init__(self, hinfo):
self.hinfo = hinfo
self.etag = None
self.itag = None
self.H0 = None
@classmethod
def load_from_file(class_, filename):
# parse the filename to get intrinsic/extrinsic tags
etag, itag = filename.split('/')[-2:]
itag = itag.split('.')[0]
with open(filename) as f:
hinfo = pickle.load(f)
# create and populate instance
instance = class_(hinfo)
instance.etag = etag
instance.itag = itag
return instance
def homography_at_center(self):
if self.H0 is None:
H_wi, c_w, _ = self.hinfo
self.H0 = H_wi.get_homography_at(c_w)
return self.H0
#-------------------------------------
class IntrinsicsNode(object):
#-------------------------------------
def __init__(self, fx, fy, cx, cy, tag):
self.fx = fx
self.fy = fy
self.cx = cx
self.cy = cy
self.tag = tag # convenient identification
def to_tuple(self):
return self.fx, self.fy, self.cx, self.cy
def set_value(self, *tupl):
self.fx = tupl[0]
self.fy = tupl[1]
self.cx = tupl[2]
self.cy = tupl[3]
def __repr__(self):
return repr(self.to_tuple())
def to_matrix(self):
return intrinsics_to_matrix(*self.to_tuple())
#-------------------------------------
class ExtrinsicsNode(object):
#-------------------------------------
def __init__(self, x, y, z, r, p, h, tag):
self.x = x
self.y = y
self.z = z
self.r = r
self.p = p
self.h = h
self.tag = tag # convenient identification
def to_tuple(self):
return self.x, self.y, self.z, self.r, self.p, self.h
def set_value(self, *tupl):
self.x = tupl[0]
self.y = tupl[1]
self.z = tupl[2]
self.r = tupl[3]
self.p = tupl[4]
self.h = tupl[5]
def __repr__(self):
return repr(self.to_tuple())
def to_matrix(self):
return xyzrph_to_matrix(*self.to_tuple())
#--------------------------------------
class HomographyConstraint(object):
#--------------------------------------
def __init__(self, hmodel, inode, enode):
H_wi, c_w, _ = hmodel.hinfo
weights = H_wi.get_correspondence_weights(c_w)
# 3-D homogeneous form with z=0
p_src = [ c.source for c in H_wi._corrs ]
N = len(p_src)
p_src = np.hstack([ p_src, np.zeros((N,1)), np.ones((N,1)) ])
self.p_src = p_src.T
self.p_tgt = np.array([ c.target for c in H_wi._corrs ]).T
self.W = np.diag(weights)
self.inode = inode
self.enode = enode
def sq_unweighted_reprojection_errors(self):
"""
compute the geometric reprojection error of the world
points `self.p_w` through the homography described
the composition of intrinsics and extrinsics.
"""
K = self.inode.to_matrix()
E = self.enode.to_matrix()
H = K.dot(E)
p_mapped = H.dot(self.p_src)[:3,:]
# normalize homogeneous coordinates
M = np.diag(1./p_mapped[2,:])
p_mapped = p_mapped[:2,:].dot(M)
return ((p_mapped - self.p_tgt)**2)
def sq_errors(self):
sqerr = self.sq_unweighted_reprojection_errors()
return sqerr.dot(self.W).ravel()
#--------------------------------------
class ConstraintGraph(object):
#--------------------------------------
def __init__(self):
self.inodes = OrderedDict()
self.enodes = OrderedDict()
self.constraints = list()
def constraint_errors(self):
return np.hstack([ c.sq_errors() for c in self.constraints ])
def sq_pixel_errors(self):
homography_constraints = ( c for c in self.constraints if isinstance(c, HomographyConstraint) )
return np.hstack([ c.sq_unweighted_reprojection_errors() for c in homography_constraints ])
def _pack_into_vector(self):
""" pack node states into a vector """
istate = np.hstack([ i.to_tuple() for i in self.inodes.values() ])
estate = np.hstack([ e.to_tuple() for e in self.enodes.values() ])
return np.hstack(( istate, estate ))
def _unpack_from_vector(self, v):
""" Set node values from the vector `v` """
N = len(self.inodes)
istate = np.reshape(v[:4*N], (-1, 4))
estate = np.reshape(v[4*N:], (-1, 6))
for inode, ival in zip(self.inodes.values(), istate):
inode.set_value(*ival)
for enode, eval_ in zip(self.enodes.values(), estate):
enode.set_value(*eval_)
state = property(_pack_into_vector, _unpack_from_vector)
def _pack_intrinsics_into_vector(self):
""" pack intrinsic node states into a vector """
return np.hstack([ i.to_tuple() for i in self.inodes.values() ])
def _unpack_intrinsics_from_vector(self, v):
""" Set intrinsic node values from the vector `v` """
istate = np.reshape(v, (-1, 4))
for inode, ival in zip(self.inodes.values(), istate):
inode.set_value(*ival)
istate = property(_pack_intrinsics_into_vector, _unpack_intrinsics_from_vector)
def main():
import sys
from glob import iglob
from itertools import groupby
np.set_printoptions(precision=4, suppress=True)
folder = sys.argv[1]
saved_files = iglob(folder + '/pose?/*.lh0')
hmodels = [ HomographyModel.load_from_file(f) for f in saved_files ]
#
# Deconstruct information in the HomographyModels into
# a graph of nodes and constraints
#
graph = ConstraintGraph()
#
# Construct intrinsic nodes
#
itag_getter = lambda e: e.itag
hmodels.sort(key=itag_getter)
for itag, group in groupby(hmodels, key=itag_getter):
homographies = [ hm.homography_at_center() for hm in group ]
K = estimate_intrinsics_noskew(homographies)
graph.inodes[itag] = IntrinsicsNode(*matrix_to_intrinsics(K), tag=itag)
#
# Construct extrinsic nodes
#
etag_getter = lambda e: e.etag
hmodels.sort(key=etag_getter)
for etag, group in groupby(hmodels, key=etag_getter):
estimates = []
for hm in group:
K = graph.inodes.get(hm.itag).to_matrix()[:,:3]
E = get_extrinsics_from_homography(hm.homography_at_center(), K)
estimates.append(matrix_to_xyzrph(E))
graph.enodes[etag] = ExtrinsicsNode(*np.mean(estimates, axis=0), tag=etag)
print 'Graph'
print '-----'
print ' %d intrinsic nodes' % len(graph.inodes)
print ' %d extrinsic nodes' % len(graph.enodes)
print ''
#
# Connect nodes by constraints
#
for hm in hmodels:
inode = graph.inodes[hm.itag]
enode = graph.enodes[hm.etag]
constraint = HomographyConstraint(hm, inode, enode)
graph.constraints.append(constraint)
rmse = np.sqrt(constraint.sq_unweighted_reprojection_errors().mean())
print ' %s %s rmse: %.2f' % (hm.etag, hm.itag, rmse)
#
# Optimize graph to reduce error in constraints
#
def print_graph_summary(title):
print '\n' + title
print '-'*len(title)
print ' rmse: %.4f' % np.sqrt(graph.sq_pixel_errors().mean())
print ' rmaxse: %.4f' % np.sqrt(graph.sq_pixel_errors().max())
print ''
for itag, inode in graph.inodes.iteritems():
print ' intrinsics@ ' + itag + " =", np.array(inode.to_tuple())
print ' extrinsics@ pose0 =', np.array(graph.enodes['pose0'].to_tuple())
def objective(x):
graph.state = x
return graph.constraint_errors()
def optimize_graph():
x0 = graph.state
print_graph_summary('Initial:')
print '\nOptimizing graph ...'
result = root(objective, x0, method='lm', options={'factor': 0.1, 'col_deriv': 1})
print ' Success: ' + str(result.success)
print ' %s' % result.message
graph.state = result.x
print_graph_summary('Final:')
print '\n'
print '====================='
print ' Optimization 1'
print '====================='
print ' Optimizing all intrinsics and extrinisics'
optimize_graph()
#
# Now optimize with just the constraints of
# the poses required for estimating distortion
#
candidate_poses = set(sys.argv[2:])
candidate_constraints = ( c for c in graph.constraints if isinstance(c, HomographyConstraint) )
candidate_constraints = [ c for c in candidate_constraints if c.enode.tag in candidate_poses ]
graph.constraints = candidate_constraints
print '\n'
print '====================='
print ' Optimization 2'
print '====================='
print ' Optimizing candidate intrinsics (%d constraints)' % len(graph.constraints)
optimize_graph()
#
# Write out the refined intrinsics and extrinsics
#
homography_constraints = ( c for c in graph.constraints if isinstance(c, HomographyConstraint) )
for constraint in homography_constraints:
etag, itag = constraint.enode.tag, constraint.inode.tag
K = constraint.inode.to_matrix()
E = constraint.enode.to_matrix()
H = K.dot(E)
filename = '%s/%s/%s.lh0+' % (folder, etag, itag)
with open(filename, 'w') as f:
pickle.dump((K, E), f)
if __name__ == '__main__':
main()