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 }