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  
17  package com.irurueta.ar.sfm;
18  
19  import com.irurueta.ar.calibration.estimators.LMSEImageOfAbsoluteConicEstimator;
20  import com.irurueta.ar.epipolar.Corrector;
21  import com.irurueta.ar.epipolar.EpipolarException;
22  import com.irurueta.ar.epipolar.EssentialMatrix;
23  import com.irurueta.ar.epipolar.FundamentalMatrix;
24  import com.irurueta.ar.epipolar.estimators.EightPointsFundamentalMatrixEstimator;
25  import com.irurueta.ar.epipolar.estimators.FundamentalMatrixEstimatorMethod;
26  import com.irurueta.ar.epipolar.estimators.FundamentalMatrixRobustEstimator;
27  import com.irurueta.ar.epipolar.estimators.LMedSFundamentalMatrixRobustEstimator;
28  import com.irurueta.ar.epipolar.estimators.MSACFundamentalMatrixRobustEstimator;
29  import com.irurueta.ar.epipolar.estimators.PROMedSFundamentalMatrixRobustEstimator;
30  import com.irurueta.ar.epipolar.estimators.PROSACFundamentalMatrixRobustEstimator;
31  import com.irurueta.ar.epipolar.estimators.RANSACFundamentalMatrixRobustEstimator;
32  import com.irurueta.ar.epipolar.estimators.SevenPointsFundamentalMatrixEstimator;
33  import com.irurueta.geometry.PinholeCamera;
34  import com.irurueta.geometry.PinholeCameraIntrinsicParameters;
35  import com.irurueta.geometry.Point2D;
36  import com.irurueta.geometry.Point3D;
37  import com.irurueta.geometry.Transformation2D;
38  import com.irurueta.geometry.estimators.LMedSPointCorrespondenceProjectiveTransformation2DRobustEstimator;
39  import com.irurueta.geometry.estimators.MSACPointCorrespondenceProjectiveTransformation2DRobustEstimator;
40  import com.irurueta.geometry.estimators.NotReadyException;
41  import com.irurueta.geometry.estimators.PROMedSPointCorrespondenceProjectiveTransformation2DRobustEstimator;
42  import com.irurueta.geometry.estimators.PROSACPointCorrespondenceProjectiveTransformation2DRobustEstimator;
43  import com.irurueta.geometry.estimators.PointCorrespondenceProjectiveTransformation2DRobustEstimator;
44  import com.irurueta.geometry.estimators.ProjectiveTransformation2DRobustEstimator;
45  import com.irurueta.geometry.estimators.RANSACPointCorrespondenceProjectiveTransformation2DRobustEstimator;
46  
47  import java.util.ArrayList;
48  import java.util.List;
49  
50  /**
51   * Base class in charge of estimating cameras and 3D reconstructed points from
52   * sparse image point correspondences in two views.
53   *
54   * @param <C> type of configuration.
55   * @param <R> type of re-constructor.
56   * @param <L> type of listener.
57   */
58  @SuppressWarnings("DuplicatedCode")
59  public abstract class BaseTwoViewsSparseReconstructor<
60          C extends BaseTwoViewsSparseReconstructorConfiguration<C>,
61          R extends BaseTwoViewsSparseReconstructor<C, R, L>,
62          L extends BaseTwoViewsSparseReconstructorListener<R>> {
63  
64      /**
65       * Number of views.
66       */
67      public static final int NUMBER_OF_VIEWS = 2;
68  
69      /**
70       * Estimated fundamental matrix.
71       */
72      protected EstimatedFundamentalMatrix estimatedFundamentalMatrix;
73  
74      /**
75       * Estimated first camera.
76       */
77      protected EstimatedCamera estimatedCamera1;
78  
79      /**
80       * Estimated second camera.
81       */
82      protected EstimatedCamera estimatedCamera2;
83  
84      /**
85       * Reconstructed 3D points.
86       */
87      protected List<ReconstructedPoint3D> reconstructedPoints;
88  
89      /**
90       * Configuration for this re-constructor.
91       */
92      protected C configuration;
93  
94      /**
95       * Listener in charge of handling events such as when reconstruction starts,
96       * ends, when certain data is needed or when estimation of data has been
97       * computed.
98       */
99      protected L listener;
100 
101     /**
102      * Indicates whether reconstruction has failed or not.
103      */
104     protected volatile boolean failed;
105 
106     /**
107      * Indicates whether reconstruction is running or not.
108      */
109     protected volatile boolean running;
110 
111     /**
112      * Indicates whether reconstruction has been cancelled or not.
113      */
114     private volatile boolean cancelled;
115 
116     /**
117      * Counter of number of processed views.
118      */
119     private int viewCount;
120 
121     /**
122      * Indicates whether reconstruction has finished or not.
123      */
124     private boolean finished = false;
125 
126     /**
127      * Samples on first view.
128      */
129     private List<Sample2D> firstViewSamples = null;
130 
131     /**
132      * Samples on last processed view (i.e. current view).
133      */
134     private List<Sample2D> currentViewSamples;
135 
136     /**
137      * Matches between first and current view.
138      */
139     private final List<MatchedSamples> matches = new ArrayList<>();
140 
141     /**
142      * ID of first view.
143      */
144     private int firstViewId = 0;
145 
146     /**
147      * Constructor.
148      *
149      * @param configuration configuration for this re-constructor.
150      * @param listener      listener in charge of handling events.
151      * @throws NullPointerException if listener or configuration is not
152      *                              provided.
153      */
154     protected BaseTwoViewsSparseReconstructor(final C configuration, final L listener) {
155         if (configuration == null || listener == null) {
156             throw new NullPointerException();
157         }
158         this.configuration = configuration;
159         this.listener = listener;
160     }
161 
162     /**
163      * Gets configuration for this re-constructor.
164      *
165      * @return configuration for this re-constructor.
166      */
167     public C getConfiguration() {
168         return configuration;
169     }
170 
171     /**
172      * Gets listener in charge of handling events such as when reconstruction
173      * starts, ends, when certain data is needed or when estimation of data has
174      * been computed.
175      *
176      * @return listener in charge of handling events.
177      */
178     public BaseTwoViewsSparseReconstructorListener<R> getListener() {
179         return listener;
180     }
181 
182     /**
183      * Indicates whether reconstruction is running or not.
184      *
185      * @return true if reconstruction is running, false if reconstruction has
186      * stopped for any reason.
187      */
188     public boolean isRunning() {
189         return running;
190     }
191 
192     /**
193      * Indicates whether reconstruction has been cancelled or not.
194      *
195      * @return true if reconstruction has been cancelled, false otherwise.
196      */
197     public boolean isCancelled() {
198         return cancelled;
199     }
200 
201     /**
202      * Indicates whether reconstruction has failed or not.
203      *
204      * @return true if reconstruction has failed, false otherwise.
205      */
206     public boolean hasFailed() {
207         return failed;
208     }
209 
210     /**
211      * Indicates whether the reconstruction has finished.
212      *
213      * @return true if reconstruction has finished, false otherwise.
214      */
215     public boolean isFinished() {
216         return finished;
217     }
218 
219     /**
220      * Gets counter of number of processed views.
221      *
222      * @return counter of number of processed views.
223      */
224     public int getViewCount() {
225         return viewCount;
226     }
227 
228     /**
229      * Gets estimated fundamental matrix.
230      *
231      * @return estimated fundamental matrix.
232      */
233     public EstimatedFundamentalMatrix getEstimatedFundamentalMatrix() {
234         return estimatedFundamentalMatrix;
235     }
236 
237     /**
238      * Gets estimated first camera.
239      *
240      * @return estimated first camera.
241      */
242     public EstimatedCamera getEstimatedCamera1() {
243         return estimatedCamera1;
244     }
245 
246     /**
247      * Gets estimated second camera.
248      *
249      * @return estimated second camera.
250      */
251     public EstimatedCamera getEstimatedCamera2() {
252         return estimatedCamera2;
253     }
254 
255     /**
256      * Gets reconstructed 3D points.
257      *
258      * @return reconstructed 3D points.
259      */
260     public List<ReconstructedPoint3D> getReconstructedPoints() {
261         return reconstructedPoints;
262     }
263 
264     /**
265      * Process one view of all the available data during the reconstruction.
266      * This method can be called multiple times instead of {@link #start()} to build the reconstruction
267      * step by step, one view at a time.
268      *
269      * @return true if more views can be processed, false when reconstruction has finished.
270      */
271     public boolean processOneView() {
272         if (viewCount == 0) {
273             if (running) {
274                 // already started
275                 return true;
276             }
277 
278             reset();
279             running = true;
280 
281             //noinspection unchecked
282             listener.onStart((R) this);
283         }
284 
285         //noinspection unchecked
286         if (!listener.hasMoreViewsAvailable((R) this)) {
287             return false;
288         }
289 
290         estimatedFundamentalMatrix = null;
291         currentViewSamples = new ArrayList<>();
292         //noinspection unchecked
293         listener.onRequestSamplesForCurrentView((R) this, viewCount, currentViewSamples);
294 
295         if (firstViewSamples == null) {
296             // for first view we simply keep samples (if enough are provided)
297             if (hasEnoughSamples(currentViewSamples)) {
298                 //noinspection unchecked
299                 listener.onSamplesAccepted((R) this, viewCount, currentViewSamples);
300                 firstViewSamples = currentViewSamples;
301                 firstViewId = viewCount;
302             }
303 
304         } else {
305 
306             // for second view, check that we have enough samples
307             if (hasEnoughSamples(currentViewSamples)) {
308 
309                 // find matches
310                 matches.clear();
311                 //noinspection unchecked
312                 listener.onRequestMatches((R) this, firstViewSamples, currentViewSamples, firstViewId, viewCount,
313                         matches);
314 
315                 if (hasEnoughMatches(matches)) {
316                     // if enough matches are retrieved, attempt to compute
317                     // fundamental matrix
318                     if ((configuration.isGeneralSceneAllowed()
319                             && estimateFundamentalMatrix(matches, firstViewId, viewCount))
320                             || (configuration.isPlanarSceneAllowed()
321                             && estimatePlanarFundamentalMatrix(matches, firstViewId, viewCount))) {
322                         // fundamental matrix could be estimated
323                         //noinspection unchecked
324                         listener.onSamplesAccepted((R) this, viewCount, currentViewSamples);
325                         var secondViewId = viewCount;
326 
327                         //noinspection unchecked
328                         listener.onFundamentalMatrixEstimated((R) this, estimatedFundamentalMatrix);
329 
330                         if (estimateInitialCamerasAndPoints()) {
331                             // cameras and points have been estimated
332                             //noinspection unchecked
333                             listener.onCamerasEstimated((R) this, firstViewId, secondViewId, estimatedCamera1,
334                                     estimatedCamera2);
335                             //noinspection unchecked
336                             listener.onReconstructedPointsEstimated((R) this, matches, reconstructedPoints);
337                             if (postProcessOne()) {
338                                 //noinspection unchecked
339                                 listener.onFinish((R) this);
340                                 running = false;
341                                 finished = true;
342                             }
343                         } else {
344                             // initial cameras failed
345                             failed = true;
346                             //noinspection unchecked
347                             listener.onFail((R) this);
348                         }
349                     } else {
350                         // estimation of fundamental matrix failed
351                         //noinspection unchecked
352                         listener.onSamplesRejected((R) this, viewCount, currentViewSamples);
353                     }
354                 }
355             }
356         }
357 
358         viewCount++;
359 
360         if (cancelled) {
361             //noinspection unchecked
362             listener.onCancel((R) this);
363         }
364 
365         return !finished;
366     }
367 
368     /**
369      * Starts reconstruction of all available data to reconstruct the whole scene.
370      * If reconstruction has already started and is running, calling this method
371      * has no effect.
372      */
373     public void start() {
374         while (processOneView()) {
375             if (cancelled) {
376                 break;
377             }
378         }
379     }
380 
381     /**
382      * Cancels reconstruction.
383      * If reconstruction has already been cancelled, calling this method has no
384      * effect.
385      */
386     public void cancel() {
387         if (cancelled) {
388             // already cancelled
389             return;
390         }
391 
392         cancelled = true;
393     }
394 
395     /**
396      * Resets this instance so that a reconstruction can be started from the beginning without cancelling
397      * current one.
398      */
399     public void reset() {
400         firstViewSamples = currentViewSamples = null;
401 
402         cancelled = failed = false;
403         viewCount = 0;
404         running = false;
405 
406         estimatedFundamentalMatrix = null;
407         estimatedCamera1 = estimatedCamera2 = null;
408         reconstructedPoints = null;
409 
410         finished = false;
411     }
412 
413     /**
414      * Called when processing one frame is successfully finished. This can be done to estimate scale on
415      * those implementations where scale can be measured or is already known.
416      *
417      * @return true if post-processing succeeded, false otherwise.
418      */
419     protected abstract boolean postProcessOne();
420 
421     /**
422      * Indicates whether there are enough samples to estimate a fundamental
423      * matrix.
424      *
425      * @param samples samples to check.
426      * @return true if there are enough samples, false otherwise.
427      */
428     private boolean hasEnoughSamples(final List<Sample2D> samples) {
429         return hasEnoughSamplesOrMatches(samples != null ? samples.size() : 0);
430     }
431 
432     /**
433      * Indicates whether there are enough matches to estimate a fundamental
434      * matrix.
435      *
436      * @param matches matches to check.
437      * @return true if there are enough matches, false otherwise.
438      */
439     private boolean hasEnoughMatches(final List<MatchedSamples> matches) {
440         return hasEnoughSamplesOrMatches(matches != null ? matches.size() : 0);
441     }
442 
443     /**
444      * Indicates whether there are enough matches or samples to estimate a
445      * fundamental matrix.
446      *
447      * @param count number of matches or samples.
448      * @return true if there are enough matches or samples, false otherwise.
449      */
450     private boolean hasEnoughSamplesOrMatches(final int count) {
451         if (configuration.isGeneralSceneAllowed()) {
452             if (configuration.getNonRobustFundamentalMatrixEstimatorMethod()
453                     == FundamentalMatrixEstimatorMethod.EIGHT_POINTS_ALGORITHM) {
454                 return count >= EightPointsFundamentalMatrixEstimator.MIN_REQUIRED_POINTS;
455             } else if (configuration.getNonRobustFundamentalMatrixEstimatorMethod()
456                     == FundamentalMatrixEstimatorMethod.SEVEN_POINTS_ALGORITHM) {
457                 return count >= SevenPointsFundamentalMatrixEstimator.MIN_REQUIRED_POINTS;
458             }
459         } else if (configuration.isPlanarSceneAllowed()) {
460             return count >= ProjectiveTransformation2DRobustEstimator.MINIMUM_SIZE;
461         }
462         return false;
463     }
464 
465     /**
466      * Estimates fundamental matrix for provided matches, when 3D points lay in
467      * a general non-degenerate 3D configuration.
468      *
469      * @param matches pairs of matches to find fundamental matrix.
470      * @param viewId1 id of first view.
471      * @param viewId2 id of second view.
472      * @return true if estimation succeeded, false otherwise.
473      */
474     private boolean estimateFundamentalMatrix(final List<MatchedSamples> matches, final int viewId1,
475                                               final int viewId2) {
476         if (matches == null) {
477             return false;
478         }
479 
480         final var count = matches.size();
481         final var leftSamples = new ArrayList<Sample2D>(count);
482         final var rightSamples = new ArrayList<Sample2D>(count);
483         final var leftPoints = new ArrayList<Point2D>(count);
484         final var rightPoints = new ArrayList<Point2D>(count);
485         final var qualityScores = new double[count];
486         final double principalPointX;
487         final double principalPointY;
488         if (configuration.getInitialCamerasEstimatorMethod() == InitialCamerasEstimatorMethod.DUAL_ABSOLUTE_QUADRIC
489                 || configuration.getInitialCamerasEstimatorMethod()
490                 == InitialCamerasEstimatorMethod.DUAL_ABSOLUTE_QUADRIC_AND_ESSENTIAL_MATRIX) {
491             principalPointX = configuration.getPrincipalPointX();
492             principalPointY = configuration.getPrincipalPointY();
493         } else {
494             principalPointX = principalPointY = 0.0;
495         }
496 
497         var i = 0;
498         for (final var match : matches) {
499             final var samples = match.getSamples();
500             if (samples.length != NUMBER_OF_VIEWS) {
501                 return false;
502             }
503 
504             leftSamples.add(samples[0]);
505             rightSamples.add(samples[1]);
506 
507             final var leftPoint = Point2D.create();
508             leftPoint.setInhomogeneousCoordinates(
509                     samples[0].getPoint().getInhomX() - principalPointX,
510                     samples[0].getPoint().getInhomY() - principalPointY);
511             leftPoints.add(leftPoint);
512 
513             final var rightPoint = Point2D.create();
514             rightPoint.setInhomogeneousCoordinates(
515                     samples[1].getPoint().getInhomX() - principalPointX,
516                     samples[1].getPoint().getInhomY() - principalPointY);
517             rightPoints.add(rightPoint);
518 
519             qualityScores[i] = match.getQualityScore();
520             i++;
521         }
522 
523         try {
524             final var estimator = FundamentalMatrixRobustEstimator.create(leftPoints, rightPoints, qualityScores,
525                     configuration.getRobustFundamentalMatrixEstimatorMethod());
526             estimator.setNonRobustFundamentalMatrixEstimatorMethod(
527                     configuration.getNonRobustFundamentalMatrixEstimatorMethod());
528             estimator.setResultRefined(configuration.isFundamentalMatrixRefined());
529             estimator.setCovarianceKept(configuration.isFundamentalMatrixCovarianceKept());
530             estimator.setConfidence(configuration.getFundamentalMatrixConfidence());
531             estimator.setMaxIterations(configuration.getFundamentalMatrixMaxIterations());
532 
533             switch (configuration.getRobustFundamentalMatrixEstimatorMethod()) {
534                 case LMEDS:
535                     ((LMedSFundamentalMatrixRobustEstimator) estimator)
536                             .setStopThreshold(configuration.getFundamentalMatrixThreshold());
537                     break;
538                 case MSAC:
539                     ((MSACFundamentalMatrixRobustEstimator) estimator)
540                             .setThreshold(configuration.getFundamentalMatrixThreshold());
541                     break;
542                 case PROMEDS:
543                     ((PROMedSFundamentalMatrixRobustEstimator) estimator)
544                             .setStopThreshold(configuration.getFundamentalMatrixThreshold());
545                     break;
546                 case PROSAC:
547                     var prosacEstimator = (PROSACFundamentalMatrixRobustEstimator) estimator;
548                     prosacEstimator.setThreshold(configuration.getFundamentalMatrixThreshold());
549                     prosacEstimator.setComputeAndKeepInliersEnabled(
550                             configuration.getFundamentalMatrixComputeAndKeepInliers());
551                     prosacEstimator.setComputeAndKeepResidualsEnabled(
552                             configuration.getFundamentalMatrixComputeAndKeepResiduals());
553                     break;
554                 case RANSAC:
555                     var ransacEstimator = (RANSACFundamentalMatrixRobustEstimator) estimator;
556                     ransacEstimator.setThreshold(configuration.getFundamentalMatrixThreshold());
557                     ransacEstimator.setComputeAndKeepInliersEnabled(
558                             configuration.getFundamentalMatrixComputeAndKeepInliers());
559                     ransacEstimator.setComputeAndKeepResidualsEnabled(
560                             configuration.getFundamentalMatrixComputeAndKeepResiduals());
561                     break;
562                 default:
563                     break;
564             }
565 
566 
567             final var fundamentalMatrix = estimator.estimate();
568 
569             estimatedFundamentalMatrix = new EstimatedFundamentalMatrix();
570             estimatedFundamentalMatrix.setFundamentalMatrix(fundamentalMatrix);
571             estimatedFundamentalMatrix.setViewId1(viewId1);
572             estimatedFundamentalMatrix.setViewId2(viewId2);
573             estimatedFundamentalMatrix.setCovariance(estimator.getCovariance());
574 
575             // determine quality score and inliers
576             final var inliersData = estimator.getInliersData();
577             if (inliersData != null) {
578                 final var numInliers = inliersData.getNumInliers();
579                 final var inliers = inliersData.getInliers();
580                 final var length = inliers.length();
581                 var fundamentalMatrixQualityScore = 0.0;
582                 for (i = 0; i < length; i++) {
583                     if (inliers.get(i)) {
584                         // inlier
585                         fundamentalMatrixQualityScore += qualityScores[i] / numInliers;
586                     }
587                 }
588                 estimatedFundamentalMatrix.setQualityScore(fundamentalMatrixQualityScore);
589                 estimatedFundamentalMatrix.setInliers(inliers);
590             }
591 
592             // store left/right samples
593             estimatedFundamentalMatrix.setLeftSamples(leftSamples);
594             estimatedFundamentalMatrix.setRightSamples(rightSamples);
595 
596             return true;
597         } catch (final Exception e) {
598             return false;
599         }
600     }
601 
602     /**
603      * Estimates fundamental matrix for provided matches, when 3D points lay in
604      * a planar 3D scene.
605      *
606      * @param matches pairs of matches to find fundamental matrix.
607      * @param viewId1 id of first view.
608      * @param viewId2 id of second view.
609      * @return true if estimation succeeded, false otherwise.
610      */
611     private boolean estimatePlanarFundamentalMatrix(
612             final List<MatchedSamples> matches, final int viewId1, final int viewId2) {
613         if (matches == null) {
614             return false;
615         }
616 
617         final var count = matches.size();
618         final var leftSamples = new ArrayList<Sample2D>();
619         final var rightSamples = new ArrayList<Sample2D>();
620         final var leftPoints = new ArrayList<Point2D>();
621         final var rightPoints = new ArrayList<Point2D>();
622         final var qualityScores = new double[count];
623         final double principalPointX;
624         final double principalPointY;
625         if (configuration.getInitialCamerasEstimatorMethod() == InitialCamerasEstimatorMethod.DUAL_ABSOLUTE_QUADRIC
626                 || configuration.getInitialCamerasEstimatorMethod()
627                 == InitialCamerasEstimatorMethod.DUAL_ABSOLUTE_QUADRIC_AND_ESSENTIAL_MATRIX) {
628             principalPointX = configuration.getPrincipalPointX();
629             principalPointY = configuration.getPrincipalPointY();
630         } else {
631             principalPointX = principalPointY = 0.0;
632         }
633 
634         var i = 0;
635         for (final var match : matches) {
636             final var samples = match.getSamples();
637             if (samples.length != NUMBER_OF_VIEWS) {
638                 return false;
639             }
640 
641             leftSamples.add(samples[0]);
642             rightSamples.add(samples[1]);
643 
644             final var leftPoint = Point2D.create();
645             leftPoint.setInhomogeneousCoordinates(
646                     samples[0].getPoint().getInhomX() - principalPointX,
647                     samples[0].getPoint().getInhomY() - principalPointY);
648             leftPoints.add(leftPoint);
649 
650             final var rightPoint = Point2D.create();
651             rightPoint.setInhomogeneousCoordinates(
652                     samples[1].getPoint().getInhomX() - principalPointX,
653                     samples[1].getPoint().getInhomY() - principalPointY);
654             rightPoints.add(rightPoint);
655 
656             qualityScores[i] = match.getQualityScore();
657             i++;
658         }
659 
660         try {
661             final var homographyEstimator = PointCorrespondenceProjectiveTransformation2DRobustEstimator.create(
662                     configuration.getRobustPlanarHomographyEstimatorMethod());
663             homographyEstimator.setResultRefined(configuration.isPlanarHomographyRefined());
664             homographyEstimator.setCovarianceKept(configuration.isPlanarHomographyCovarianceKept());
665             homographyEstimator.setConfidence(configuration.getPlanarHomographyConfidence());
666             homographyEstimator.setMaxIterations(configuration.getPlanarHomographyMaxIterations());
667 
668             switch (configuration.getRobustPlanarHomographyEstimatorMethod()) {
669                 case LMEDS:
670                     ((LMedSPointCorrespondenceProjectiveTransformation2DRobustEstimator) homographyEstimator)
671                             .setStopThreshold(configuration.getPlanarHomographyThreshold());
672                     break;
673                 case MSAC:
674                     ((MSACPointCorrespondenceProjectiveTransformation2DRobustEstimator) homographyEstimator)
675                             .setThreshold(configuration.getPlanarHomographyThreshold());
676                     break;
677                 case PROMEDS:
678                     ((PROMedSPointCorrespondenceProjectiveTransformation2DRobustEstimator) homographyEstimator)
679                             .setStopThreshold(configuration.getPlanarHomographyThreshold());
680                     break;
681                 case PROSAC:
682                     final var prosacHomographyEstimator =
683                             (PROSACPointCorrespondenceProjectiveTransformation2DRobustEstimator) homographyEstimator;
684 
685                     prosacHomographyEstimator.setThreshold(configuration.getPlanarHomographyThreshold());
686                     prosacHomographyEstimator.setComputeAndKeepInliersEnabled(
687                             configuration.getPlanarHomographyComputeAndKeepInliers());
688                     prosacHomographyEstimator.setComputeAndKeepResidualsEnabled(
689                             configuration.getPlanarHomographyComputeAndKeepResiduals());
690                     break;
691                 case RANSAC:
692                 default:
693                     final var ransacHomographyEstimator =
694                             (RANSACPointCorrespondenceProjectiveTransformation2DRobustEstimator) homographyEstimator;
695 
696                     ransacHomographyEstimator.setThreshold(configuration.getPlanarHomographyThreshold());
697                     ransacHomographyEstimator.setComputeAndKeepInliersEnabled(
698                             configuration.getPlanarHomographyComputeAndKeepInliers());
699                     ransacHomographyEstimator.setComputeAndKeepResidualsEnabled(
700                             configuration.getPlanarHomographyComputeAndKeepResiduals());
701                     break;
702             }
703 
704             final var fundamentalMatrixEstimator = new PlanarBestFundamentalMatrixEstimatorAndReconstructor();
705             fundamentalMatrixEstimator.setHomographyEstimator(homographyEstimator);
706             fundamentalMatrixEstimator.setLeftAndRightPoints(leftPoints, rightPoints);
707             fundamentalMatrixEstimator.setQualityScores(qualityScores);
708 
709             var intrinsic1 = configuration.getInitialIntrinsic1();
710             var intrinsic2 = configuration.getInitialIntrinsic1();
711             if (intrinsic1 == null && intrinsic2 == null) {
712                 // estimate homography
713                 final var homography = homographyEstimator.estimate();
714 
715                 // estimate intrinsic parameters using the Image of Absolute
716                 // Conic (IAC)
717                 final var homographies = new ArrayList<Transformation2D>();
718                 homographies.add(homography);
719 
720                 final var iacEstimator = new LMSEImageOfAbsoluteConicEstimator(homographies);
721                 final var iac = iacEstimator.estimate();
722 
723                 intrinsic1 = intrinsic2 = iac.getIntrinsicParameters();
724 
725             } else if (intrinsic1 == null) { // && intrinsic2 != null
726                 intrinsic1 = intrinsic2;
727             } else if (intrinsic2 == null) { // && intrinsic1 != null
728                 intrinsic2 = intrinsic1;
729             }
730             fundamentalMatrixEstimator.setLeftIntrinsics(intrinsic1);
731             fundamentalMatrixEstimator.setRightIntrinsics(intrinsic2);
732 
733             fundamentalMatrixEstimator.estimateAndReconstruct();
734 
735             final var fundamentalMatrix = fundamentalMatrixEstimator.getFundamentalMatrix();
736 
737             estimatedFundamentalMatrix = new EstimatedFundamentalMatrix();
738             estimatedFundamentalMatrix.setFundamentalMatrix(fundamentalMatrix);
739             estimatedFundamentalMatrix.setViewId1(viewId1);
740             estimatedFundamentalMatrix.setViewId2(viewId2);
741 
742             // determine quality score and inliers
743             final var inliersData = homographyEstimator.getInliersData();
744             if (inliersData != null) {
745                 final var numInliers = inliersData.getNumInliers();
746                 final var inliers = inliersData.getInliers();
747                 final var length = inliers.length();
748                 var fundamentalMatrixQualityScore = 0.0;
749                 for (i = 0; i < length; i++) {
750                     if (inliers.get(i)) {
751                         // inlier
752                         fundamentalMatrixQualityScore += qualityScores[i] / numInliers;
753                     }
754                 }
755                 estimatedFundamentalMatrix.setQualityScore(fundamentalMatrixQualityScore);
756                 estimatedFundamentalMatrix.setInliers(inliers);
757             }
758 
759             // store left/right samples
760             estimatedFundamentalMatrix.setLeftSamples(leftSamples);
761             estimatedFundamentalMatrix.setRightSamples(rightSamples);
762 
763             return true;
764         } catch (final Exception e) {
765             return false;
766         }
767     }
768 
769     /**
770      * Estimates initial cameras and reconstructed points.
771      *
772      * @return true if cameras and points could be estimated, false if something
773      * failed.
774      */
775     private boolean estimateInitialCamerasAndPoints() {
776         return switch (configuration.getInitialCamerasEstimatorMethod()) {
777             case ESSENTIAL_MATRIX -> estimateInitialCamerasAndPointsEssential();
778             case DUAL_IMAGE_OF_ABSOLUTE_CONIC -> estimateInitialCamerasAndPointsDIAC();
779             case DUAL_ABSOLUTE_QUADRIC -> estimateInitialCamerasAndPointsDAQ();
780             default -> estimateInitialCamerasAndPointsDAQAndEssential();
781         };
782     }
783 
784     /**
785      * Estimates initial cameras and reconstructed points using the Dual
786      * Absolute Quadric to estimate intrinsic parameters and then use those
787      * intrinsic parameters with the essential matrix.
788      *
789      * @return true if cameras and points could be estimated, false if something
790      * failed.
791      */
792     private boolean estimateInitialCamerasAndPointsDAQAndEssential() {
793         try {
794             final var fundamentalMatrix = estimatedFundamentalMatrix.getFundamentalMatrix();
795 
796             final var estimator = new DualAbsoluteQuadricInitialCamerasEstimator(fundamentalMatrix);
797             estimator.setAspectRatio(configuration.getInitialCamerasAspectRatio());
798             estimator.estimate();
799 
800             final var camera1 = estimator.getEstimatedLeftCamera();
801             final var camera2 = estimator.getEstimatedRightCamera();
802 
803             camera1.decompose();
804             camera2.decompose();
805 
806             final var intrinsicZeroPrincipalPoint1 = camera1.getIntrinsicParameters();
807             final var intrinsicZeroPrincipalPoint2 = camera2.getIntrinsicParameters();
808 
809             final var principalPointX = configuration.getPrincipalPointX();
810             final var principalPointY = configuration.getPrincipalPointY();
811 
812             final var intrinsic1 = new PinholeCameraIntrinsicParameters(intrinsicZeroPrincipalPoint1);
813             intrinsic1.setHorizontalPrincipalPoint(intrinsic1.getHorizontalPrincipalPoint() + principalPointX);
814             intrinsic1.setVerticalPrincipalPoint(intrinsic1.getVerticalPrincipalPoint() + principalPointY);
815 
816             final var intrinsic2 = new PinholeCameraIntrinsicParameters(intrinsicZeroPrincipalPoint2);
817             intrinsic2.setHorizontalPrincipalPoint(intrinsic2.getHorizontalPrincipalPoint() + principalPointX);
818             intrinsic2.setVerticalPrincipalPoint(intrinsic2.getVerticalPrincipalPoint() + principalPointY);
819 
820             // fix fundamental matrix to account for principal point different
821             // from zero
822             fixFundamentalMatrix(fundamentalMatrix, intrinsicZeroPrincipalPoint1, intrinsicZeroPrincipalPoint2,
823                     intrinsic1, intrinsic2);
824 
825             return estimateInitialCamerasAndPointsEssential(intrinsic1, intrinsic2);
826         } catch (final Exception e) {
827             return false;
828         }
829     }
830 
831     /**
832      * Estimates initial cameras and reconstructed points using the Dual
833      * Absolute Quadric.
834      *
835      * @return true if cameras and points could be estimated, false if something
836      * failed.
837      */
838     private boolean estimateInitialCamerasAndPointsDAQ() {
839         try {
840             final var fundamentalMatrix = estimatedFundamentalMatrix.getFundamentalMatrix();
841             fundamentalMatrix.normalize();
842 
843             final var estimator = new DualAbsoluteQuadricInitialCamerasEstimator(fundamentalMatrix);
844             estimator.setAspectRatio(configuration.getInitialCamerasAspectRatio());
845             estimator.estimate();
846 
847             final var camera1 = estimator.getEstimatedLeftCamera();
848             final var camera2 = estimator.getEstimatedRightCamera();
849 
850             camera1.decompose();
851             camera2.decompose();
852 
853             final var intrinsicZeroPrincipalPoint1 = camera1.getIntrinsicParameters();
854             final var intrinsicZeroPrincipalPoint2 = camera2.getIntrinsicParameters();
855 
856             final var principalPointX = configuration.getPrincipalPointX();
857             final var principalPointY = configuration.getPrincipalPointY();
858 
859             final var intrinsic1 = new PinholeCameraIntrinsicParameters(intrinsicZeroPrincipalPoint1);
860             intrinsic1.setHorizontalPrincipalPoint(intrinsic1.getHorizontalPrincipalPoint() + principalPointX);
861             intrinsic1.setVerticalPrincipalPoint(intrinsic1.getVerticalPrincipalPoint() + principalPointY);
862             camera1.setIntrinsicParameters(intrinsic1);
863 
864             final var intrinsic2 = new PinholeCameraIntrinsicParameters(intrinsicZeroPrincipalPoint2);
865             intrinsic2.setHorizontalPrincipalPoint(intrinsic2.getHorizontalPrincipalPoint() + principalPointX);
866             intrinsic2.setVerticalPrincipalPoint(intrinsic2.getVerticalPrincipalPoint() + principalPointY);
867             camera2.setIntrinsicParameters(intrinsic2);
868 
869             estimatedCamera1 = new EstimatedCamera();
870             estimatedCamera1.setCamera(camera1);
871 
872             estimatedCamera2 = new EstimatedCamera();
873             estimatedCamera2.setCamera(camera2);
874 
875             // fix fundamental matrix to account for principal point different
876             // from zero
877             fixFundamentalMatrix(fundamentalMatrix, intrinsicZeroPrincipalPoint1, intrinsicZeroPrincipalPoint2,
878                     intrinsic1, intrinsic2);
879 
880             // triangulate points
881             Corrector corrector = null;
882             if (configuration.getInitialCamerasCorrectorType() != null) {
883                 corrector = Corrector.create(fundamentalMatrix, configuration.getInitialCamerasCorrectorType());
884             }
885 
886             // use all points used for fundamental matrix estimation
887             final var samples1 = estimatedFundamentalMatrix.getLeftSamples();
888             final var samples2 = estimatedFundamentalMatrix.getRightSamples();
889 
890             final var points1 = new ArrayList<Point2D>();
891             final var points2 = new ArrayList<Point2D>();
892             final var length = samples1.size();
893             for (var i = 0; i < length; i++) {
894                 final var sample1 = samples1.get(i);
895                 final var sample2 = samples2.get(i);
896 
897                 final var point1 = sample1.getPoint();
898                 final var point2 = sample2.getPoint();
899 
900                 points1.add(point1);
901                 points2.add(point2);
902             }
903 
904             // correct points if needed
905             List<Point2D> correctedPoints1;
906             List<Point2D> correctedPoints2;
907             if (corrector != null) {
908                 corrector.setLeftAndRightPoints(points1, points2);
909                 corrector.correct();
910 
911                 correctedPoints1 = corrector.getLeftCorrectedPoints();
912                 correctedPoints2 = corrector.getRightCorrectedPoints();
913             } else {
914                 correctedPoints1 = points1;
915                 correctedPoints2 = points2;
916             }
917 
918             // triangulate points
919             final SinglePoint3DTriangulator triangulator;
920             if (configuration.getDaqUseHomogeneousPointTriangulator()) {
921                 triangulator = SinglePoint3DTriangulator.create(Point3DTriangulatorType.LMSE_HOMOGENEOUS_TRIANGULATOR);
922             } else {
923                 triangulator = SinglePoint3DTriangulator.create(
924                         Point3DTriangulatorType.LMSE_INHOMOGENEOUS_TRIANGULATOR);
925             }
926 
927             final var cameras = new ArrayList<PinholeCamera>();
928             cameras.add(camera1);
929             cameras.add(camera2);
930 
931             reconstructedPoints = new ArrayList<>();
932             final var points = new ArrayList<Point2D>();
933             final var numPoints = correctedPoints1.size();
934             Point3D triangulatedPoint;
935             ReconstructedPoint3D reconstructedPoint;
936             for (var i = 0; i < numPoints; i++) {
937                 points.clear();
938                 points.add(correctedPoints1.get(i));
939                 points.add(correctedPoints2.get(i));
940 
941                 triangulator.setPointsAndCameras(points, cameras);
942                 triangulatedPoint = triangulator.triangulate();
943 
944                 reconstructedPoint = new ReconstructedPoint3D();
945                 reconstructedPoint.setPoint(triangulatedPoint);
946 
947                 // only points reconstructed in front of both cameras are
948                 // considered valid
949                 final var front1 = camera1.isPointInFrontOfCamera(triangulatedPoint);
950                 final var front2 = camera2.isPointInFrontOfCamera(triangulatedPoint);
951                 reconstructedPoint.setInlier(front1 && front2);
952 
953                 reconstructedPoints.add(reconstructedPoint);
954             }
955 
956             return true;
957         } catch (final Exception e) {
958             return false;
959         }
960     }
961 
962     /**
963      * Estimates initial cameras and reconstructed points using Dual Image of
964      * Absolute Conic.
965      *
966      * @return true if cameras and points could be estimated, false if something
967      * failed.
968      */
969     private boolean estimateInitialCamerasAndPointsDIAC() {
970         final var fundamentalMatrix = estimatedFundamentalMatrix.getFundamentalMatrix();
971 
972         // use inlier points used for fundamental matrix estimation
973         final var samples1 = estimatedFundamentalMatrix.getLeftSamples();
974         final var samples2 = estimatedFundamentalMatrix.getRightSamples();
975 
976         final var points1 = new ArrayList<Point2D>();
977         final var points2 = new ArrayList<Point2D>();
978         final var length = samples1.size();
979         for (var i = 0; i < length; i++) {
980             final var sample1 = samples1.get(i);
981             final var sample2 = samples2.get(i);
982 
983             final var point1 = sample1.getPoint();
984             final var point2 = sample2.getPoint();
985 
986             points1.add(point1);
987             points2.add(point2);
988         }
989 
990         try {
991             final var estimator = new DualImageOfAbsoluteConicInitialCamerasEstimator(fundamentalMatrix, points1,
992                     points2);
993             estimator.setPrincipalPoint(configuration.getPrincipalPointX(), configuration.getPrincipalPointY());
994             estimator.setAspectRatio(configuration.getInitialCamerasAspectRatio());
995             estimator.setCorrectorType(configuration.getInitialCamerasCorrectorType());
996             estimator.setPointsTriangulated(true);
997             estimator.setValidTriangulatedPointsMarked(configuration.getInitialCamerasMarkValidTriangulatedPoints());
998 
999             estimator.estimate();
1000 
1001             // store cameras
1002             final var camera1 = estimator.getEstimatedLeftCamera();
1003             final var camera2 = estimator.getEstimatedRightCamera();
1004 
1005             estimatedCamera1 = new EstimatedCamera();
1006             estimatedCamera1.setCamera(camera1);
1007 
1008             estimatedCamera2 = new EstimatedCamera();
1009             estimatedCamera2.setCamera(camera2);
1010 
1011             // store points
1012             final var triangulatedPoints = estimator.getTriangulatedPoints();
1013             final var validTriangulatedPoints = estimator.getValidTriangulatedPoints();
1014 
1015             reconstructedPoints = new ArrayList<>();
1016             final var size = triangulatedPoints.size();
1017             for (var i = 0; i < size; i++) {
1018                 final var reconstructedPoint = new ReconstructedPoint3D();
1019                 reconstructedPoint.setPoint(triangulatedPoints.get(i));
1020                 reconstructedPoint.setInlier(validTriangulatedPoints.get(i));
1021                 reconstructedPoints.add(reconstructedPoint);
1022             }
1023 
1024             return true;
1025         } catch (final Exception e) {
1026             return false;
1027         }
1028     }
1029 
1030     /**
1031      * Estimates initial cameras and reconstructed points using the essential
1032      * matrix and provided intrinsic parameters that must have been set during
1033      * offline calibration.
1034      *
1035      * @return true if cameras and points could be estimated, false if something
1036      * failed.
1037      */
1038     private boolean estimateInitialCamerasAndPointsEssential() {
1039         final var intrinsic1 = configuration.getInitialIntrinsic1();
1040         final var intrinsic2 = configuration.getInitialIntrinsic2();
1041         return estimateInitialCamerasAndPointsEssential(intrinsic1, intrinsic2);
1042     }
1043 
1044     /**
1045      * Estimates initial cameras and reconstructed points using the essential
1046      * matrix and provided intrinsic parameters that must have been set during
1047      * offline calibration.
1048      *
1049      * @param intrinsic1 intrinsic parameters of 1st camera.
1050      * @param intrinsic2 intrinsic parameters of 2nd camera.
1051      * @return true if cameras and points could be estimated, false if something
1052      * failed.
1053      */
1054     private boolean estimateInitialCamerasAndPointsEssential(
1055             final PinholeCameraIntrinsicParameters intrinsic1,
1056             final PinholeCameraIntrinsicParameters intrinsic2) {
1057         final var fundamentalMatrix = estimatedFundamentalMatrix.getFundamentalMatrix();
1058 
1059         // use all points used for fundamental matrix estimation
1060         final var samples1 = estimatedFundamentalMatrix.getLeftSamples();
1061         final var samples2 = estimatedFundamentalMatrix.getRightSamples();
1062 
1063         final var points1 = new ArrayList<Point2D>();
1064         final var points2 = new ArrayList<Point2D>();
1065         final var length = samples1.size();
1066         for (var i = 0; i < length; i++) {
1067             final var sample1 = samples1.get(i);
1068             final var sample2 = samples2.get(i);
1069 
1070             final var point1 = sample1.getPoint();
1071             final var point2 = sample2.getPoint();
1072 
1073             points1.add(point1);
1074             points2.add(point2);
1075         }
1076 
1077         try {
1078             final var estimator = new EssentialMatrixInitialCamerasEstimator(fundamentalMatrix, intrinsic1, intrinsic2,
1079                     points1, points2);
1080 
1081             estimator.setCorrectorType(configuration.getInitialCamerasCorrectorType());
1082             estimator.setPointsTriangulated(true);
1083             estimator.setValidTriangulatedPointsMarked(configuration.getInitialCamerasMarkValidTriangulatedPoints());
1084 
1085             estimator.estimate();
1086 
1087             // store cameras
1088             final var camera1 = estimator.getEstimatedLeftCamera();
1089             final var camera2 = estimator.getEstimatedRightCamera();
1090 
1091             estimatedCamera1 = new EstimatedCamera();
1092             estimatedCamera1.setCamera(camera1);
1093 
1094             estimatedCamera2 = new EstimatedCamera();
1095             estimatedCamera2.setCamera(camera2);
1096 
1097             // store points
1098             final var triangulatedPoints = estimator.getTriangulatedPoints();
1099             final var validTriangulatedPoints = estimator.getValidTriangulatedPoints();
1100 
1101             reconstructedPoints = new ArrayList<>();
1102             final var size = triangulatedPoints.size();
1103             for (var i = 0; i < size; i++) {
1104                 final var reconstructedPoint = new ReconstructedPoint3D();
1105                 reconstructedPoint.setPoint(triangulatedPoints.get(i));
1106                 reconstructedPoint.setInlier(validTriangulatedPoints.get(i));
1107                 reconstructedPoints.add(reconstructedPoint);
1108             }
1109 
1110             return true;
1111         } catch (final Exception e) {
1112             return false;
1113         }
1114     }
1115 
1116     /**
1117      * Fixes fundamental matrix to account for principal point different from
1118      * zero when using DAQ estimation.
1119      *
1120      * @param fundamentalMatrix            fundamental matrix to be fixed.
1121      * @param intrinsicZeroPrincipalPoint1 intrinsic parameters of camera 1
1122      *                                     assuming zero principal point.
1123      * @param intrinsicZeroPrincipalPoint2 intrinsic parameters of camera 2
1124      *                                     assuming zero principal point.
1125      * @param intrinsicPrincipalPoint1     intrinsic parameters of camera 1 using
1126      *                                     proper principal point.
1127      * @param intrinsicPrincipalPoint2     intrinsic parameters of camera 2 using
1128      *                                     proper principal point.
1129      * @throws EpipolarException if something fails.
1130      * @throws NotReadyException never happens.
1131      */
1132     private void fixFundamentalMatrix(
1133             final FundamentalMatrix fundamentalMatrix,
1134             final PinholeCameraIntrinsicParameters intrinsicZeroPrincipalPoint1,
1135             final PinholeCameraIntrinsicParameters intrinsicZeroPrincipalPoint2,
1136             final PinholeCameraIntrinsicParameters intrinsicPrincipalPoint1,
1137             final PinholeCameraIntrinsicParameters intrinsicPrincipalPoint2)
1138             throws EpipolarException, NotReadyException {
1139 
1140         // first compute essential matrix as E = K2a'F*K1a
1141         final var essential = new EssentialMatrix(fundamentalMatrix, intrinsicZeroPrincipalPoint1,
1142                 intrinsicZeroPrincipalPoint2);
1143         final var fixedFundamentalMatrix = essential.toFundamentalMatrix(intrinsicPrincipalPoint1,
1144                 intrinsicPrincipalPoint2);
1145         fixedFundamentalMatrix.normalize();
1146         estimatedFundamentalMatrix.setFundamentalMatrix(fixedFundamentalMatrix);
1147         estimatedFundamentalMatrix.setCovariance(null);
1148     }
1149 }