View Javadoc
1   /*
2    * Copyright (C) 2015 Alberto Irurueta Carro (alberto@irurueta.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.irurueta.ar.epipolar.estimators;
17  
18  import com.irurueta.algebra.Matrix;
19  import com.irurueta.ar.epipolar.FundamentalMatrix;
20  import com.irurueta.ar.epipolar.refiners.FundamentalMatrixRefiner;
21  import com.irurueta.geometry.GeometryException;
22  import com.irurueta.geometry.Line2D;
23  import com.irurueta.geometry.Point2D;
24  import com.irurueta.geometry.estimators.LockedException;
25  import com.irurueta.geometry.estimators.NotReadyException;
26  import com.irurueta.numerical.robust.InliersData;
27  import com.irurueta.numerical.robust.RobustEstimatorException;
28  import com.irurueta.numerical.robust.RobustEstimatorMethod;
29  
30  import java.util.List;
31  
32  /**
33   * This is an abstract class for algorithms to robustly find the best
34   * Fundamental matrix for provided collections of matched 2D points.
35   * Implementations of this class should be able to detect and discard outliers
36   * in order to find the best solution.
37   */
38  public abstract class FundamentalMatrixRobustEstimator {
39  
40      /**
41       * Default robust estimator method when none is provided.
42       */
43      public static final RobustEstimatorMethod DEFAULT_ROBUST_METHOD = RobustEstimatorMethod.PROSAC;
44  
45      /**
46       * Default non-robust method to estimate a fundamental matrix.
47       */
48      public static final FundamentalMatrixEstimatorMethod DEFAULT_FUNDAMENTAL_MATRIX_ESTIMATOR_METHOD =
49              FundamentalMatrixEstimatorMethod.SEVEN_POINTS_ALGORITHM;
50  
51      /**
52       * Indicates that result is refined by default using Levenberg-Marquardt
53       * fitting algorithm over found inliers.
54       */
55      public static final boolean DEFAULT_REFINE_RESULT = true;
56  
57      /**
58       * Indicates that covariance is not kept by default after refining result.
59       */
60      public static final boolean DEFAULT_KEEP_COVARIANCE = false;
61  
62      /**
63       * Default amount of progress variation before notifying a change in
64       * estimation progress. By default, this is set to 5%.
65       */
66      public static final float DEFAULT_PROGRESS_DELTA = 0.05f;
67  
68      /**
69       * Minimum allowed value for progress delta.
70       */
71      public static final float MIN_PROGRESS_DELTA = 0.0f;
72  
73      /**
74       * Maximum allowed value for progress delta.
75       */
76      public static final float MAX_PROGRESS_DELTA = 1.0f;
77  
78      /**
79       * Constant defining default confidence of the estimated result, which is
80       * 99%. This means that with a probability of 99% estimation will be
81       * accurate because chosen sub-samples will be inliers.
82       */
83      public static final double DEFAULT_CONFIDENCE = 0.99;
84  
85      /**
86       * Default maximum allowed number of iterations.
87       */
88      public static final int DEFAULT_MAX_ITERATIONS = 5000;
89  
90      /**
91       * Minimum allowed confidence value.
92       */
93      public static final double MIN_CONFIDENCE = 0.0;
94  
95      /**
96       * Maximum allowed confidence value.
97       */
98      public static final double MAX_CONFIDENCE = 1.0;
99  
100     /**
101      * Minimum allowed number of iterations.
102      */
103     public static final int MIN_ITERATIONS = 1;
104 
105     /**
106      * List of 2D points corresponding to left view.
107      */
108     protected List<Point2D> leftPoints;
109 
110     /**
111      * List of 2D points corresponding to right view.
112      */
113     protected List<Point2D> rightPoints;
114 
115     /**
116      * Listener to be notified of events such as when estimation starts, ends or
117      * its progress significantly changes.
118      */
119     protected FundamentalMatrixRobustEstimatorListener listener;
120 
121     /**
122      * Indicates if this estimator is locked because an estimation is being
123      * computed.
124      */
125     protected boolean locked;
126 
127     /**
128      * Amount of progress variation before notifying a progress change during
129      * estimation.
130      */
131     protected float progressDelta;
132 
133     /**
134      * Amount of confidence expressed as a value between 0.0 and 1.0 (which is
135      * equivalent to 100%). The amount of confidence indicates the probability
136      * that the estimated result is correct. Usually this value will be close
137      * to 1.0, but not exactly 1.0.
138      */
139     protected double confidence;
140 
141     /**
142      * Maximum allowed number of iterations. When the maximum number of
143      * iterations is exceeded, result will not be available, however an
144      * approximate result will be available for retrieval.
145      */
146     protected int maxIterations;
147 
148     /**
149      * Data related to inliers found after estimation.
150      */
151     protected InliersData inliersData;
152 
153     /**
154      * Indicates whether result must be refined using Levenberg-Marquardt
155      * fitting algorithm over found inliers.
156      * If true, inliers will be computed and kept in any implementation
157      * regardless of the settings.
158      */
159     protected boolean refineResult;
160 
161     /**
162      * Indicates whether covariance must be kept after refining result.
163      * This setting is only taken into account if result is refined.
164      */
165     private boolean keepCovariance;
166 
167     /**
168      * Estimated covariance of estimated fundamental matrix.
169      * This is only available when result has been refined and covariance is
170      * kept.
171      */
172     private Matrix covariance;
173 
174     /**
175      * Test line to compute epipolar residuals.
176      */
177     private final Line2D testLine = new Line2D();
178 
179     /**
180      * Internal non robust estimator of fundamental matrix.
181      */
182     private FundamentalMatrixEstimator fundMatrixEstimator;
183 
184     /**
185      * Constructor.
186      *
187      * @param fundMatrixEstimatorMethod method for non-robust fundamental matrix
188      *                                  estimator.
189      */
190     protected FundamentalMatrixRobustEstimator(final FundamentalMatrixEstimatorMethod fundMatrixEstimatorMethod) {
191         progressDelta = DEFAULT_PROGRESS_DELTA;
192         confidence = DEFAULT_CONFIDENCE;
193         maxIterations = DEFAULT_MAX_ITERATIONS;
194         fundMatrixEstimator = FundamentalMatrixEstimator.create(fundMatrixEstimatorMethod);
195         refineResult = DEFAULT_REFINE_RESULT;
196         keepCovariance = DEFAULT_KEEP_COVARIANCE;
197     }
198 
199     /**
200      * Constructor.
201      *
202      * @param fundMatrixEstimatorMethod method for non-robust fundamental matrix
203      *                                  estimator.
204      * @param listener                  listener to be notified of events such as when estimation
205      *                                  starts, ends or its progress significantly changes.
206      */
207     protected FundamentalMatrixRobustEstimator(
208             final FundamentalMatrixEstimatorMethod fundMatrixEstimatorMethod,
209             final FundamentalMatrixRobustEstimatorListener listener) {
210         this(fundMatrixEstimatorMethod);
211         this.listener = listener;
212     }
213 
214     /**
215      * Constructor with matched 2D points.
216      *
217      * @param fundMatrixEstimatorMethod method for non-robust fundamental matrix
218      *                                  estimator.
219      * @param leftPoints                2D points on left view.
220      * @param rightPoints               2D points on right view.
221      * @throws IllegalArgumentException if provided list of points do not have
222      *                                  the same length or their length is less than 7 points.
223      */
224     protected FundamentalMatrixRobustEstimator(
225             final FundamentalMatrixEstimatorMethod fundMatrixEstimatorMethod,
226             final List<Point2D> leftPoints, final List<Point2D> rightPoints) {
227         this(fundMatrixEstimatorMethod);
228         internalSetPoints(leftPoints, rightPoints);
229     }
230 
231     /**
232      * Constructor with matched 2D points.
233      *
234      * @param fundMatrixEstimatorMethod method for non-robust fundamental matrix
235      *                                  estimator.
236      * @param leftPoints                2D points on left view.
237      * @param rightPoints               2D points on right view.
238      * @param listener                  listener to be notified of events such as when estimation
239      *                                  starts, ends or its progress significantly changes.
240      * @throws IllegalArgumentException if provided list of points do not have
241      *                                  the same length or their length is less than 7 points.
242      */
243     protected FundamentalMatrixRobustEstimator(
244             final FundamentalMatrixEstimatorMethod fundMatrixEstimatorMethod,
245             final List<Point2D> leftPoints, final List<Point2D> rightPoints,
246             final FundamentalMatrixRobustEstimatorListener listener) {
247         this(fundMatrixEstimatorMethod, listener);
248         internalSetPoints(leftPoints, rightPoints);
249     }
250 
251     /**
252      * Constructor.
253      */
254     protected FundamentalMatrixRobustEstimator() {
255         this(DEFAULT_FUNDAMENTAL_MATRIX_ESTIMATOR_METHOD);
256     }
257     
258     /**
259      * Returns non-robust method to estimate a fundamental matrix.
260      *
261      * @return non-robust method to estimate a fundamental matrix.
262      */
263     public FundamentalMatrixEstimatorMethod getNonRobustFundamentalMatrixEstimatorMethod() {
264         return fundMatrixEstimator.getMethod();
265     }
266 
267     /**
268      * Sets non-robust method to estimate a fundamental matrix.
269      *
270      * @param method non-robust method to estimate a fundamental matrix.
271      * @throws LockedException if this fundamental matrix estimator is locked.
272      */
273     public void setNonRobustFundamentalMatrixEstimatorMethod(final FundamentalMatrixEstimatorMethod method)
274             throws LockedException {
275         if (isLocked()) {
276             throw new LockedException();
277         }
278 
279         if (method != getNonRobustFundamentalMatrixEstimatorMethod()) {
280             // if method changes, recreate internal non-robust fundamental matrix
281             // estimator
282             fundMatrixEstimator = FundamentalMatrixEstimator.create(method);
283         }
284     }
285 
286     /**
287      * Returns matched 2D points on left view.
288      *
289      * @return 2D points on left view.
290      */
291     public List<Point2D> getLeftPoints() {
292         return leftPoints;
293     }
294 
295     /**
296      * Returns matched 2D points on right view.
297      *
298      * @return 2D points on right view.
299      */
300     public List<Point2D> getRightPoints() {
301         return rightPoints;
302     }
303 
304     /**
305      * Sets matched 2D points on both left and right views.
306      *
307      * @param leftPoints  matched 2D points on left view.
308      * @param rightPoints matched 2D points on right view.
309      * @throws LockedException          if this fundamental matrix estimator is locked.
310      * @throws IllegalArgumentException if provided matched points on left and
311      *                                  right views do not have the same length or if their length is
312      *                                  less than 7 points.
313      */
314     public void setPoints(final List<Point2D> leftPoints, final List<Point2D> rightPoints) throws LockedException {
315         if (isLocked()) {
316             throw new LockedException();
317         }
318 
319         internalSetPoints(leftPoints, rightPoints);
320     }
321 
322     /**
323      * Returns reference to listener to be notified of events such as when
324      * estimation starts, ends or its progress significantly changes.
325      *
326      * @return listener to be notified of events.
327      */
328     public FundamentalMatrixRobustEstimatorListener getListener() {
329         return listener;
330     }
331 
332     /**
333      * Sets listener to be notified of events such as when estimation starts,
334      * ends or its progress significantly changes.
335      *
336      * @param listener listener to be notified of events.
337      * @throws LockedException if robust estimator is locked.
338      */
339     public void setListener(final FundamentalMatrixRobustEstimatorListener listener) throws LockedException {
340         if (isLocked()) {
341             throw new LockedException();
342         }
343         this.listener = listener;
344     }
345 
346     /**
347      * Indicates whether listener has been provided and is available for
348      * retrieval.
349      *
350      * @return true if available, false otherwise.
351      */
352     public boolean isListenerAvailable() {
353         return listener != null;
354     }
355 
356     /**
357      * Returns boolean indicating if estimator is locked because estimation is
358      * under progress.
359      *
360      * @return true if estimator is locked, false otherwise.
361      */
362     public boolean isLocked() {
363         return locked;
364     }
365 
366     /**
367      * Returns amount of progress variation before notifying a progress change
368      * during estimation.
369      *
370      * @return amount of progress variation before notifying a progress change
371      * during estimation.
372      */
373     public float getProgressDelta() {
374         return progressDelta;
375     }
376 
377     /**
378      * Sets amount of progress variation before notifying a progress change
379      * during estimation.
380      *
381      * @param progressDelta amount of progress variation before notifying a
382      *                      progress change during estimation.
383      * @throws IllegalArgumentException if progress delta is less than zero or
384      *                                  greater than 1.
385      * @throws LockedException          if this estimator is locked because an estimation
386      *                                  is being computed.
387      */
388     public void setProgressDelta(final float progressDelta) throws LockedException {
389         if (isLocked()) {
390             throw new LockedException();
391         }
392         if (progressDelta < MIN_PROGRESS_DELTA || progressDelta > MAX_PROGRESS_DELTA) {
393             throw new IllegalArgumentException();
394         }
395         this.progressDelta = progressDelta;
396     }
397 
398     /**
399      * Returns amount of confidence expressed as a value between 0.0 and 1.0
400      * (which is equivalent to 100%). The amount of confidence indicates the
401      * probability that the estimated result is correct. Usually this value will
402      * be close to 1.0, but not exactly 1.0.
403      *
404      * @return amount of confidence as a value between 0.0 and 1.0.
405      */
406     public double getConfidence() {
407         return confidence;
408     }
409 
410     /**
411      * Sets amount of confidence expressed as a value between 0.0 and 1.0 (which
412      * is equivalent to 100%). The amount of confidence indicates the
413      * probability that the estimated result is correct. Usually this value will
414      * be close to 1.0, but not exactly 1.0.
415      *
416      * @param confidence confidence to be set as a value between 0.0 and 1.0.
417      * @throws IllegalArgumentException if provided value is not between 0.0 and
418      *                                  1.0.
419      * @throws LockedException          if this estimator is locked because an estimator
420      *                                  is being computed.
421      */
422     public void setConfidence(final double confidence) throws LockedException {
423         if (isLocked()) {
424             throw new LockedException();
425         }
426         if (confidence < MIN_CONFIDENCE || confidence > MAX_CONFIDENCE) {
427             throw new IllegalArgumentException();
428         }
429         this.confidence = confidence;
430     }
431 
432     /**
433      * Returns maximum allowed number of iterations. If maximum allowed number
434      * of iterations is achieved without converting to a result when calling
435      * estimate(), a RobustEstimatorException will be raised.
436      *
437      * @return maximum allowed number of iterations.
438      */
439     public int getMaxIterations() {
440         return maxIterations;
441     }
442 
443     /**
444      * Sets maximum allowed number of iterations. When the maximum number of
445      * iterations is exceeded, result will not be available, however an
446      * approximate result will be available for retrieval.
447      *
448      * @param maxIterations maximum allowed number of iterations to be set.
449      * @throws IllegalArgumentException if provided value is less than 1.
450      * @throws LockedException          if this estimator is locked because an estimation
451      *                                  is being computed.
452      */
453     public void setMaxIterations(final int maxIterations) throws LockedException {
454         if (isLocked()) {
455             throw new LockedException();
456         }
457         if (maxIterations < MIN_ITERATIONS) {
458             throw new IllegalArgumentException();
459         }
460         this.maxIterations = maxIterations;
461     }
462 
463     /**
464      * Gets data related to inliers found after estimation.
465      *
466      * @return data related to inliers found after estimation.
467      */
468     public InliersData getInliersData() {
469         return inliersData;
470     }
471 
472     /**
473      * Indicates whether result must be refined using Levenberg-Marquardt
474      * fitting algorithm over found inliers.
475      * If true, inliers will be computed and kept in any implementation
476      * regardless of the settings.
477      *
478      * @return true to refine result, false to simply use result found by
479      * robust estimator without further refining.
480      */
481     public boolean isResultRefined() {
482         return refineResult;
483     }
484 
485     /**
486      * Specifies whether result must be refined using Levenberg-Marquardt
487      * fitting algorithm over found inliers.
488      *
489      * @param refineResult true to refine result, false to simply use result
490      *                     found by robust estimator without further refining.
491      * @throws LockedException if estimator is locked.
492      */
493     public void setResultRefined(final boolean refineResult) throws LockedException {
494         if (isLocked()) {
495             throw new LockedException();
496         }
497         this.refineResult = refineResult;
498     }
499 
500     /**
501      * Indicates whether covariance must be kept after refining result.
502      * This setting is only taken into account if result is refined.
503      *
504      * @return true if covariance must be kept after refining result, false
505      * otherwise.
506      */
507     public boolean isCovarianceKept() {
508         return keepCovariance;
509     }
510 
511     /**
512      * Specifies whether covariance must be kept after refining result.
513      * This setting is only taken into account if result is refined.
514      *
515      * @param keepCovariance true if covariance must be kept after refining
516      *                       result, false otherwise.
517      * @throws LockedException if estimator is locked.
518      */
519     public void setCovarianceKept(final boolean keepCovariance) throws LockedException {
520         if (isLocked()) {
521             throw new LockedException();
522         }
523         this.keepCovariance = keepCovariance;
524     }
525 
526     /**
527      * Returns minimum number of matched pair of points required to start
528      * the estimation.
529      *
530      * @return minimum number of matched pair of points required to start
531      * the estimation.
532      */
533     public int getMinRequiredPoints() {
534         return fundMatrixEstimator.getMinRequiredPoints();
535     }
536 
537     /**
538      * Returns value indicating whether required data has been provided so that
539      * fundamental matrix estimation can start.
540      * If true, estimator is ready to compute a fundamental matrix, otherwise
541      * more data needs to be provided.
542      *
543      * @return true if estimator is ready, false otherwise.
544      */
545     public boolean isReady() {
546         return leftPoints != null && rightPoints != null && leftPoints.size() == rightPoints.size()
547                 && leftPoints.size() >= SevenPointsFundamentalMatrixEstimator.MIN_REQUIRED_POINTS;
548     }
549 
550     /**
551      * Returns quality scores corresponding to each pair of matched points.
552      * The larger the score value the better the quality of the pair of matched
553      * points.
554      * This implementation always returns null.
555      * Subclasses using quality scores must implement proper behaviour.
556      *
557      * @return quality scores corresponding to each pair of matched points.
558      */
559     public double[] getQualityScores() {
560         return null;
561     }
562 
563     /**
564      * Sets quality scores corresponding to each pair of matched points.
565      * The larger the score value the better the quality of the pair of matched
566      * points.
567      * This implementation makes no action.
568      * Subclasses using quality scores must implement proper behaviour.
569      *
570      * @param qualityScores quality scores corresponding to each pair of matched
571      *                      points.
572      * @throws LockedException          if robust estimator is locked because an
573      *                                  estimation is already in progress.
574      * @throws IllegalArgumentException if provided quality scores length is
575      *                                  smaller than minimum required number of homographies.
576      */
577     public void setQualityScores(final double[] qualityScores) throws LockedException {
578     }
579 
580     /**
581      * Gets estimated covariance of estimated fundamental matrix if available.
582      * This is only available when result has been refined and covariance is
583      * kept.
584      *
585      * @return estimated covariance or null.
586      */
587     public Matrix getCovariance() {
588         return covariance;
589     }
590 
591     /**
592      * Estimates fundamental matrix.
593      *
594      * @return estimated fundamental matrix.
595      * @throws LockedException          if robust estimator is locked because an
596      *                                  estimation is already in progress.
597      * @throws NotReadyException        if provided input data is not enough to start
598      *                                  the estimation.
599      * @throws RobustEstimatorException if estimation fails for any reason
600      *                                  (i.e. numerical instability, no solution available, etc).
601      */
602     public abstract FundamentalMatrix estimate() throws LockedException, NotReadyException, RobustEstimatorException;
603 
604     /**
605      * Returns method being used for robust estimation.
606      *
607      * @return method being used for robust estimation.
608      */
609     public abstract RobustEstimatorMethod getMethod();
610 
611     /**
612      * Creates a fundamental matrix robust estimator using provided method.
613      *
614      * @param method method of a robust estimator algorithm to estimate the best
615      *               fundamental matrix.
616      * @return an instance of a fundamental matrix robust estimator.
617      */
618     public static FundamentalMatrixRobustEstimator create(final RobustEstimatorMethod method) {
619         return switch (method) {
620             case LMEDS -> new LMedSFundamentalMatrixRobustEstimator();
621             case MSAC -> new MSACFundamentalMatrixRobustEstimator();
622             case PROSAC -> new PROSACFundamentalMatrixRobustEstimator();
623             case PROMEDS -> new PROMedSFundamentalMatrixRobustEstimator();
624             default -> new RANSACFundamentalMatrixRobustEstimator();
625         };
626     }
627 
628     /**
629      * Creates a fundamental matrix robust estimator using provided lists of
630      * matched points and provided method.
631      *
632      * @param leftPoints  2D points on left view.
633      * @param rightPoints 2D points on left view.
634      * @param method      method of a robust estimator algorithm to estimate the best
635      *                    fundamental matrix.
636      * @return an instance of a fundamental matrix robust estimator.
637      * @throws IllegalArgumentException if provided list of points do not have
638      *                                  the same length or their length is less than 7 points.
639      */
640     public static FundamentalMatrixRobustEstimator create(
641             final List<Point2D> leftPoints, final List<Point2D> rightPoints, final RobustEstimatorMethod method) {
642         return switch (method) {
643             case LMEDS -> new LMedSFundamentalMatrixRobustEstimator(leftPoints, rightPoints);
644             case MSAC -> new MSACFundamentalMatrixRobustEstimator(leftPoints, rightPoints);
645             case PROSAC -> new PROSACFundamentalMatrixRobustEstimator(leftPoints, rightPoints);
646             case PROMEDS -> new PROMedSFundamentalMatrixRobustEstimator(leftPoints, rightPoints);
647             default -> new RANSACFundamentalMatrixRobustEstimator(leftPoints, rightPoints);
648         };
649     }
650 
651     /**
652      * Creates a fundamental matrix robust estimator using provided lists of
653      * matched points and provided method.
654      *
655      * @param leftPoints    2D points on left view.
656      * @param rightPoints   2D points on left view.
657      * @param qualityScores quality scores corresponding to each pair of matched
658      *                      points.
659      * @param method        method of a robust estimator algorithm to estimate the best
660      *                      fundamental matrix.
661      * @return an instance of a fundamental matrix robust estimator.
662      * @throws IllegalArgumentException if provided list of points do not have
663      *                                  the same length or their length is less than 7 points.
664      */
665     public static FundamentalMatrixRobustEstimator create(
666             final List<Point2D> leftPoints, final List<Point2D> rightPoints, final double[] qualityScores,
667             final RobustEstimatorMethod method) {
668         return switch (method) {
669             case LMEDS -> new LMedSFundamentalMatrixRobustEstimator(leftPoints, rightPoints);
670             case MSAC -> new MSACFundamentalMatrixRobustEstimator(leftPoints, rightPoints);
671             case PROSAC -> new PROSACFundamentalMatrixRobustEstimator(qualityScores, leftPoints, rightPoints);
672             case PROMEDS -> new PROMedSFundamentalMatrixRobustEstimator(qualityScores, leftPoints, rightPoints);
673             default -> new RANSACFundamentalMatrixRobustEstimator(leftPoints, rightPoints);
674         };
675     }
676 
677     /**
678      * Creates a fundamental matrix robust estimator using default method.
679      *
680      * @return an instance of a fundamental matrix robust estimator.
681      */
682     public static FundamentalMatrixRobustEstimator create() {
683         return create(DEFAULT_ROBUST_METHOD);
684     }
685 
686     /**
687      * Creates a fundamental matrix robust estimator using provided lists of
688      * matched points and default method.
689      *
690      * @param leftPoints  2D points on left view.
691      * @param rightPoints 2D points on left view.
692      * @return an instance of a fundamental matrix robust estimator.
693      * @throws IllegalArgumentException if provided list of points do not have
694      *                                  the same length or their length is less than 7 points.
695      */
696     public static FundamentalMatrixRobustEstimator create(
697             final List<Point2D> leftPoints, final List<Point2D> rightPoints) {
698         return create(leftPoints, rightPoints, DEFAULT_ROBUST_METHOD);
699     }
700 
701     /**
702      * Creates a fundamental matrix robust estimator using provided lists of
703      * matched points and default method.
704      *
705      * @param leftPoints    2D points on left view.
706      * @param rightPoints   2D points on left view.
707      * @param qualityScores quality scores corresponding to each pair of matched
708      *                      points.
709      * @return an instance of a fundamental matrix robust estimator.
710      * @throws IllegalArgumentException if provided list of points do not have
711      *                                  the same length or their length is less than 7 points.
712      */
713     public static FundamentalMatrixRobustEstimator create(
714             final List<Point2D> leftPoints, final List<Point2D> rightPoints, final double[] qualityScores) {
715         return create(leftPoints, rightPoints, qualityScores, DEFAULT_ROBUST_METHOD);
716     }
717 
718     /**
719      * Computes the residual between a fundamental matrix and a pair of matched
720      * points.
721      *
722      * @param fundamentalMatrix a fundamental matrix.
723      * @param leftPoint         left 2D point.
724      * @param rightPoint        right 2D point.
725      * @return residual (distance of point to epipolar line).
726      */
727     @SuppressWarnings("DuplicatedCode")
728     protected double residual(
729             final FundamentalMatrix fundamentalMatrix, final Point2D leftPoint, final Point2D rightPoint) {
730         try {
731             leftPoint.normalize();
732             rightPoint.normalize();
733             fundamentalMatrix.normalize();
734             fundamentalMatrix.leftEpipolarLine(rightPoint, testLine);
735             final var leftDistance = Math.abs(testLine.signedDistance(leftPoint));
736             fundamentalMatrix.rightEpipolarLine(leftPoint, testLine);
737             final var rightDistance = Math.abs(testLine.signedDistance(rightPoint));
738             // return average distance as an error residual
739             return 0.5 * (leftDistance + rightDistance);
740         } catch (final NotReadyException e) {
741             return Double.MAX_VALUE;
742         }
743     }
744 
745     /**
746      * Estimates a fundamental matrix using a non-robust method and provided
747      * subset of matched points and stores the solution in provided array of
748      * solutions.
749      *
750      * @param solutions         list where solutions will be stored.
751      * @param subsetLeftPoints  subset of left view matched points.
752      * @param subsetRightPoints subset of right view matched points.
753      */
754     protected void nonRobustEstimate(
755             final List<FundamentalMatrix> solutions, final List<Point2D> subsetLeftPoints,
756             final List<Point2D> subsetRightPoints) {
757         try {
758             fundMatrixEstimator.setPoints(subsetLeftPoints, subsetRightPoints);
759             if (fundMatrixEstimator.getMethod() == FundamentalMatrixEstimatorMethod.SEVEN_POINTS_ALGORITHM) {
760                 final var matrices = ((SevenPointsFundamentalMatrixEstimator) fundMatrixEstimator).estimateAll();
761                 solutions.addAll(matrices);
762             } else {
763                 solutions.add(fundMatrixEstimator.estimate());
764             }
765         } catch (final GeometryException e) {
766             // if anything fails, no solution is added
767         }
768     }
769 
770     /**
771      * Attempts to refine provided solution if refinement is requested.
772      * This method returns a refined solution or the same provided solution
773      * if refinement is not requested or has failed.
774      * If refinement is enabled, and it is requested to keep covariance, this
775      * method will also keep covariance of refined fundamental matrix.
776      *
777      * @param fundamentalMatrix fundamental matrix estimated by a robust
778      *                          estimator without refinement.
779      * @return solution after refinement (if requested) or the provided
780      * non-refined solution if not requested or if refinement failed.
781      */
782     protected FundamentalMatrix attemptRefine(final FundamentalMatrix fundamentalMatrix) {
783         if (refineResult) {
784             final var refiner = new FundamentalMatrixRefiner(fundamentalMatrix, keepCovariance, getInliersData(),
785                     leftPoints, rightPoints, getRefinementStandardDeviation());
786 
787             try {
788                 final var result = new FundamentalMatrix();
789                 final var improved = refiner.refine(result);
790 
791                 if (keepCovariance) {
792                     // keep covariance
793                     covariance = refiner.getCovariance();
794                 }
795 
796                 return improved ? result : fundamentalMatrix;
797             } catch (final Exception e) {
798                 // refinement failed, so we return input value
799                 return fundamentalMatrix;
800             }
801 
802         } else {
803             return fundamentalMatrix;
804         }
805     }
806 
807     /**
808      * Gets standard deviation used for Levenberg-Marquardt fitting during
809      * refinement.
810      * Returned value gives an indication of how much variance each residual
811      * has.
812      * Typically, this value is related to the threshold used on each robust
813      * estimation, since residuals of found inliers are within the range of
814      * such threshold.
815      *
816      * @return standard deviation used for refinement.
817      */
818     protected abstract double getRefinementStandardDeviation();
819 
820     /**
821      * Sets matched 2D points on left and right views.
822      * This method does not check whether instance is locked or not.
823      *
824      * @param leftPoints  matched 2D points on left view.
825      * @param rightPoints matched 2D points on right view.
826      * @throws IllegalArgumentException if provided lists of points don't have
827      *                                  the same size.
828      */
829     private void internalSetPoints(final List<Point2D> leftPoints, final List<Point2D> rightPoints) {
830         if (leftPoints.size() != rightPoints.size()) {
831             throw new IllegalArgumentException();
832         }
833         if (leftPoints.size() < getMinRequiredPoints()) {
834             throw new IllegalArgumentException();
835         }
836 
837         this.leftPoints = leftPoints;
838         this.rightPoints = rightPoints;
839     }
840 }