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.ar.epipolar.FundamentalMatrix;
19  import com.irurueta.geometry.Point2D;
20  import com.irurueta.geometry.estimators.LockedException;
21  import com.irurueta.geometry.estimators.NotReadyException;
22  import com.irurueta.numerical.robust.LMedSRobustEstimator;
23  import com.irurueta.numerical.robust.LMedSRobustEstimatorListener;
24  import com.irurueta.numerical.robust.RobustEstimator;
25  import com.irurueta.numerical.robust.RobustEstimatorException;
26  import com.irurueta.numerical.robust.RobustEstimatorMethod;
27  
28  import java.util.ArrayList;
29  import java.util.List;
30  
31  /**
32   * Finds the best fundamental matrix for provided collections of matched 2D
33   * points using LMedS algorithm.
34   */
35  public class LMedSFundamentalMatrixRobustEstimator extends FundamentalMatrixRobustEstimator {
36  
37      /**
38       * Default value to be used for stop threshold. Stop threshold can be used
39       * to keep the algorithm iterating in case that best estimated threshold
40       * using median of residuals is not small enough. Once a solution is found
41       * that generates a threshold below this value, the algorithm will stop.
42       * The stop threshold can be used to prevent the LMedS algorithm iterating
43       * too many times in cases where samples have a very similar accuracy.
44       * For instance, in cases where proportion of outliers is very small (close
45       * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
46       * iterate for a long time trying to find the best solution when indeed
47       * there is no need to do that if a reasonable threshold has already been
48       * reached.
49       * Because of this behaviour the stop threshold can be set to a value much
50       * lower than the one typically used in RANSAC, and yet the algorithm could
51       * still produce even smaller thresholds in estimated results.
52       */
53      public static final double DEFAULT_STOP_THRESHOLD = 1e-3;
54  
55      /**
56       * Minimum allowed stop threshold value.
57       */
58      public static final double MIN_STOP_THRESHOLD = 0.0;
59  
60      /**
61       * Threshold to be used to keep the algorithm iterating in case that best
62       * estimated threshold using median of residuals is not small enough. Once
63       * a solution is found that generates a threshold below this value, the
64       * algorithm will stop.
65       * The stop threshold can be used to prevent the LMedS algorithm iterating
66       * too many times in cases where samples have a very similar accuracy.
67       * For instance, in cases where proportion of outliers is very small (close
68       * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
69       * iterate for a long time trying to find the best solution when indeed
70       * there is no need to do that if a reasonable threshold has already been
71       * reached.
72       * Because of this behaviour the stop threshold can be set to a value much
73       * lower than the one typically used in RANSAC, and yet the algorithm could
74       * still produce even smaller thresholds in estimated results.
75       */
76      private double stopThreshold;
77  
78      /**
79       * Constructor.
80       *
81       * @param fundMatrixEstimatorMethod method for non-robust fundamental matrix
82       *                                  estimator.
83       */
84      public LMedSFundamentalMatrixRobustEstimator(final FundamentalMatrixEstimatorMethod fundMatrixEstimatorMethod) {
85          super(fundMatrixEstimatorMethod);
86          stopThreshold = DEFAULT_STOP_THRESHOLD;
87      }
88  
89      /**
90       * Constructor.
91       *
92       * @param fundMatrixEstimatorMethod method for non-robust fundamental matrix
93       *                                  estimator.
94       * @param listener                  listener to be notified of events such as when estimation
95       *                                  starts, ends or its progress significantly changes.
96       */
97      public LMedSFundamentalMatrixRobustEstimator(
98              final FundamentalMatrixEstimatorMethod fundMatrixEstimatorMethod,
99              final FundamentalMatrixRobustEstimatorListener listener) {
100         super(fundMatrixEstimatorMethod, listener);
101         stopThreshold = DEFAULT_STOP_THRESHOLD;
102     }
103 
104     /**
105      * Constructor.
106      *
107      * @param fundMatrixEstimatorMethod method for non-robust fundamental matrix
108      *                                  estimator.
109      * @param leftPoints                2D points on left view.
110      * @param rightPoints               2D points on right view.
111      * @throws IllegalArgumentException if provided list of points do not have
112      *                                  the same length or their length is less than 7 points.
113      */
114     public LMedSFundamentalMatrixRobustEstimator(
115             final FundamentalMatrixEstimatorMethod fundMatrixEstimatorMethod,
116             final List<Point2D> leftPoints, final List<Point2D> rightPoints) {
117         super(fundMatrixEstimatorMethod, leftPoints, rightPoints);
118         stopThreshold = DEFAULT_STOP_THRESHOLD;
119     }
120 
121     /**
122      * Constructor.
123      *
124      * @param fundMatrixEstimatorMethod method for non-robust fundamental matrix
125      *                                  estimator.
126      * @param leftPoints                2D points on left view.
127      * @param rightPoints               2D points on right view.
128      * @param listener                  listener to be notified of events such as when estimation
129      *                                  starts, ends or its progress significantly changes.
130      * @throws IllegalArgumentException if provided list of points do not have
131      *                                  the same length or their length is less than 7 points.
132      */
133     public LMedSFundamentalMatrixRobustEstimator(
134             final FundamentalMatrixEstimatorMethod fundMatrixEstimatorMethod,
135             final List<Point2D> leftPoints, final List<Point2D> rightPoints,
136             final FundamentalMatrixRobustEstimatorListener listener) {
137         super(fundMatrixEstimatorMethod, leftPoints, rightPoints, listener);
138         stopThreshold = DEFAULT_STOP_THRESHOLD;
139     }
140 
141     /**
142      * Constructor.
143      */
144     public LMedSFundamentalMatrixRobustEstimator() {
145         this(DEFAULT_FUNDAMENTAL_MATRIX_ESTIMATOR_METHOD);
146     }
147 
148     /**
149      * Constructor.
150      *
151      * @param listener listener to be notified of events such as when estimation
152      *                 starts, ends or its progress significantly changes.
153      */
154     public LMedSFundamentalMatrixRobustEstimator(final FundamentalMatrixRobustEstimatorListener listener) {
155         this(DEFAULT_FUNDAMENTAL_MATRIX_ESTIMATOR_METHOD, listener);
156     }
157 
158     /**
159      * Constructor.
160      *
161      * @param leftPoints  2D points on left view.
162      * @param rightPoints 2D points on right view.
163      * @throws IllegalArgumentException if provided list of points do not have
164      *                                  the same length or their length is less than 7 points.
165      */
166     public LMedSFundamentalMatrixRobustEstimator(final List<Point2D> leftPoints, final List<Point2D> rightPoints) {
167         this(DEFAULT_FUNDAMENTAL_MATRIX_ESTIMATOR_METHOD, leftPoints, rightPoints);
168     }
169 
170     /**
171      * Constructor.
172      *
173      * @param leftPoints  2D points on left view.
174      * @param rightPoints 2D points on right view.
175      * @param listener    listener to be notified of events such as when estimation
176      *                    starts, ends or its progress significantly changes.
177      * @throws IllegalArgumentException if provided list of points do not have
178      *                                  the same length or their length is less than 7 points.
179      */
180     public LMedSFundamentalMatrixRobustEstimator(final List<Point2D> leftPoints, final List<Point2D> rightPoints,
181                                                  final FundamentalMatrixRobustEstimatorListener listener) {
182         this(DEFAULT_FUNDAMENTAL_MATRIX_ESTIMATOR_METHOD, leftPoints, rightPoints, listener);
183     }
184 
185     /**
186      * Returns threshold to be used to keep the algorithm iterating in case that
187      * best estimated threshold using median of residuals is not small enough.
188      * Once a solution is found that generates a threshold below this value, the
189      * algorithm will stop.
190      * The stop threshold can be used to prevent the LMedS algorithm iterating
191      * too many times in cases where samples have a very similar accuracy.
192      * For instance, in cases where proportion of outliers is very small (close
193      * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
194      * iterate for a long time trying to find the best solution when indeed
195      * there is no need to do that if a reasonable threshold has already been
196      * reached.
197      * Because of this behaviour the stop threshold can be set to a value much
198      * lower than the one typically used in RANSAC, and yet the algorithm could
199      * still produce even smaller thresholds in estimated results.
200      *
201      * @return stop threshold to stop the algorithm prematurely when a certain
202      * accuracy has been reached.
203      */
204     public double getStopThreshold() {
205         return stopThreshold;
206     }
207 
208     /**
209      * Sets threshold to be used to keep the algorithm iterating in case that
210      * best estimated threshold using median of residuals is not small enough.
211      * Once a solution is found that generates a threshold below this value, the
212      * algorithm will stop.
213      * The stop threshold can be used to prevent the LMedS algorithm iterating
214      * too many times in cases where samples have a very similar accuracy.
215      * For instance, in cases where proportion of outliers is very small (close
216      * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
217      * iterate for a long time trying to find the best solution when indeed
218      * there is no need to do that if a reasonable threshold has already been
219      * reached.
220      * Because of this behaviour the stop threshold can be set to a value much
221      * lower than the one typically used in RANSAC, and yet the algorithm could
222      * still produce even smaller thresholds in estimated results.
223      *
224      * @param stopThreshold stop threshold to stop the algorithm prematurely
225      *                      when a certain accuracy has been reached.
226      * @throws IllegalArgumentException if provided value is zero or negative.
227      * @throws LockedException          if robust estimator is locked because an
228      *                                  estimation is already in progress.
229      */
230     public void setStopThreshold(final double stopThreshold) throws LockedException {
231         if (isLocked()) {
232             throw new LockedException();
233         }
234         if (stopThreshold <= MIN_STOP_THRESHOLD) {
235             throw new IllegalArgumentException();
236         }
237 
238         this.stopThreshold = stopThreshold;
239     }
240 
241     /**
242      * Estimates a radial distortion using a robust estimator and
243      * the best set of matched 2D points found using the robust estimator.
244      *
245      * @return a radial distortion.
246      * @throws LockedException          if robust estimator is locked because an
247      *                                  estimation is already in progress.
248      * @throws NotReadyException        if provided input data is not enough to start
249      *                                  the estimation.
250      * @throws RobustEstimatorException if estimation fails for any reason
251      *                                  (i.e. numerical instability, no solution available, etc).
252      */
253     @Override
254     public FundamentalMatrix estimate() throws LockedException, NotReadyException, RobustEstimatorException {
255         if (isLocked()) {
256             throw new LockedException();
257         }
258         if (!isReady()) {
259             throw new NotReadyException();
260         }
261 
262         final var innerEstimator = new LMedSRobustEstimator<FundamentalMatrix>(new LMedSRobustEstimatorListener<>() {
263 
264             // subset of left points
265             private final List<Point2D> subsetLeftPoints = new ArrayList<>();
266 
267             // subset of right points
268             private final List<Point2D> subsetRightPoints = new ArrayList<>();
269 
270             @Override
271             public int getTotalSamples() {
272                 return leftPoints.size();
273             }
274 
275             @Override
276             public int getSubsetSize() {
277                 return getMinRequiredPoints();
278             }
279 
280             @Override
281             public void estimatePreliminarSolutions(
282                     final int[] samplesIndices, final List<FundamentalMatrix> solutions) {
283 
284                 subsetLeftPoints.clear();
285                 subsetRightPoints.clear();
286                 for (final var samplesIndex : samplesIndices) {
287                     subsetLeftPoints.add(leftPoints.get(samplesIndex));
288                     subsetRightPoints.add(rightPoints.get(samplesIndex));
289                 }
290 
291                 nonRobustEstimate(solutions, subsetLeftPoints, subsetRightPoints);
292             }
293 
294             @Override
295             public double computeResidual(final FundamentalMatrix currentEstimation, final int i) {
296                 final var leftPoint = leftPoints.get(i);
297                 final var rightPoint = rightPoints.get(i);
298                 return residual(currentEstimation, leftPoint, rightPoint);
299             }
300 
301             @Override
302             public boolean isReady() {
303                 return LMedSFundamentalMatrixRobustEstimator.this.isReady();
304             }
305 
306             @Override
307             public void onEstimateStart(final RobustEstimator<FundamentalMatrix> estimator) {
308                 if (listener != null) {
309                     listener.onEstimateStart(LMedSFundamentalMatrixRobustEstimator.this);
310                 }
311             }
312 
313             @Override
314             public void onEstimateEnd(final RobustEstimator<FundamentalMatrix> estimator) {
315                 if (listener != null) {
316                     listener.onEstimateEnd(LMedSFundamentalMatrixRobustEstimator.this);
317                 }
318             }
319 
320             @Override
321             public void onEstimateNextIteration(
322                     final RobustEstimator<FundamentalMatrix> estimator, final int iteration) {
323                 if (listener != null) {
324                     listener.onEstimateNextIteration(LMedSFundamentalMatrixRobustEstimator.this, iteration);
325                 }
326             }
327 
328             @Override
329             public void onEstimateProgressChange(
330                     final RobustEstimator<FundamentalMatrix> estimator, final float progress) {
331                 if (listener != null) {
332                     listener.onEstimateProgressChange(LMedSFundamentalMatrixRobustEstimator.this, progress);
333                 }
334             }
335         });
336 
337         try {
338             locked = true;
339             inliersData = null;
340             innerEstimator.setConfidence(confidence);
341             innerEstimator.setMaxIterations(maxIterations);
342             innerEstimator.setProgressDelta(progressDelta);
343             innerEstimator.setStopThreshold(stopThreshold);
344             final var result = innerEstimator.estimate();
345             inliersData = innerEstimator.getInliersData();
346             return attemptRefine(result);
347         } catch (final com.irurueta.numerical.LockedException e) {
348             throw new LockedException(e);
349         } catch (final com.irurueta.numerical.NotReadyException e) {
350             throw new NotReadyException(e);
351         } finally {
352             locked = false;
353         }
354     }
355 
356     /**
357      * Returns method being used for robust estimation.
358      *
359      * @return method being used for robust estimation.
360      */
361     @Override
362     public RobustEstimatorMethod getMethod() {
363         return RobustEstimatorMethod.LMEDS;
364     }
365 
366     /**
367      * Gets standard deviation used for Levenberg-Marquardt fitting during
368      * refinement.
369      * Returned value gives an indication of how much variance each residual
370      * has.
371      * Typically, this value is related to the threshold used on each robust
372      * estimation, since residuals of found inliers are within the range of
373      * such threshold.
374      *
375      * @return standard deviation used for refinement.
376      */
377     @Override
378     protected double getRefinementStandardDeviation() {
379         final var inliersData = (LMedSRobustEstimator.LMedSInliersData) getInliersData();
380         return inliersData.getEstimatedThreshold();
381     }
382 }