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.ar.epipolar.estimators;
17
18 import com.irurueta.ar.epipolar.FundamentalMatrix;
19 import com.irurueta.geometry.Point2D;
20 import com.irurueta.geometry.estimators.LockedException;
21 import com.irurueta.geometry.estimators.NotReadyException;
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.ArrayList;
29 import java.util.List;
30
31 /**
32 * Finds the best fundamental matrix for provided collections of matched 2D
33 * points using RANSAC algorithm.
34 */
35 public class RANSACFundamentalMatrixRobustEstimator extends FundamentalMatrixRobustEstimator {
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 in 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 pairs of matched points are inliers or not
63 * when testing possible estimation solutions.
64 * The threshold refers to the amount of error (i.e. distance) a given
65 * point has respect to the epipolar line generated by its matched point.
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 * @param fundMatrixEstimatorMethod method for non-robust fundamental matrix
83 * estimator.
84 */
85 public RANSACFundamentalMatrixRobustEstimator(final FundamentalMatrixEstimatorMethod fundMatrixEstimatorMethod) {
86 super(fundMatrixEstimatorMethod);
87 threshold = DEFAULT_THRESHOLD;
88 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
89 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
90 }
91
92 /**
93 * Constructor.
94 *
95 * @param fundMatrixEstimatorMethod method for non-robust fundamental matrix
96 * estimator.
97 * @param listener listener to be notified of events such as when
98 * estimation starts, ends or its progress significantly changes.
99 */
100 public RANSACFundamentalMatrixRobustEstimator(
101 final FundamentalMatrixEstimatorMethod fundMatrixEstimatorMethod,
102 final FundamentalMatrixRobustEstimatorListener listener) {
103 super(fundMatrixEstimatorMethod, listener);
104 threshold = DEFAULT_THRESHOLD;
105 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
106 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
107 }
108
109 /**
110 * Constructor.
111 *
112 * @param fundMatrixEstimatorMethod method for non-robust fundamental matrix
113 * estimator.
114 * @param leftPoints 2D points on left view.
115 * @param rightPoints 2D points on right view.
116 * @throws IllegalArgumentException if provided list of points do not have
117 * the same length or their length is less than 7 points.
118 */
119 public RANSACFundamentalMatrixRobustEstimator(
120 final FundamentalMatrixEstimatorMethod fundMatrixEstimatorMethod,
121 final List<Point2D> leftPoints, final List<Point2D> rightPoints) {
122 super(fundMatrixEstimatorMethod, leftPoints, rightPoints);
123 threshold = DEFAULT_THRESHOLD;
124 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
125 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
126 }
127
128 /**
129 * Constructor.
130 *
131 * @param fundMatrixEstimatorMethod method for non-robust fundamental matrix
132 * estimator.
133 * @param leftPoints 2D points on left view.
134 * @param rightPoints 2D points on right view.
135 * @param listener listener to be notified of events such as when estimation
136 * starts, ends or its progress significantly changes.
137 * @throws IllegalArgumentException if provided list of points do not have
138 * the same length or their length is less than 7 points.
139 */
140 public RANSACFundamentalMatrixRobustEstimator(
141 final FundamentalMatrixEstimatorMethod fundMatrixEstimatorMethod,
142 final List<Point2D> leftPoints, final List<Point2D> rightPoints,
143 final FundamentalMatrixRobustEstimatorListener listener) {
144 super(fundMatrixEstimatorMethod, leftPoints, rightPoints, listener);
145 threshold = DEFAULT_THRESHOLD;
146 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
147 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
148 }
149
150 /**
151 * Constructor.
152 */
153 public RANSACFundamentalMatrixRobustEstimator() {
154 this(DEFAULT_FUNDAMENTAL_MATRIX_ESTIMATOR_METHOD);
155 }
156
157 /**
158 * Constructor.
159 *
160 * @param listener listener to be notified of events such as when
161 * estimation starts, ends or its progress significantly changes.
162 */
163 public RANSACFundamentalMatrixRobustEstimator(final FundamentalMatrixRobustEstimatorListener listener) {
164 this(DEFAULT_FUNDAMENTAL_MATRIX_ESTIMATOR_METHOD, listener);
165 }
166
167 /**
168 * Constructor.
169 *
170 * @param leftPoints 2D points on left view.
171 * @param rightPoints 2D points on right view.
172 * @throws IllegalArgumentException if provided list of points do not have
173 * the same length or their length is less than 7 points.
174 */
175 public RANSACFundamentalMatrixRobustEstimator(final List<Point2D> leftPoints, final List<Point2D> rightPoints) {
176 this(DEFAULT_FUNDAMENTAL_MATRIX_ESTIMATOR_METHOD, leftPoints, rightPoints);
177 }
178
179 /**
180 * Constructor.
181 *
182 * @param leftPoints 2D points on left view.
183 * @param rightPoints 2D points on right view.
184 * @param listener listener to be notified of events such as when estimation
185 * starts, ends or its progress significantly changes.
186 * @throws IllegalArgumentException if provided list of points do not have
187 * the same length or their length is less than 7 points.
188 */
189 public RANSACFundamentalMatrixRobustEstimator(
190 final List<Point2D> leftPoints, final List<Point2D> rightPoints,
191 final FundamentalMatrixRobustEstimatorListener listener) {
192 this(DEFAULT_FUNDAMENTAL_MATRIX_ESTIMATOR_METHOD, leftPoints, rightPoints, listener);
193 }
194
195 /**
196 * Returns threshold to determine whether matched pairs of points are
197 * inliers or not when testing possible estimation solutions.
198 * The threshold refers to the amount of error (i.e. distance) a given
199 * point has respect to the epipolar line generated by its matched point.
200 *
201 * @return threshold to determine whether matched pairs of points are
202 * inliers or not.
203 */
204 public double getThreshold() {
205 return threshold;
206 }
207
208 /**
209 * Sets threshold to determine whether matched pairs of points are inliers
210 * or not when testing possible estimation solutions.
211 *
212 * @param threshold threshold to be set.
213 * @throws IllegalArgumentException if provided value is equal or less than
214 * zero.
215 * @throws LockedException if robust estimator is locked because an
216 * estimation is already in progress.
217 */
218 public void setThreshold(final double threshold) throws LockedException {
219 if (isLocked()) {
220 throw new LockedException();
221 }
222 if (threshold <= MIN_THRESHOLD) {
223 throw new IllegalArgumentException();
224 }
225 this.threshold = threshold;
226 }
227
228 /**
229 * Indicates whether inliers must be computed and kept.
230 *
231 * @return true if inliers must be computed and kept, false if inliers
232 * only need to be computed but not kept.
233 */
234 public boolean isComputeAndKeepInliersEnabled() {
235 return computeAndKeepInliers;
236 }
237
238 /**
239 * Specifies whether inliers must be computed and kept.
240 *
241 * @param computeAndKeepInliers true if inliers must be computed and kept,
242 * false if inliers only need to be computed but not kept.
243 * @throws LockedException if estimator is locked.
244 */
245 public void setComputeAndKeepInliersEnabled(final boolean computeAndKeepInliers) throws LockedException {
246 if (isLocked()) {
247 throw new LockedException();
248 }
249 this.computeAndKeepInliers = computeAndKeepInliers;
250 }
251
252 /**
253 * Indicates whether residuals must be computed and kept.
254 *
255 * @return true if residuals must be computed and kept, false if residuals
256 * only need to be computed but not kept.
257 */
258 public boolean isComputeAndKeepResidualsEnabled() {
259 return computeAndKeepResiduals;
260 }
261
262 /**
263 * Specifies whether residuals must be computed and kept.
264 *
265 * @param computeAndKeepResiduals true if residuals must be computed and
266 * kept, false if residuals only need to be computed but not kept.
267 * @throws LockedException if estimator is locked.
268 */
269 public void setComputeAndKeepResidualsEnabled(final boolean computeAndKeepResiduals) throws LockedException {
270 if (isLocked()) {
271 throw new LockedException();
272 }
273 this.computeAndKeepResiduals = computeAndKeepResiduals;
274 }
275
276 /**
277 * Estimates a radial distortion using a robust estimator and
278 * the best set of matched 2D points found using the robust estimator.
279 *
280 * @return a radial distortion.
281 * @throws LockedException if robust estimator is locked because an
282 * estimation is already in progress.
283 * @throws NotReadyException if provided input data is not enough to start
284 * the estimation.
285 * @throws RobustEstimatorException if estimation fails for any reason
286 * (i.e. numerical instability, no solution available, etc).
287 */
288 @SuppressWarnings("DuplicatedCode")
289 @Override
290 public FundamentalMatrix estimate() throws LockedException, NotReadyException, RobustEstimatorException {
291 if (isLocked()) {
292 throw new LockedException();
293 }
294 if (!isReady()) {
295 throw new NotReadyException();
296 }
297
298 final var innerEstimator = new RANSACRobustEstimator<FundamentalMatrix>(new RANSACRobustEstimatorListener<>() {
299
300 // subset of left points
301 private final List<Point2D> subsetLeftPoints = new ArrayList<>();
302
303 // subset of right points
304 private final List<Point2D> subsetRightPoints = new ArrayList<>();
305
306 @Override
307 public double getThreshold() {
308 return threshold;
309 }
310
311 @Override
312 public int getTotalSamples() {
313 return leftPoints.size();
314 }
315
316 @Override
317 public int getSubsetSize() {
318 return getMinRequiredPoints();
319 }
320
321 @Override
322 public void estimatePreliminarSolutions(
323 final int[] samplesIndices, final List<FundamentalMatrix> solutions) {
324
325 subsetLeftPoints.clear();
326 subsetRightPoints.clear();
327 for (final var samplesIndex : samplesIndices) {
328 subsetLeftPoints.add(leftPoints.get(samplesIndex));
329 subsetRightPoints.add(rightPoints.get(samplesIndex));
330 }
331
332 nonRobustEstimate(solutions, subsetLeftPoints, subsetRightPoints);
333 }
334
335 @Override
336 public double computeResidual(final FundamentalMatrix currentEstimation, int i) {
337 final var leftPoint = leftPoints.get(i);
338 final var rightPoint = rightPoints.get(i);
339 return residual(currentEstimation, leftPoint, rightPoint);
340 }
341
342 @Override
343 public boolean isReady() {
344 return RANSACFundamentalMatrixRobustEstimator.this.isReady();
345 }
346
347 @Override
348 public void onEstimateStart(final RobustEstimator<FundamentalMatrix> estimator) {
349 if (listener != null) {
350 listener.onEstimateStart(RANSACFundamentalMatrixRobustEstimator.this);
351 }
352 }
353
354 @Override
355 public void onEstimateEnd(final RobustEstimator<FundamentalMatrix> estimator) {
356 if (listener != null) {
357 listener.onEstimateEnd(RANSACFundamentalMatrixRobustEstimator.this);
358 }
359 }
360
361 @Override
362 public void onEstimateNextIteration(
363 final RobustEstimator<FundamentalMatrix> estimator, final int iteration) {
364 if (listener != null) {
365 listener.onEstimateNextIteration(RANSACFundamentalMatrixRobustEstimator.this, iteration);
366 }
367 }
368
369 @Override
370 public void onEstimateProgressChange(
371 final RobustEstimator<FundamentalMatrix> estimator, final float progress) {
372 if (listener != null) {
373 listener.onEstimateProgressChange(RANSACFundamentalMatrixRobustEstimator.this, progress);
374 }
375 }
376 });
377
378 try {
379 locked = true;
380 inliersData = null;
381 innerEstimator.setComputeAndKeepInliersEnabled(computeAndKeepInliers || refineResult);
382 innerEstimator.setComputeAndKeepResidualsEnabled(computeAndKeepResiduals || refineResult);
383 innerEstimator.setConfidence(confidence);
384 innerEstimator.setMaxIterations(maxIterations);
385 innerEstimator.setProgressDelta(progressDelta);
386 final var result = innerEstimator.estimate();
387 inliersData = innerEstimator.getInliersData();
388 return attemptRefine(result);
389 } catch (final com.irurueta.numerical.LockedException e) {
390 throw new LockedException(e);
391 } catch (final com.irurueta.numerical.NotReadyException e) {
392 throw new NotReadyException(e);
393 } finally {
394 locked = false;
395 }
396 }
397
398 /**
399 * Returns method being used for robust estimation.
400 *
401 * @return method being used for robust estimation.
402 */
403 @Override
404 public RobustEstimatorMethod getMethod() {
405 return RobustEstimatorMethod.RANSAC;
406 }
407
408 /**
409 * Gets standard deviation used for Levenberg-Marquardt fitting during
410 * refinement.
411 * Returned value gives an indication of how much variance each residual
412 * has.
413 * Typically, this value is related to the threshold used on each robust
414 * estimation, since residuals of found inliers are within the range of
415 * such threshold.
416 *
417 * @return standard deviation used for refinement.
418 */
419 @Override
420 protected double getRefinementStandardDeviation() {
421 return threshold;
422 }
423 }