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.MSACRobustEstimator;
23  import com.irurueta.numerical.robust.MSACRobustEstimatorListener;
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 MSAC algorithm.
34   */
35  public class MSACDualAbsoluteQuadricRobustEstimator extends DualAbsoluteQuadricRobustEstimator {
36  
37      /**
38       * Constant defining default threshold to determine whether cameras are
39       * inliers or not.
40       * Threshold is defined by the equations used to estimate the DAQ depending
41       * on the required settings (zero skewness, principal point at origin, and
42       * known aspect ratio).
43       */
44      public static final double DEFAULT_THRESHOLD = 1e-6;
45  
46      /**
47       * Minimum value that can be set as threshold.
48       * Threshold must be strictly greater than 0.0.
49       */
50      public static final double MIN_THRESHOLD = 0.0;
51  
52      /**
53       * Threshold to determine whether cameras are inliers or not when
54       * testing possible estimation solutions.
55       * The threshold refers to the amount of error a possible solution has.
56       */
57      private double threshold;
58  
59      /**
60       * Constructor.
61       */
62      public MSACDualAbsoluteQuadricRobustEstimator() {
63          super();
64          threshold = DEFAULT_THRESHOLD;
65      }
66  
67      /**
68       * Constructor.
69       *
70       * @param listener listener to be notified of events such as when
71       *                 estimation starts, ends or its progress significantly changes.
72       */
73      public MSACDualAbsoluteQuadricRobustEstimator(final DualAbsoluteQuadricRobustEstimatorListener listener) {
74          super(listener);
75          threshold = DEFAULT_THRESHOLD;
76      }
77  
78      /**
79       * Constructor.
80       *
81       * @param cameras list of cameras used to estimate the dual absolute quadric
82       *                (DAQ), which can be used to obtain pinhole camera intrinsic parameters.
83       * @throws IllegalArgumentException if not enough cameras are provided for
84       *                                  default settings. Hence, at least 2 cameras must be provided.
85       */
86      public MSACDualAbsoluteQuadricRobustEstimator(final List<PinholeCamera> cameras) {
87          super(cameras);
88          threshold = DEFAULT_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       * @param listener listener to be notified of events such as when
97       *                 estimation starts, ends or its progress significantly changes.
98       * @throws IllegalArgumentException if not enough cameras are provided for
99       *                                  default settings. Hence, at least 2 cameras must be provided.
100      */
101     public MSACDualAbsoluteQuadricRobustEstimator(
102             final List<PinholeCamera> cameras, final DualAbsoluteQuadricRobustEstimatorListener listener) {
103         super(cameras, listener);
104         threshold = DEFAULT_THRESHOLD;
105     }
106 
107     /**
108      * Returns threshold to determine whether cameras are inliers or not
109      * when testing possible estimation solutions.
110      * The threshold refers to the amount of error a possible solution has.
111      *
112      * @return threshold to determine whether cameras are inliers when testing
113      * possible estimation solutions.
114      */
115     public double getThreshold() {
116         return threshold;
117     }
118 
119     /**
120      * Sets threshold to determine whether cameras are inliers or not when
121      * testing possible estimation solutions.
122      * The threshold refers to the amount of error a possible solution has.
123      *
124      * @param threshold threshold to determine whether cameras are inliers or
125      *                  not when testing possible estimation solutions.
126      * @throws IllegalArgumentException if provided value is equal or less than
127      *                                  zero.
128      * @throws LockedException          if robust estimator is locked because an
129      *                                  estimation is already in progress.
130      */
131     public void setThreshold(final double threshold) throws LockedException {
132         if (isLocked()) {
133             throw new LockedException();
134         }
135         if (threshold <= MIN_THRESHOLD) {
136             throw new IllegalArgumentException();
137         }
138         this.threshold = threshold;
139     }
140 
141     /**
142      * Estimates the Dual Absolute Quadric using provided cameras.
143      *
144      * @return estimated Dual Absolute Quadric (DAQ).
145      * @throws LockedException          if robust estimator is locked.
146      * @throws NotReadyException        if no valid input data has already been
147      *                                  provided.
148      * @throws RobustEstimatorException if estimation fails for any reason
149      *                                  (i.e. numerical instability, no solution available, etc).
150      */
151     @SuppressWarnings("DuplicatedCode")
152     @Override
153     public DualAbsoluteQuadric estimate() throws LockedException, NotReadyException, RobustEstimatorException {
154         if (isLocked()) {
155             throw new LockedException();
156         }
157         if (!isReady()) {
158             throw new NotReadyException();
159         }
160 
161         final var innerEstimator = new MSACRobustEstimator<DualAbsoluteQuadric>(new MSACRobustEstimatorListener<>() {
162 
163             // subset of cameras picked on each iteration
164             private final List<PinholeCamera> subsetCameras = new ArrayList<>();
165 
166             @Override
167             public double getThreshold() {
168                 return threshold;
169             }
170 
171             @Override
172             public int getTotalSamples() {
173                 return cameras.size();
174             }
175 
176             @Override
177             public int getSubsetSize() {
178                 return daqEstimator.getMinNumberOfRequiredCameras();
179             }
180 
181             @Override
182             public void estimatePreliminarSolutions(
183                     final int[] samplesIndices, final List<DualAbsoluteQuadric> solutions) {
184                 subsetCameras.clear();
185                 for (final var samplesIndex : samplesIndices) {
186                     subsetCameras.add(cameras.get(samplesIndex));
187                 }
188 
189                 try {
190                     daqEstimator.setLMSESolutionAllowed(false);
191                     daqEstimator.setCameras(subsetCameras);
192 
193                     final var daq = daqEstimator.estimate();
194                     solutions.add(daq);
195                 } catch (final Exception e) {
196                     // if anything fails, no solution is added
197                 }
198             }
199 
200             @Override
201             public double computeResidual(final DualAbsoluteQuadric currentEstimation, final int i) {
202                 return residual(currentEstimation, cameras.get(i));
203             }
204 
205             @Override
206             public boolean isReady() {
207                 return MSACDualAbsoluteQuadricRobustEstimator.this.isReady();
208             }
209 
210             @Override
211             public void onEstimateStart(final RobustEstimator<DualAbsoluteQuadric> estimator) {
212                 if (listener != null) {
213                     listener.onEstimateStart(MSACDualAbsoluteQuadricRobustEstimator.this);
214                 }
215             }
216 
217             @Override
218             public void onEstimateEnd(final RobustEstimator<DualAbsoluteQuadric> estimator) {
219                 if (listener != null) {
220                     listener.onEstimateEnd(MSACDualAbsoluteQuadricRobustEstimator.this);
221                 }
222             }
223 
224             @Override
225             public void onEstimateNextIteration(
226                     final RobustEstimator<DualAbsoluteQuadric> estimator, final int iteration) {
227                 if (listener != null) {
228                     listener.onEstimateNextIteration(MSACDualAbsoluteQuadricRobustEstimator.this, iteration);
229                 }
230             }
231 
232             @Override
233             public void onEstimateProgressChange(
234                     final RobustEstimator<DualAbsoluteQuadric> estimator, final float progress) {
235                 if (listener != null) {
236                     listener.onEstimateProgressChange(MSACDualAbsoluteQuadricRobustEstimator.this, progress);
237                 }
238             }
239         });
240 
241         try {
242             locked = true;
243             innerEstimator.setConfidence(confidence);
244             innerEstimator.setMaxIterations(maxIterations);
245             innerEstimator.setProgressDelta(progressDelta);
246             return innerEstimator.estimate();
247         } catch (final com.irurueta.numerical.LockedException e) {
248             throw new LockedException(e);
249         } catch (final com.irurueta.numerical.NotReadyException e) {
250             throw new NotReadyException(e);
251         } finally {
252             locked = false;
253         }
254     }
255 
256     /**
257      * Returns method being used for robust estimation.
258      *
259      * @return method being used for robust estimation.
260      */
261     @Override
262     public RobustEstimatorMethod getMethod() {
263         return RobustEstimatorMethod.MSAC;
264     }
265 }