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.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 Dual Absolute Quadric (DAQ) for provided collection of cameras
33   * using LMedS algorithm.
34   */
35  public class LMedSDualAbsoluteQuadricRobustEstimator 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 LMedS algorithm iterating
60       * too many times in cases where samples have a very similar accuracy.
61       * For instance, in cases where proportion of outliers 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 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       * Constructor.
74       */
75      public LMedSDualAbsoluteQuadricRobustEstimator() {
76          super();
77          stopThreshold = DEFAULT_STOP_THRESHOLD;
78      }
79  
80      /**
81       * Constructor.
82       *
83       * @param listener listener to be notified of events such as when estimation
84       *                 starts, ends or its progress significantly changes.
85       */
86      public LMedSDualAbsoluteQuadricRobustEstimator(final DualAbsoluteQuadricRobustEstimatorListener listener) {
87          super(listener);
88          stopThreshold = DEFAULT_STOP_THRESHOLD;
89      }
90  
91      /**
92       * Constructor.
93       *
94       * @param cameras list of cameras used to estimate the Dual Absolute Quadric
95       *                (DAQ), which can be used to obtain pinhole camera intrinsic parameters.
96       * @throws IllegalArgumentException if not enough cameras are provided
97       *                                  for default settings. Hence, at least 2 cameras must be provided.
98       */
99      public LMedSDualAbsoluteQuadricRobustEstimator(final List<PinholeCamera> cameras) {
100         super(cameras);
101         stopThreshold = DEFAULT_STOP_THRESHOLD;
102     }
103 
104     /**
105      * Constructor.
106      *
107      * @param cameras  list of cameras used to estimate the Dual AbsoluteQuadric
108      *                 (DAQ), which can be used to obtain pinhole camera intrinsic parameters.
109      * @param listener listener to be notified of events such as when estimation
110      *                 starts, ends or its progress significantly changes.
111      * @throws IllegalArgumentException if not enough cameras are provided
112      *                                  for default settings. Hence, at least 2 cameras must be provided.
113      */
114     public LMedSDualAbsoluteQuadricRobustEstimator(
115             final List<PinholeCamera> cameras, final DualAbsoluteQuadricRobustEstimatorListener listener) {
116         super(cameras, listener);
117         stopThreshold = DEFAULT_STOP_THRESHOLD;
118     }
119 
120     /**
121      * Returns threshold to be used to keep the algorithm iterating in case that
122      * best estimated threshold using median of residuals is not small enough.
123      * Once a solution is found that generates a threshold below this value, the
124      * algorithm will stop.
125      * The stop threshold can be used to prevent the LMedS algorithm iterating
126      * too many times in cases where samples have a very similar accuracy.
127      * For instance, in cases where proportion of outliers is very small (close
128      * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
129      * iterate for a long time trying to find the best solution when indeed
130      * there is no need to do that if a reasonable threshold has already been
131      * reached.
132      * Because of this behaviour the stop threshold can be set to a value much
133      * lower than the one typically used in RANSAC, and yet the algorithm could
134      * still produce even smaller thresholds in estimated results.
135      *
136      * @return stop threshold to stop the algorithm prematurely when a certain
137      * accuracy has been reached.
138      */
139     public double getStopThreshold() {
140         return stopThreshold;
141     }
142 
143     /**
144      * Sets threshold to be used to keep the algorithm iterating in case that
145      * best estimated threshold using median of residuals is not small enough.
146      * Once a solution is found that generates a threshold below this value, the
147      * algorithm will stop.
148      * The stop threshold can be used to prevent the LMedS algorithm iterating
149      * too many times in cases where samples have a very similar accuracy.
150      * For instance, in cases where proportion of outliers is very small (close
151      * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
152      * iterate for a long time trying to find the best solution when indeed
153      * there is no need to do tat if a reasonable threshold has already been
154      * reached.
155      * Because of this behaviour the stop threshold can be set to a value
156      * lower than the one typically used in RANSAC, and yet the algorithm still
157      * produce even smaller thresholds in estimated results.
158      *
159      * @param stopThreshold stop threshold to stop the algorithm prematurely
160      *                      when a certain accuracy has been reached.
161      * @throws IllegalArgumentException if provided value is zero or negative.
162      * @throws LockedException          if robust estimator is locked because an
163      *                                  estimation is already in progress.
164      */
165     public void setStopThreshold(final double stopThreshold) throws LockedException {
166         if (isLocked()) {
167             throw new LockedException();
168         }
169         if (stopThreshold <= MIN_STOP_THRESHOLD) {
170             throw new IllegalArgumentException();
171         }
172         this.stopThreshold = stopThreshold;
173     }
174 
175     /**
176      * Estimates the Dual Absolute Quadric using provided cameras.
177      *
178      * @return estimated Dual Absolute Quadric (DAQ).
179      * @throws LockedException          if robust estimator is locked.
180      * @throws NotReadyException        if no valid input data has already been
181      *                                  provided.
182      * @throws RobustEstimatorException if estimation fails for any reason
183      *                                  (i.e. numerical instability, no solution available, etc).
184      */
185     @SuppressWarnings("DuplicatedCode")
186     @Override
187     public DualAbsoluteQuadric estimate() throws LockedException, NotReadyException, RobustEstimatorException {
188         if (isLocked()) {
189             throw new LockedException();
190         }
191         if (!isReady()) {
192             throw new NotReadyException();
193         }
194 
195         final var innerEstimator = new LMedSRobustEstimator<DualAbsoluteQuadric>(new LMedSRobustEstimatorListener<>() {
196 
197             // subset of cameras picked on each iteration
198             private final List<PinholeCamera> subsetCameras = new ArrayList<>();
199 
200             @Override
201             public int getTotalSamples() {
202                 return cameras.size();
203             }
204 
205             @Override
206             public int getSubsetSize() {
207                 return daqEstimator.getMinNumberOfRequiredCameras();
208             }
209 
210             @Override
211             public void estimatePreliminarSolutions(
212                     final int[] samplesIndices, final List<DualAbsoluteQuadric> solutions) {
213                 subsetCameras.clear();
214                 for (var samplesIndex : samplesIndices) {
215                     subsetCameras.add(cameras.get(samplesIndex));
216                 }
217 
218                 try {
219                     daqEstimator.setLMSESolutionAllowed(false);
220                     daqEstimator.setCameras(subsetCameras);
221 
222                     var daq = daqEstimator.estimate();
223                     solutions.add(daq);
224                 } catch (final Exception e) {
225                     // if anything fails, no solution is added
226                 }
227             }
228 
229             @Override
230             public double computeResidual(final DualAbsoluteQuadric currentEstimation, final int i) {
231                 return residual(currentEstimation, cameras.get(i));
232             }
233 
234             @Override
235             public boolean isReady() {
236                 return LMedSDualAbsoluteQuadricRobustEstimator.this.isReady();
237             }
238 
239             @Override
240             public void onEstimateStart(final RobustEstimator<DualAbsoluteQuadric> estimator) {
241                 if (listener != null) {
242                     listener.onEstimateStart(LMedSDualAbsoluteQuadricRobustEstimator.this);
243                 }
244             }
245 
246             @Override
247             public void onEstimateEnd(final RobustEstimator<DualAbsoluteQuadric> estimator) {
248                 if (listener != null) {
249                     listener.onEstimateEnd(LMedSDualAbsoluteQuadricRobustEstimator.this);
250                 }
251             }
252 
253             @Override
254             public void onEstimateNextIteration(
255                     final RobustEstimator<DualAbsoluteQuadric> estimator, final int iteration) {
256                 if (listener != null) {
257                     listener.onEstimateNextIteration(LMedSDualAbsoluteQuadricRobustEstimator.this, iteration);
258                 }
259             }
260 
261             @Override
262             public void onEstimateProgressChange(
263                     final RobustEstimator<DualAbsoluteQuadric> estimator, final float progress) {
264                 if (listener != null) {
265                     listener.onEstimateProgressChange(LMedSDualAbsoluteQuadricRobustEstimator.this, progress);
266                 }
267             }
268         });
269 
270         try {
271             locked = true;
272             innerEstimator.setConfidence(confidence);
273             innerEstimator.setMaxIterations(maxIterations);
274             innerEstimator.setProgressDelta(progressDelta);
275             innerEstimator.setStopThreshold(stopThreshold);
276             return innerEstimator.estimate();
277         } catch (final com.irurueta.numerical.LockedException e) {
278             throw new LockedException(e);
279         } catch (final com.irurueta.numerical.NotReadyException e) {
280             throw new NotReadyException(e);
281         } finally {
282             locked = false;
283         }
284     }
285 
286     /**
287      * Returns method being used for robust estimation.
288      *
289      * @return method being used for robust estimation.
290      */
291     @Override
292     public RobustEstimatorMethod getMethod() {
293         return RobustEstimatorMethod.LMEDS;
294     }
295 }