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