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 }