blob: 48e7f02d85629524e3ed7d528f15b9f7b8a5378d [file] [log] [blame]
Austin Schuh3333ec72022-12-29 16:21:06 -08001/* Copyright (C) 2013-2016, The Regents of The University of Michigan.
2All rights reserved.
3This software was developed in the APRIL Robotics Lab under the
4direction of Edwin Olson, ebolson@umich.edu. This software may be
5available under alternative licensing terms; contact the address above.
6Redistribution and use in source and binary forms, with or without
7modification, are permitted provided that the following conditions are met:
81. Redistributions of source code must retain the above copyright notice, this
9 list of conditions and the following disclaimer.
102. Redistributions in binary form must reproduce the above copyright notice,
11 this list of conditions and the following disclaimer in the documentation
12 and/or other materials provided with the distribution.
13THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23The views and conclusions contained in the software and documentation are those
24of the authors and should not be interpreted as representing official policies,
25either expressed or implied, of the Regents of The University of Michigan.
26*/
27
28#include <math.h>
29
30#include "common/matd.h"
31#include "common/zarray.h"
32#include "common/homography.h"
33#include "common/math_util.h"
34
35// correspondences is a list of float[4]s, consisting of the points x
36// and y concatenated. We will compute a homography such that y = Hx
37matd_t *homography_compute(zarray_t *correspondences, int flags)
38{
39 // compute centroids of both sets of points (yields a better
40 // conditioned information matrix)
41 double x_cx = 0, x_cy = 0;
42 double y_cx = 0, y_cy = 0;
43
44 for (int i = 0; i < zarray_size(correspondences); i++) {
45 float *c;
46 zarray_get_volatile(correspondences, i, &c);
47
48 x_cx += c[0];
49 x_cy += c[1];
50 y_cx += c[2];
51 y_cy += c[3];
52 }
53
54 int sz = zarray_size(correspondences);
55 x_cx /= sz;
56 x_cy /= sz;
57 y_cx /= sz;
58 y_cy /= sz;
59
60 // NB We don't normalize scale; it seems implausible that it could
61 // possibly make any difference given the dynamic range of IEEE
62 // doubles.
63
64 matd_t *A = matd_create(9,9);
65 for (int i = 0; i < zarray_size(correspondences); i++) {
66 float *c;
67 zarray_get_volatile(correspondences, i, &c);
68
69 // (below world is "x", and image is "y")
70 double worldx = c[0] - x_cx;
71 double worldy = c[1] - x_cy;
72 double imagex = c[2] - y_cx;
73 double imagey = c[3] - y_cy;
74
75 double a03 = -worldx;
76 double a04 = -worldy;
77 double a05 = -1;
78 double a06 = worldx*imagey;
79 double a07 = worldy*imagey;
80 double a08 = imagey;
81
82 MATD_EL(A, 3, 3) += a03*a03;
83 MATD_EL(A, 3, 4) += a03*a04;
84 MATD_EL(A, 3, 5) += a03*a05;
85 MATD_EL(A, 3, 6) += a03*a06;
86 MATD_EL(A, 3, 7) += a03*a07;
87 MATD_EL(A, 3, 8) += a03*a08;
88 MATD_EL(A, 4, 4) += a04*a04;
89 MATD_EL(A, 4, 5) += a04*a05;
90 MATD_EL(A, 4, 6) += a04*a06;
91 MATD_EL(A, 4, 7) += a04*a07;
92 MATD_EL(A, 4, 8) += a04*a08;
93 MATD_EL(A, 5, 5) += a05*a05;
94 MATD_EL(A, 5, 6) += a05*a06;
95 MATD_EL(A, 5, 7) += a05*a07;
96 MATD_EL(A, 5, 8) += a05*a08;
97 MATD_EL(A, 6, 6) += a06*a06;
98 MATD_EL(A, 6, 7) += a06*a07;
99 MATD_EL(A, 6, 8) += a06*a08;
100 MATD_EL(A, 7, 7) += a07*a07;
101 MATD_EL(A, 7, 8) += a07*a08;
102 MATD_EL(A, 8, 8) += a08*a08;
103
104 double a10 = worldx;
105 double a11 = worldy;
106 double a12 = 1;
107 double a16 = -worldx*imagex;
108 double a17 = -worldy*imagex;
109 double a18 = -imagex;
110
111 MATD_EL(A, 0, 0) += a10*a10;
112 MATD_EL(A, 0, 1) += a10*a11;
113 MATD_EL(A, 0, 2) += a10*a12;
114 MATD_EL(A, 0, 6) += a10*a16;
115 MATD_EL(A, 0, 7) += a10*a17;
116 MATD_EL(A, 0, 8) += a10*a18;
117 MATD_EL(A, 1, 1) += a11*a11;
118 MATD_EL(A, 1, 2) += a11*a12;
119 MATD_EL(A, 1, 6) += a11*a16;
120 MATD_EL(A, 1, 7) += a11*a17;
121 MATD_EL(A, 1, 8) += a11*a18;
122 MATD_EL(A, 2, 2) += a12*a12;
123 MATD_EL(A, 2, 6) += a12*a16;
124 MATD_EL(A, 2, 7) += a12*a17;
125 MATD_EL(A, 2, 8) += a12*a18;
126 MATD_EL(A, 6, 6) += a16*a16;
127 MATD_EL(A, 6, 7) += a16*a17;
128 MATD_EL(A, 6, 8) += a16*a18;
129 MATD_EL(A, 7, 7) += a17*a17;
130 MATD_EL(A, 7, 8) += a17*a18;
131 MATD_EL(A, 8, 8) += a18*a18;
132
133 double a20 = -worldx*imagey;
134 double a21 = -worldy*imagey;
135 double a22 = -imagey;
136 double a23 = worldx*imagex;
137 double a24 = worldy*imagex;
138 double a25 = imagex;
139
140 MATD_EL(A, 0, 0) += a20*a20;
141 MATD_EL(A, 0, 1) += a20*a21;
142 MATD_EL(A, 0, 2) += a20*a22;
143 MATD_EL(A, 0, 3) += a20*a23;
144 MATD_EL(A, 0, 4) += a20*a24;
145 MATD_EL(A, 0, 5) += a20*a25;
146 MATD_EL(A, 1, 1) += a21*a21;
147 MATD_EL(A, 1, 2) += a21*a22;
148 MATD_EL(A, 1, 3) += a21*a23;
149 MATD_EL(A, 1, 4) += a21*a24;
150 MATD_EL(A, 1, 5) += a21*a25;
151 MATD_EL(A, 2, 2) += a22*a22;
152 MATD_EL(A, 2, 3) += a22*a23;
153 MATD_EL(A, 2, 4) += a22*a24;
154 MATD_EL(A, 2, 5) += a22*a25;
155 MATD_EL(A, 3, 3) += a23*a23;
156 MATD_EL(A, 3, 4) += a23*a24;
157 MATD_EL(A, 3, 5) += a23*a25;
158 MATD_EL(A, 4, 4) += a24*a24;
159 MATD_EL(A, 4, 5) += a24*a25;
160 MATD_EL(A, 5, 5) += a25*a25;
161 }
162
163 // make symmetric
164 for (int i = 0; i < 9; i++)
165 for (int j = i+1; j < 9; j++)
166 MATD_EL(A, j, i) = MATD_EL(A, i, j);
167
168 matd_t *H = matd_create(3,3);
169
170 if (flags & HOMOGRAPHY_COMPUTE_FLAG_INVERSE) {
171 // compute singular vector by (carefully) inverting the rank-deficient matrix.
172
173 if (1) {
174 matd_t *Ainv = matd_inverse(A);
175 double scale = 0;
176
177 for (int i = 0; i < 9; i++)
178 scale += sq(MATD_EL(Ainv, i, 0));
179 scale = sqrt(scale);
180
181 for (int i = 0; i < 3; i++)
182 for (int j = 0; j < 3; j++)
183 MATD_EL(H, i, j) = MATD_EL(Ainv, 3*i+j, 0) / scale;
184
185 matd_destroy(Ainv);
186 } else {
187
188 matd_t *b = matd_create_data(9, 1, (double[]) { 1, 0, 0, 0, 0, 0, 0, 0, 0 });
189 matd_t *Ainv = NULL;
190
191 if (0) {
192 matd_plu_t *lu = matd_plu(A);
193 Ainv = matd_plu_solve(lu, b);
194 matd_plu_destroy(lu);
195 } else {
196 matd_chol_t *chol = matd_chol(A);
197 Ainv = matd_chol_solve(chol, b);
198 matd_chol_destroy(chol);
199 }
200
201 double scale = 0;
202
203 for (int i = 0; i < 9; i++)
204 scale += sq(MATD_EL(Ainv, i, 0));
205 scale = sqrt(scale);
206
207 for (int i = 0; i < 3; i++)
208 for (int j = 0; j < 3; j++)
209 MATD_EL(H, i, j) = MATD_EL(Ainv, 3*i+j, 0) / scale;
210
211 matd_destroy(b);
212 matd_destroy(Ainv);
213 }
214
215 } else {
216 // compute singular vector using SVD. A bit slower, but more accurate.
217 matd_svd_t svd = matd_svd_flags(A, MATD_SVD_NO_WARNINGS);
218
219 for (int i = 0; i < 3; i++)
220 for (int j = 0; j < 3; j++)
221 MATD_EL(H, i, j) = MATD_EL(svd.U, 3*i+j, 8);
222
223 matd_destroy(svd.U);
224 matd_destroy(svd.S);
225 matd_destroy(svd.V);
226
227 }
228
229 matd_t *Tx = matd_identity(3);
230 MATD_EL(Tx,0,2) = -x_cx;
231 MATD_EL(Tx,1,2) = -x_cy;
232
233 matd_t *Ty = matd_identity(3);
234 MATD_EL(Ty,0,2) = y_cx;
235 MATD_EL(Ty,1,2) = y_cy;
236
237 matd_t *H2 = matd_op("M*M*M", Ty, H, Tx);
238
239 matd_destroy(A);
240 matd_destroy(Tx);
241 matd_destroy(Ty);
242 matd_destroy(H);
243
244 return H2;
245}
246
247
248// assuming that the projection matrix is:
249// [ fx 0 cx 0 ]
250// [ 0 fy cy 0 ]
251// [ 0 0 1 0 ]
252//
253// And that the homography is equal to the projection matrix times the
254// model matrix, recover the model matrix (which is returned). Note
255// that the third column of the model matrix is missing in the
256// expresison below, reflecting the fact that the homography assumes
257// all points are at z=0 (i.e., planar) and that the element of z is
258// thus omitted. (3x1 instead of 4x1).
259//
260// [ fx 0 cx 0 ] [ R00 R01 TX ] [ H00 H01 H02 ]
261// [ 0 fy cy 0 ] [ R10 R11 TY ] = [ H10 H11 H12 ]
262// [ 0 0 1 0 ] [ R20 R21 TZ ] = [ H20 H21 H22 ]
263// [ 0 0 1 ]
264//
265// fx*R00 + cx*R20 = H00 (note, H only known up to scale; some additional adjustments required; see code.)
266// fx*R01 + cx*R21 = H01
267// fx*TX + cx*TZ = H02
268// fy*R10 + cy*R20 = H10
269// fy*R11 + cy*R21 = H11
270// fy*TY + cy*TZ = H12
271// R20 = H20
272// R21 = H21
273// TZ = H22
274
275matd_t *homography_to_pose(const matd_t *H, double fx, double fy, double cx, double cy)
276{
277 // Note that every variable that we compute is proportional to the scale factor of H.
278 double R20 = MATD_EL(H, 2, 0);
279 double R21 = MATD_EL(H, 2, 1);
280 double TZ = MATD_EL(H, 2, 2);
281 double R00 = (MATD_EL(H, 0, 0) - cx*R20) / fx;
282 double R01 = (MATD_EL(H, 0, 1) - cx*R21) / fx;
283 double TX = (MATD_EL(H, 0, 2) - cx*TZ) / fx;
284 double R10 = (MATD_EL(H, 1, 0) - cy*R20) / fy;
285 double R11 = (MATD_EL(H, 1, 1) - cy*R21) / fy;
286 double TY = (MATD_EL(H, 1, 2) - cy*TZ) / fy;
287
288 // compute the scale by requiring that the rotation columns are unit length
289 // (Use geometric average of the two length vectors we have)
290 double length1 = sqrtf(R00*R00 + R10*R10 + R20*R20);
291 double length2 = sqrtf(R01*R01 + R11*R11 + R21*R21);
292 double s = 1.0 / sqrtf(length1 * length2);
293
294 // get sign of S by requiring the tag to be in front the camera;
295 // we assume camera looks in the -Z direction.
296 if (TZ > 0)
297 s *= -1;
298
299 R20 *= s;
300 R21 *= s;
301 TZ *= s;
302 R00 *= s;
303 R01 *= s;
304 TX *= s;
305 R10 *= s;
306 R11 *= s;
307 TY *= s;
308
309 // now recover [R02 R12 R22] by noting that it is the cross product of the other two columns.
310 double R02 = R10*R21 - R20*R11;
311 double R12 = R20*R01 - R00*R21;
312 double R22 = R00*R11 - R10*R01;
313
314 // Improve rotation matrix by applying polar decomposition.
315 if (1) {
316 // do polar decomposition. This makes the rotation matrix
317 // "proper", but probably increases the reprojection error. An
318 // iterative alignment step would be superior.
319
320 matd_t *R = matd_create_data(3, 3, (double[]) { R00, R01, R02,
321 R10, R11, R12,
322 R20, R21, R22 });
323
324 matd_svd_t svd = matd_svd(R);
325 matd_destroy(R);
326
327 R = matd_op("M*M'", svd.U, svd.V);
328
329 matd_destroy(svd.U);
330 matd_destroy(svd.S);
331 matd_destroy(svd.V);
332
333 R00 = MATD_EL(R, 0, 0);
334 R01 = MATD_EL(R, 0, 1);
335 R02 = MATD_EL(R, 0, 2);
336 R10 = MATD_EL(R, 1, 0);
337 R11 = MATD_EL(R, 1, 1);
338 R12 = MATD_EL(R, 1, 2);
339 R20 = MATD_EL(R, 2, 0);
340 R21 = MATD_EL(R, 2, 1);
341 R22 = MATD_EL(R, 2, 2);
342
343 matd_destroy(R);
344 }
345
346 return matd_create_data(4, 4, (double[]) { R00, R01, R02, TX,
347 R10, R11, R12, TY,
348 R20, R21, R22, TZ,
349 0, 0, 0, 1 });
350}
351
352// Similar to above
353// Recover the model view matrix assuming that the projection matrix is:
354//
355// [ F 0 A 0 ] (see glFrustrum)
356// [ 0 G B 0 ]
357// [ 0 0 C D ]
358// [ 0 0 -1 0 ]
359
360matd_t *homography_to_model_view(const matd_t *H, double F, double G, double A, double B, double C, double D)
361{
362 // Note that every variable that we compute is proportional to the scale factor of H.
363 double R20 = -MATD_EL(H, 2, 0);
364 double R21 = -MATD_EL(H, 2, 1);
365 double TZ = -MATD_EL(H, 2, 2);
366 double R00 = (MATD_EL(H, 0, 0) - A*R20) / F;
367 double R01 = (MATD_EL(H, 0, 1) - A*R21) / F;
368 double TX = (MATD_EL(H, 0, 2) - A*TZ) / F;
369 double R10 = (MATD_EL(H, 1, 0) - B*R20) / G;
370 double R11 = (MATD_EL(H, 1, 1) - B*R21) / G;
371 double TY = (MATD_EL(H, 1, 2) - B*TZ) / G;
372
373 // compute the scale by requiring that the rotation columns are unit length
374 // (Use geometric average of the two length vectors we have)
375 double length1 = sqrtf(R00*R00 + R10*R10 + R20*R20);
376 double length2 = sqrtf(R01*R01 + R11*R11 + R21*R21);
377 double s = 1.0 / sqrtf(length1 * length2);
378
379 // get sign of S by requiring the tag to be in front of the camera
380 // (which is Z < 0) for our conventions.
381 if (TZ > 0)
382 s *= -1;
383
384 R20 *= s;
385 R21 *= s;
386 TZ *= s;
387 R00 *= s;
388 R01 *= s;
389 TX *= s;
390 R10 *= s;
391 R11 *= s;
392 TY *= s;
393
394 // now recover [R02 R12 R22] by noting that it is the cross product of the other two columns.
395 double R02 = R10*R21 - R20*R11;
396 double R12 = R20*R01 - R00*R21;
397 double R22 = R00*R11 - R10*R01;
398
399 // TODO XXX: Improve rotation matrix by applying polar decomposition.
400
401 return matd_create_data(4, 4, (double[]) { R00, R01, R02, TX,
402 R10, R11, R12, TY,
403 R20, R21, R22, TZ,
404 0, 0, 0, 1 });
405}
406
407// Only uses the upper 3x3 matrix.
408/*
409static void matrix_to_quat(const matd_t *R, double q[4])
410{
411 // see: "from quaternion to matrix and back"
412
413 // trace: get the same result if R is 4x4 or 3x3:
414 double T = MATD_EL(R, 0, 0) + MATD_EL(R, 1, 1) + MATD_EL(R, 2, 2) + 1;
415 double S = 0;
416
417 double m0 = MATD_EL(R, 0, 0);
418 double m1 = MATD_EL(R, 1, 0);
419 double m2 = MATD_EL(R, 2, 0);
420 double m4 = MATD_EL(R, 0, 1);
421 double m5 = MATD_EL(R, 1, 1);
422 double m6 = MATD_EL(R, 2, 1);
423 double m8 = MATD_EL(R, 0, 2);
424 double m9 = MATD_EL(R, 1, 2);
425 double m10 = MATD_EL(R, 2, 2);
426
427 if (T > 0.0000001) {
428 S = sqrtf(T) * 2;
429 q[1] = -( m9 - m6 ) / S;
430 q[2] = -( m2 - m8 ) / S;
431 q[3] = -( m4 - m1 ) / S;
432 q[0] = 0.25 * S;
433 } else if ( m0 > m5 && m0 > m10 ) { // Column 0:
434 S = sqrtf( 1.0 + m0 - m5 - m10 ) * 2;
435 q[1] = -0.25 * S;
436 q[2] = -(m4 + m1 ) / S;
437 q[3] = -(m2 + m8 ) / S;
438 q[0] = (m9 - m6 ) / S;
439 } else if ( m5 > m10 ) { // Column 1:
440 S = sqrtf( 1.0 + m5 - m0 - m10 ) * 2;
441 q[1] = -(m4 + m1 ) / S;
442 q[2] = -0.25 * S;
443 q[3] = -(m9 + m6 ) / S;
444 q[0] = (m2 - m8 ) / S;
445 } else {
446 // Column 2:
447 S = sqrtf( 1.0 + m10 - m0 - m5 ) * 2;
448 q[1] = -(m2 + m8 ) / S;
449 q[2] = -(m9 + m6 ) / S;
450 q[3] = -0.25 * S;
451 q[0] = (m4 - m1 ) / S;
452 }
453
454 double mag2 = 0;
455 for (int i = 0; i < 4; i++)
456 mag2 += q[i]*q[i];
457 double norm = 1.0 / sqrtf(mag2);
458 for (int i = 0; i < 4; i++)
459 q[i] *= norm;
460}
461*/
462
463// overwrites upper 3x3 area of matrix M. Doesn't touch any other elements of M.
464void quat_to_matrix(const double q[4], matd_t *M)
465{
466 double w = q[0], x = q[1], y = q[2], z = q[3];
467
468 MATD_EL(M, 0, 0) = w*w + x*x - y*y - z*z;
469 MATD_EL(M, 0, 1) = 2*x*y - 2*w*z;
470 MATD_EL(M, 0, 2) = 2*x*z + 2*w*y;
471
472 MATD_EL(M, 1, 0) = 2*x*y + 2*w*z;
473 MATD_EL(M, 1, 1) = w*w - x*x + y*y - z*z;
474 MATD_EL(M, 1, 2) = 2*y*z - 2*w*x;
475
476 MATD_EL(M, 2, 0) = 2*x*z - 2*w*y;
477 MATD_EL(M, 2, 1) = 2*y*z + 2*w*x;
478 MATD_EL(M, 2, 2) = w*w - x*x - y*y + z*z;
479}