1 /*
2 * Copyright (C) 2016 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.calibration.estimators;
17
18 import com.irurueta.ar.calibration.DualAbsoluteQuadric;
19 import com.irurueta.geometry.PinholeCamera;
20 import com.irurueta.geometry.estimators.LockedException;
21 import com.irurueta.geometry.estimators.NotReadyException;
22 import com.irurueta.numerical.robust.PROMedSRobustEstimator;
23 import com.irurueta.numerical.robust.PROMedSRobustEstimatorListener;
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 dual absolute quadric (DAQ) for provided collection of
33 * cameras using PROMedS algorithm.
34 */
35 public class PROMedSDualAbsoluteQuadricRobustEstimator extends DualAbsoluteQuadricRobustEstimator {
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 * Threshold is defined by the equations used to estimate the DAQ depending
43 * on the required settings (zero skewness, principal point at origin, and
44 * known aspect ratio).
45 */
46 public static final double DEFAULT_STOP_THRESHOLD = 1e-6;
47
48 /**
49 * Minimum value that can be set as stop threshold.
50 * Threshold must be strictly greater than 0.0.
51 */
52 public static final double MIN_STOP_THRESHOLD = 0.0;
53
54 /**
55 * Threshold to be used to keep the algorithm iterating in case that best
56 * estimated threshold using median of residuals is not small enough. Once
57 * a solution is found that generates a threshold below this value, the
58 * algorithm will stop.
59 * The stop threshold can be used to prevent the PROMedS algorithm iterating
60 * too many times in cases where samples have a very similar accuracy.
61 * For instance, in cases where proportion of inliers is very small (close
62 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
63 * iterate for a long time trying to find the best solution when indeed
64 * there is no need to do that if a reasonable threshold has already been
65 * reached.
66 * Because of this behaviour the stop threshold can be set to a value much
67 * lower than the one typically used in RANSAC, and yet the algorithm could
68 * still produce even smaller thresholds in estimated results.
69 */
70 private double stopThreshold;
71
72 /**
73 * Quality scores corresponding to each provided camera.
74 * The larger the score value the better the quality of the sample.
75 */
76 private double[] qualityScores;
77
78 /**
79 * Constructor.
80 */
81 public PROMedSDualAbsoluteQuadricRobustEstimator() {
82 super();
83 stopThreshold = DEFAULT_STOP_THRESHOLD;
84 }
85
86 /**
87 * Constructor.
88 *
89 * @param listener listener to be notified of events such as when estimation
90 * starts, ends or its progress significantly changes.
91 */
92 public PROMedSDualAbsoluteQuadricRobustEstimator(final DualAbsoluteQuadricRobustEstimatorListener listener) {
93 super(listener);
94 stopThreshold = DEFAULT_STOP_THRESHOLD;
95 }
96
97 /**
98 * Constructor.
99 *
100 * @param cameras list of cameras used to estimate the dual absolute quadric
101 * (DAC), which can be used to obtain pinhole camera intrinsic parameters.
102 * @throws IllegalArgumentException if not enough cameras are provided for
103 * default settings. Hence, at least 2 cameras must be provided.
104 */
105 public PROMedSDualAbsoluteQuadricRobustEstimator(final List<PinholeCamera> cameras) {
106 super(cameras);
107 stopThreshold = DEFAULT_STOP_THRESHOLD;
108 }
109
110 /**
111 * Constructor.
112 *
113 * @param cameras list of cameras used to estimate the dual absolute quadric
114 * (DAQ), which can be used to obtain pinhole camera intrinsic parameters.
115 * @param listener listener to be notified of events such as when estimation
116 * starts, ends or estimation progress changes.
117 * @throws IllegalArgumentException if not enough cameras are provided for
118 * default settings. Hence, at least 2 cameras must be provided.
119 */
120 public PROMedSDualAbsoluteQuadricRobustEstimator(
121 final List<PinholeCamera> cameras, final DualAbsoluteQuadricRobustEstimatorListener listener) {
122 super(cameras, listener);
123 stopThreshold = DEFAULT_STOP_THRESHOLD;
124 }
125
126 /**
127 * Constructor.
128 *
129 * @param qualityScores quality scores corresponding to each provided
130 * camera.
131 * @throws IllegalArgumentException if provided quality scores length is
132 * smaller than required number of homographies for default
133 * settings (i.e. 2 cameras).
134 */
135 public PROMedSDualAbsoluteQuadricRobustEstimator(final double[] qualityScores) {
136 this();
137 internalSetQualityScores(qualityScores);
138 }
139
140 /**
141 * Constructor.
142 *
143 * @param qualityScores quality scores corresponding to each provided
144 * camera.
145 * @param listener listener to be notified of events such as when estimation
146 * starts, ends or its progress significantly changes.
147 * @throws IllegalArgumentException if provided quality scores length is
148 * smaller than required number of cameras for default settings (i.e.
149 * 2 cameras).
150 */
151 public PROMedSDualAbsoluteQuadricRobustEstimator(
152 final double[] qualityScores, final DualAbsoluteQuadricRobustEstimatorListener listener) {
153 this(listener);
154 internalSetQualityScores(qualityScores);
155 }
156
157 /**
158 * Constructor.
159 *
160 * @param cameras list of cameras used to estimate the dual absolute quadric
161 * (DAQ), which can be used to obtain pinhole camera intrinsic parameters.
162 * @param qualityScores quality scores corresponding to each provided
163 * camera.
164 * @throws IllegalArgumentException if not enough cameras are provided for
165 * default settings (i.e. 2 cameras) or quality scores and cameras
166 * don't have the same size.
167 */
168 public PROMedSDualAbsoluteQuadricRobustEstimator(final List<PinholeCamera> cameras, final double[] qualityScores) {
169 this(cameras);
170 internalSetQualityScores(qualityScores);
171 }
172
173 /**
174 * Constructor.
175 *
176 * @param cameras list of cameras used to estimate the dual absolute quadric
177 * (DAQ), which can be used to obtain pinhole camera intrinsic parameters.
178 * @param qualityScores quality scores corresponding to each provided
179 * camera.
180 * @param listener listener to be notified of events such as when estimation
181 * starts, ends or estimation progress changes.
182 * @throws IllegalArgumentException if not enough cameras are provided for
183 * default settings (i.e. 2 cameras) or quality scores and cameras
184 * don't have the same size.
185 */
186 public PROMedSDualAbsoluteQuadricRobustEstimator(
187 final List<PinholeCamera> cameras, final double[] qualityScores,
188 final DualAbsoluteQuadricRobustEstimatorListener listener) {
189 this(cameras, listener);
190 internalSetQualityScores(qualityScores);
191 }
192
193 /**
194 * Returns threshold to be used to keep the algorithm iterating in case that
195 * best estimated threshold using median of residuals is not small enough.
196 * Once a solution is found that generates a threshold below this value, the
197 * algorithm will stop.
198 * The stop threshold can be used to prevent the PROMedS algorithm iterating
199 * too many times in cases where samples have a very similar accuracy.
200 * For instance, in cases where proportion of outliers is very small (close
201 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
202 * iterate for a long time trying to find the best solution when indeed
203 * there is no need to do that if a reasonable threshold has already been
204 * reached.
205 * Because of this behaviour the stop threshold can be set to a value much
206 * lower than the one typically used in RANSAC, and yet the algorithm could
207 * still produce even smaller thresholds in estimated results.
208 *
209 * @return stop threshold to stop the algorithm prematurely when a certain
210 * accuracy has been reached.
211 */
212 public double getStopThreshold() {
213 return stopThreshold;
214 }
215
216 /**
217 * Sets threshold to be used to keep the algorithm iterating in case that
218 * best estimated threshold using median of residuals is not small enough.
219 * Once a solution is found that generates a threshold below this value, the
220 * algorithm will stop.
221 * The stop threshold can be used to prevent the PROMedS algorithm iterating
222 * too many times in cases where samples have a very similar accuracy.
223 * For instance, in cases where proportion of outliers is very small (close
224 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
225 * iterate for a long time trying to find the best solution when indeed
226 * there is no need to do that if a reasonable threshold has already been
227 * reached.
228 * Because of this behaviour the stop threshold can be set to a value much
229 * lower than the one typically used in RANSAC, and yet the algorithm could
230 * still produce even smaller thresholds in estimated results.
231 *
232 * @param stopThreshold stop threshold to stop the algorithm prematurely
233 * when a certain accuracy has been reached.
234 * @throws IllegalArgumentException if provided value is zero or negative.
235 * @throws LockedException if robust estimator is locked because an
236 * estimation is already in progress.
237 */
238 public void setStopThreshold(final double stopThreshold) throws LockedException {
239 if (isLocked()) {
240 throw new LockedException();
241 }
242 if (stopThreshold <= MIN_STOP_THRESHOLD) {
243 throw new IllegalArgumentException();
244 }
245 this.stopThreshold = stopThreshold;
246 }
247
248 /**
249 * Returns quality scores corresponding to each provided camera.
250 * The larger the score value the better the quality of the sampled
251 * camera.
252 *
253 * @return quality scores corresponding to each camera.
254 */
255 @Override
256 public double[] getQualityScores() {
257 return qualityScores;
258 }
259
260 /**
261 * Sets quality scores corresponding to each provided camera.
262 * The larger the score value the better the quality of the sampled
263 * camera.
264 *
265 * @param qualityScores quality scores corresponding to each camera.
266 * @throws LockedException if robust estimator is locked because an
267 * estimation is already in progress.
268 * @throws IllegalArgumentException if provided quality scores length is
269 * smaller than minimum required number of cameras (i.e. 2 cameras).
270 */
271 @Override
272 public void setQualityScores(final double[] qualityScores) throws LockedException {
273 if (isLocked()) {
274 throw new LockedException();
275 }
276 internalSetQualityScores(qualityScores);
277 }
278
279 /**
280 * Indicates if estimator is ready to start the DAQ estimation.
281 * This is true when input data (i.e. cameras) is provided and list contains
282 * at least the minimum number of required cameras, and also quality scores
283 * are provided.
284 *
285 * @return true if estimator is ready, false otherwise.
286 */
287 @Override
288 public boolean isReady() {
289 return super.isReady() && qualityScores != null && qualityScores.length == cameras.size();
290 }
291
292 /**
293 * Estimates the Dual Absolute Quadric using provided cameras.
294 *
295 * @return estimated Dual Absolute Quadric (DAQ).
296 * @throws LockedException if robust estimator is locked.
297 * @throws NotReadyException if no valid input data has already been
298 * provided.
299 * @throws RobustEstimatorException if estimation fails for any reason
300 * (i.e. numerical instability, no solution available, etc).
301 */
302 @SuppressWarnings("DuplicatedCode")
303 @Override
304 public DualAbsoluteQuadric estimate() throws LockedException, NotReadyException, RobustEstimatorException {
305 if (isLocked()) {
306 throw new LockedException();
307 }
308 if (!isReady()) {
309 throw new NotReadyException();
310 }
311
312 final var innerEstimator = new PROMedSRobustEstimator<DualAbsoluteQuadric>(new PROMedSRobustEstimatorListener<>() {
313
314 // subset of cameras picked on each iteration
315 private final List<PinholeCamera> subsetCameras = new ArrayList<>();
316
317 @Override
318 public double getThreshold() {
319 return stopThreshold;
320 }
321
322 @Override
323 public int getTotalSamples() {
324 return cameras.size();
325 }
326
327 @Override
328 public int getSubsetSize() {
329 return daqEstimator.getMinNumberOfRequiredCameras();
330 }
331
332 @Override
333 public void estimatePreliminarSolutions(
334 final int[] samplesIndices, final List<DualAbsoluteQuadric> solutions) {
335 subsetCameras.clear();
336 for (final var samplesIndex : samplesIndices) {
337 subsetCameras.add(cameras.get(samplesIndex));
338 }
339
340 try {
341 daqEstimator.setLMSESolutionAllowed(false);
342 daqEstimator.setCameras(subsetCameras);
343
344 final var daq = daqEstimator.estimate();
345 solutions.add(daq);
346 } catch (final Exception e) {
347 // if anything fails, no solution is added
348 }
349 }
350
351 @Override
352 public double computeResidual(final DualAbsoluteQuadric currentEstimation, final int i) {
353 return residual(currentEstimation, cameras.get(i));
354 }
355
356 @Override
357 public boolean isReady() {
358 return PROMedSDualAbsoluteQuadricRobustEstimator.this.isReady();
359 }
360
361 @Override
362 public void onEstimateStart(final RobustEstimator<DualAbsoluteQuadric> estimator) {
363 if (listener != null) {
364 listener.onEstimateStart(PROMedSDualAbsoluteQuadricRobustEstimator.this);
365 }
366 }
367
368 @Override
369 public void onEstimateEnd(final RobustEstimator<DualAbsoluteQuadric> estimator) {
370 if (listener != null) {
371 listener.onEstimateEnd(PROMedSDualAbsoluteQuadricRobustEstimator.this);
372 }
373 }
374
375 @Override
376 public void onEstimateNextIteration(
377 final RobustEstimator<DualAbsoluteQuadric> estimator, final int iteration) {
378 if (listener != null) {
379 listener.onEstimateNextIteration(PROMedSDualAbsoluteQuadricRobustEstimator.this,
380 iteration);
381 }
382 }
383
384 @Override
385 public void onEstimateProgressChange(
386 final RobustEstimator<DualAbsoluteQuadric> estimator, final float progress) {
387 if (listener != null) {
388 listener.onEstimateProgressChange(PROMedSDualAbsoluteQuadricRobustEstimator.this,
389 progress);
390 }
391 }
392
393 @Override
394 public double[] getQualityScores() {
395 return qualityScores;
396 }
397 });
398
399 try {
400 locked = true;
401 innerEstimator.setConfidence(confidence);
402 innerEstimator.setMaxIterations(maxIterations);
403 innerEstimator.setProgressDelta(progressDelta);
404 return innerEstimator.estimate();
405 } catch (final com.irurueta.numerical.LockedException e) {
406 throw new LockedException(e);
407 } catch (final com.irurueta.numerical.NotReadyException e) {
408 throw new NotReadyException(e);
409 } finally {
410 locked = false;
411 }
412 }
413
414 /**
415 * Returns method being used for robust estimation.
416 *
417 * @return method being used for robust estimation.
418 */
419 @Override
420 public RobustEstimatorMethod getMethod() {
421 return RobustEstimatorMethod.PROMEDS;
422 }
423
424 /**
425 * Sets quality scores corresponding to each camera.
426 * This method is used internally and does not check whether instance is
427 * locked or not.
428 *
429 * @param qualityScores quality scores to be set.
430 * @throws IllegalArgumentException if provided quality scores length is
431 * smaller than the minimum number of required homographies for
432 * current settings.
433 */
434 private void internalSetQualityScores(final double[] qualityScores) {
435 if (qualityScores.length < daqEstimator.getMinNumberOfRequiredCameras()) {
436 throw new IllegalArgumentException();
437 }
438
439 this.qualityScores = qualityScores;
440 }
441 }