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