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 }