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.ar.epipolar.Corrector;
20  import com.irurueta.ar.epipolar.CorrectorType;
21  import com.irurueta.ar.epipolar.FundamentalMatrix;
22  import com.irurueta.ar.epipolar.estimators.FundamentalMatrixEstimatorException;
23  import com.irurueta.ar.epipolar.estimators.PlanarFundamentalMatrixEstimator;
24  import com.irurueta.geometry.PinholeCamera;
25  import com.irurueta.geometry.PinholeCameraIntrinsicParameters;
26  import com.irurueta.geometry.Point2D;
27  import com.irurueta.geometry.Point3D;
28  import com.irurueta.geometry.ProjectiveTransformation2D;
29  import com.irurueta.geometry.estimators.LockedException;
30  import com.irurueta.geometry.estimators.NotReadyException;
31  import com.irurueta.geometry.estimators.PROMedSPointCorrespondenceProjectiveTransformation2DRobustEstimator;
32  import com.irurueta.geometry.estimators.PROSACPointCorrespondenceProjectiveTransformation2DRobustEstimator;
33  import com.irurueta.geometry.estimators.PointCorrespondenceProjectiveTransformation2DRobustEstimator;
34  import com.irurueta.geometry.estimators.RANSACPointCorrespondenceProjectiveTransformation2DRobustEstimator;
35  import com.irurueta.numerical.robust.RobustEstimatorException;
36  import com.irurueta.numerical.robust.RobustEstimatorMethod;
37  
38  import java.util.ArrayList;
39  import java.util.Arrays;
40  import java.util.BitSet;
41  import java.util.List;
42  
43  /**
44   * This class takes matched pairs of 2D points corresponding to a planar scene,
45   * estimates an homography relating both sets of points, decomposes such
46   * homography induced by the 3D plane on the scene, and uses such decomposition
47   * to determine the best epipolar geometry (e.g. fundamental matrix) by using
48   * the essential matrix and provided intrinsic camera parameters on both views
49   * corresponding to both sets of points to reconstruct points and choose the
50   * solution that produces the largest amount of points located in front of both
51   * cameras.
52   * This class requires 2 sets of matched 2D points and the intrinsic parameters
53   * of the cameras in both views, hence cameras must be calibrated in some way
54   * before using this class.
55   * This class is similar to PlanarFundamentalMatrixEstimator but picks the best
56   * solution by reconstructing the 3D points in the scene and choosing the
57   * solution that produces the largest amount of points located in front of both
58   * cameras.
59   */
60  public class PlanarBestFundamentalMatrixEstimatorAndReconstructor {
61  
62      /**
63       * Minimum number of matched points required to find a solution.
64       */
65      public static final int MINIMUM_SIZE = 4;
66  
67      /**
68       * List of matched 2D points in the left view.
69       */
70      private List<Point2D> leftPoints;
71  
72      /**
73       * List of matched 2D points in the right view.
74       */
75      private List<Point2D> rightPoints;
76  
77      /**
78       * Intrinsic parameters for the camera on the left view.
79       */
80      private PinholeCameraIntrinsicParameters leftIntrinsics;
81  
82      /**
83       * Intrinsic parameters for the camera on the right view.
84       */
85      private PinholeCameraIntrinsicParameters rightIntrinsics;
86  
87      /**
88       * Listener to attend events generated by this instance.
89       */
90      private PlanarBestFundamentalMatrixEstimatorAndReconstructorListener listener;
91  
92      /**
93       * Indicates whether this instance is locked while computing a solution.
94       */
95      private boolean locked;
96  
97      /**
98       * Homography estimator relating both views.
99       */
100     private PointCorrespondenceProjectiveTransformation2DRobustEstimator homographyEstimator;
101 
102     /**
103      * Type of corrector to use to triangulate matched points using the
104      * corresponding essential matrix or null if no corrector needs to be used.
105      */
106     private CorrectorType essentialCameraEstimatorCorrectorType = Corrector.DEFAULT_TYPE;
107 
108     /**
109      * Estimated homography.
110      */
111     private ProjectiveTransformation2D homography;
112 
113     /**
114      * Best estimated fundamental matrix.
115      */
116     private FundamentalMatrix fundamentalMatrix;
117 
118     /**
119      * Best estimated triangulated points.
120      */
121     private List<Point3D> triangulatedPoints;
122 
123     /**
124      * Contains booleans indicating which of the best triangulated points are
125      * valid (i.e. lie in front of both estimated cameras) or not.
126      */
127     private BitSet validTriangulatedPoints;
128 
129     /**
130      * Best estimated camera for left view.
131      */
132     private PinholeCamera estimatedLeftCamera;
133 
134     /**
135      * Best estimated camera for right view.
136      */
137     private PinholeCamera estimatedRightCamera;
138 
139     /**
140      * Constructor.
141      */
142     public PlanarBestFundamentalMatrixEstimatorAndReconstructor() {
143         homographyEstimator = PointCorrespondenceProjectiveTransformation2DRobustEstimator.create();
144     }
145 
146     /**
147      * Constructor.
148      *
149      * @param leftPoints      list of matched 2D points in the left view.
150      * @param rightPoints     list of matched 2D points in the right view.
151      * @param leftIntrinsics  intrinsic parameters for the camera on the left view.
152      * @param rightIntrinsics intrinsic parameters for the camera on the right view.
153      * @throws IllegalArgumentException if provided list of matched points do
154      *                                  not contain enough points or if they have different sizes.
155      */
156     public PlanarBestFundamentalMatrixEstimatorAndReconstructor(
157             final List<Point2D> leftPoints, final List<Point2D> rightPoints,
158             final PinholeCameraIntrinsicParameters leftIntrinsics,
159             final PinholeCameraIntrinsicParameters rightIntrinsics) {
160         this();
161         internalSetLeftAndRightPoints(leftPoints, rightPoints);
162         this.leftIntrinsics = leftIntrinsics;
163         this.rightIntrinsics = rightIntrinsics;
164     }
165 
166     /**
167      * Constructor.
168      *
169      * @param leftPoints      list of matched 2D points in the left view.
170      * @param rightPoints     list of matched 2D points in the right view.
171      * @param leftIntrinsics  intrinsic parameters for the camera on the left view.
172      * @param rightIntrinsics intrinsic parameters for the camera on the right view.
173      * @param listener        listener to be notified of events generated by this instance.
174      * @throws IllegalArgumentException if provided list of matched points do
175      *                                  not contain enough points or if they have different sizes.
176      */
177     public PlanarBestFundamentalMatrixEstimatorAndReconstructor(
178             final List<Point2D> leftPoints, final List<Point2D> rightPoints,
179             final PinholeCameraIntrinsicParameters leftIntrinsics,
180             final PinholeCameraIntrinsicParameters rightIntrinsics,
181             final PlanarBestFundamentalMatrixEstimatorAndReconstructorListener listener) {
182         this(leftPoints, rightPoints, leftIntrinsics, rightIntrinsics);
183         this.listener = listener;
184     }
185 
186     /**
187      * Gets list of matched 2D points in the left view.
188      *
189      * @return list of matched 2D points in the left view.
190      */
191     public List<Point2D> getLeftPoints() {
192         return leftPoints;
193     }
194 
195     /**
196      * Sets list of matched 2D points in the left view.
197      *
198      * @param leftPoints list of matched 2D points in the left view.
199      * @throws LockedException          if this instance is locked.
200      * @throws IllegalArgumentException if provided points do not have enough
201      *                                  points.
202      */
203     public void setLeftPoints(final List<Point2D> leftPoints) throws LockedException {
204         if (isLocked()) {
205             throw new LockedException();
206         }
207         if (leftPoints.size() < MINIMUM_SIZE) {
208             throw new IllegalArgumentException();
209         }
210 
211         this.leftPoints = leftPoints;
212     }
213 
214     /**
215      * Gets list of matched 2D points in the right view.
216      *
217      * @return list of matched 2D points in the right view.
218      */
219     public List<Point2D> getRightPoints() {
220         return rightPoints;
221     }
222 
223     /**
224      * Sets list of matched 2D points in the right view.
225      *
226      * @param rightPoints list of matched 2D points in the right view.
227      * @throws LockedException          if this instance is locked.
228      * @throws IllegalArgumentException if provided points do not have enough
229      *                                  points.
230      */
231     public void setRightPoints(final List<Point2D> rightPoints) throws LockedException {
232         if (isLocked()) {
233             throw new LockedException();
234         }
235         if (rightPoints.size() < MINIMUM_SIZE) {
236             throw new IllegalArgumentException();
237         }
238 
239         this.rightPoints = rightPoints;
240     }
241 
242     /**
243      * Sets lists of matched 2D points in the left and right views.
244      *
245      * @param leftPoints  list of matched 2D points in the left view.
246      * @param rightPoints list of matched 2D points in the right view.
247      * @throws LockedException          if this instance is locked.
248      * @throws IllegalArgumentException if provided points do not have enough
249      *                                  points or lists have different sizes.
250      */
251     public void setLeftAndRightPoints(final List<Point2D> leftPoints, final List<Point2D> rightPoints)
252             throws LockedException {
253         if (isLocked()) {
254             throw new LockedException();
255         }
256 
257         internalSetLeftAndRightPoints(leftPoints, rightPoints);
258     }
259 
260     /**
261      * Gets intrinsic parameters for the camera on the left view.
262      *
263      * @return intrinsic parameters for the camera on the left view.
264      */
265     public PinholeCameraIntrinsicParameters getLeftIntrinsics() {
266         return leftIntrinsics;
267     }
268 
269     /**
270      * Sets intrinsic parameters for the camera on the left view.
271      *
272      * @param leftIntrinsics intrinsic parameters for the camera on the left
273      *                       view.
274      * @throws LockedException if estimator is locked.
275      */
276     public void setLeftIntrinsics(final PinholeCameraIntrinsicParameters leftIntrinsics) throws LockedException {
277         if (isLocked()) {
278             throw new LockedException();
279         }
280 
281         this.leftIntrinsics = leftIntrinsics;
282     }
283 
284     /**
285      * Gets intrinsic parameters for the camera on the right view.
286      *
287      * @return intrinsic parameters for the camera on the right view.
288      */
289     public PinholeCameraIntrinsicParameters getRightIntrinsics() {
290         return rightIntrinsics;
291     }
292 
293     /**
294      * Sets intrinsic parameters for the camera on the right view.
295      *
296      * @param rightIntrinsics intrinsic parameters for the camera on the right
297      *                        view.
298      * @throws LockedException if estimator is locked.
299      */
300     public void setRightIntrinsics(final PinholeCameraIntrinsicParameters rightIntrinsics) throws LockedException {
301         if (isLocked()) {
302             throw new LockedException();
303         }
304 
305         this.rightIntrinsics = rightIntrinsics;
306     }
307 
308     /**
309      * Gets internal homography estimator.
310      *
311      * @return internal homography estimator.
312      */
313     public PointCorrespondenceProjectiveTransformation2DRobustEstimator getHomographyEstimator() {
314         return homographyEstimator;
315     }
316 
317     /**
318      * Sets internal homography estimator.
319      *
320      * @param homographyEstimator internal homography estimator.
321      * @throws LockedException      if estimator is locked.
322      * @throws NullPointerException if provided estimator is null.
323      */
324     public void setHomographyEstimator(
325             final PointCorrespondenceProjectiveTransformation2DRobustEstimator homographyEstimator)
326             throws LockedException {
327         if (isLocked()) {
328             throw new LockedException();
329         }
330         if (homographyEstimator == null) {
331             throw new NullPointerException();
332         }
333 
334         this.homographyEstimator = homographyEstimator;
335     }
336 
337     /**
338      * Gets type of corrector to use to triangulate matched points using the
339      * corresponding essential matrix or null if no corrector needs to be used.
340      *
341      * @return corrector to use for triangulation or null.
342      */
343     public CorrectorType getEssentialCameraEstimatorCorrectorType() {
344         return essentialCameraEstimatorCorrectorType;
345     }
346 
347     /**
348      * Sets type of corrector to use to triangulate matched points using the
349      * corresponding essential matrix or null if no corrector needs to be used.
350      *
351      * @param correctorType corrector to use for triangulation or null.
352      * @throws LockedException if estimator is locked.
353      */
354     public void setEssentialCameraEstimatorCorrectorType(final CorrectorType correctorType) throws LockedException {
355         if (isLocked()) {
356             throw new LockedException();
357         }
358 
359         essentialCameraEstimatorCorrectorType = correctorType;
360     }
361 
362     /**
363      * Gets amount of confidence on homography estimation expressed as a value
364      * between 0.0 and 1.0 (which is equivalent to 100%). The amount of
365      * confidence indicates the probability that the estimated result is
366      * correct. Usually this value will be close to 1.0, but not exactly 1.0.
367      *
368      * @return amount of confidence as a value between 0.0 and 1.0.
369      */
370     public double getHomographyConfidence() {
371         return homographyEstimator.getConfidence();
372     }
373 
374     /**
375      * Sets amount of confidence on homography estimation expressed as a value
376      * between 0.0 and 1.0 (which is equivalent to 100%). The amount of
377      * confidence indicates the probability that the estimated result is
378      * correct. Usually this value will be close to 1.0, but not exactly 1.0.
379      *
380      * @param confidence confidence to be set as a value between 0.0 and 1.0.
381      * @throws IllegalArgumentException if provided value is not between 0.0 and
382      *                                  1.0.
383      * @throws LockedException          if estimator is locked.
384      */
385     public void setHomographyConfidence(final double confidence) throws LockedException {
386         if (isLocked()) {
387             throw new LockedException();
388         }
389 
390         homographyEstimator.setConfidence(confidence);
391     }
392 
393     /**
394      * Returns maximum allowed number of iterations for homography estimation.
395      *
396      * @return maximum allowed number of iterations.
397      */
398     public int getHomographyMaxIterations() {
399         return homographyEstimator.getMaxIterations();
400     }
401 
402     /**
403      * Sets maximum allowed number of iterations for homography estimation.
404      *
405      * @param maxIterations maximum allowed number of iterations.
406      * @throws IllegalArgumentException if provided value is less than 1.
407      * @throws LockedException          if estimator is locked.
408      */
409     public void setHomographyMaxIterations(final int maxIterations) throws LockedException {
410         if (isLocked()) {
411             throw new LockedException();
412         }
413 
414         homographyEstimator.setMaxIterations(maxIterations);
415     }
416 
417     /**
418      * Indicates whether result of homography estimation is refined.
419      *
420      * @return true to refine homography result, false to simply use result
421      * found by robust estimation without further refining.
422      */
423     public boolean isHomographyRefined() {
424         return homographyEstimator.isResultRefined();
425     }
426 
427     /**
428      * Specifies whether homography estimation must be refined or not.
429      *
430      * @param refineResult true to refine homography result, false to simply use
431      *                     result found by robust estimator without further refining.
432      * @throws LockedException if estimator is locked.
433      */
434     public void setHomographyRefined(final boolean refineResult) throws LockedException {
435         if (isLocked()) {
436             throw new LockedException();
437         }
438 
439         homographyEstimator.setResultRefined(refineResult);
440     }
441 
442     /**
443      * Indicates whether homography covariance must be kept after estimation.
444      * This setting is only taken into account if homography is refined.
445      *
446      * @return true if homography covariance must be kept after estimation,
447      * false otherwise.
448      */
449     public boolean isHomographyCovarianceKept() {
450         return homographyEstimator.isCovarianceKept();
451     }
452 
453     /**
454      * Specifies whether homography covariance must be kept after estimation.
455      * This setting is only taken into account if homography is refined.
456      *
457      * @param keepCovariance true if homography covariance must be kept after
458      *                       estimation, false otherwise.
459      * @throws LockedException if estimator is locked.
460      */
461     public void setHomographyCovarianceKept(final boolean keepCovariance) throws LockedException {
462         if (isLocked()) {
463             throw new LockedException();
464         }
465 
466         homographyEstimator.setCovarianceKept(keepCovariance);
467     }
468 
469     /**
470      * Gets covariance for estimated homography if available.
471      * This is only available when homography has been refined and covariance is
472      * kept.
473      *
474      * @return estimated homography covariance or null.
475      */
476     public Matrix getHomographyCovariance() {
477         return homographyEstimator.getCovariance();
478     }
479 
480     /**
481      * Returns method being used for homography robust estimation.
482      *
483      * @return method being used for homography robust estimation.
484      */
485     public RobustEstimatorMethod getHomographyMethod() {
486         return homographyEstimator.getMethod();
487     }
488 
489     /**
490      * Returns quality scores corresponding to each pair of matched points.
491      * This is used for homography estimation.
492      * The larger the score value the better the quality of the matching.
493      *
494      * @return quality scores for each pair of matched points.
495      */
496     public double[] getQualityScores() {
497         return homographyEstimator.getQualityScores();
498     }
499 
500     /**
501      * Sets quality scores corresponding to each pair of matched points.
502      * This is used for homography estimation.
503      * The larger the score value the better the quality of the matching.
504      *
505      * @param qualityScore quality scores corresponding to eac pair of matched
506      *                     points.
507      * @throws LockedException          if estimator is locked.
508      * @throws IllegalArgumentException if provided quality scores length is
509      *                                  smaller than minimum required size (4 points).
510      */
511     public void setQualityScores(final double[] qualityScore) throws LockedException {
512         if (isLocked()) {
513             throw new LockedException();
514         }
515 
516         homographyEstimator.setQualityScores(qualityScore);
517     }
518 
519     /**
520      * Gets listener to attend events generated by this instance.
521      *
522      * @return listener to attend events generated by this instance.
523      */
524     public PlanarBestFundamentalMatrixEstimatorAndReconstructorListener getListener() {
525         return listener;
526     }
527 
528     /**
529      * Sets listener to attend events generated by this instance.
530      *
531      * @param listener listener to attend events generated by this instance.
532      */
533     public void setListener(final PlanarBestFundamentalMatrixEstimatorAndReconstructorListener listener) {
534         this.listener = listener;
535     }
536 
537     /**
538      * Indicates whether this instance is locked while computing a solution.
539      *
540      * @return true if this instance is locked, false otherwise.
541      */
542     public boolean isLocked() {
543         return locked;
544     }
545 
546     /**
547      * Indicates whether this instance is ready to start the estimation when all
548      * required data has been provided.
549      *
550      * @return true if this instance is ready, false otherwise.
551      */
552     public boolean isReady() {
553         return leftPoints != null && leftPoints.size() >= MINIMUM_SIZE && rightPoints != null
554                 && rightPoints.size() >= MINIMUM_SIZE && leftPoints.size() == rightPoints.size()
555                 && leftIntrinsics != null && rightIntrinsics != null && homographyEstimator.isReady();
556     }
557 
558     /**
559      * Gets estimated homography.
560      *
561      * @return estimated homography.
562      */
563     public ProjectiveTransformation2D getHomography() {
564         return homography;
565     }
566 
567     /**
568      * Gets best estimated fundamental matrix.
569      *
570      * @return best estimated fundamental matrix.
571      */
572     public FundamentalMatrix getFundamentalMatrix() {
573         return fundamentalMatrix;
574     }
575 
576     /**
577      * Gets best estimated triangulated points.
578      *
579      * @return best estimated triangulated points.
580      */
581     public List<Point3D> getTriangulatedPoints() {
582         return triangulatedPoints;
583     }
584 
585     /**
586      * Gets set containing booleans indicating which of the best triangulated
587      * points are valid (i.e. lie in front of both estimated cameras) or not.
588      *
589      * @return set indicating which of the best triangulated points are valid.
590      */
591     public BitSet getValidTriangulatedPoints() {
592         return validTriangulatedPoints;
593     }
594 
595     /**
596      * Gets best estimated camera for left view.
597      *
598      * @return best estimated camera for left view.
599      */
600     public PinholeCamera getEstimatedLeftCamera() {
601         return estimatedLeftCamera;
602     }
603 
604     /**
605      * Gets best estimated camera for right view.
606      *
607      * @return best estimated camera for right view.
608      */
609     public PinholeCamera getEstimatedRightCamera() {
610         return estimatedRightCamera;
611     }
612 
613     /**
614      * Estimates homography, the best fundamental matrix, their cameras and
615      * reconstructs matched points.
616      *
617      * @throws LockedException                     if estimator is locked.
618      * @throws NotReadyException                   if estimator is not ready because required data
619      *                                             is missing.
620      * @throws FundamentalMatrixEstimatorException if something fails, typically
621      *                                             due to numerical instabilities.
622      */
623     public void estimateAndReconstruct() throws LockedException, NotReadyException,
624             FundamentalMatrixEstimatorException {
625         if (isLocked()) {
626             throw new LockedException();
627         }
628         if (!isReady()) {
629             throw new NotReadyException();
630         }
631 
632         // always enable inlier estimation for homography
633         enableHomographyInliersEstimation();
634 
635         try {
636             locked = true;
637 
638             if (listener != null) {
639                 listener.onEstimateStart(this);
640             }
641 
642             // estimate homography
643             homography = homographyEstimator.estimate();
644 
645             final var homographyInliers = homographyEstimator.getInliersData();
646 
647             // estimate all fundamental matrices for homography
648             final var fundamentalMatrixEstimator = new PlanarFundamentalMatrixEstimator(homography, leftIntrinsics,
649                     rightIntrinsics);
650 
651             final var fundamentalMatrices = fundamentalMatrixEstimator.estimate();
652 
653             if (fundamentalMatrices == null) {
654                 throw new FundamentalMatrixEstimatorException();
655             }
656 
657             // select homography inlier points
658             final var lPoints = new ArrayList<Point2D>();
659             final var rPoints = new ArrayList<Point2D>();
660             final var bitset = homographyInliers.getInliers();
661             final var bitsetLength = bitset.length();
662             for (var i = 0; i < bitsetLength; i++) {
663                 if (bitset.get(i)) {
664                     // is inlier
665                     lPoints.add(this.leftPoints.get(i));
666                     rPoints.add(this.rightPoints.get(i));
667                 }
668             }
669 
670             // pick best fundamental matrix
671             final var essentialCamerasEstimator = new EssentialMatrixInitialCamerasEstimator(leftIntrinsics,
672                     rightIntrinsics, lPoints, rPoints);
673             essentialCamerasEstimator.setCorrectorType(essentialCameraEstimatorCorrectorType);
674             essentialCamerasEstimator.setPointsTriangulated(true);
675             essentialCamerasEstimator.setValidTriangulatedPointsMarked(true);
676 
677             var numBest = 0;
678             for (final var fMatrix : fundamentalMatrices) {
679                 essentialCamerasEstimator.setFundamentalMatrix(fMatrix);
680 
681                 essentialCamerasEstimator.estimate();
682 
683                 final var validPoints = essentialCamerasEstimator.getValidTriangulatedPoints();
684                 final var num = validPoints.cardinality();
685                 if (num > numBest) {
686                     numBest = num;
687                     this.fundamentalMatrix = fMatrix;
688                     triangulatedPoints = essentialCamerasEstimator.getTriangulatedPoints();
689                     validTriangulatedPoints = validPoints;
690                     estimatedLeftCamera = essentialCamerasEstimator.getEstimatedLeftCamera();
691                     estimatedRightCamera = essentialCamerasEstimator.getEstimatedRightCamera();
692                 }
693             }
694 
695             if (listener != null) {
696                 listener.onEstimateEnd(this);
697             }
698 
699         } catch (final RobustEstimatorException | InitialCamerasEstimationFailedException e) {
700             throw new FundamentalMatrixEstimatorException(e);
701         } finally {
702             locked = false;
703         }
704     }
705 
706     /**
707      * Internal method that sets list of matched 2D points in the left and right
708      * views.
709      *
710      * @param leftPoints  list of matched 2D points in the left view.
711      * @param rightPoints list of matched 2D points in the right view.
712      * @throws IllegalArgumentException if provided points do not have enough
713      *                                  points or lists have different sizes.
714      */
715     private void internalSetLeftAndRightPoints(final List<Point2D> leftPoints, final List<Point2D> rightPoints) {
716         if (leftPoints.size() < MINIMUM_SIZE || rightPoints.size() < MINIMUM_SIZE
717                 || leftPoints.size() != rightPoints.size()) {
718             throw new IllegalArgumentException();
719         }
720 
721         this.leftPoints = leftPoints;
722         this.rightPoints = rightPoints;
723         try {
724             homographyEstimator.setPoints(leftPoints, rightPoints);
725 
726             if (homographyEstimator.getMethod() == RobustEstimatorMethod.PROMEDS) {
727                 final var promedsEstimator =
728                         (PROMedSPointCorrespondenceProjectiveTransformation2DRobustEstimator) homographyEstimator;
729                 if (promedsEstimator.getQualityScores() == null) {
730                     final var qualityScores = new double[leftPoints.size()];
731                     Arrays.fill(qualityScores, 1.0);
732                     promedsEstimator.setQualityScores(qualityScores);
733                 }
734             } else if (homographyEstimator.getMethod() == RobustEstimatorMethod.PROSAC) {
735                 final var prosacEstimator =
736                         (PROSACPointCorrespondenceProjectiveTransformation2DRobustEstimator) homographyEstimator;
737                 if (prosacEstimator.getQualityScores() == null) {
738                     final var qualityScores = new double[leftPoints.size()];
739                     Arrays.fill(qualityScores, 1.0);
740                     prosacEstimator.setQualityScores(qualityScores);
741                 }
742             }
743         } catch (final LockedException ignore) {
744             // never happens
745         }
746     }
747 
748     /**
749      * Ensures that inlier estimation is enabled on homography estimator.
750      */
751     private void enableHomographyInliersEstimation() {
752         try {
753             if (homographyEstimator.getMethod() == RobustEstimatorMethod.RANSAC) {
754                 final var ransacHomographyEstimator =
755                         (RANSACPointCorrespondenceProjectiveTransformation2DRobustEstimator) homographyEstimator;
756                 ransacHomographyEstimator.setComputeAndKeepInliersEnabled(true);
757                 ransacHomographyEstimator.setComputeAndKeepResidualsEnabled(true);
758             } else if (homographyEstimator.getMethod() == RobustEstimatorMethod.PROSAC) {
759                 final var prosacHomographyEstimator =
760                         (PROSACPointCorrespondenceProjectiveTransformation2DRobustEstimator) homographyEstimator;
761                 prosacHomographyEstimator.setComputeAndKeepInliersEnabled(true);
762                 prosacHomographyEstimator.setComputeAndKeepResidualsEnabled(true);
763             }
764         } catch (final LockedException ignore) {
765             // never happens
766         }
767     }
768 }