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 }