View Javadoc
1   /*
2    * Copyright (C) 2017 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.sfm;
17  
18  import com.irurueta.algebra.Matrix;
19  import com.irurueta.algebra.WrongSizeException;
20  import com.irurueta.ar.epipolar.Corrector;
21  import com.irurueta.ar.epipolar.CorrectorType;
22  import com.irurueta.ar.epipolar.EssentialMatrix;
23  import com.irurueta.ar.epipolar.FundamentalMatrix;
24  import com.irurueta.geometry.CameraException;
25  import com.irurueta.geometry.PinholeCamera;
26  import com.irurueta.geometry.PinholeCameraIntrinsicParameters;
27  import com.irurueta.geometry.Point2D;
28  import com.irurueta.geometry.Point3D;
29  import com.irurueta.geometry.Rotation3D;
30  import com.irurueta.geometry.estimators.LockedException;
31  import com.irurueta.geometry.estimators.NotReadyException;
32  
33  import java.util.ArrayList;
34  import java.util.BitSet;
35  import java.util.List;
36  
37  /**
38   * Estimates an initial pair of cameras in the metric stratum (up to an
39   * arbitrary scale) using a given fundamental matrix and provided intrinsic
40   * parameters on left and right views (which can be obtained by offline
41   * calibration) to compute the essential matrix and choose the best combination
42   * of rotation and translation on estimated cameras so that triangulated 3D
43   * points obtained from provided matched 2D points are located in front of
44   * the estimated cameras.
45   */
46  @SuppressWarnings("DuplicatedCode")
47  public class EssentialMatrixInitialCamerasEstimator extends InitialCamerasEstimator {
48  
49      /**
50       * Indicates whether matched 2D points must be triangulated by default.
51       */
52      public static final boolean DEFAULT_TRIANGULATE_POINTS = false;
53  
54      /**
55       * Indicates whether triangulated points must be marked as valid (i.e. when
56       * they lie in front of both of the estimated cameras) or not.
57       */
58      public static final boolean DEFAULT_MARK_VALID_TRIANGULATED_POINTS = false;
59  
60      /**
61       * Intrinsic parameters to be used for estimated left camera.
62       */
63      private PinholeCameraIntrinsicParameters leftIntrinsic;
64  
65      /**
66       * Intrinsic parameters to be used for estimated right camera.
67       */
68      private PinholeCameraIntrinsicParameters rightIntrinsic;
69  
70      /**
71       * Matched 2D points on left view.
72       */
73      private List<Point2D> leftPoints;
74  
75      /**
76       * Matched 2D points on right view.
77       */
78      private List<Point2D> rightPoints;
79  
80      /**
81       * Type of corrector to use to triangulate matched points or null if no
82       * corrector needs to be used.
83       */
84      private CorrectorType correctorType = Corrector.DEFAULT_TYPE;
85  
86      /**
87       * Indicates whether matched 2D points need to be triangulated.
88       */
89      private boolean triangulatePoints = DEFAULT_TRIANGULATE_POINTS;
90  
91      /**
92       * Marks which of the triangulated points are marked as valid (lie in front
93       * of both of the estimated cameras) and which ones aren't.
94       */
95      private boolean markValidTriangulatedPoints = DEFAULT_MARK_VALID_TRIANGULATED_POINTS;
96  
97      /**
98       * Contains triangulated points.
99       */
100     private List<Point3D> triangulatedPoints;
101 
102     /**
103      * Contains booleans indicating whether triangulated points are valid (i.e.
104      * lie in front of both estimated cameras) or not.
105      */
106     private BitSet validTriangulatedPoints;
107 
108     /**
109      * Constructor.
110      */
111     public EssentialMatrixInitialCamerasEstimator() {
112         super();
113     }
114 
115     /**
116      * Constructor.
117      *
118      * @param fundamentalMatrix fundamental matrix relating two views.
119      */
120     public EssentialMatrixInitialCamerasEstimator(final FundamentalMatrix fundamentalMatrix) {
121         super(fundamentalMatrix);
122     }
123 
124     /**
125      * Constructor.
126      *
127      * @param leftIntrinsic  intrinsic parameters to be used for estimated left
128      *                       camera.
129      * @param rightIntrinsic intrinsic parameters to be used for estimated right
130      *                       camera.
131      */
132     public EssentialMatrixInitialCamerasEstimator(
133             final PinholeCameraIntrinsicParameters leftIntrinsic,
134             final PinholeCameraIntrinsicParameters rightIntrinsic) {
135         super();
136         this.leftIntrinsic = leftIntrinsic;
137         this.rightIntrinsic = rightIntrinsic;
138     }
139 
140     /**
141      * Constructor.
142      *
143      * @param fundamentalMatrix fundamental matrix relating two views.
144      * @param leftIntrinsic     intrinsic parameters to be used for estimated left
145      *                          camera.
146      * @param rightIntrinsic    intrinsic parameters to be used for estimated right
147      *                          camera.
148      */
149     public EssentialMatrixInitialCamerasEstimator(
150             final FundamentalMatrix fundamentalMatrix,
151             final PinholeCameraIntrinsicParameters leftIntrinsic,
152             final PinholeCameraIntrinsicParameters rightIntrinsic) {
153         super(fundamentalMatrix);
154         this.leftIntrinsic = leftIntrinsic;
155         this.rightIntrinsic = rightIntrinsic;
156     }
157 
158     /**
159      * Constructor.
160      *
161      * @param leftPoints  matched 2D points on left view.
162      * @param rightPoints matched 2D points on right view.
163      * @throws IllegalArgumentException if provided lists don't have the same
164      *                                  size.
165      */
166     public EssentialMatrixInitialCamerasEstimator(final List<Point2D> leftPoints, final List<Point2D> rightPoints) {
167         super();
168         internalSetLeftAndRightPoints(leftPoints, rightPoints);
169     }
170 
171     /**
172      * Constructor.
173      *
174      * @param fundamentalMatrix fundamental matrix relating two views.
175      * @param leftPoints        matched 2D points on left view.
176      * @param rightPoints       matched 2D points on right view.
177      * @throws IllegalArgumentException if provided lists don't have the same
178      *                                  size.
179      */
180     public EssentialMatrixInitialCamerasEstimator(
181             final FundamentalMatrix fundamentalMatrix, final List<Point2D> leftPoints,
182             final List<Point2D> rightPoints) {
183         super(fundamentalMatrix);
184         internalSetLeftAndRightPoints(leftPoints, rightPoints);
185     }
186 
187     /**
188      * Constructor.
189      *
190      * @param leftIntrinsic  intrinsic parameters to be used for estimated left
191      *                       camera.
192      * @param rightIntrinsic intrinsic parameters to be used for estimated right
193      *                       camera.
194      * @param leftPoints     matched 2D points on left view.
195      * @param rightPoints    matched 2D points on right view.
196      * @throws IllegalArgumentException if provided lists don't have the same
197      *                                  size.
198      */
199     public EssentialMatrixInitialCamerasEstimator(
200             final PinholeCameraIntrinsicParameters leftIntrinsic,
201             final PinholeCameraIntrinsicParameters rightIntrinsic,
202             final List<Point2D> leftPoints, final List<Point2D> rightPoints) {
203         this(leftIntrinsic, rightIntrinsic);
204         internalSetLeftAndRightPoints(leftPoints, rightPoints);
205     }
206 
207     /**
208      * Constructor.
209      *
210      * @param fundamentalMatrix fundamental matrix relating two views.
211      * @param leftIntrinsic     intrinsic parameters to be used for estimated left
212      *                          camera.
213      * @param rightIntrinsic    intrinsic parameters to be used for estimated right
214      *                          camera.
215      * @param leftPoints        matched 2D points on left view.
216      * @param rightPoints       matched 2D points on right view.
217      * @throws IllegalArgumentException if provided lists don't have the same
218      *                                  size.
219      */
220     public EssentialMatrixInitialCamerasEstimator(
221             final FundamentalMatrix fundamentalMatrix,
222             final PinholeCameraIntrinsicParameters leftIntrinsic,
223             final PinholeCameraIntrinsicParameters rightIntrinsic,
224             final List<Point2D> leftPoints, final List<Point2D> rightPoints) {
225         this(fundamentalMatrix, leftIntrinsic, rightIntrinsic);
226         internalSetLeftAndRightPoints(leftPoints, rightPoints);
227     }
228 
229     /**
230      * Constructor.
231      *
232      * @param listener listener to handle events raised by this instance.
233      */
234     public EssentialMatrixInitialCamerasEstimator(final InitialCamerasEstimatorListener listener) {
235         super(listener);
236     }
237 
238     /**
239      * Constructor.
240      *
241      * @param fundamentalMatrix fundamental matrix relating two views.
242      * @param listener          listener to handle events raised by this instance.
243      */
244     public EssentialMatrixInitialCamerasEstimator(
245             final FundamentalMatrix fundamentalMatrix, final InitialCamerasEstimatorListener listener) {
246         super(fundamentalMatrix, listener);
247     }
248 
249     /**
250      * Constructor.
251      *
252      * @param leftIntrinsic  intrinsic parameters to be used for estimated left
253      *                       camera.
254      * @param rightIntrinsic intrinsic parameters to be used for estimated right
255      *                       camera.
256      * @param listener       listener to handle events raised by this instance.
257      */
258     public EssentialMatrixInitialCamerasEstimator(
259             final PinholeCameraIntrinsicParameters leftIntrinsic,
260             final PinholeCameraIntrinsicParameters rightIntrinsic,
261             final InitialCamerasEstimatorListener listener) {
262         super(listener);
263         this.leftIntrinsic = leftIntrinsic;
264         this.rightIntrinsic = rightIntrinsic;
265     }
266 
267     /**
268      * Constructor.
269      *
270      * @param fundamentalMatrix fundamental matrix relating two views.
271      * @param leftIntrinsic     intrinsic parameters to be used for estimated left
272      *                          camera.
273      * @param rightIntrinsic    intrinsic parameters to be used for estimated right
274      *                          camera.
275      * @param listener          listener to handle events raised by this instance.
276      */
277     public EssentialMatrixInitialCamerasEstimator(
278             final FundamentalMatrix fundamentalMatrix,
279             final PinholeCameraIntrinsicParameters leftIntrinsic,
280             final PinholeCameraIntrinsicParameters rightIntrinsic,
281             final InitialCamerasEstimatorListener listener) {
282         super(fundamentalMatrix, listener);
283         this.leftIntrinsic = leftIntrinsic;
284         this.rightIntrinsic = rightIntrinsic;
285     }
286 
287     /**
288      * Constructor.
289      *
290      * @param leftPoints  matched 2D points on left view.
291      * @param rightPoints matched 2D points on right view.
292      * @param listener    listener to handle events raised by this instance.
293      * @throws IllegalArgumentException if provided lists don't have the same
294      *                                  size.
295      */
296     public EssentialMatrixInitialCamerasEstimator(
297             final List<Point2D> leftPoints,
298             final List<Point2D> rightPoints,
299             final InitialCamerasEstimatorListener listener) {
300         super(listener);
301         internalSetLeftAndRightPoints(leftPoints, rightPoints);
302     }
303 
304     /**
305      * Constructor.
306      *
307      * @param fundamentalMatrix fundamental matrix relating two views.
308      * @param leftPoints        matched 2D points on left view.
309      * @param rightPoints       matched 2D points on right view.
310      * @param listener          listener to handle events raised by this instance.
311      * @throws IllegalArgumentException if provided lists don't have the same
312      *                                  size.
313      */
314     public EssentialMatrixInitialCamerasEstimator(
315             final FundamentalMatrix fundamentalMatrix,
316             final List<Point2D> leftPoints,
317             final List<Point2D> rightPoints,
318             final InitialCamerasEstimatorListener listener) {
319         super(fundamentalMatrix, listener);
320         internalSetLeftAndRightPoints(leftPoints, rightPoints);
321     }
322 
323     /**
324      * Constructor.
325      *
326      * @param leftIntrinsic  intrinsic parameters to be used for estimated left
327      *                       camera.
328      * @param rightIntrinsic intrinsic parameters to be used for estimated right
329      *                       camera.
330      * @param leftPoints     matched 2D points on left view.
331      * @param rightPoints    matched 2D points on right view.
332      * @param listener       listener to handle events raised by this instance.
333      * @throws IllegalArgumentException if provided lists don't have the same
334      *                                  size.
335      */
336     public EssentialMatrixInitialCamerasEstimator(
337             final PinholeCameraIntrinsicParameters leftIntrinsic,
338             final PinholeCameraIntrinsicParameters rightIntrinsic,
339             final List<Point2D> leftPoints,
340             final List<Point2D> rightPoints,
341             final InitialCamerasEstimatorListener listener) {
342         this(leftIntrinsic, rightIntrinsic, listener);
343         internalSetLeftAndRightPoints(leftPoints, rightPoints);
344     }
345 
346     /**
347      * Constructor.
348      *
349      * @param fundamentalMatrix fundamental matrix relating two views.
350      * @param leftIntrinsic     intrinsic parameters to be used for estimated left
351      *                          camera.
352      * @param rightIntrinsic    intrinsic parameters to be used for estimated right
353      *                          camera.
354      * @param leftPoints        matched 2D points on left view.
355      * @param rightPoints       matched 2D points on right view.
356      * @param listener          listener to handle events raised by this instance.
357      * @throws IllegalArgumentException if provided lists don't have the same
358      *                                  size.
359      */
360     public EssentialMatrixInitialCamerasEstimator(
361             final FundamentalMatrix fundamentalMatrix,
362             final PinholeCameraIntrinsicParameters leftIntrinsic,
363             final PinholeCameraIntrinsicParameters rightIntrinsic,
364             final List<Point2D> leftPoints,
365             final List<Point2D> rightPoints,
366             final InitialCamerasEstimatorListener listener) {
367         this(fundamentalMatrix, leftIntrinsic, rightIntrinsic, listener);
368         internalSetLeftAndRightPoints(leftPoints, rightPoints);
369     }
370 
371     /**
372      * Returns method used by this estimator.
373      *
374      * @return method used by this estimator.
375      */
376     @Override
377     public InitialCamerasEstimatorMethod getMethod() {
378         return InitialCamerasEstimatorMethod.ESSENTIAL_MATRIX;
379     }
380 
381     /**
382      * Indicates if estimator is ready.
383      *
384      * @return true if estimator is ready, false otherwise.
385      */
386     @Override
387     public boolean isReady() {
388         return fundamentalMatrix != null && leftIntrinsic != null && rightIntrinsic != null && leftPoints != null
389                 && rightPoints != null && leftPoints.size() == rightPoints.size();
390     }
391 
392     /**
393      * Estimates cameras.
394      *
395      * @throws LockedException                         if estimator is locked.
396      * @throws NotReadyException                       if estimator is not ready.
397      * @throws InitialCamerasEstimationFailedException if estimation of cameras
398      *                                                 fails for some reason, typically due to numerical
399      *                                                 instabilities.
400      */
401     @Override
402     public void estimate() throws LockedException, NotReadyException, InitialCamerasEstimationFailedException {
403         if (isLocked()) {
404             throw new LockedException();
405         }
406 
407         if (!isReady()) {
408             throw new NotReadyException();
409         }
410 
411         try {
412             locked = true;
413 
414             if (listener != null) {
415                 listener.onStart(this);
416             }
417 
418             if (triangulatePoints) {
419                 triangulatedPoints = new ArrayList<>();
420             } else {
421                 triangulatedPoints = null;
422             }
423 
424             final var nPoints = leftPoints.size();
425             if (markValidTriangulatedPoints) {
426                 validTriangulatedPoints = new BitSet(nPoints);
427             } else {
428                 validTriangulatedPoints = null;
429             }
430 
431             if (estimatedLeftCamera == null) {
432                 estimatedLeftCamera = new PinholeCamera();
433             }
434             if (estimatedRightCamera == null) {
435                 estimatedRightCamera = new PinholeCamera();
436             }
437 
438             generateInitialMetricCamerasFromEssentialMatrix(fundamentalMatrix, leftIntrinsic, rightIntrinsic,
439                     leftPoints, rightPoints, correctorType, estimatedLeftCamera, estimatedRightCamera,
440                     triangulatedPoints, validTriangulatedPoints);
441 
442             if (listener != null) {
443                 listener.onFinish(this, estimatedLeftCamera, estimatedRightCamera);
444             }
445         } catch (final InitialCamerasEstimationFailedException e) {
446             if (listener != null) {
447                 listener.onFail(this, e);
448             }
449             throw e;
450         } finally {
451             locked = false;
452         }
453     }
454 
455     /**
456      * Gets intrinsic parameters to be used for estimated left camera.
457      *
458      * @return intrinsic parameters to be used for estimated left camera.
459      */
460     public PinholeCameraIntrinsicParameters getLeftIntrinsic() {
461         return leftIntrinsic;
462     }
463 
464     /**
465      * Sets intrinsic parameters to be used for estimated left camera.
466      *
467      * @param leftIntrinsic intrinsic parameters to be used for estimated left
468      *                      camera.
469      * @throws LockedException if estimator is locked.
470      */
471     public void setLeftIntrinsic(
472             final PinholeCameraIntrinsicParameters leftIntrinsic) throws LockedException {
473         if (isLocked()) {
474             throw new LockedException();
475         }
476         this.leftIntrinsic = leftIntrinsic;
477     }
478 
479     /**
480      * Gets intrinsic parameters to be used for estimated right camera.
481      *
482      * @return intrinsic parameters to be used for estimated right camera.
483      */
484     public PinholeCameraIntrinsicParameters getRightIntrinsic() {
485         return rightIntrinsic;
486     }
487 
488     /**
489      * Sets intrinsic parameters to be used for estimated right camera.
490      *
491      * @param rightIntrinsic intrinsic parameters to be used for estimated right
492      *                       camera.
493      * @throws LockedException if estimator is locked.
494      */
495     public void setRightIntrinsic(
496             final PinholeCameraIntrinsicParameters rightIntrinsic) throws LockedException {
497         if (isLocked()) {
498             throw new LockedException();
499         }
500         this.rightIntrinsic = rightIntrinsic;
501     }
502 
503     /**
504      * Sets intrinsic parameters to be used for estimated left and right
505      * cameras.
506      *
507      * @param leftIntrinsic  intrinsic parameters to be used for estimated left
508      *                       camera.
509      * @param rightIntrinsic intrinsic parameters to be used for estimated right
510      *                       camera.
511      * @throws LockedException if estimator is locked.
512      */
513     public void setLeftAndRightIntrinsics(
514             final PinholeCameraIntrinsicParameters leftIntrinsic,
515             final PinholeCameraIntrinsicParameters rightIntrinsic) throws LockedException {
516         if (isLocked()) {
517             throw new LockedException();
518         }
519         this.leftIntrinsic = leftIntrinsic;
520         this.rightIntrinsic = rightIntrinsic;
521     }
522 
523     /**
524      * Sets the same intrinsic parameters to be used for both estimated left
525      * and right cameras.
526      *
527      * @param intrinsic intrinsic parameters to be used for both cameras.
528      * @throws LockedException if estimator is locked.
529      */
530     public void setIntrinsicsForBoth(final PinholeCameraIntrinsicParameters intrinsic) throws LockedException {
531         if (isLocked()) {
532             throw new LockedException();
533         }
534         leftIntrinsic = rightIntrinsic = intrinsic;
535     }
536 
537     /**
538      * Gets matched 2D points on left view.
539      *
540      * @return matched 2D points on left view.
541      */
542     public List<Point2D> getLeftPoints() {
543         return leftPoints;
544     }
545 
546     /**
547      * Sets matched 2D points on left view.
548      *
549      * @param leftPoints matched 2D points on left view.
550      * @throws LockedException if estimator is locked.
551      */
552     public void setLeftPoints(final List<Point2D> leftPoints) throws LockedException {
553         if (isLocked()) {
554             throw new LockedException();
555         }
556         this.leftPoints = leftPoints;
557     }
558 
559     /**
560      * Gets matched 2D points on right view.
561      *
562      * @return matched 2D points on right view.
563      */
564     public List<Point2D> getRightPoints() {
565         return rightPoints;
566     }
567 
568     /**
569      * Sets matched 2D points on right view.
570      *
571      * @param rightPoints matched 2D points on right view.
572      * @throws LockedException if estimator is locked.
573      */
574     public void setRightPoints(final List<Point2D> rightPoints) throws LockedException {
575         if (isLocked()) {
576             throw new LockedException();
577         }
578         this.rightPoints = rightPoints;
579     }
580 
581     /**
582      * Sets matched 2D points on left and right views.
583      *
584      * @param leftPoints  matched 2D points on left view.
585      * @param rightPoints matched 2D points on right view.
586      * @throws LockedException          if estimator is locked.
587      * @throws IllegalArgumentException if provided lists don't have the same
588      *                                  size.
589      */
590     public void setLeftAndRightPoints(
591             final List<Point2D> leftPoints, final List<Point2D> rightPoints) throws LockedException {
592         if (isLocked()) {
593             throw new LockedException();
594         }
595         internalSetLeftAndRightPoints(leftPoints, rightPoints);
596     }
597 
598     /**
599      * Gets type of corrector to use to triangulate matched points or null if
600      * no corrector needs to be used.
601      *
602      * @return type of corrector to use.
603      */
604     public CorrectorType getCorrectorType() {
605         return correctorType;
606     }
607 
608     /**
609      * Sets type of corrector to use to triangulate matched points or null if
610      * no corrector needs to be used.
611      *
612      * @param correctorType type of corrector to use.
613      * @throws LockedException if estimator is locked.
614      */
615     public void setCorrectorType(final CorrectorType correctorType) throws LockedException {
616         if (isLocked()) {
617             throw new LockedException();
618         }
619         this.correctorType = correctorType;
620     }
621 
622     /**
623      * Indicates whether matched 2D points need to be triangulated or not.
624      *
625      * @return true if 2D points need to be triangulated, false otherwise.
626      */
627     public boolean arePointsTriangulated() {
628         return triangulatePoints;
629     }
630 
631     /**
632      * Specifies whether matched 2D points need to be triangulated or not.
633      *
634      * @param triangulatePoints true if 2D points need to be triangulated, false
635      *                          otherwise.
636      * @throws LockedException if estimator is locked.
637      */
638     public void setPointsTriangulated(final boolean triangulatePoints) throws LockedException {
639         if (isLocked()) {
640             throw new LockedException();
641         }
642         this.triangulatePoints = triangulatePoints;
643     }
644 
645     /**
646      * Indicates which triangulated points are marked as valid (lie in front
647      * of both of the estimated cameras) and which ones aren't.
648      *
649      * @return true to mark valid and invalid triangulated points, false
650      * otherwise.
651      */
652     public boolean areValidTriangulatedPointsMarked() {
653         return markValidTriangulatedPoints;
654     }
655 
656     /**
657      * Specifies whether triangulated points are marked as valid (lie in front
658      * of both of the estimated cameras) and which ones aren't.
659      *
660      * @param markValidTriangulatedPoints true to mark valid and invalid
661      *                                    triangulated points, false otherwise.
662      * @throws LockedException if estimator is locked.
663      */
664     public void setValidTriangulatedPointsMarked(final boolean markValidTriangulatedPoints) throws LockedException {
665         if (isLocked()) {
666             throw new LockedException();
667         }
668         this.markValidTriangulatedPoints = markValidTriangulatedPoints;
669     }
670 
671     /**
672      * Gets triangulated points, if available.
673      *
674      * @return triangulated points or null.
675      */
676     public List<Point3D> getTriangulatedPoints() {
677         return triangulatedPoints;
678     }
679 
680     /**
681      * Gets bitset indicating which of the triangulated points are valid and
682      * which ones aren't.
683      *
684      * @return bitset indicating validity of triangulated points or null if not
685      * available.
686      */
687     public BitSet getValidTriangulatedPoints() {
688         return validTriangulatedPoints;
689     }
690 
691     /**
692      * Generates a pair of metric cameras (up to an arbitrary space) by
693      * computing the essential matrix from provided fundamental matrix and
694      * intrinsic parameters of left and right cameras, and choosing the best
695      * pair of camera pose and translation that yields the largest number of
696      * triangulated points laying in front of both of the estimated cameras.
697      * This method uses default corrector type and does not keep triangulated
698      * points or valid triangulated points.
699      *
700      * @param fundamentalMatrix fundamental matrix relating both left and right
701      *                          views.
702      * @param leftIntrinsic     intrinsic parameters to be set on left view.
703      *                          This can be used when cameras have been previously calibrated.
704      * @param rightIntrinsic    intrinsic parameters to be set on right view.
705      *                          This can be used when cameras have been previously calibrated.
706      * @param leftPoints        points on left view matched with points on right view,
707      *                          so they can be triangulated using estimated cameras. Both lists of points
708      *                          must have the same size.
709      * @param rightPoints       points on right view matched with points on left view,
710      *                          so they can be triangulated using estimated cameras. Both lists of points
711      *                          must have the same size.
712      * @param leftCamera        instance where estimated left camera will be stored.
713      * @param rightCamera       instance where estimated right camera will be stored.
714      * @return number of valid triangulated points which lie in front of the two
715      * estimated cameras.
716      * @throws InitialCamerasEstimationFailedException if estimation of cameras
717      *                                                 fails for some reason, typically due to numerical
718      *                                                 instabilities.
719      * @throws IllegalArgumentException                if provided lists of left and right
720      *                                                 points don't have the same size.
721      */
722     public static int generateInitialMetricCamerasFromEssentialMatrix(
723             final FundamentalMatrix fundamentalMatrix,
724             final PinholeCameraIntrinsicParameters leftIntrinsic,
725             final PinholeCameraIntrinsicParameters rightIntrinsic,
726             final List<Point2D> leftPoints, final List<Point2D> rightPoints,
727             final PinholeCamera leftCamera, final PinholeCamera rightCamera)
728             throws InitialCamerasEstimationFailedException {
729         return generateInitialMetricCamerasFromEssentialMatrix(
730                 fundamentalMatrix, leftIntrinsic, rightIntrinsic, leftPoints,
731                 rightPoints, Corrector.DEFAULT_TYPE, leftCamera, rightCamera);
732     }
733 
734     /**
735      * Generates a pair of metric cameras (up to an arbitrary space) by
736      * computing the essential matrix from provided fundamental matrix and
737      * intrinsic parameters of left and right cameras, and choosing the best
738      * pair of camera pose and translation that yields the largest number of
739      * triangulated points laying in front of both of the estimated cameras.
740      * This method does not keep triangulated points or valid triangulated
741      * points.
742      *
743      * @param fundamentalMatrix fundamental matrix relating both left and right
744      *                          views.
745      * @param leftIntrinsic     intrinsic parameters to be set on left view.
746      *                          This can be used when cameras have been previously calibrated.
747      * @param rightIntrinsic    intrinsic parameters to be set on right view.
748      *                          This can be used when cameras have been previously calibrated.
749      * @param leftPoints        points on left view matched with points on right view,
750      *                          so they can be triangulated using estimated cameras. Both lists of points
751      *                          must have the same size.
752      * @param rightPoints       points on right view matched with points on left view,
753      *                          so they can be triangulated using estimated cameras. Both lists of points
754      *                          must have the same size.
755      * @param correctorType     corrector type to be used to correct 2D points, so
756      *                          they follow the epipolar geometry defined by provided fundamental matrix
757      *                          so that error on triangulated points is reduced. If null, no corrector
758      *                          will be used.
759      * @param leftCamera        instance where estimated left camera will be stored.
760      * @param rightCamera       instance where estimated right camera will be stored.
761      * @return number of valid triangulated points which lie in front of the two
762      * estimated cameras.
763      * @throws InitialCamerasEstimationFailedException if estimation of
764      *                                                 cameras fails for some reason, typically due to
765      *                                                 numerical instabilities.
766      * @throws IllegalArgumentException                if provided lists of left and right
767      *                                                 points don't have the same size.
768      */
769     public static int generateInitialMetricCamerasFromEssentialMatrix(
770             final FundamentalMatrix fundamentalMatrix,
771             final PinholeCameraIntrinsicParameters leftIntrinsic,
772             final PinholeCameraIntrinsicParameters rightIntrinsic,
773             final List<Point2D> leftPoints, final List<Point2D> rightPoints,
774             final CorrectorType correctorType, final PinholeCamera leftCamera,
775             final PinholeCamera rightCamera) throws InitialCamerasEstimationFailedException {
776         return generateInitialMetricCamerasFromEssentialMatrix(
777                 fundamentalMatrix, leftIntrinsic, rightIntrinsic, leftPoints,
778                 rightPoints, correctorType, leftCamera, rightCamera, null,
779                 null);
780     }
781 
782     /**
783      * Generates a pair of metric cameras (up to an arbitrary space) by
784      * computing the essential matrix from provided fundamental matrix and
785      * intrinsic parameters of left and right cameras, and choosing the best
786      * pair of camera pose and translation that yields the largest number of
787      * triangulated points laying in front of both of the estimated cameras.
788      * This method uses default corrector type.
789      *
790      * @param fundamentalMatrix       fundamental matrix relating both left and right
791      *                                views.
792      * @param leftIntrinsic           intrinsic parameters to be set on left view.
793      *                                This can be used when cameras have been previously calibrated.
794      * @param rightIntrinsic          intrinsic parameters to be set on right view.
795      *                                This can be used when cameras have been previously calibrated.
796      * @param leftPoints              points on left view matched with points on right view,
797      *                                so they can be triangulated using estimated cameras. Both lists of
798      *                                points must have the same size.
799      * @param rightPoints             points on right view matched with points on left view,
800      *                                so they can be triangulated using estimated cameras. Both lists of
801      *                                points must have the same size.
802      * @param leftCamera              instance where estimated left camera will be stored.
803      * @param rightCamera             instance where estimated right camera will be stored.
804      * @param triangulatedPoints      instance where triangulated 3D points will be
805      *                                stored or null if triangulated points don't need to be kept.
806      * @param validTriangulatedPoints instance which indicates which
807      *                                triangulated 3D points are considered valid because they lie in
808      *                                front of both cameras or null if such data doesn't need to be kept.
809      * @return number of valid triangulated points which lie in front of the two
810      * estimated cameras.
811      * @throws InitialCamerasEstimationFailedException if estimation of cameras
812      *                                                 fails for some reason, typically due to numerical
813      *                                                 instabilities.
814      * @throws IllegalArgumentException                if provided lists of left and right
815      *                                                 points don't have the same size.
816      */
817     public static int generateInitialMetricCamerasFromEssentialMatrix(
818             final FundamentalMatrix fundamentalMatrix,
819             final PinholeCameraIntrinsicParameters leftIntrinsic,
820             final PinholeCameraIntrinsicParameters rightIntrinsic,
821             final List<Point2D> leftPoints, final List<Point2D> rightPoints,
822             final PinholeCamera leftCamera, final PinholeCamera rightCamera,
823             final List<Point3D> triangulatedPoints, final BitSet validTriangulatedPoints)
824             throws InitialCamerasEstimationFailedException {
825 
826         return generateInitialMetricCamerasFromEssentialMatrix(
827                 fundamentalMatrix, leftIntrinsic, rightIntrinsic, leftPoints,
828                 rightPoints, Corrector.DEFAULT_TYPE, leftCamera, rightCamera,
829                 triangulatedPoints, validTriangulatedPoints);
830     }
831 
832     /**
833      * Generates a pair of metric cameras (up to an arbitrary space) by
834      * computing the essential matrix from provided fundamental matrix and
835      * intrinsic parameters of left and right cameras, and choosing the best
836      * pair of camera pose and translation that yields the largest number of
837      * triangulated points laying in front of both of the estimated cameras.
838      *
839      * @param fundamentalMatrix       fundamental matrix relating both left and right
840      *                                views.
841      * @param leftIntrinsic           intrinsic parameters to be set on left view.
842      *                                This can be used when cameras have been previously calibrated.
843      * @param rightIntrinsic          intrinsic parameters to be set on right view.
844      *                                This can be used when cameras have been previously calibrated.
845      * @param leftPoints              points on left view matched with points on right view,
846      *                                so they can be triangulated using estimated cameras. Both lists
847      *                                of points must have the same size.
848      * @param rightPoints             points on right view matched with points on left view,
849      *                                so they can be triangulated using estimated cameras. Both lists
850      *                                of points must have the same size.
851      * @param correctorType           corrector type to be used to correct 2D points, so
852      *                                they follow the epipolar geometry defined by provided fundamental
853      *                                matrix so that error on triangulated points is reduced. If null,
854      *                                no corrector will be used.
855      * @param leftCamera              instance where estimated left camera will be stored.
856      * @param rightCamera             instance where estimated right camera will be stored.
857      * @param triangulatedPoints      instance where triangulated 3D points will be
858      *                                stored or null if triangulated points don't need to be kept.
859      * @param validTriangulatedPoints instance which indicates which
860      *                                triangulated 3D points are considered valid because they lie in
861      *                                front of both cameras or null if such data doesn't need to be kept.
862      * @return number of valid triangulated points which lie in front of the two
863      * estimated cameras.
864      * @throws InitialCamerasEstimationFailedException if estimation of
865      *                                                 cameras fails for some reason, typically due to
866      *                                                 numerical instabilities.
867      * @throws IllegalArgumentException                if provided lists of left and right
868      *                                                 points don't have the same size.
869      */
870     public static int generateInitialMetricCamerasFromEssentialMatrix(
871             final FundamentalMatrix fundamentalMatrix,
872             final PinholeCameraIntrinsicParameters leftIntrinsic,
873             final PinholeCameraIntrinsicParameters rightIntrinsic,
874             final List<Point2D> leftPoints, final List<Point2D> rightPoints,
875             final CorrectorType correctorType, final PinholeCamera leftCamera,
876             final PinholeCamera rightCamera, final List<Point3D> triangulatedPoints,
877             final BitSet validTriangulatedPoints)
878             throws InitialCamerasEstimationFailedException {
879 
880         if (leftPoints.size() != rightPoints.size()) {
881             throw new IllegalArgumentException(
882                     "left and right points must have the same size");
883         }
884 
885         final List<Point2D> correctedLeftPoints;
886         final List<Point2D> correctedRightPoints;
887         final Rotation3D rotation1;
888         final Rotation3D rotation2;
889         final Point2D translation1;
890         final Point2D translation2;
891         try {
892             final var essential = new EssentialMatrix(fundamentalMatrix, leftIntrinsic, rightIntrinsic);
893 
894             essential.computePossibleRotationAndTranslations();
895 
896             rotation1 = essential.getFirstPossibleRotation();
897             translation1 = essential.getFirstPossibleTranslation();
898 
899             rotation2 = essential.getSecondPossibleRotation();
900             translation2 = essential.getSecondPossibleTranslation();
901 
902             if (correctorType != null) {
903                 // use corrector
904                 final var corrector = Corrector.create(leftPoints, rightPoints, fundamentalMatrix, correctorType);
905                 corrector.correct();
906 
907                 correctedLeftPoints = corrector.getLeftCorrectedPoints();
908                 correctedRightPoints = corrector.getRightCorrectedPoints();
909             } else {
910                 // don't use corrector
911                 correctedLeftPoints = leftPoints;
912                 correctedRightPoints = rightPoints;
913             }
914         } catch (final Exception e) {
915             throw new InitialCamerasEstimationFailedException(e);
916         }
917 
918         if (triangulatedPoints != null) {
919             triangulatedPoints.clear();
920         }
921         if (validTriangulatedPoints != null) {
922             validTriangulatedPoints.clear();
923         }
924         int numValidTriangulatedPoints;
925 
926         final var numPoints = correctedLeftPoints.size();
927         var skip = false;
928 
929         // obtain 1st pair of possible cameras and their corresponding
930         // triangulated points
931         try {
932             numValidTriangulatedPoints = computeCamerasAndTriangulation(rotation1, translation1,
933                     leftIntrinsic, rightIntrinsic, correctedLeftPoints, correctedRightPoints, leftCamera, rightCamera,
934                     triangulatedPoints, validTriangulatedPoints);
935         } catch (final Exception e) {
936             numValidTriangulatedPoints = 0;
937         }
938 
939         if (numValidTriangulatedPoints >= numPoints) {
940             // all points are valid, hence, we can set current pair of
941             // cameras as the best result
942             skip = true;
943         }
944 
945         final var attemptLeftCamera = new PinholeCamera();
946         final var attemptRightCamera = new PinholeCamera();
947         List<Point3D> attemptTriangulatedPoints = null;
948         if (triangulatedPoints != null) {
949             attemptTriangulatedPoints = new ArrayList<>();
950         }
951         BitSet attemptValidTriangulatedPoints = null;
952         if (validTriangulatedPoints != null) {
953             attemptValidTriangulatedPoints = new BitSet(numPoints);
954         }
955         int attemptNumValidTriangulatedPoints;
956 
957         if (!skip) {
958             // obtain 2nd pair of possible cameras and their corresponding
959             // triangulated points
960             try {
961                 attemptNumValidTriangulatedPoints = computeCamerasAndTriangulation(rotation1, translation2,
962                         leftIntrinsic, rightIntrinsic, correctedLeftPoints, correctedRightPoints, attemptLeftCamera,
963                         attemptRightCamera, attemptTriangulatedPoints, attemptValidTriangulatedPoints);
964             } catch (final Exception e) {
965                 attemptNumValidTriangulatedPoints = 0;
966             }
967 
968             if (attemptNumValidTriangulatedPoints >= numPoints) {
969                 // all points are valid, hence, we can set current pair of
970                 // cameras as the best result
971                 skip = true;
972             }
973 
974             if (attemptNumValidTriangulatedPoints > numValidTriangulatedPoints) {
975                 // a better solution containing more valid points has been found
976 
977                 // keep better solution
978                 updateBestSolutionData(leftCamera, rightCamera, triangulatedPoints, validTriangulatedPoints,
979                         attemptLeftCamera, attemptRightCamera, attemptTriangulatedPoints,
980                         attemptValidTriangulatedPoints);
981                 numValidTriangulatedPoints = attemptNumValidTriangulatedPoints;
982             }
983         }
984 
985         if (!skip) {
986             // obtain 3rd pair of possible cameras and their corresponding
987             // triangulated points
988             try {
989                 attemptNumValidTriangulatedPoints = computeCamerasAndTriangulation(rotation2, translation1,
990                         leftIntrinsic, rightIntrinsic, correctedLeftPoints, correctedRightPoints, attemptLeftCamera,
991                         attemptRightCamera, attemptTriangulatedPoints, attemptValidTriangulatedPoints);
992             } catch (final Exception e) {
993                 attemptNumValidTriangulatedPoints = 0;
994             }
995 
996             if (attemptNumValidTriangulatedPoints >= numPoints) {
997                 // all points are valid, hence, we can set current pair of
998                 // cameras as the best result
999                 skip = true;
1000             }
1001 
1002             if (attemptNumValidTriangulatedPoints > numValidTriangulatedPoints) {
1003                 // a better solution containing more valid points has been found
1004 
1005                 // keep better solution
1006                 updateBestSolutionData(leftCamera, rightCamera, triangulatedPoints, validTriangulatedPoints,
1007                         attemptLeftCamera, attemptRightCamera, attemptTriangulatedPoints,
1008                         attemptValidTriangulatedPoints);
1009                 numValidTriangulatedPoints = attemptNumValidTriangulatedPoints;
1010             }
1011         }
1012 
1013         if (!skip) {
1014             // obtain 4th pair of possible cameras and their corresponding
1015             // triangulated points
1016             try {
1017                 attemptNumValidTriangulatedPoints = computeCamerasAndTriangulation(rotation2, translation2,
1018                         leftIntrinsic, rightIntrinsic, correctedLeftPoints, correctedRightPoints, attemptLeftCamera,
1019                         attemptRightCamera, attemptTriangulatedPoints, attemptValidTriangulatedPoints);
1020             } catch (final Exception e) {
1021                 attemptNumValidTriangulatedPoints = 0;
1022             }
1023 
1024             if (attemptNumValidTriangulatedPoints > numValidTriangulatedPoints) {
1025                 // a better solution containing more valid points has been found
1026 
1027                 // keep better solution
1028                 updateBestSolutionData(leftCamera, rightCamera, triangulatedPoints, validTriangulatedPoints,
1029                         attemptLeftCamera, attemptRightCamera, attemptTriangulatedPoints,
1030                         attemptValidTriangulatedPoints);
1031                 numValidTriangulatedPoints = attemptNumValidTriangulatedPoints;
1032             }
1033         }
1034 
1035         if (numValidTriangulatedPoints == 0) {
1036             throw new InitialCamerasEstimationFailedException("no valid points found");
1037         }
1038 
1039         return numValidTriangulatedPoints;
1040     }
1041 
1042     /**
1043      * Internal method to set matched 2D points on left and right views.
1044      * This method does not check whether the estimator is locked or not, only
1045      * ensures that provided lists have the same size.
1046      *
1047      * @param leftPoints  matched 2D points on left view.
1048      * @param rightPoints matched 2D points on right view.
1049      * @throws IllegalArgumentException if provided lists don't have the same
1050      *                                  size.
1051      */
1052     private void internalSetLeftAndRightPoints(final List<Point2D> leftPoints, final List<Point2D> rightPoints) {
1053         if (leftPoints == null || rightPoints == null || leftPoints.size() != rightPoints.size()) {
1054             throw new IllegalArgumentException();
1055         }
1056         this.leftPoints = leftPoints;
1057         this.rightPoints = rightPoints;
1058     }
1059 
1060     /**
1061      * Updates data for best solution found so far.
1062      *
1063      * @param leftCamera                     instance where best found left camera will be stored.
1064      * @param rightCamera                    instance where best found right camera will be stored.
1065      * @param triangulatedPoints             instance where triangulated points for best
1066      *                                       found solution will be stored or null if points don't need to
1067      *                                       be kept.
1068      * @param validTriangulatedPoints        instance where valid triangulated points
1069      *                                       for best found solution will be stored or null if such data
1070      *                                       doesn't need to be kept.
1071      * @param attemptLeftCamera              estimated left camera to be copied into the best
1072      *                                       solution.
1073      * @param attemptRightCamera             estimated right camera to be copied into the best
1074      *                                       solution.
1075      * @param attemptTriangulatedPoints      triangulated points to be copied into the
1076      *                                       best solution, or null if nothing needs to be copied.
1077      * @param attemptValidTriangulatedPoints valid triangulated points to be
1078      *                                       copied into the best solution, or null if nothing needs to
1079      *                                       be copied.
1080      * @throws InitialCamerasEstimationFailedException if something fails.
1081      */
1082     private static void updateBestSolutionData(
1083             final PinholeCamera leftCamera,
1084             final PinholeCamera rightCamera,
1085             final List<Point3D> triangulatedPoints,
1086             final BitSet validTriangulatedPoints,
1087             final PinholeCamera attemptLeftCamera,
1088             final PinholeCamera attemptRightCamera,
1089             final List<Point3D> attemptTriangulatedPoints,
1090             final BitSet attemptValidTriangulatedPoints)
1091             throws InitialCamerasEstimationFailedException {
1092 
1093         try {
1094             leftCamera.setInternalMatrix(attemptLeftCamera.getInternalMatrix());
1095             rightCamera.setInternalMatrix(attemptRightCamera.getInternalMatrix());
1096             if (triangulatedPoints != null && attemptTriangulatedPoints != null) {
1097                 triangulatedPoints.clear();
1098                 triangulatedPoints.addAll(attemptTriangulatedPoints);
1099             }
1100             if (validTriangulatedPoints != null && attemptValidTriangulatedPoints != null) {
1101                 validTriangulatedPoints.clear();
1102                 validTriangulatedPoints.or(attemptValidTriangulatedPoints);
1103             }
1104         } catch (final WrongSizeException e) {
1105             throw new InitialCamerasEstimationFailedException(e);
1106         }
1107     }
1108 
1109     /**
1110      * Computes a pair of cameras for provided rotation and translation
1111      * using provided intrinsic parameters.
1112      * This method also triangulates proved matched 2D points and determines
1113      * how many of them lie in front of both estimated cameras.
1114      *
1115      * @param rotation                rotation between estimated left and right cameras.
1116      * @param translation             translation between estimated left and right cameras.
1117      * @param leftIntrinsic           intrinsic parameters to set on estimated left
1118      *                                camera.
1119      * @param rightIntrinsic          intrinsic parameters to set on estimated right
1120      *                                camera.
1121      * @param leftPoints              points on left view matched with points on right view,
1122      *                                so they can be triangulated using estimated cameras. Both lists
1123      *                                of points must have the same size.
1124      * @param rightPoints             points on right view matched with points on left view,
1125      *                                so they can be triangulated using estimated cameras. Both lists
1126      *                                of points must have the same size.
1127      * @param estimatedLeftCamera     instance where estimated left camera will be
1128      *                                stored.
1129      * @param estimatedRightCamera    instance where estimated right camera will be
1130      *                                stored.
1131      * @param triangulatedPoints      instance where triangulated 3D points will be
1132      *                                stored or null if triangulated points don't need to be kept.
1133      * @param validTriangulatedPoints instance which indicates which
1134      *                                triangulated 3D points are considered valid because they lie in
1135      *                                front of both cameras or null if such data doesn't need to be kept.
1136      * @return number of valid triangulated points which lie in front of the two
1137      * estimated cameras.
1138      * @throws WrongSizeException            never occurs.
1139      * @throws CameraException               if any of the estimated cameras become
1140      *                                       numerically unstable.
1141      * @throws LockedException               never occurs.
1142      * @throws NotReadyException             never occurs.
1143      * @throws Point3DTriangulationException if points cannot be triangulated
1144      *                                       because of numerical instabilities.
1145      */
1146     private static int computeCamerasAndTriangulation(
1147             final Rotation3D rotation,
1148             final Point2D translation,
1149             final PinholeCameraIntrinsicParameters leftIntrinsic,
1150             final PinholeCameraIntrinsicParameters rightIntrinsic,
1151             final List<Point2D> leftPoints,
1152             final List<Point2D> rightPoints,
1153             final PinholeCamera estimatedLeftCamera,
1154             final PinholeCamera estimatedRightCamera,
1155             final List<Point3D> triangulatedPoints,
1156             final BitSet validTriangulatedPoints) throws WrongSizeException, CameraException, LockedException,
1157             NotReadyException, Point3DTriangulationException {
1158 
1159         if (triangulatedPoints != null) {
1160             triangulatedPoints.clear();
1161         }
1162         if (validTriangulatedPoints != null) {
1163             validTriangulatedPoints.clear();
1164         }
1165         var numValidTriangulatedPoints = 0;
1166 
1167         final var leftIntrinsicMatrix = leftIntrinsic.getInternalMatrix();
1168         final var rightIntrinsicMatrix = rightIntrinsic.getInternalMatrix();
1169 
1170         final var rotationMatrix = rotation.asInhomogeneousMatrix();
1171 
1172         // 1st camera
1173 
1174         // set camera as a canonical matrix
1175         final var tmp = Matrix.identity(PinholeCamera.PINHOLE_CAMERA_MATRIX_ROWS,
1176                 PinholeCamera.PINHOLE_CAMERA_MATRIX_COLS);
1177 
1178         // add intrinsic parameters
1179         leftIntrinsicMatrix.multiply(tmp);
1180 
1181         // set internal matrix, normalize and fix camera sign
1182         estimatedLeftCamera.setInternalMatrix(leftIntrinsicMatrix);
1183         estimatedLeftCamera.normalize();
1184         estimatedLeftCamera.fixCameraSign();
1185 
1186         // 2nd camera
1187 
1188         // set left 3x3 minor containing rotation
1189         tmp.setSubmatrix(0, 0, 2, 2, rotationMatrix);
1190 
1191         // set last column containing translation
1192         translation.normalize();
1193         tmp.setElementAt(0, 3, translation.getHomX());
1194         tmp.setElementAt(1, 3, translation.getHomY());
1195         tmp.setElementAt(2, 3, translation.getHomW());
1196 
1197         // add intrinsic parameters
1198         rightIntrinsicMatrix.multiply(tmp);
1199 
1200         // set internal matrix, normalize and fix camera sign
1201         estimatedRightCamera.setInternalMatrix(rightIntrinsicMatrix);
1202         estimatedRightCamera.normalize();
1203         estimatedRightCamera.fixCameraSign();
1204 
1205         // set cameras on triangulator
1206         final var triangulator = SinglePoint3DTriangulator.create();
1207 
1208         final var numPoints = leftPoints.size();
1209         Point2D leftPoint;
1210         Point2D rightPoint;
1211         final var points = new ArrayList<Point2D>();
1212         final var cameras = new ArrayList<PinholeCamera>();
1213         Point3D triangulatedPoint;
1214         boolean frontLeft;
1215         boolean frontRight;
1216         for (var i = 0; i < numPoints; i++) {
1217             leftPoint = leftPoints.get(i);
1218             rightPoint = rightPoints.get(i);
1219 
1220             points.clear();
1221             points.add(leftPoint);
1222             points.add(rightPoint);
1223 
1224             cameras.clear();
1225             cameras.add(estimatedLeftCamera);
1226             cameras.add(estimatedRightCamera);
1227 
1228             triangulator.setPointsAndCameras(points, cameras);
1229             triangulatedPoint = triangulator.triangulate();
1230             if (triangulatedPoints != null) {
1231                 triangulatedPoints.add(triangulatedPoint);
1232             }
1233 
1234             // check that triangulated point is in front of both cameras
1235             frontLeft = estimatedLeftCamera.isPointInFrontOfCamera(triangulatedPoint);
1236             frontRight = estimatedRightCamera.isPointInFrontOfCamera(triangulatedPoint);
1237 
1238             if (frontLeft && frontRight) {
1239                 // point is valid because it is in front of both cameras
1240                 if (validTriangulatedPoints != null) {
1241                     validTriangulatedPoints.set(i);
1242                 }
1243                 numValidTriangulatedPoints++;
1244             } else {
1245                 // point is not valid
1246                 if (validTriangulatedPoints != null) {
1247                     validTriangulatedPoints.clear(i);
1248                 }
1249             }
1250         }
1251 
1252         return numValidTriangulatedPoints;
1253     }
1254 }