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