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.DecomposerException;
20 import com.irurueta.algebra.Matrix;
21 import com.irurueta.algebra.SingularValueDecomposer;
22 import com.irurueta.algebra.Utils;
23 import com.irurueta.algebra.WrongSizeException;
24 import com.irurueta.geometry.CoordinatesType;
25 import com.irurueta.geometry.GeometryException;
26 import com.irurueta.geometry.HomogeneousPoint2D;
27 import com.irurueta.geometry.Line2D;
28 import com.irurueta.geometry.NotAvailableException;
29 import com.irurueta.geometry.PinholeCamera;
30 import com.irurueta.geometry.Point2D;
31 import com.irurueta.geometry.Transformation2D;
32 import com.irurueta.geometry.estimators.NotReadyException;
33
34 import java.io.Serializable;
35
36
37
38
39
40
41
42 @SuppressWarnings("DuplicatedCode")
43 public class FundamentalMatrix implements Serializable {
44
45
46
47 public static final int FUNDAMENTAL_MATRIX_ROWS = 3;
48
49
50
51
52 public static final int FUNDAMENTAL_MATRIX_COLS = 3;
53
54
55
56
57 public static final int FUNDAMENTAL_MATRIX_RANK = 2;
58
59
60
61
62
63 protected Matrix internalMatrix;
64
65
66
67
68
69
70 protected boolean normalized;
71
72
73
74
75
76 protected Point2D leftEpipole;
77
78
79
80
81
82 protected Point2D rightEpipole;
83
84
85
86
87 public FundamentalMatrix() {
88 }
89
90
91
92
93
94
95
96
97 public FundamentalMatrix(final Matrix internalMatrix) throws InvalidFundamentalMatrixException {
98 internalSetInternalMatrix(internalMatrix);
99 }
100
101
102
103
104
105
106
107
108
109
110 public FundamentalMatrix(final PinholeCamera leftCamera, final PinholeCamera rightCamera)
111 throws InvalidPairOfCamerasException {
112 internalSetFromPairOfCameras(leftCamera, rightCamera);
113 }
114
115
116
117
118
119
120
121
122
123 public FundamentalMatrix(final Transformation2D homography, final Point2D rightEpipole)
124 throws InvalidFundamentalMatrixException {
125 internalSetFromHomography(homography, rightEpipole);
126 }
127
128
129
130
131
132
133
134
135 public Matrix getInternalMatrix() throws NotAvailableException {
136 if (!isInternalMatrixAvailable()) {
137 throw new NotAvailableException();
138 }
139 return new Matrix(internalMatrix);
140 }
141
142
143
144
145
146
147
148
149
150 public void setInternalMatrix(final Matrix internalMatrix) throws InvalidFundamentalMatrixException {
151 internalSetInternalMatrix(internalMatrix);
152 }
153
154
155
156
157
158
159
160
161
162
163 private void internalSetInternalMatrix(final Matrix internalMatrix) throws InvalidFundamentalMatrixException {
164 if (!isValidInternalMatrix(internalMatrix)) {
165 throw new InvalidFundamentalMatrixException();
166 }
167
168
169
170 this.internalMatrix = new Matrix(internalMatrix);
171 normalized = false;
172 leftEpipole = rightEpipole = null;
173 }
174
175
176
177
178
179
180
181
182
183 public static boolean isValidInternalMatrix(final Matrix internalMatrix) {
184 if (internalMatrix == null) {
185 return false;
186 }
187
188 if (internalMatrix.getColumns() != FUNDAMENTAL_MATRIX_COLS
189 || internalMatrix.getRows() != FUNDAMENTAL_MATRIX_ROWS) {
190 return false;
191 }
192
193 try {
194 if (Utils.rank(internalMatrix) != FUNDAMENTAL_MATRIX_RANK) {
195 return false;
196 }
197 } catch (final DecomposerException e) {
198
199
200 return false;
201 }
202
203 return true;
204 }
205
206
207
208
209
210
211
212
213
214
215 public void setFromPairOfCameras(final PinholeCamera leftCamera, final PinholeCamera rightCamera)
216 throws InvalidPairOfCamerasException {
217 internalSetFromPairOfCameras(leftCamera, rightCamera);
218 }
219
220
221
222
223
224
225
226
227
228
229 private void internalSetFromPairOfCameras(final PinholeCamera leftCamera, final PinholeCamera rightCamera)
230 throws InvalidPairOfCamerasException {
231 try {
232
233
234 leftCamera.normalize();
235 rightCamera.normalize();
236
237 if (!leftCamera.isCameraSignFixed()) {
238 leftCamera.fixCameraSign();
239 }
240 if (!rightCamera.isCameraSignFixed()) {
241 rightCamera.fixCameraSign();
242 }
243
244
245
246 if (!rightCamera.isCameraCenterAvailable()) {
247
248
249 rightCamera.decompose(false, true);
250 }
251
252 final var rightCameraCenter = rightCamera.getCameraCenter();
253 final var lEpipole = leftCamera.project(rightCameraCenter);
254
255 lEpipole.normalize();
256
257
258 final var skewLeftEpipoleMatrix = Utils.skewMatrix(lEpipole.asArray());
259
260 skewLeftEpipoleMatrix.transpose();
261
262
263 var transLeftCameraMatrix = leftCamera.getInternalMatrix().transposeAndReturnNew();
264
265
266 var transRightCameraMatrix = rightCamera.getInternalMatrix().transposeAndReturnNew();
267
268
269 var pseudoTransRightCameraMatrix = Utils.pseudoInverse(transRightCameraMatrix);
270
271
272
273 transLeftCameraMatrix.multiply(skewLeftEpipoleMatrix);
274
275 pseudoTransRightCameraMatrix.multiply(transLeftCameraMatrix);
276
277
278
279 if (!isValidInternalMatrix(pseudoTransRightCameraMatrix)) {
280 throw new InvalidPairOfCamerasException();
281 }
282
283 internalMatrix = pseudoTransRightCameraMatrix;
284 normalized = false;
285 leftEpipole = rightEpipole = null;
286 } catch (final InvalidPairOfCamerasException e) {
287 throw e;
288 } catch (final AlgebraException | GeometryException e) {
289 throw new InvalidPairOfCamerasException(e);
290 }
291 }
292
293
294
295
296
297
298
299
300
301 public void setFromHomography(final Transformation2D homography, final Point2D rightEpipole)
302 throws InvalidFundamentalMatrixException {
303 internalSetFromHomography(homography, rightEpipole);
304 }
305
306
307
308
309
310
311
312
313
314
315 private void internalSetFromHomography(
316 final Transformation2D homography, final Point2D rightEpipole) throws InvalidFundamentalMatrixException {
317
318 rightEpipole.normalize();
319
320 Matrix f = null;
321 try {
322 f = Utils.skewMatrix(new double[]{
323 rightEpipole.getHomX(),
324 rightEpipole.getHomY(),
325 rightEpipole.getHomW()
326 });
327
328 f.multiply(homography.asMatrix());
329 } catch (final WrongSizeException ignore) {
330
331 }
332
333
334
335
336 if (!isValidInternalMatrix(f)) {
337 throw new InvalidFundamentalMatrixException();
338 }
339
340 internalMatrix = f;
341 }
342
343
344
345
346
347
348 public boolean isInternalMatrixAvailable() {
349 return internalMatrix != null;
350 }
351
352
353
354
355
356
357
358
359 public Line2D getLeftEpipolarLine(final Point2D rightPoint) throws NotReadyException {
360 final var line = new Line2D();
361 leftEpipolarLine(rightPoint, line);
362 return line;
363 }
364
365
366
367
368
369
370
371
372 public void leftEpipolarLine(Point2D rightPoint, final Line2D result) throws NotReadyException {
373
374 if (!isInternalMatrixAvailable()) {
375 throw new NotReadyException();
376 }
377
378
379 if (rightPoint.getType() != CoordinatesType.HOMOGENEOUS_COORDINATES) {
380 rightPoint = new HomogeneousPoint2D(rightPoint);
381 }
382
383
384 rightPoint.normalize();
385 normalize();
386
387
388 final var transFundMatrix = internalMatrix.transposeAndReturnNew();
389
390
391
392 final var rightPointArray = rightPoint.asArray();
393 final var rightPointMatrix = Matrix.newFromArray(rightPointArray, true);
394
395 try {
396 final var leftEpipolarLineMatrix = transFundMatrix.multiplyAndReturnNew(rightPointMatrix);
397
398 result.setParameters(leftEpipolarLineMatrix.getBuffer());
399 } catch (final WrongSizeException ignore) {
400
401 }
402
403
404 result.normalize();
405 }
406
407
408
409
410
411
412
413
414 public Line2D getRightEpipolarLine(final Point2D leftPoint) throws NotReadyException {
415 final var line = new Line2D();
416 rightEpipolarLine(leftPoint, line);
417 return line;
418 }
419
420
421
422
423
424
425
426
427 public void rightEpipolarLine(Point2D leftPoint, final Line2D result) throws NotReadyException {
428
429 if (!isInternalMatrixAvailable()) {
430 throw new NotReadyException();
431 }
432
433
434 if (leftPoint.getType() != CoordinatesType.HOMOGENEOUS_COORDINATES) {
435 leftPoint = new HomogeneousPoint2D(leftPoint);
436 }
437
438
439 leftPoint.normalize();
440 normalize();
441
442
443
444 final var leftPointArray = leftPoint.asArray();
445 final var leftPointMatrix = Matrix.newFromArray(leftPointArray, true);
446 try {
447 final var rightEpipolarPointMatrix = internalMatrix.multiplyAndReturnNew(leftPointMatrix);
448
449 result.setParameters(rightEpipolarPointMatrix.getBuffer());
450 } catch (final WrongSizeException ignore) {
451
452 }
453
454
455 result.normalize();
456 }
457
458
459
460
461
462
463
464
465 public Point2D getLeftEpipole() throws NotAvailableException {
466 if (!areEpipolesAvailable()) {
467 throw new NotAvailableException();
468 }
469
470 return leftEpipole;
471 }
472
473
474
475
476
477
478
479
480 public Point2D getRightEpipole() throws NotAvailableException {
481 if (!areEpipolesAvailable()) {
482 throw new NotAvailableException();
483 }
484
485 return rightEpipole;
486 }
487
488
489
490
491
492
493
494
495 public void normalize() throws NotReadyException {
496 if (!normalized) {
497 if (!isInternalMatrixAvailable()) {
498 throw new NotReadyException();
499 }
500
501 final var norm = Utils.normF(internalMatrix);
502
503 internalMatrix.multiplyByScalar(1.0 / norm);
504
505 normalized = true;
506 }
507 }
508
509
510
511
512
513
514 public boolean isNormalized() {
515 return normalized;
516 }
517
518
519
520
521
522
523
524
525
526 public void computeEpipoles() throws NotReadyException, InvalidFundamentalMatrixException {
527 if (!isInternalMatrixAvailable()) {
528 throw new NotReadyException();
529 }
530
531
532 normalize();
533
534
535
536
537
538
539 final var decomposer = new SingularValueDecomposer(internalMatrix);
540
541 try {
542 decomposer.decompose();
543
544 final var u = decomposer.getU();
545 final var v = decomposer.getV();
546 final var array = new double[Point2D.POINT2D_HOMOGENEOUS_COORDINATES_LENGTH];
547
548
549 v.getSubmatrixAsArray(0, 2, 2, 2, array);
550 leftEpipole = new HomogeneousPoint2D(array);
551
552
553 u.getSubmatrixAsArray(0, 2, 2, 2, array);
554 rightEpipole = new HomogeneousPoint2D(array);
555 } catch (final AlgebraException e) {
556 throw new InvalidFundamentalMatrixException(e);
557 }
558 }
559
560
561
562
563
564
565
566 @SuppressWarnings("BooleanMethodIsAlwaysInverted")
567 public boolean areEpipolesAvailable() {
568 return leftEpipole != null && rightEpipole != null;
569 }
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605 public void generateCamerasInArbitraryProjectiveSpace(
606 final PinholeCamera leftCamera, final PinholeCamera rightCamera,
607 final double referencePlaneDirectorVectorX, final double referencePlaneDirectorVectorY,
608 final double referencePlaneDirectorVectorZ, final double scaleFactor)
609 throws InvalidFundamentalMatrixException, NotReadyException {
610
611 normalize();
612
613 try {
614 if (!areEpipolesAvailable()) {
615 computeEpipoles();
616 }
617 final var rEpipole = getRightEpipole();
618 rEpipole.normalize();
619
620 final var e2 = new Matrix(Point2D.POINT2D_HOMOGENEOUS_COORDINATES_LENGTH, 1);
621 e2.setElementAtIndex(0, rEpipole.getHomX());
622 e2.setElementAtIndex(1, rEpipole.getHomY());
623 e2.setElementAtIndex(2, rEpipole.getHomW());
624
625 final var tmp = Utils.skewMatrix(e2);
626 tmp.multiply(internalMatrix);
627
628 if (referencePlaneDirectorVectorX != 0.0 || referencePlaneDirectorVectorY != 0.0
629 || referencePlaneDirectorVectorZ != 0.0) {
630 final var tmp2 = new Matrix(1, Point2D.POINT2D_HOMOGENEOUS_COORDINATES_LENGTH);
631 tmp2.setElementAtIndex(0, referencePlaneDirectorVectorX);
632 tmp2.setElementAtIndex(1, referencePlaneDirectorVectorY);
633 tmp2.setElementAtIndex(2, referencePlaneDirectorVectorZ);
634 final var tmp3 = e2.multiplyAndReturnNew(tmp2);
635
636 tmp.add(tmp3);
637 }
638
639 e2.multiplyByScalar(scaleFactor);
640
641 final var leftCameraMatrix = Matrix.identity(PinholeCamera.PINHOLE_CAMERA_MATRIX_ROWS,
642 PinholeCamera.PINHOLE_CAMERA_MATRIX_COLS);
643
644 final var rightCameraMatrix = new Matrix(PinholeCamera.PINHOLE_CAMERA_MATRIX_ROWS,
645 PinholeCamera.PINHOLE_CAMERA_MATRIX_COLS);
646 rightCameraMatrix.setSubmatrix(0, 0, 2, 2, tmp);
647 rightCameraMatrix.setSubmatrix(0, 3, 2, 3, e2);
648
649 leftCamera.setInternalMatrix(leftCameraMatrix);
650 rightCamera.setInternalMatrix(rightCameraMatrix);
651
652 } catch (final NotAvailableException | WrongSizeException ignore) {
653
654 }
655 }
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676 public void generateCamerasInArbitraryProjectiveSpace(
677 final PinholeCamera leftCamera, final PinholeCamera rightCamera)
678 throws InvalidFundamentalMatrixException, NotReadyException {
679 generateCamerasInArbitraryProjectiveSpace(leftCamera, rightCamera, 0.0,
680 0.0, 0.0, 1.0);
681 }
682 }