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.LMedSRobustEstimator;
23 import com.irurueta.numerical.robust.LMedSRobustEstimatorListener;
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 LMedS algorithm.
34 */
35 public class LMedSFundamentalMatrixRobustEstimator extends FundamentalMatrixRobustEstimator {
36
37 /**
38 * Default value to be used for stop threshold. Stop threshold can be used
39 * to keep the algorithm iterating in case that best estimated threshold
40 * using median of residuals is not small enough. Once a solution is found
41 * that generates a threshold below this value, the algorithm will stop.
42 * The stop threshold can be used to prevent the LMedS algorithm iterating
43 * too many times in cases where samples have a very similar accuracy.
44 * For instance, in cases where proportion of outliers is very small (close
45 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
46 * iterate for a long time trying to find the best solution when indeed
47 * there is no need to do that if a reasonable threshold has already been
48 * reached.
49 * Because of this behaviour the stop threshold can be set to a value much
50 * lower than the one typically used in RANSAC, and yet the algorithm could
51 * still produce even smaller thresholds in estimated results.
52 */
53 public static final double DEFAULT_STOP_THRESHOLD = 1e-3;
54
55 /**
56 * Minimum allowed stop threshold value.
57 */
58 public static final double MIN_STOP_THRESHOLD = 0.0;
59
60 /**
61 * Threshold to be used to keep the algorithm iterating in case that best
62 * estimated threshold using median of residuals is not small enough. Once
63 * a solution is found that generates a threshold below this value, the
64 * algorithm will stop.
65 * The stop threshold can be used to prevent the LMedS algorithm iterating
66 * too many times in cases where samples have a very similar accuracy.
67 * For instance, in cases where proportion of outliers is very small (close
68 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
69 * iterate for a long time trying to find the best solution when indeed
70 * there is no need to do that if a reasonable threshold has already been
71 * reached.
72 * Because of this behaviour the stop threshold can be set to a value much
73 * lower than the one typically used in RANSAC, and yet the algorithm could
74 * still produce even smaller thresholds in estimated results.
75 */
76 private double stopThreshold;
77
78 /**
79 * Constructor.
80 *
81 * @param fundMatrixEstimatorMethod method for non-robust fundamental matrix
82 * estimator.
83 */
84 public LMedSFundamentalMatrixRobustEstimator(final FundamentalMatrixEstimatorMethod fundMatrixEstimatorMethod) {
85 super(fundMatrixEstimatorMethod);
86 stopThreshold = DEFAULT_STOP_THRESHOLD;
87 }
88
89 /**
90 * Constructor.
91 *
92 * @param fundMatrixEstimatorMethod method for non-robust fundamental matrix
93 * estimator.
94 * @param listener listener to be notified of events such as when estimation
95 * starts, ends or its progress significantly changes.
96 */
97 public LMedSFundamentalMatrixRobustEstimator(
98 final FundamentalMatrixEstimatorMethod fundMatrixEstimatorMethod,
99 final FundamentalMatrixRobustEstimatorListener listener) {
100 super(fundMatrixEstimatorMethod, listener);
101 stopThreshold = DEFAULT_STOP_THRESHOLD;
102 }
103
104 /**
105 * Constructor.
106 *
107 * @param fundMatrixEstimatorMethod method for non-robust fundamental matrix
108 * estimator.
109 * @param leftPoints 2D points on left view.
110 * @param rightPoints 2D points on right view.
111 * @throws IllegalArgumentException if provided list of points do not have
112 * the same length or their length is less than 7 points.
113 */
114 public LMedSFundamentalMatrixRobustEstimator(
115 final FundamentalMatrixEstimatorMethod fundMatrixEstimatorMethod,
116 final List<Point2D> leftPoints, final List<Point2D> rightPoints) {
117 super(fundMatrixEstimatorMethod, leftPoints, rightPoints);
118 stopThreshold = DEFAULT_STOP_THRESHOLD;
119 }
120
121 /**
122 * Constructor.
123 *
124 * @param fundMatrixEstimatorMethod method for non-robust fundamental matrix
125 * estimator.
126 * @param leftPoints 2D points on left view.
127 * @param rightPoints 2D points on right view.
128 * @param listener listener to be notified of events such as when estimation
129 * starts, ends or its progress significantly changes.
130 * @throws IllegalArgumentException if provided list of points do not have
131 * the same length or their length is less than 7 points.
132 */
133 public LMedSFundamentalMatrixRobustEstimator(
134 final FundamentalMatrixEstimatorMethod fundMatrixEstimatorMethod,
135 final List<Point2D> leftPoints, final List<Point2D> rightPoints,
136 final FundamentalMatrixRobustEstimatorListener listener) {
137 super(fundMatrixEstimatorMethod, leftPoints, rightPoints, listener);
138 stopThreshold = DEFAULT_STOP_THRESHOLD;
139 }
140
141 /**
142 * Constructor.
143 */
144 public LMedSFundamentalMatrixRobustEstimator() {
145 this(DEFAULT_FUNDAMENTAL_MATRIX_ESTIMATOR_METHOD);
146 }
147
148 /**
149 * Constructor.
150 *
151 * @param listener listener to be notified of events such as when estimation
152 * starts, ends or its progress significantly changes.
153 */
154 public LMedSFundamentalMatrixRobustEstimator(final FundamentalMatrixRobustEstimatorListener listener) {
155 this(DEFAULT_FUNDAMENTAL_MATRIX_ESTIMATOR_METHOD, listener);
156 }
157
158 /**
159 * Constructor.
160 *
161 * @param leftPoints 2D points on left view.
162 * @param rightPoints 2D points on right view.
163 * @throws IllegalArgumentException if provided list of points do not have
164 * the same length or their length is less than 7 points.
165 */
166 public LMedSFundamentalMatrixRobustEstimator(final List<Point2D> leftPoints, final List<Point2D> rightPoints) {
167 this(DEFAULT_FUNDAMENTAL_MATRIX_ESTIMATOR_METHOD, leftPoints, rightPoints);
168 }
169
170 /**
171 * Constructor.
172 *
173 * @param leftPoints 2D points on left view.
174 * @param rightPoints 2D points on right view.
175 * @param listener listener to be notified of events such as when estimation
176 * starts, ends or its progress significantly changes.
177 * @throws IllegalArgumentException if provided list of points do not have
178 * the same length or their length is less than 7 points.
179 */
180 public LMedSFundamentalMatrixRobustEstimator(final List<Point2D> leftPoints, final List<Point2D> rightPoints,
181 final FundamentalMatrixRobustEstimatorListener listener) {
182 this(DEFAULT_FUNDAMENTAL_MATRIX_ESTIMATOR_METHOD, leftPoints, rightPoints, listener);
183 }
184
185 /**
186 * Returns threshold to be used to keep the algorithm iterating in case that
187 * best estimated threshold using median of residuals is not small enough.
188 * Once a solution is found that generates a threshold below this value, the
189 * algorithm will stop.
190 * The stop threshold can be used to prevent the LMedS algorithm iterating
191 * too many times in cases where samples have a very similar accuracy.
192 * For instance, in cases where proportion of outliers is very small (close
193 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
194 * iterate for a long time trying to find the best solution when indeed
195 * there is no need to do that if a reasonable threshold has already been
196 * reached.
197 * Because of this behaviour the stop threshold can be set to a value much
198 * lower than the one typically used in RANSAC, and yet the algorithm could
199 * still produce even smaller thresholds in estimated results.
200 *
201 * @return stop threshold to stop the algorithm prematurely when a certain
202 * accuracy has been reached.
203 */
204 public double getStopThreshold() {
205 return stopThreshold;
206 }
207
208 /**
209 * Sets threshold to be used to keep the algorithm iterating in case that
210 * best estimated threshold using median of residuals is not small enough.
211 * Once a solution is found that generates a threshold below this value, the
212 * algorithm will stop.
213 * The stop threshold can be used to prevent the LMedS algorithm iterating
214 * too many times in cases where samples have a very similar accuracy.
215 * For instance, in cases where proportion of outliers is very small (close
216 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
217 * iterate for a long time trying to find the best solution when indeed
218 * there is no need to do that if a reasonable threshold has already been
219 * reached.
220 * Because of this behaviour the stop threshold can be set to a value much
221 * lower than the one typically used in RANSAC, and yet the algorithm could
222 * still produce even smaller thresholds in estimated results.
223 *
224 * @param stopThreshold stop threshold to stop the algorithm prematurely
225 * when a certain accuracy has been reached.
226 * @throws IllegalArgumentException if provided value is zero or negative.
227 * @throws LockedException if robust estimator is locked because an
228 * estimation is already in progress.
229 */
230 public void setStopThreshold(final double stopThreshold) throws LockedException {
231 if (isLocked()) {
232 throw new LockedException();
233 }
234 if (stopThreshold <= MIN_STOP_THRESHOLD) {
235 throw new IllegalArgumentException();
236 }
237
238 this.stopThreshold = stopThreshold;
239 }
240
241 /**
242 * Estimates a radial distortion using a robust estimator and
243 * the best set of matched 2D points found using the robust estimator.
244 *
245 * @return a radial distortion.
246 * @throws LockedException if robust estimator is locked because an
247 * estimation is already in progress.
248 * @throws NotReadyException if provided input data is not enough to start
249 * the estimation.
250 * @throws RobustEstimatorException if estimation fails for any reason
251 * (i.e. numerical instability, no solution available, etc).
252 */
253 @Override
254 public FundamentalMatrix estimate() throws LockedException, NotReadyException, RobustEstimatorException {
255 if (isLocked()) {
256 throw new LockedException();
257 }
258 if (!isReady()) {
259 throw new NotReadyException();
260 }
261
262 final var innerEstimator = new LMedSRobustEstimator<FundamentalMatrix>(new LMedSRobustEstimatorListener<>() {
263
264 // subset of left points
265 private final List<Point2D> subsetLeftPoints = new ArrayList<>();
266
267 // subset of right points
268 private final List<Point2D> subsetRightPoints = new ArrayList<>();
269
270 @Override
271 public int getTotalSamples() {
272 return leftPoints.size();
273 }
274
275 @Override
276 public int getSubsetSize() {
277 return getMinRequiredPoints();
278 }
279
280 @Override
281 public void estimatePreliminarSolutions(
282 final int[] samplesIndices, final List<FundamentalMatrix> solutions) {
283
284 subsetLeftPoints.clear();
285 subsetRightPoints.clear();
286 for (final var samplesIndex : samplesIndices) {
287 subsetLeftPoints.add(leftPoints.get(samplesIndex));
288 subsetRightPoints.add(rightPoints.get(samplesIndex));
289 }
290
291 nonRobustEstimate(solutions, subsetLeftPoints, subsetRightPoints);
292 }
293
294 @Override
295 public double computeResidual(final FundamentalMatrix currentEstimation, final int i) {
296 final var leftPoint = leftPoints.get(i);
297 final var rightPoint = rightPoints.get(i);
298 return residual(currentEstimation, leftPoint, rightPoint);
299 }
300
301 @Override
302 public boolean isReady() {
303 return LMedSFundamentalMatrixRobustEstimator.this.isReady();
304 }
305
306 @Override
307 public void onEstimateStart(final RobustEstimator<FundamentalMatrix> estimator) {
308 if (listener != null) {
309 listener.onEstimateStart(LMedSFundamentalMatrixRobustEstimator.this);
310 }
311 }
312
313 @Override
314 public void onEstimateEnd(final RobustEstimator<FundamentalMatrix> estimator) {
315 if (listener != null) {
316 listener.onEstimateEnd(LMedSFundamentalMatrixRobustEstimator.this);
317 }
318 }
319
320 @Override
321 public void onEstimateNextIteration(
322 final RobustEstimator<FundamentalMatrix> estimator, final int iteration) {
323 if (listener != null) {
324 listener.onEstimateNextIteration(LMedSFundamentalMatrixRobustEstimator.this, iteration);
325 }
326 }
327
328 @Override
329 public void onEstimateProgressChange(
330 final RobustEstimator<FundamentalMatrix> estimator, final float progress) {
331 if (listener != null) {
332 listener.onEstimateProgressChange(LMedSFundamentalMatrixRobustEstimator.this, progress);
333 }
334 }
335 });
336
337 try {
338 locked = true;
339 inliersData = null;
340 innerEstimator.setConfidence(confidence);
341 innerEstimator.setMaxIterations(maxIterations);
342 innerEstimator.setProgressDelta(progressDelta);
343 innerEstimator.setStopThreshold(stopThreshold);
344 final var result = innerEstimator.estimate();
345 inliersData = innerEstimator.getInliersData();
346 return attemptRefine(result);
347 } catch (final com.irurueta.numerical.LockedException e) {
348 throw new LockedException(e);
349 } catch (final com.irurueta.numerical.NotReadyException e) {
350 throw new NotReadyException(e);
351 } finally {
352 locked = false;
353 }
354 }
355
356 /**
357 * Returns method being used for robust estimation.
358 *
359 * @return method being used for robust estimation.
360 */
361 @Override
362 public RobustEstimatorMethod getMethod() {
363 return RobustEstimatorMethod.LMEDS;
364 }
365
366 /**
367 * Gets standard deviation used for Levenberg-Marquardt fitting during
368 * refinement.
369 * Returned value gives an indication of how much variance each residual
370 * has.
371 * Typically, this value is related to the threshold used on each robust
372 * estimation, since residuals of found inliers are within the range of
373 * such threshold.
374 *
375 * @return standard deviation used for refinement.
376 */
377 @Override
378 protected double getRefinementStandardDeviation() {
379 final var inliersData = (LMedSRobustEstimator.LMedSInliersData) getInliersData();
380 return inliersData.getEstimatedThreshold();
381 }
382 }