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