View Javadoc
1   /*
2    * Copyright (C) 2015 Alberto Irurueta Carro (alberto@irurueta.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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   * The fundamental matrix describes the epipolar geometry for a pair of cameras.
38   * Epipoles are the projections of the opposite camera against the other.
39   * By means of point correspondences it is possible to estimate the fundamental
40   * matrix, which can later be used to estimate the associated pair of cameras.
41   */
42  @SuppressWarnings("DuplicatedCode")
43  public class FundamentalMatrix implements Serializable {
44      /**
45       * Number of rows of fundamental matrix.
46       */
47      public static final int FUNDAMENTAL_MATRIX_ROWS = 3;
48  
49      /**
50       * Number of columns of fundamental matrix.
51       */
52      public static final int FUNDAMENTAL_MATRIX_COLS = 3;
53  
54      /**
55       * Rank of fundamental matrix.
56       */
57      public static final int FUNDAMENTAL_MATRIX_RANK = 2;
58  
59      /**
60       * Contains the internal representation of the fundamental matrix, which is
61       * a 3x3 matrix having rank 2 defined up to scale.
62       */
63      protected Matrix internalMatrix;
64  
65      /**
66       * Indicates whether fundamental matrix has been normalized. Normalization
67       * can be used to increase the accuracy of estimations, since fundamental
68       * matrix is defined up to scale.
69       */
70      protected boolean normalized;
71  
72      /**
73       * Epipole for left view. Corresponds to the projection of the center of the
74       * right camera on the left view.
75       */
76      protected Point2D leftEpipole;
77  
78      /**
79       * Epipole for right view. Corresponds to the projection of the center of
80       * the left camera on the right view.
81       */
82      protected Point2D rightEpipole;
83  
84      /**
85       * Constructor.
86       */
87      public FundamentalMatrix() {
88      }
89  
90      /**
91       * Constructor.
92       *
93       * @param internalMatrix matrix to be set internally.
94       * @throws InvalidFundamentalMatrixException if provided matrix is not 3x3
95       *                                           or does not have rank 2.
96       */
97      public FundamentalMatrix(final Matrix internalMatrix) throws InvalidFundamentalMatrixException {
98          internalSetInternalMatrix(internalMatrix);
99      }
100 
101     /**
102      * Constructor from a pair of cameras.
103      *
104      * @param leftCamera  camera corresponding to left view.
105      * @param rightCamera camera corresponding to right view.
106      * @throws InvalidPairOfCamerasException if provided cameras do not span a
107      *                                       valid epipolar geometry (i.e. they are planed in a degenerate
108      *                                       configuration).
109      */
110     public FundamentalMatrix(final PinholeCamera leftCamera, final PinholeCamera rightCamera)
111             throws InvalidPairOfCamerasException {
112         internalSetFromPairOfCameras(leftCamera, rightCamera);
113     }
114 
115     /**
116      * Constructor from an homography and right epipole.
117      *
118      * @param homography   2D homography.
119      * @param rightEpipole right epipole.
120      * @throws InvalidFundamentalMatrixException if resulting fundamental matrix
121      *                                           is invalid, typically because of numerical instabilities.
122      */
123     public FundamentalMatrix(final Transformation2D homography, final Point2D rightEpipole)
124             throws InvalidFundamentalMatrixException {
125         internalSetFromHomography(homography, rightEpipole);
126     }
127 
128     /**
129      * Returns a copy of the internal matrix assigned to this instance.
130      *
131      * @return copy of the internal matrix.
132      * @throws NotAvailableException if internal matrix has not yet been
133      *                               provided.
134      */
135     public Matrix getInternalMatrix() throws NotAvailableException {
136         if (!isInternalMatrixAvailable()) {
137             throw new NotAvailableException();
138         }
139         return new Matrix(internalMatrix);
140     }
141 
142     /**
143      * Sets internal matrix associated to this instance.
144      * This method makes a copy of provided matrix.
145      *
146      * @param internalMatrix matrix to be assigned to this instance.
147      * @throws InvalidFundamentalMatrixException if provided matrix is not 3x3
148      *                                           or does not have rank 2.
149      */
150     public void setInternalMatrix(final Matrix internalMatrix) throws InvalidFundamentalMatrixException {
151         internalSetInternalMatrix(internalMatrix);
152     }
153 
154     /**
155      * Method used internally to set the internal matrix associated to this
156      * instance.
157      * This method makes a copy of provided matrix.
158      *
159      * @param internalMatrix matrix to be assigned to this instance.
160      * @throws InvalidFundamentalMatrixException if provided matrix is not 3x3
161      *                                           or does not have rank 2.
162      */
163     private void internalSetInternalMatrix(final Matrix internalMatrix) throws InvalidFundamentalMatrixException {
164         if (!isValidInternalMatrix(internalMatrix)) {
165             throw new InvalidFundamentalMatrixException();
166         }
167 
168         // because provided matrix is valid, we proceed to setting it
169 
170         this.internalMatrix = new Matrix(internalMatrix);
171         normalized = false;
172         leftEpipole = rightEpipole = null;
173     }
174 
175     /**
176      * Returns a boolean indicating whether provided matrix is a valid
177      * fundamental matrix (i.e. has size 3x3 and rank 2).
178      *
179      * @param internalMatrix matrix to be checked.
180      * @return true if provided matrix is a valid fundamental matrix, false
181      * otherwise.
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             // an exception might be raised if matrix is not numerically
199             // valid (i.e. contains infinity or nan values)
200             return false;
201         }
202 
203         return true;
204     }
205 
206     /**
207      * Sets fundamental matrix from provided pair of cameras.
208      *
209      * @param leftCamera  camera corresponding to left view.
210      * @param rightCamera camera corresponding to right view.
211      * @throws InvalidPairOfCamerasException if provided cameras do not span a
212      *                                       valid epipolar geometry (i.e. they are planed in a degenerate
213      *                                       configuration).
214      */
215     public void setFromPairOfCameras(final PinholeCamera leftCamera, final PinholeCamera rightCamera)
216             throws InvalidPairOfCamerasException {
217         internalSetFromPairOfCameras(leftCamera, rightCamera);
218     }
219 
220     /**
221      * Internal method to set fundamental matrix from provided a pair of cameras.
222      *
223      * @param leftCamera  camera corresponding to left view.
224      * @param rightCamera camera corresponding to right view.
225      * @throws InvalidPairOfCamerasException if provided cameras do not span a
226      *                                       valid epipolar geometry (i.e. they are planed in a degenerate
227      *                                       configuration).
228      */
229     private void internalSetFromPairOfCameras(final PinholeCamera leftCamera, final PinholeCamera rightCamera)
230             throws InvalidPairOfCamerasException {
231         try {
232             // normalize cameras to increase accuracy of results and fix their
233             // signs if needed
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             // left epipole consists on the projection of right camera center
245             // (C') using left camera P
246             if (!rightCamera.isCameraCenterAvailable()) {
247                 // if camera center is not available, we need to decompose such
248                 // camera
249                 rightCamera.decompose(false, true);
250             }
251 
252             final var rightCameraCenter = rightCamera.getCameraCenter();
253             final var lEpipole = leftCamera.project(rightCameraCenter);
254             // normalize to increase accuracy
255             lEpipole.normalize();
256 
257             // compute skew matrix of left epipole
258             final var skewLeftEpipoleMatrix = Utils.skewMatrix(lEpipole.asArray());
259             // transSkewLeftEpipoleMatrix = skewLeftEpipoleMatrix
260             skewLeftEpipoleMatrix.transpose();
261 
262             // compute transposed of internal left pinhole camera
263             var transLeftCameraMatrix = leftCamera.getInternalMatrix().transposeAndReturnNew();
264 
265             // compute transposed of internal right pinhole camera
266             var transRightCameraMatrix = rightCamera.getInternalMatrix().transposeAndReturnNew();
267 
268             // compute pseudo-inverse of transposed right pinhole camera
269             var pseudoTransRightCameraMatrix = Utils.pseudoInverse(transRightCameraMatrix);
270 
271             // compute pseudoTransRightCameraMatrix * transLeftCameraMatrix *
272             // transSkewLeftEpipoleMatrix
273             transLeftCameraMatrix.multiply(skewLeftEpipoleMatrix);
274             // fundamentalMatrix = pseudoTransRightCameraMatrix
275             pseudoTransRightCameraMatrix.multiply(transLeftCameraMatrix);
276 
277             // test that resulting matrix is 3x3 and rank 2, otherwise provided
278             // cameras span a degenerate epipolar geometry and are not valid
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      * Sets fundamental matrix from provided 2D homography and right epipole.
295      *
296      * @param homography   2D homography.
297      * @param rightEpipole right epipole.
298      * @throws InvalidFundamentalMatrixException if resulting fundamental matrix
299      *                                           is invalid, typically because of numerical instabilities.
300      */
301     public void setFromHomography(final Transformation2D homography, final Point2D rightEpipole)
302             throws InvalidFundamentalMatrixException {
303         internalSetFromHomography(homography, rightEpipole);
304     }
305 
306     /**
307      * Internal method to sets fundamental matrix from provided 2D homography
308      * and right epipole.
309      *
310      * @param homography   2D homography.
311      * @param rightEpipole right epipole.
312      * @throws InvalidFundamentalMatrixException if resulting fundamental matrix
313      *                                           is invalid, typically because of numerical instabilities.
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             // never happens
331         }
332 
333 
334         // test that resulting matrix is 3x3 and rank 2, otherwise provided
335         // matrix is numerically unstable and not valid
336         if (!isValidInternalMatrix(f)) {
337             throw new InvalidFundamentalMatrixException();
338         }
339 
340         internalMatrix = f;
341     }
342 
343     /**
344      * Indicates whether this instance has its internal matrix set.
345      *
346      * @return true if internal matrix has been set, false otherwise.
347      */
348     public boolean isInternalMatrixAvailable() {
349         return internalMatrix != null;
350     }
351 
352     /**
353      * Returns epipolar line on left view corresponding to point on right view.
354      *
355      * @param rightPoint a point on the right view.
356      * @return epipolar line on left view.
357      * @throws NotReadyException if internal matrix has not yet been set.
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      * Computes epipolar line on left view corresponding to point on right view.
367      *
368      * @param rightPoint a point on the right view.
369      * @param result     line instance where result will be stored.
370      * @throws NotReadyException if internal matrix has not yet been set.
371      */
372     public void leftEpipolarLine(Point2D rightPoint, final Line2D result) throws NotReadyException {
373 
374         if (!isInternalMatrixAvailable()) {
375             throw new NotReadyException();
376         }
377 
378         //make sure that point is homogeneous
379         if (rightPoint.getType() != CoordinatesType.HOMOGENEOUS_COORDINATES) {
380             rightPoint = new HomogeneousPoint2D(rightPoint);
381         }
382 
383         // normalize to increase accuracy
384         rightPoint.normalize();
385         normalize();
386 
387         // compute transposed fundamental matrix
388         final var transFundMatrix = internalMatrix.transposeAndReturnNew();
389 
390         // compute left epipolar line as the product of transposed fundamental
391         // matrix with homogeneous right 2D point
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             // this exception will never occur
401         }
402 
403         // normalize line to increase accuracy
404         result.normalize();
405     }
406 
407     /**
408      * Returns epipolar line on right view corresponding to point on left view.
409      *
410      * @param leftPoint a point on the left view.
411      * @return epipolar line on right view.
412      * @throws NotReadyException if internal matrix has not yet been set.
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      * Computes epipolar line on right view corresponding to point on left view.
422      *
423      * @param leftPoint a point on the left view.
424      * @param result    line instance where result will be stored.
425      * @throws NotReadyException if internal matrix has not yet been set.
426      */
427     public void rightEpipolarLine(Point2D leftPoint, final Line2D result) throws NotReadyException {
428 
429         if (!isInternalMatrixAvailable()) {
430             throw new NotReadyException();
431         }
432 
433         // make sure that point is homogeneous
434         if (leftPoint.getType() != CoordinatesType.HOMOGENEOUS_COORDINATES) {
435             leftPoint = new HomogeneousPoint2D(leftPoint);
436         }
437 
438         // normalize to increase accuracy
439         leftPoint.normalize();
440         normalize();
441 
442         // compute right epipolar line as the product of fundamental matrix with
443         // homogeneous left 2D point
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             // this exception will never occur
452         }
453 
454         // normalize line to increase accuracy
455         result.normalize();
456     }
457 
458     /**
459      * Returns left epipole, which corresponds to the center of right camera
460      * projected on left view.
461      *
462      * @return left epipole.
463      * @throws NotAvailableException if epipoles haven't been computed.
464      */
465     public Point2D getLeftEpipole() throws NotAvailableException {
466         if (!areEpipolesAvailable()) {
467             throw new NotAvailableException();
468         }
469 
470         return leftEpipole;
471     }
472 
473     /**
474      * Returns right epipole, which corresponds to the center of left camera
475      * projected on right view.
476      *
477      * @return right epipole.
478      * @throws NotAvailableException if epipoles haven't been computed.
479      */
480     public Point2D getRightEpipole() throws NotAvailableException {
481         if (!areEpipolesAvailable()) {
482             throw new NotAvailableException();
483         }
484 
485         return rightEpipole;
486     }
487 
488     /**
489      * Normalizes the internal representation of this instance.
490      * Normalization is done to increase accuracy of computations with this
491      * instance.
492      *
493      * @throws NotReadyException if internal matrix has not already been set.
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      * Indicates whether this instance is currently normalized or not.
511      *
512      * @return true if this instance is normalized, false otherwise.
513      */
514     public boolean isNormalized() {
515         return normalized;
516     }
517 
518     /**
519      * Computes the left and right epipoles of this instance.
520      *
521      * @throws NotReadyException                 if an internal matrix has not yet been
522      *                                           provided.
523      * @throws InvalidFundamentalMatrixException if internal matrix is
524      *                                           numerically unstable and epipoles couldn't be computed.
525      */
526     public void computeEpipoles() throws NotReadyException, InvalidFundamentalMatrixException {
527         if (!isInternalMatrixAvailable()) {
528             throw new NotReadyException();
529         }
530 
531         // to increase accuracy
532         normalize();
533 
534         // compute SVD of internal fundamental matrix with singular values
535         // ordered from largest to smallest. Since fundamental matrix has rank 2,
536         // last singular value is zero
537 
538         // F = U*S*V'
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             // left epipole is the last column of V (right null-space)
549             v.getSubmatrixAsArray(0, 2, 2, 2, array);
550             leftEpipole = new HomogeneousPoint2D(array);
551 
552             // right epipole is the last column of U (left null-space)
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      * Indicates whether epipoles have been computed and are available for
562      * retrieval.
563      *
564      * @return true if epipoles are available, false otherwise.
565      */
566     @SuppressWarnings("BooleanMethodIsAlwaysInverted")
567     public boolean areEpipolesAvailable() {
568         return leftEpipole != null && rightEpipole != null;
569     }
570 
571     /**
572      * Generates a pair of cameras in any arbitrary projective space which
573      * produce this fundamental matrix.
574      * This method can be used to obtain a pair of cameras related by this
575      * fundamental matrix in order to initialize geometry and get an initial set
576      * of cameras.
577      * However, because cameras are in any arbitrary projective space, they need
578      * to be transformed into a metric space using a Dual Absolute Quadric
579      * estimator.
580      *
581      * @param leftCamera                    instance where left camera will be stored.
582      * @param rightCamera                   instance where right camera will be stored.
583      * @param referencePlaneDirectorVectorX x coordinate of reference plane
584      *                                      director vector. This can be any arbitrary value, however
585      *                                      typically the reference plane is assumed to be the plane at
586      *                                      infinity, hence the value typically is zero.
587      * @param referencePlaneDirectorVectorY y coordinate of reference plane
588      *                                      director vector. This can be any arbitrary value, however
589      *                                      typically the reference plane is assumed to be the plane at
590      *                                      infinity, hence the value typically is zero.
591      * @param referencePlaneDirectorVectorZ z coordinate of reference plane
592      *                                      director vector. This can be any arbitrary value, however
593      *                                      typically the reference plane is assumed to be the plane at
594      *                                      infinity, hence the value typically is zero.
595      * @param scaleFactor                   scale factor defining the length of the baseline in
596      *                                      a metric stratum. This can be any value, since cameras are
597      *                                      obtained in an arbitrary projective stratum. However, even if
598      *                                      the stratum was metric, cameras can only be defined up to scale.
599      *                                      A typical value is a scale factor of one.
600      * @throws InvalidFundamentalMatrixException if internal matrix is numerically unstable and epipoles
601      *                                           couldn't be computed.
602      * @throws NotReadyException                 if an internal matrix has not yet been
603      *                                           provided.
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             // never happens
654         }
655     }
656 
657     /**
658      * Generates a pair of cameras in any arbitrary projective space which
659      * produce this fundamental matrix.
660      * This method can be used to obtain a pair of cameras related by this
661      * fundamental matrix in order to initialize geometry and get an initial set
662      * of cameras.
663      * However, because cameras are in any arbitrary projective space, they need
664      * to be transformed into a metric space using a Dual Absolute Quadric
665      * estimator.
666      * This method assumes that the reference plane is the plane at infinity and
667      * that scale factor is one.
668      *
669      * @param leftCamera  instance where left camera will be stored.
670      * @param rightCamera instance where right camera will be stored.
671      * @throws InvalidFundamentalMatrixException if internal matrix is
672      *                                           numerically unstable and epipoles couldn't be computed.
673      * @throws NotReadyException                 if an internal matrix has not yet been
674      *                                           provided.
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 }