1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package com.irurueta.ar.epipolar;
17
18 import com.irurueta.algebra.AlgebraException;
19 import com.irurueta.algebra.Matrix;
20 import com.irurueta.algebra.SingularValueDecomposer;
21 import com.irurueta.algebra.Utils;
22 import com.irurueta.geometry.*;
23
24 import java.io.Serializable;
25
26
27
28
29
30
31
32
33 public class EssentialMatrix extends FundamentalMatrix implements Serializable {
34
35
36
37
38
39 public static final double DEFAULT_SINGULAR_VALUES_THRESHOLD = 1e-8;
40
41 private Rotation3D rotation1;
42 private Rotation3D rotation2;
43
44 private Point2D translation1;
45 private Point2D translation2;
46
47 private boolean possibleRotationsAndTranslationsAvailable;
48
49
50
51
52 public EssentialMatrix() {
53 super();
54 }
55
56
57
58
59
60
61
62
63
64
65
66
67 public EssentialMatrix(final Matrix internalMatrix, final double singularValuesThreshold)
68 throws InvalidEssentialMatrixException {
69 super();
70 setInternalMatrix(internalMatrix, singularValuesThreshold);
71 }
72
73
74
75
76
77
78
79
80
81 public EssentialMatrix(final Matrix internalMatrix) throws InvalidEssentialMatrixException {
82 this(internalMatrix, DEFAULT_SINGULAR_VALUES_THRESHOLD);
83 }
84
85
86
87
88
89
90
91
92
93
94
95
96
97 public EssentialMatrix(
98 final PinholeCamera leftCamera, final PinholeCamera rightCamera, final double singularValuesThreshold)
99 throws InvalidPairOfCamerasException {
100 super();
101 setFromPairOfCameras(leftCamera, rightCamera, singularValuesThreshold);
102 }
103
104
105
106
107
108
109
110
111
112
113 public EssentialMatrix(
114 final PinholeCamera leftCamera, final PinholeCamera rightCamera) throws InvalidPairOfCamerasException {
115 this(leftCamera, rightCamera, DEFAULT_SINGULAR_VALUES_THRESHOLD);
116 }
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132 public EssentialMatrix(
133 final Rotation3D rotation, final Point2D translation, final double singularValuesThreshold)
134 throws InvalidRotationAndTranslationException {
135 super();
136 setFromRotationAndTranslation(rotation, translation, singularValuesThreshold);
137 }
138
139
140
141
142
143
144
145
146
147
148
149
150 public EssentialMatrix(
151 final Rotation3D rotation, final Point2D translation) throws InvalidRotationAndTranslationException {
152 this(rotation, translation, DEFAULT_SINGULAR_VALUES_THRESHOLD);
153 }
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168 public EssentialMatrix(
169 final Rotation3D rotation, final Point3D cameraCenter, final double singularValuesThreshold)
170 throws InvalidRotationAndTranslationException {
171 setFromRotationAndCameraCenter(rotation, cameraCenter, singularValuesThreshold);
172 }
173
174
175
176
177
178
179
180
181
182
183
184 public EssentialMatrix(
185 final Rotation3D rotation, final Point3D cameraCenter) throws InvalidRotationAndTranslationException {
186 this(rotation, cameraCenter, DEFAULT_SINGULAR_VALUES_THRESHOLD);
187 }
188
189
190
191
192
193
194
195
196
197
198 public EssentialMatrix(
199 final FundamentalMatrix fundamentalMatrix,
200 final PinholeCameraIntrinsicParameters leftIntrinsicParameters,
201 final PinholeCameraIntrinsicParameters rightIntrinsicParameters)
202 throws InvalidPairOfIntrinsicParametersException {
203 setFromFundamentalMatrixAndIntrinsics(fundamentalMatrix, leftIntrinsicParameters, rightIntrinsicParameters);
204 }
205
206
207
208
209
210
211
212
213
214
215 @Override
216 public final void setInternalMatrix(final Matrix internalMatrix) throws InvalidEssentialMatrixException {
217 setInternalMatrix(internalMatrix, DEFAULT_SINGULAR_VALUES_THRESHOLD);
218 }
219
220
221
222
223
224
225
226
227
228
229
230
231
232 public final void setInternalMatrix(
233 final Matrix internalMatrix, final double singularValuesThreshold) throws InvalidEssentialMatrixException {
234 if (!isValidInternalMatrix(internalMatrix, singularValuesThreshold)) {
235 throw new InvalidEssentialMatrixException();
236 }
237
238
239 this.internalMatrix = new Matrix(internalMatrix);
240 normalized = false;
241 leftEpipole = rightEpipole = null;
242 }
243
244
245
246
247
248
249
250
251
252
253 public static boolean isValidInternalMatrix(final Matrix internalMatrix) {
254 return isValidInternalMatrix(internalMatrix, DEFAULT_SINGULAR_VALUES_THRESHOLD);
255 }
256
257
258
259
260
261
262
263
264
265
266
267
268
269 public static boolean isValidInternalMatrix(final Matrix internalMatrix, final double singularValuesThreshold) {
270 if (singularValuesThreshold < 0) {
271 throw new IllegalArgumentException();
272 }
273
274 if (internalMatrix.getColumns() != FUNDAMENTAL_MATRIX_COLS
275 || internalMatrix.getRows() != FUNDAMENTAL_MATRIX_ROWS) {
276 return false;
277 }
278
279 try {
280 final var decomposer = new SingularValueDecomposer(internalMatrix);
281
282 decomposer.decompose();
283
284 final var rankEssential = decomposer.getRank();
285
286 if (rankEssential != FUNDAMENTAL_MATRIX_RANK) {
287 return false;
288 }
289
290 final var singularValues = decomposer.getSingularValues();
291
292 return (Math.abs(singularValues[0] - singularValues[1]) <= singularValuesThreshold);
293 } catch (final AlgebraException e) {
294 return false;
295 }
296 }
297
298
299
300
301
302
303
304
305
306
307 @Override
308 public void setFromPairOfCameras(final PinholeCamera leftCamera, final PinholeCamera rightCamera)
309 throws InvalidPairOfCamerasException {
310 setFromPairOfCameras(leftCamera, rightCamera, DEFAULT_SINGULAR_VALUES_THRESHOLD);
311 }
312
313
314
315
316
317
318
319
320
321
322
323
324
325 public final void setFromPairOfCameras(
326 final PinholeCamera leftCamera, final PinholeCamera rightCamera, final double singularValuesThreshold)
327 throws InvalidPairOfCamerasException {
328
329 if (singularValuesThreshold < 0) {
330 throw new IllegalArgumentException();
331 }
332
333 try {
334
335
336 leftCamera.normalize();
337 rightCamera.normalize();
338
339 if (!leftCamera.isCameraSignFixed()) {
340 leftCamera.fixCameraSign();
341 }
342 if (!rightCamera.isCameraSignFixed()) {
343 rightCamera.fixCameraSign();
344 }
345
346
347
348 if (!leftCamera.areIntrinsicParametersAvailable()) {
349 leftCamera.decompose(true, false);
350 }
351 if (!rightCamera.areIntrinsicParametersAvailable()) {
352 rightCamera.decompose(true, false);
353 }
354
355 final var leftIntrinsics = leftCamera.getIntrinsicParameters();
356 final var rightIntrinsics = rightCamera.getIntrinsicParameters();
357
358 final var leftIntrinsicsMatrix = leftIntrinsics.getInternalMatrix();
359 final var rightIntrinsicsMatrix = rightIntrinsics.getInternalMatrix();
360
361
362 final var leftCameraInternalMatrix = leftCamera.getInternalMatrix();
363 final var rightCameraInternalMatrix = rightCamera.getInternalMatrix();
364
365
366 final var invLeftIntrinsicsMatrix = Utils.inverse(leftIntrinsicsMatrix);
367 final var invRightIntrinsicsMatrix = Utils.inverse(rightIntrinsicsMatrix);
368
369
370
371 invLeftIntrinsicsMatrix.multiply(leftCameraInternalMatrix);
372
373
374 invRightIntrinsicsMatrix.multiply(rightCameraInternalMatrix);
375
376
377
378 final var normLeftCamera = new PinholeCamera(invLeftIntrinsicsMatrix);
379
380
381
382 final var normRightCamera = new PinholeCamera(invRightIntrinsicsMatrix);
383
384 normRightCamera.decompose(false, true);
385
386 final var rightCameraCenter = normRightCamera.getCameraCenter();
387 final var normLeftEpipole = normLeftCamera.project(rightCameraCenter);
388
389 normLeftEpipole.normalize();
390
391
392 final var skewNormLeftEpipoleMatrix = Utils.skewMatrix(new double[]{
393 normLeftEpipole.getHomX(), normLeftEpipole.getHomY(), normLeftEpipole.getHomW()});
394
395
396 final var transNormLeftCameraMatrix = invLeftIntrinsicsMatrix.transposeAndReturnNew();
397
398
399 final var transNormRightCameraMatrix = invRightIntrinsicsMatrix.transposeAndReturnNew();
400
401
402 final var pseudoTransNormRightCameraMatrix = Utils.pseudoInverse(transNormRightCameraMatrix);
403
404
405 transNormLeftCameraMatrix.multiply(skewNormLeftEpipoleMatrix);
406 pseudoTransNormRightCameraMatrix.multiply(transNormLeftCameraMatrix);
407
408 setInternalMatrix(pseudoTransNormRightCameraMatrix, singularValuesThreshold);
409 } catch (final GeometryException | AlgebraException e) {
410 throw new InvalidPairOfCamerasException(e);
411 }
412 }
413
414
415
416
417
418
419
420
421
422
423
424
425 public void setFromRotationAndTranslation(
426 final Rotation3D rotation, final Point2D translation) throws InvalidRotationAndTranslationException {
427 setFromRotationAndTranslation(rotation, translation, DEFAULT_SINGULAR_VALUES_THRESHOLD);
428 }
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444 public final void setFromRotationAndTranslation(
445 final Rotation3D rotation, final Point2D translation, final double singularValuesThreshold)
446 throws InvalidRotationAndTranslationException {
447
448 if (singularValuesThreshold < 0) {
449 throw new IllegalArgumentException();
450 }
451
452 try {
453
454 translation.normalize();
455 final var translationArray = new double[]{
456 translation.getHomX(), translation.getHomY(), translation.getHomW()
457 };
458
459 final var skewTranslationMatrix = Utils.skewMatrix(translationArray);
460
461 final var rotationMatrix = rotation.asInhomogeneousMatrix();
462
463
464 skewTranslationMatrix.multiply(rotationMatrix);
465
466 setInternalMatrix(skewTranslationMatrix, singularValuesThreshold);
467 } catch (final AlgebraException | InvalidEssentialMatrixException e) {
468 throw new InvalidRotationAndTranslationException(e);
469 }
470 }
471
472
473
474
475
476
477
478
479
480
481
482 public void setFromRotationAndCameraCenter(
483 final Rotation3D rotation, final Point3D cameraCenter) throws InvalidRotationAndTranslationException {
484 setFromRotationAndCameraCenter(rotation, cameraCenter, DEFAULT_SINGULAR_VALUES_THRESHOLD);
485 }
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500 public final void setFromRotationAndCameraCenter(
501 final Rotation3D rotation, final Point3D cameraCenter, final double singularValuesThreshold)
502 throws InvalidRotationAndTranslationException {
503
504 if (singularValuesThreshold < 0) {
505 throw new IllegalArgumentException();
506 }
507
508 try {
509 var rotationMatrix = rotation.asInhomogeneousMatrix();
510
511
512 cameraCenter.normalize();
513 final var inhomCenterMatrix = new Matrix(Point3D.POINT3D_INHOMOGENEOUS_COORDINATES_LENGTH, 1);
514 inhomCenterMatrix.setElementAtIndex(0, cameraCenter.getInhomX());
515 inhomCenterMatrix.setElementAtIndex(1, cameraCenter.getInhomY());
516 inhomCenterMatrix.setElementAtIndex(2, cameraCenter.getInhomZ());
517
518
519 rotationMatrix.multiplyByScalar(-1.0);
520 rotationMatrix.multiply(inhomCenterMatrix);
521 final var translationMatrix = rotationMatrix;
522
523
524 final var skewTranslationMatrix = Utils.skewMatrix(translationMatrix);
525 rotationMatrix = rotation.asInhomogeneousMatrix();
526 skewTranslationMatrix.multiply(rotationMatrix);
527
528 setInternalMatrix(skewTranslationMatrix, singularValuesThreshold);
529
530 } catch (final AlgebraException | InvalidEssentialMatrixException e) {
531 throw new InvalidRotationAndTranslationException(e);
532 }
533 }
534
535
536
537
538
539
540
541
542
543
544
545 public final void setFromFundamentalMatrixAndIntrinsics(
546 final FundamentalMatrix fundamentalMatrix, final PinholeCameraIntrinsicParameters leftIntrinsicParameters,
547 final PinholeCameraIntrinsicParameters rightIntrinsicParameters)
548 throws InvalidPairOfIntrinsicParametersException {
549
550 try {
551 final var k1 = leftIntrinsicParameters.getInternalMatrix();
552 final var normK1 = Utils.normF(k1);
553 k1.multiplyByScalar(1.0 / normK1);
554
555 final var k2 = rightIntrinsicParameters.getInternalMatrix();
556 final var normK2 = Utils.normF(k2);
557 k2.multiplyByScalar(1.0 / normK2);
558
559
560 fundamentalMatrix.normalize();
561 final var fundMatrix = fundamentalMatrix.getInternalMatrix();
562
563 k2.transpose();
564
565
566 fundMatrix.multiply(k1);
567 k2.multiply(fundMatrix);
568
569 final var normEssential = Utils.normF(k2);
570 k2.multiplyByScalar(1.0 / normEssential);
571
572 internalMatrix = k2;
573 normalized = false;
574 leftEpipole = rightEpipole = null;
575 } catch (final AlgebraException | GeometryException e) {
576 throw new InvalidPairOfIntrinsicParametersException(e);
577 }
578 }
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594 public FundamentalMatrix toFundamentalMatrix(
595 final PinholeCameraIntrinsicParameters leftIntrinsicParameters,
596 final PinholeCameraIntrinsicParameters rightIntrinsicParameters) throws EpipolarException {
597 try {
598 normalize();
599
600 final var essentialMatrix = getInternalMatrix();
601
602 final var k1 = leftIntrinsicParameters.getInternalMatrix();
603 final var invK1 = Utils.inverse(k1);
604 final var normInvK1 = Utils.normF(invK1);
605 invK1.multiplyByScalar(1.0 / normInvK1);
606
607 final var k2 = rightIntrinsicParameters.getInternalMatrix();
608 final var invK2 = Utils.inverse(k2);
609 final var normInvK2 = Utils.normF(invK2);
610 invK2.multiplyByScalar(1.0 / normInvK2);
611 invK2.transpose();
612
613 essentialMatrix.multiply(invK1);
614 invK2.multiply(essentialMatrix);
615
616 return new FundamentalMatrix(invK2);
617
618 } catch (final AlgebraException | GeometryException e) {
619 throw new EpipolarException(e);
620 }
621 }
622
623
624
625
626
627
628
629
630 public void computePossibleRotationAndTranslations() throws InvalidEssentialMatrixException {
631 try {
632 final var decomposer = new SingularValueDecomposer(internalMatrix);
633
634 decomposer.decompose();
635
636 final var u = decomposer.getU();
637 final var v = decomposer.getV();
638
639 v.transpose();
640
641 translation1 = new HomogeneousPoint2D(u.getElementAt(0, 2),
642 u.getElementAt(1, 2), u.getElementAt(2, 2));
643 translation2 = new HomogeneousPoint2D(-u.getElementAt(0, 2),
644 -u.getElementAt(1, 2), -u.getElementAt(2, 2));
645
646
647
648 final var w = new Matrix(FUNDAMENTAL_MATRIX_ROWS, FUNDAMENTAL_MATRIX_COLS);
649 w.setElementAt(0, 1, -1.0);
650 w.setElementAt(1, 0, 1.0);
651 w.setElementAt(2, 2, 1.0);
652
653 final var transW = w.transposeAndReturnNew();
654
655
656 w.multiply(v);
657 final var rotationMatrix1 = u.multiplyAndReturnNew(w);
658
659
660 transW.multiply(v);
661 final var rotationMatrix2 = u.multiplyAndReturnNew(transW);
662
663 rotation1 = new MatrixRotation3D(rotationMatrix1);
664 rotation2 = new MatrixRotation3D(rotationMatrix2);
665
666 possibleRotationsAndTranslationsAvailable = true;
667 } catch (final AlgebraException | InvalidRotationMatrixException e) {
668 throw new InvalidEssentialMatrixException(e);
669 }
670 }
671
672
673
674
675
676
677
678
679 public boolean arePossibleRotationsAndTranslationsAvailable() {
680 return possibleRotationsAndTranslationsAvailable;
681 }
682
683
684
685
686
687
688
689
690 public Rotation3D getFirstPossibleRotation() throws NotAvailableException {
691 if (!arePossibleRotationsAndTranslationsAvailable()) {
692 throw new NotAvailableException();
693 }
694
695 return rotation1;
696 }
697
698
699
700
701
702
703
704
705 public Rotation3D getSecondPossibleRotation() throws NotAvailableException {
706 if (!arePossibleRotationsAndTranslationsAvailable()) {
707 throw new NotAvailableException();
708 }
709
710 return rotation2;
711 }
712
713
714
715
716
717
718
719
720 public Point2D getFirstPossibleTranslation() throws NotAvailableException {
721 if (!arePossibleRotationsAndTranslationsAvailable()) {
722 throw new NotAvailableException();
723 }
724
725 return translation1;
726 }
727
728
729
730
731
732
733
734
735 public Point2D getSecondPossibleTranslation() throws NotAvailableException {
736 if (!arePossibleRotationsAndTranslationsAvailable()) {
737 throw new NotAvailableException();
738 }
739
740 return translation2;
741 }
742 }