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 }