1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package com.irurueta.ar.epipolar.estimators;
17
18 import com.irurueta.algebra.AlgebraException;
19 import com.irurueta.algebra.Matrix;
20 import com.irurueta.algebra.SingularValueDecomposer;
21 import com.irurueta.geometry.EuclideanTransformation3D;
22 import com.irurueta.geometry.InvalidRotationMatrixException;
23 import com.irurueta.geometry.MatrixRotation3D;
24 import com.irurueta.geometry.PinholeCameraIntrinsicParameters;
25 import com.irurueta.geometry.Transformation2D;
26 import com.irurueta.geometry.estimators.LockedException;
27 import com.irurueta.geometry.estimators.NotReadyException;
28
29 import java.util.ArrayList;
30 import java.util.List;
31
32
33
34
35
36
37
38
39
40
41
42
43
44 @SuppressWarnings("DuplicatedCode")
45 public class HomographyDecomposer {
46
47
48
49
50 public static final int NUM_COORDS_3D = 3;
51
52
53
54
55 public static final double EQUAL_SINGULAR_VALUE_THRESHOLD = 1e-12;
56
57
58
59
60 private Transformation2D homography;
61
62
63
64
65 private PinholeCameraIntrinsicParameters leftIntrinsics;
66
67
68
69
70 private PinholeCameraIntrinsicParameters rightIntrinsics;
71
72
73
74
75 private HomographyDecomposerListener listener;
76
77
78
79
80 private boolean locked;
81
82
83
84
85 public HomographyDecomposer() {
86 }
87
88
89
90
91
92
93
94
95
96 public HomographyDecomposer(final Transformation2D homography,
97 final PinholeCameraIntrinsicParameters leftIntrinsics,
98 final PinholeCameraIntrinsicParameters rightIntrinsics) {
99 this.homography = homography;
100 this.leftIntrinsics = leftIntrinsics;
101 this.rightIntrinsics = rightIntrinsics;
102 }
103
104
105
106
107
108
109
110
111
112
113 public HomographyDecomposer(final Transformation2D homography,
114 final PinholeCameraIntrinsicParameters leftIntrinsics,
115 final PinholeCameraIntrinsicParameters rightIntrinsics,
116 final HomographyDecomposerListener listener) {
117 this(homography, leftIntrinsics, rightIntrinsics);
118 this.listener = listener;
119 }
120
121
122
123
124
125
126 public Transformation2D getHomography() {
127 return homography;
128 }
129
130
131
132
133
134
135
136 public void setHomography(final Transformation2D homography) throws LockedException {
137 if (isLocked()) {
138 throw new LockedException();
139 }
140 this.homography = homography;
141 }
142
143
144
145
146
147
148 public PinholeCameraIntrinsicParameters getLeftIntrinsics() {
149 return leftIntrinsics;
150 }
151
152
153
154
155
156
157
158 public void setLeftIntrinsics(final PinholeCameraIntrinsicParameters leftIntrinsics) throws LockedException {
159 if (isLocked()) {
160 throw new LockedException();
161 }
162 this.leftIntrinsics = leftIntrinsics;
163 }
164
165
166
167
168
169
170 public PinholeCameraIntrinsicParameters getRightIntrinsics() {
171 return rightIntrinsics;
172 }
173
174
175
176
177
178
179
180 public void setRightIntrinsics(final PinholeCameraIntrinsicParameters rightIntrinsics) throws LockedException {
181 if (isLocked()) {
182 throw new LockedException();
183 }
184 this.rightIntrinsics = rightIntrinsics;
185 }
186
187
188
189
190
191
192 public HomographyDecomposerListener getListener() {
193 return listener;
194 }
195
196
197
198
199
200
201 public void setListener(final HomographyDecomposerListener listener) {
202 this.listener = listener;
203 }
204
205
206
207
208
209
210 public boolean isLocked() {
211 return locked;
212 }
213
214
215
216
217
218
219
220 public boolean isReady() {
221 return homography != null && leftIntrinsics != null && rightIntrinsics != null;
222 }
223
224
225
226
227
228
229
230
231
232
233
234
235 public List<HomographyDecomposition> decompose() throws LockedException, NotReadyException,
236 HomographyDecomposerException {
237 final var result = new ArrayList<HomographyDecomposition>();
238 decompose(result);
239 return result;
240 }
241
242
243
244
245
246
247
248
249
250
251
252
253 public void decompose(final List<HomographyDecomposition> result) throws LockedException, NotReadyException,
254 HomographyDecomposerException {
255 if (isLocked()) {
256 throw new LockedException();
257 }
258
259 if (!isReady()) {
260 throw new NotReadyException();
261 }
262
263 try {
264 locked = true;
265 result.clear();
266
267 if (listener != null) {
268 listener.onDecomposeStart(this);
269 }
270
271 final var h = computeNormalizedCoordinatesHomographyMatrix();
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295 final var svdDecomposer = new SingularValueDecomposer(h);
296 svdDecomposer.decompose();
297 final var u = svdDecomposer.getU();
298 final var singularValues = svdDecomposer.getSingularValues();
299 final var v = svdDecomposer.getV();
300
301 final var n = new ArrayList<double[]>();
302 final var r = new ArrayList<Matrix>();
303 final var t = new ArrayList<double[]>();
304 final var d = new ArrayList<Double>();
305 final var numSolutions = decomposeAllFromSingularValues(singularValues, n, r, t, d);
306
307 final var transV = v.transposeAndReturnNew();
308 final var translationMatrix = new Matrix(NUM_COORDS_3D, 1);
309 final var planeNormalMatrix = new Matrix(NUM_COORDS_3D, 1);
310
311 for (var i = 0; i < numSolutions; i++) {
312
313
314
315 final var denormalizedR = new Matrix(u);
316 denormalizedR.multiply(r.get(i));
317 denormalizedR.multiply(transV);
318
319
320 translationMatrix.fromArray(t.get(i), true);
321 final var denormalizedTranslationMatrix = u.multiplyAndReturnNew(translationMatrix);
322
323
324 planeNormalMatrix.fromArray(n.get(i));
325 final var denormalizedPlaneNormalMatrix = v.multiplyAndReturnNew(planeNormalMatrix);
326
327 final var denormalizedPlaneDistance = d.get(i);
328
329
330 final var denormalizedRotation = new MatrixRotation3D(denormalizedR);
331 final var denormalizedTranslation = denormalizedTranslationMatrix.getBuffer();
332 final var denormalizedPlaneNormal = denormalizedPlaneNormalMatrix.getBuffer();
333
334
335 final var transformation = new EuclideanTransformation3D(denormalizedRotation, denormalizedTranslation);
336
337 result.add(new HomographyDecomposition(transformation, denormalizedPlaneNormal,
338 denormalizedPlaneDistance));
339 }
340 } catch (final InvalidRotationMatrixException | AlgebraException e) {
341 throw new HomographyDecomposerException(e);
342 } finally {
343 if (listener != null) {
344 listener.onDecomposeEnd(this, result);
345 }
346 locked = false;
347 }
348 }
349
350
351
352
353
354
355
356
357
358
359
360
361 private int decomposeAllFromSingularValues(
362 final double[] singularValues,
363 final List<double[]> n,
364 final List<Matrix> r,
365 final List<double[]> t,
366 final List<Double> d)
367 throws HomographyDecomposerException {
368
369 n.clear();
370 r.clear();
371 t.clear();
372 d.clear();
373
374 if (areThreeDifferentSingularValues(singularValues)) {
375
376 final var n1 = new double[NUM_COORDS_3D];
377 final var n2 = new double[NUM_COORDS_3D];
378 final var n3 = new double[NUM_COORDS_3D];
379 final var n4 = new double[NUM_COORDS_3D];
380 Matrix r1 = null;
381 Matrix r2 = null;
382 Matrix r3 = null;
383 Matrix r4 = null;
384 try {
385 r1 = new Matrix(NUM_COORDS_3D, NUM_COORDS_3D);
386 r2 = new Matrix(NUM_COORDS_3D, NUM_COORDS_3D);
387 r3 = new Matrix(NUM_COORDS_3D, NUM_COORDS_3D);
388 r4 = new Matrix(NUM_COORDS_3D, NUM_COORDS_3D);
389 } catch (final AlgebraException ignore) {
390
391 }
392
393 final var t1 = new double[NUM_COORDS_3D];
394 final var t2 = new double[NUM_COORDS_3D];
395 final var t3 = new double[NUM_COORDS_3D];
396 final var t4 = new double[NUM_COORDS_3D];
397
398 final var planeDistance1 = decomposeFromSingularValues(singularValues, n1, r1, t1, true,
399 true);
400 final var planeDistance2 = decomposeFromSingularValues(singularValues, n2, r2, t2, false,
401 true);
402 final var planeDistance3 = decomposeFromSingularValues(singularValues, n3, r3, t3, true,
403 false);
404 final var planeDistance4 = decomposeFromSingularValues(singularValues, n4, r4, t4, false,
405 false);
406
407 n.add(n1);
408 n.add(n2);
409 n.add(n3);
410 n.add(n4);
411
412 r.add(r1);
413 r.add(r2);
414 r.add(r3);
415 r.add(r4);
416
417 t.add(t1);
418 t.add(t2);
419 t.add(t3);
420 t.add(t4);
421
422 d.add(planeDistance1);
423 d.add(planeDistance2);
424 d.add(planeDistance3);
425 d.add(planeDistance4);
426
427 return n.size();
428
429 } else if (areTwoEqualSingularValues(singularValues)) {
430
431 final var n1 = new double[NUM_COORDS_3D];
432 final var n2 = new double[NUM_COORDS_3D];
433 Matrix r1 = null;
434 Matrix r2 = null;
435 try {
436 r1 = new Matrix(NUM_COORDS_3D, NUM_COORDS_3D);
437 r2 = new Matrix(NUM_COORDS_3D, NUM_COORDS_3D);
438 } catch (final AlgebraException ignore) {
439
440 }
441
442 final var t1 = new double[NUM_COORDS_3D];
443 final var t2 = new double[NUM_COORDS_3D];
444
445 final var planeDistance1 = decomposeFromSingularValues(singularValues, n1, r1, t1, true,
446 true);
447 final var planeDistance2 = decomposeFromSingularValues(singularValues, n2, r2, t2, true,
448 false);
449
450 n.add(n1);
451 n.add(n2);
452
453 r.add(r1);
454 r.add(r2);
455
456 t.add(t1);
457 t.add(t2);
458
459 d.add(planeDistance1);
460 d.add(planeDistance2);
461
462 return n.size();
463
464 } else {
465
466 throw new HomographyDecomposerException("undefined plane normal");
467 }
468 }
469
470
471
472
473
474
475
476
477 private static boolean areThreeDifferentSingularValues(final double[] singularValues) {
478 final var d1 = singularValues[0];
479 final var d2 = singularValues[1];
480 final var d3 = singularValues[2];
481
482 return (Math.abs(d1 - d2) > EQUAL_SINGULAR_VALUE_THRESHOLD)
483 && (Math.abs(d2 - d3) > EQUAL_SINGULAR_VALUE_THRESHOLD);
484 }
485
486
487
488
489
490
491
492 private static boolean areTwoEqualSingularValues(final double[] singularValues) {
493 final var d1 = singularValues[0];
494 final var d2 = singularValues[1];
495 final var d3 = singularValues[2];
496
497 return ((Math.abs(d1 - d2) <= EQUAL_SINGULAR_VALUE_THRESHOLD)
498 && (Math.abs(d2 - d3) > EQUAL_SINGULAR_VALUE_THRESHOLD))
499 || ((Math.abs(d1 - d2) > EQUAL_SINGULAR_VALUE_THRESHOLD)
500 && (Math.abs(d2 - d3) <= EQUAL_SINGULAR_VALUE_THRESHOLD));
501 }
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516 private double decomposeFromSingularValues(
517 final double[] singularValues, final double[] n, final Matrix r, final double[] t, final boolean positive1,
518 final boolean positive3) throws HomographyDecomposerException {
519 final var d1 = singularValues[0];
520 final var d2 = singularValues[1];
521 final var d3 = singularValues[2];
522
523 if (areThreeDifferentSingularValues(singularValues)) {
524
525 if (d2 > 0.0) {
526
527 return decomposeFromThreeDifferentSingularValuesPositive(d1, d2, d3, n, r, t, positive1, positive3);
528 } else {
529
530 return decomposeFromThreeDifferentSingularValuesNegative(d1, d2, d3, n, r, t, positive1, positive3);
531 }
532 }
533 if (areTwoEqualSingularValues(singularValues)) {
534
535 if (d2 > 0.0) {
536
537
538 return decomposeFromTwoDifferentSingularValuesPositive(d1, d2, d3, n, r, t, positive3);
539 } else {
540
541
542 return decomposeFromTwoDifferentSingularValuesNegative(d1, d2, d3, n, r, t, positive3);
543 }
544 } else {
545
546 throw new HomographyDecomposerException("undefined plane normal");
547 }
548 }
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564 private double decomposeFromTwoDifferentSingularValuesNegative(
565 final double d1, final double d2, final double d3, final double[] n, final Matrix r, final double[] t,
566 final boolean positive3) {
567
568
569 n[0] = 0.0;
570 n[1] = 0.0;
571 n[2] = positive3 ? 1.0 : -1.0;
572
573
574
575
576 r.setElementAt(0, 0, -1.0);
577 r.setElementAt(1, 0, 0.0);
578 r.setElementAt(2, 0, 0.0);
579
580 r.setElementAt(0, 1, 0.0);
581 r.setElementAt(1, 1, -1.0);
582 r.setElementAt(2, 1, 0.0);
583
584 r.setElementAt(0, 2, 0.0);
585 r.setElementAt(1, 2, 0.0);
586 r.setElementAt(2, 2, 1.0);
587
588
589 final var sum = d3 + d1;
590 t[0] = 0.0;
591 t[1] = 0.0;
592 t[2] = sum * n[2];
593
594
595 return d2;
596 }
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612 private double decomposeFromTwoDifferentSingularValuesPositive(
613 final double d1, final double d2, final double d3, final double[] n, final Matrix r, final double[] t,
614 final boolean positive3) {
615
616
617 n[0] = 0.0;
618 n[1] = 0.0;
619 n[2] = positive3 ? 1.0 : -1.0;
620
621
622
623
624 r.setElementAt(0, 0, 1.0);
625 r.setElementAt(1, 0, 0.0);
626 r.setElementAt(2, 0, 0.0);
627
628 r.setElementAt(0, 1, 0.0);
629 r.setElementAt(1, 1, 1.0);
630 r.setElementAt(2, 1, 0.0);
631
632 r.setElementAt(0, 2, 0.0);
633 r.setElementAt(1, 2, 0.0);
634 r.setElementAt(2, 2, 1.0);
635
636
637 final var diff = d1 - d3;
638 t[0] = 0.0;
639 t[1] = 0.0;
640 t[2] = -diff * n[2];
641
642
643 return d2;
644 }
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661 private double decomposeFromThreeDifferentSingularValuesNegative(
662 final double d1, final double d2, final double d3, final double[] n, final Matrix r, final double[] t,
663 final boolean positive1, final boolean positive3) {
664 final var d1Sqr = d1 * d1;
665 final var d2Sqr = d2 * d2;
666 final var d3Sqr = d3 * d3;
667
668
669 final var denom = d1Sqr - d3Sqr;
670
671 var x1 = Math.sqrt((d1Sqr - d2Sqr) / denom);
672 if (!positive1) {
673 x1 = -x1;
674 }
675
676 final var x2 = 0.0;
677
678 var x3 = Math.sqrt((d2Sqr - d3Sqr) / denom);
679 if (!positive3) {
680 x3 = -x3;
681 }
682
683
684 n[0] = x1;
685 n[1] = x2;
686 n[2] = x3;
687
688
689 final var x1Sqr = x1 * x1;
690 final var x3Sqr = x3 * x3;
691
692 final var sinTheta = (d1 + d3) * x1 * x3 / d2;
693 final var cosTheta = (d3 * x1Sqr - d1 * x3Sqr) / d2;
694
695
696 r.setElementAt(0, 0, cosTheta);
697 r.setElementAt(1, 0, 0.0);
698 r.setElementAt(2, 0, sinTheta);
699
700 r.setElementAt(0, 1, 0.0);
701 r.setElementAt(1, 1, 1.0);
702 r.setElementAt(2, 1, 0.0);
703
704 r.setElementAt(0, 2, -sinTheta);
705 r.setElementAt(1, 2, 0.0);
706 r.setElementAt(2, 2, cosTheta);
707
708
709 final var sum = d1 + d3;
710 t[0] = sum * x1;
711 t[1] = 0.0;
712 t[2] = sum * x3;
713
714
715 return d2;
716 }
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733 private double decomposeFromThreeDifferentSingularValuesPositive(
734 final double d1, final double d2, final double d3, final double[] n, final Matrix r, final double[] t,
735 final boolean positive1, final boolean positive3) {
736 final var d1Sqr = d1 * d1;
737 final var d2Sqr = d2 * d2;
738 final var d3Sqr = d3 * d3;
739
740
741 final var denom = d1Sqr - d3Sqr;
742
743 var x1 = Math.sqrt((d1Sqr - d2Sqr) / denom);
744 if (!positive1) {
745 x1 = -x1;
746 }
747
748 final var x2 = 0.0;
749
750 var x3 = Math.sqrt((d2Sqr - d3Sqr) / denom);
751 if (!positive3) {
752 x3 = -x3;
753 }
754
755
756 n[0] = x1;
757 n[1] = x2;
758 n[2] = x3;
759
760
761 final var x1Sqr = x1 * x1;
762 final var x3Sqr = x3 * x3;
763
764 final var sinTheta = (d1 - d3) * x1 * x3 / d2;
765 final var cosTheta = (d1 * x3Sqr + d3 * x1Sqr) / d2;
766
767
768 r.setElementAt(0, 0, cosTheta);
769 r.setElementAt(1, 0, 0.0);
770 r.setElementAt(2, 0, sinTheta);
771
772 r.setElementAt(0, 1, 0.0);
773 r.setElementAt(1, 1, 1.0);
774 r.setElementAt(2, 1, 0.0);
775
776 r.setElementAt(0, 2, -sinTheta);
777 r.setElementAt(1, 2, 0.0);
778 r.setElementAt(2, 2, cosTheta);
779
780
781
782 final var diff = d1 - d3;
783 t[0] = diff * x1;
784 t[1] = 0.0;
785 t[2] = -diff * x3;
786
787
788 return d2;
789 }
790
791
792
793
794
795
796
797
798 private Matrix computeNormalizedCoordinatesHomographyMatrix() throws AlgebraException {
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813 final var k1 = leftIntrinsics.getInternalMatrix();
814 final var invK2 = rightIntrinsics.getInverseInternalMatrix();
815 final var g = homography.asMatrix();
816
817
818 g.multiply(k1);
819 invK2.multiply(g);
820 return invK2;
821 }
822 }