View Javadoc
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 }