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.MetricTransformation3D;
20 import com.irurueta.geometry.Point3D;
21 import com.irurueta.numerical.robust.RANSACRobustEstimator;
22 import com.irurueta.numerical.robust.RANSACRobustEstimatorListener;
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 3D transformation for provided collections of
32 * matched 3D points using RANSAC algorithm.
33 */
34 @SuppressWarnings("DuplicatedCode")
35 public class RANSACMetricTransformation3DRobustEstimator extends MetricTransformation3DRobustEstimator {
36
37 /**
38 * Constant defining default threshold to determine whether points are
39 * inliers or not.
40 * By default, 1.0 is considered a good value for cases where measures are
41 * done on pixels, since typically the minimum resolution is 1 pixel.
42 */
43 public static final double DEFAULT_THRESHOLD = 1.0;
44
45 /**
46 * Minimum value that can be set as threshold.
47 * Threshold must be strictly greater than 0.0.
48 */
49 public static final double MIN_THRESHOLD = 0.0;
50
51 /**
52 * Indicates that by default inliers will only be computed but not kept.
53 */
54 public static final boolean DEFAULT_COMPUTE_AND_KEEP_INLIERS = false;
55
56 /**
57 * Indicates that by default residuals will only be computed but not kept.
58 */
59 public static final boolean DEFAULT_COMPUTE_AND_KEEP_RESIDUALS = false;
60
61 /**
62 * Threshold to determine whether points are inliers or not when testing
63 * possible estimation solutions.
64 * The threshold refers to the amount of error (i.e. distance) a possible
65 * solution has on a matched pair of points.
66 */
67 private double threshold;
68
69 /**
70 * Indicates whether inliers must be computed and kept.
71 */
72 private boolean computeAndKeepInliers;
73
74 /**
75 * Indicates whether residuals must be computed and kept.
76 */
77 private boolean computeAndKeepResiduals;
78
79 /**
80 * Constructor.
81 */
82 public RANSACMetricTransformation3DRobustEstimator() {
83 super();
84 threshold = DEFAULT_THRESHOLD;
85 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
86 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
87 }
88
89 /**
90 * Constructor with lists of points to be used to estimate a metric 3D
91 * transformation.
92 * Points in the list located at the same position are considered to be
93 * matched. Hence, both lists must have the same size, and their size must
94 * be greater or equal than MINIMUM_SIZE.
95 *
96 * @param inputPoints list of input points to be used to estimate a
97 * metric 3D transformation.
98 * @param outputPoints list of output points to be used to estimate a
99 * metric 3D transformation.
100 * @throws IllegalArgumentException if provided lists of points don't have
101 * the same size or their size is smaller than MINIMUM_SIZE.
102 */
103 public RANSACMetricTransformation3DRobustEstimator(
104 final List<Point3D> inputPoints, final List<Point3D> outputPoints) {
105 super(inputPoints, outputPoints);
106 threshold = DEFAULT_THRESHOLD;
107 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
108 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
109 }
110
111 /**
112 * Constructor.
113 *
114 * @param listener listener to be notified of events such as when estimation
115 * starts, ends or its progress significantly changes.
116 */
117 public RANSACMetricTransformation3DRobustEstimator(final MetricTransformation3DRobustEstimatorListener listener) {
118 super(listener);
119 threshold = DEFAULT_THRESHOLD;
120 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
121 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
122 }
123
124 /**
125 * Constructor with listener and lists of points to be used to estimate a
126 * metric 3D transformation.
127 * Points in the list located at the same position are considered to be
128 * matched. Hence, both lists must have the same size, and their size must
129 * be greater or equal than MINIMUM_SIZE.
130 *
131 * @param listener listener to be notified of events such as when estimation
132 * starts, ends or its progress significantly changes.
133 * @param inputPoints list of input points to be used to estimate a
134 * metric 3D transformation.
135 * @param outputPoints list of output points to be used to estimate a
136 * metric 3D transformation.
137 * @throws IllegalArgumentException if provided lists of points don't have
138 * the same size or their size is smaller than MINIMUM_SIZE.
139 */
140 public RANSACMetricTransformation3DRobustEstimator(
141 final MetricTransformation3DRobustEstimatorListener listener,
142 final List<Point3D> inputPoints, final List<Point3D> outputPoints) {
143 super(listener, inputPoints, outputPoints);
144 threshold = DEFAULT_THRESHOLD;
145 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
146 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
147 }
148
149 /**
150 * Constructor.
151 *
152 * @param weakMinimumSizeAllowed true allows 3 points, false requires 4.
153 */
154 public RANSACMetricTransformation3DRobustEstimator(final boolean weakMinimumSizeAllowed) {
155 super(weakMinimumSizeAllowed);
156 threshold = DEFAULT_THRESHOLD;
157 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
158 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
159 }
160
161 /**
162 * Constructor with lists of points to be used to estimate a metric 3D
163 * transformation.
164 * Points in the list located at the same position are considered to be
165 * matched. Hence, both lists must have the same size, and their size must
166 * be greater or equal than MINIMUM_SIZE.
167 *
168 * @param inputPoints list of input points to be used to estimate a
169 * metric 3D transformation.
170 * @param outputPoints list of output points to be used to estimate a
171 * metric 3D transformation.
172 * @param weakMinimumSizeAllowed true allows 3 points, false requires 4.
173 * @throws IllegalArgumentException if provided lists of points don't have
174 * the same size or their size is smaller than MINIMUM_SIZE.
175 */
176 public RANSACMetricTransformation3DRobustEstimator(
177 final List<Point3D> inputPoints, final List<Point3D> outputPoints,
178 final boolean weakMinimumSizeAllowed) {
179 super(inputPoints, outputPoints, weakMinimumSizeAllowed);
180 threshold = DEFAULT_THRESHOLD;
181 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
182 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
183 }
184
185 /**
186 * Constructor.
187 *
188 * @param listener listener to be notified of events such as when estimation
189 * starts, ends or its progress significantly changes.
190 * @param weakMinimumSizeAllowed true allows 3 points, false requires 4.
191 */
192 public RANSACMetricTransformation3DRobustEstimator(
193 final MetricTransformation3DRobustEstimatorListener listener, final boolean weakMinimumSizeAllowed) {
194 super(listener, weakMinimumSizeAllowed);
195 threshold = DEFAULT_THRESHOLD;
196 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
197 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
198 }
199
200 /**
201 * Constructor with listener and lists of points to be used to estimate a
202 * metric 3D transformation.
203 * Points in the list located at the same position are considered to be
204 * matched. Hence, both lists must have the same size, and their size must
205 * be greater or equal than MINIMUM_SIZE.
206 *
207 * @param listener listener to be notified of events such as when estimation
208 * starts, ends or its progress significantly changes.
209 * @param inputPoints list of input points to be used to estimate a
210 * metric 3D transformation.
211 * @param outputPoints list of output points to be used to estimate a
212 * metric 3D transformation.
213 * @param weakMinimumSizeAllowed true allows 3 points, false requires 4.
214 * @throws IllegalArgumentException if provided lists of points don't have
215 * the same size or their size is smaller than MINIMUM_SIZE.
216 */
217 public RANSACMetricTransformation3DRobustEstimator(
218 final MetricTransformation3DRobustEstimatorListener listener,
219 final List<Point3D> inputPoints, final List<Point3D> outputPoints, final boolean weakMinimumSizeAllowed) {
220 super(listener, inputPoints, outputPoints, weakMinimumSizeAllowed);
221 threshold = DEFAULT_THRESHOLD;
222 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
223 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
224 }
225
226 /**
227 * Returns threshold to determine whether points are inliers or not when
228 * testing possible estimation solutions.
229 * The threshold refers to the amount of error (i.e. Euclidean distance) a
230 * possible solution has on a matched pair of points.
231 *
232 * @return threshold to determine whether points are inliers or not when
233 * testing possible estimation solutions.
234 */
235 public double getThreshold() {
236 return threshold;
237 }
238
239 /**
240 * Sets threshold to determine whether points are inliers or not when
241 * testing possible estimation solutions.
242 * The threshold refers to the amount of error (i.e. Euclidean distance) a
243 * possible solution has on a matched pair of points.
244 *
245 * @param threshold threshold to be set.
246 * @throws IllegalArgumentException if provided value is equal or less than
247 * zero.
248 * @throws LockedException if robust estimator is locked because an
249 * estimation is already in progress.
250 */
251 public void setThreshold(final double threshold) throws LockedException {
252 if (isLocked()) {
253 throw new LockedException();
254 }
255 if (threshold <= MIN_THRESHOLD) {
256 throw new IllegalArgumentException();
257 }
258 this.threshold = threshold;
259 }
260
261 /**
262 * Indicates whether inliers must be computed and kept.
263 *
264 * @return true if inliers must be computed and kept, false if inliers only
265 * need to be computed but not kept.
266 */
267 public boolean isComputeAndKeepInliersEnabled() {
268 return computeAndKeepInliers;
269 }
270
271 /**
272 * Specifies whether inliers must be computed and kept.
273 *
274 * @param computeAndKeepInliers true if inliers must be computed and kept,
275 * false if inliers only need to be computed but not kept.
276 * @throws LockedException if estimator is locked.
277 */
278 public void setComputeAndKeepInliersEnabled(final boolean computeAndKeepInliers) throws LockedException {
279 if (isLocked()) {
280 throw new LockedException();
281 }
282 this.computeAndKeepInliers = computeAndKeepInliers;
283 }
284
285 /**
286 * Indicates whether residuals must be computed and kept.
287 *
288 * @return true if residuals must be computed and kept, false if residuals
289 * only need to be computed but not kept.
290 */
291 public boolean isComputeAndKeepResidualsEnabled() {
292 return computeAndKeepResiduals;
293 }
294
295 /**
296 * Specifies whether residuals must be computed and kept.
297 *
298 * @param computeAndKeepResiduals true if residuals must be computed and
299 * kept, false if residuals only need to be computed but not kept.
300 * @throws LockedException if estimator is locked.
301 */
302 public void setComputeAndKeepResidualsEnabled(final boolean computeAndKeepResiduals) throws LockedException {
303 if (isLocked()) {
304 throw new LockedException();
305 }
306 this.computeAndKeepResiduals = computeAndKeepResiduals;
307 }
308
309 /**
310 * Estimates a metric 3D transformation using a robust estimator and
311 * the best set of matched 3D point correspondences found using the robust
312 * estimator.
313 *
314 * @return a metric 3D transformation.
315 * @throws LockedException if robust estimator is locked because an
316 * estimation is already in progress.
317 * @throws NotReadyException if provided input data is not enough to start
318 * the estimation.
319 * @throws RobustEstimatorException if estimation fails for any reason
320 * (i.e. numerical instability, no solution available, etc).
321 */
322 @Override
323 public MetricTransformation3D estimate() throws LockedException, NotReadyException, RobustEstimatorException {
324 if (isLocked()) {
325 throw new LockedException();
326 }
327 if (!isReady()) {
328 throw new NotReadyException();
329 }
330
331 final var innerEstimator = new RANSACRobustEstimator<>(
332 new RANSACRobustEstimatorListener<MetricTransformation3D>() {
333
334 // point to be reused when computing residuals
335 private final Point3D testPoint = Point3D.create(CoordinatesType.HOMOGENEOUS_COORDINATES);
336
337 private final MetricTransformation3DEstimator nonRobustEstimator =
338 new MetricTransformation3DEstimator(isWeakMinimumSizeAllowed());
339
340 private final List<Point3D> subsetInputPoints = new ArrayList<>();
341 private final List<Point3D> subsetOutputPoints = new ArrayList<>();
342
343 @Override
344 public double getThreshold() {
345 return threshold;
346 }
347
348 @Override
349 public int getTotalSamples() {
350 return inputPoints.size();
351 }
352
353 @Override
354 public int getSubsetSize() {
355 return nonRobustEstimator.getMinimumPoints();
356 }
357
358 @Override
359 public void estimatePreliminarSolutions(
360 final int[] samplesIndices, final List<MetricTransformation3D> solutions) {
361 subsetInputPoints.clear();
362 subsetOutputPoints.clear();
363 for (final var samplesIndex : samplesIndices) {
364 subsetInputPoints.add(inputPoints.get(samplesIndex));
365 subsetOutputPoints.add(outputPoints.get(samplesIndex));
366 }
367
368 try {
369 nonRobustEstimator.setPoints(subsetInputPoints, subsetOutputPoints);
370 solutions.add(nonRobustEstimator.estimate());
371 } catch (final Exception e) {
372 // if points are coincident, no solution is added
373 }
374 }
375
376 @Override
377 public double computeResidual(final MetricTransformation3D currentEstimation, final int i) {
378 final var inputPoint = inputPoints.get(i);
379 final var outputPoint = outputPoints.get(i);
380
381 // transform input point and store result in mTestPoint
382 currentEstimation.transform(inputPoint, testPoint);
383
384 return outputPoint.distanceTo(testPoint);
385 }
386
387 @Override
388 public boolean isReady() {
389 return RANSACMetricTransformation3DRobustEstimator.this.isReady();
390 }
391
392 @Override
393 public void onEstimateStart(final RobustEstimator<MetricTransformation3D> estimator) {
394 if (listener != null) {
395 listener.onEstimateStart(RANSACMetricTransformation3DRobustEstimator.this);
396 }
397 }
398
399 @Override
400 public void onEstimateEnd(final RobustEstimator<MetricTransformation3D> estimator) {
401 if (listener != null) {
402 listener.onEstimateEnd(RANSACMetricTransformation3DRobustEstimator.this);
403 }
404 }
405
406 @Override
407 public void onEstimateNextIteration(
408 final RobustEstimator<MetricTransformation3D> estimator, final int iteration) {
409 if (listener != null) {
410 listener.onEstimateNextIteration(
411 RANSACMetricTransformation3DRobustEstimator.this, iteration);
412 }
413 }
414
415 @Override
416 public void onEstimateProgressChange(
417 final RobustEstimator<MetricTransformation3D> estimator, final float progress) {
418 if (listener != null) {
419 listener.onEstimateProgressChange(
420 RANSACMetricTransformation3DRobustEstimator.this, progress);
421 }
422 }
423 });
424
425 try {
426 locked = true;
427 inliersData = null;
428 innerEstimator.setComputeAndKeepInliersEnabled(computeAndKeepInliers || refineResult);
429 innerEstimator.setComputeAndKeepResidualsEnabled(computeAndKeepResiduals || refineResult);
430 innerEstimator.setConfidence(confidence);
431 innerEstimator.setMaxIterations(maxIterations);
432 innerEstimator.setProgressDelta(progressDelta);
433 final var transformation = innerEstimator.estimate();
434 inliersData = innerEstimator.getInliersData();
435 return attemptRefine(transformation);
436 } catch (final com.irurueta.numerical.LockedException e) {
437 throw new LockedException(e);
438 } catch (final com.irurueta.numerical.NotReadyException e) {
439 throw new NotReadyException(e);
440 } finally {
441 locked = false;
442 }
443 }
444
445 /**
446 * Returns method being used for robust estimation.
447 *
448 * @return method being used for robust estimation.
449 */
450 @Override
451 public RobustEstimatorMethod getMethod() {
452 return RobustEstimatorMethod.RANSAC;
453 }
454
455 /**
456 * Gets standard deviation used for Levenberg-Marquardt fitting during
457 * refinement.
458 * Returned value gives an indication of how much variance each residual
459 * has.
460 * Typically, this value is related to the threshold used on each robust
461 * estimation, since residuals of found inliers are within the range of
462 * such threshold.
463 *
464 * @return standard deviation used for refinement.
465 */
466 @Override
467 protected double getRefinementStandardDeviation() {
468 return threshold;
469 }
470 }