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