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.geometry.estimators;
17
18 import com.irurueta.geometry.Line2D;
19 import com.irurueta.geometry.PinholeCamera;
20 import com.irurueta.geometry.Plane;
21 import com.irurueta.numerical.robust.MSACRobustEstimator;
22 import com.irurueta.numerical.robust.MSACRobustEstimatorListener;
23 import com.irurueta.numerical.robust.RobustEstimator;
24 import com.irurueta.numerical.robust.RobustEstimatorException;
25 import com.irurueta.numerical.robust.RobustEstimatorMethod;
26
27 import java.util.ArrayList;
28 import java.util.List;
29
30 /**
31 * Finds the best pinhole camera for provided collections of matched lines and
32 * planes using MSAC algorithm.
33 */
34 @SuppressWarnings("DuplicatedCode")
35 public class MSACDLTLinePlaneCorrespondencePinholeCameraRobustEstimator extends
36 DLTLinePlaneCorrespondencePinholeCameraRobustEstimator {
37
38 /**
39 * Constant defining default threshold to determine whether planes are
40 * inliers or not.
41 * Residuals to determine whether planes are inliers or not are computed by
42 * comparing two planes algebraically (e.g. doing the dot product of their
43 * parameters).
44 * A residual of 0 indicates that dot product was 1 or -1 and lines were
45 * equal.
46 * A residual of 1 indicates that dot product was 0 and lines were
47 * orthogonal.
48 * If dot product between lines is -1, then although their director vectors
49 * are opposed, lines are considered equal, since sign changes are not taken
50 * into account and their residuals will be 0.
51 */
52 public static final double DEFAULT_THRESHOLD = 1e-6;
53
54 /**
55 * Minimum value that can be set as threshold.
56 * Threshold must be strictly greater than 0.0.
57 */
58 public static final double MIN_THRESHOLD = 0.0;
59
60 /**
61 * Threshold to determine whether planes are inliers or not when testing
62 * possible estimation solutions.
63 * The threshold refers to the amount of error a possible solution has on a
64 * plane respect the back-projected plane of a line using estimated camera.
65 */
66 private double threshold;
67
68 /**
69 * Constructor.
70 */
71 public MSACDLTLinePlaneCorrespondencePinholeCameraRobustEstimator() {
72 super();
73 threshold = DEFAULT_THRESHOLD;
74 }
75
76 /**
77 * Constructor with lists of matched planes and 2D lines to estimate a
78 * pinhole camera.
79 * Points and lines in the lists located at the same position are considered
80 * to be matched. Hence, both lists must have the same size, and their size
81 * must be greater or equal than MIN_NUMBER_OF_LINE_PLANE_CORRESPONDENCES
82 * (4 matches).
83 *
84 * @param planes list of planes used to estimate a pinhole camera.
85 * @param lines list of corresponding projected 2D lines used to estimate
86 * a pinhole camera.
87 * @throws IllegalArgumentException if provided lists don't have the same
88 * size or their size is smaller than required minimum size (4 matches).
89 */
90 public MSACDLTLinePlaneCorrespondencePinholeCameraRobustEstimator(
91 final List<Plane> planes, final List<Line2D> lines) {
92 super(planes, lines);
93 threshold = DEFAULT_THRESHOLD;
94 }
95
96 /**
97 * Constructor with listener.
98 *
99 * @param listener listener to be notified of events such as when estimation
100 * starts, ends or its progress significantly changes.
101 */
102 public MSACDLTLinePlaneCorrespondencePinholeCameraRobustEstimator(
103 final PinholeCameraRobustEstimatorListener listener) {
104 super(listener);
105 threshold = DEFAULT_THRESHOLD;
106 }
107
108 /**
109 * Constructor with listener and lists of matched planes and 2D lines to
110 * estimate a pinhole camera.
111 * Points and lines in the lists located at the same position are considered
112 * to be matched. Hence, both lists must have the same size, and their size
113 * must be greater or equal than MIN_NUMBER_OF_LINE_PLANE_CORRESPONDENCES
114 * (4 matches).
115 *
116 * @param listener listener to be notified of events such as when estimation
117 * starts, ends or its progress significantly changes.
118 * @param planes list of planes used to estimate a pinhole camera.
119 * @param lines list of corresponding projected 2D lines used to estimate
120 * a pinhole camera.
121 * @throws IllegalArgumentException if provided lists don't have the same
122 * size or their size is smaller than required minimum size (4 matches).
123 */
124 public MSACDLTLinePlaneCorrespondencePinholeCameraRobustEstimator(
125 final PinholeCameraRobustEstimatorListener listener, final List<Plane> planes, final List<Line2D> lines) {
126 super(listener, planes, lines);
127 threshold = DEFAULT_THRESHOLD;
128 }
129
130 /**
131 * Returns threshold to determine whether planes are inliers or not when
132 * testing possible estimation solutions.
133 * The threshold refers to the amount of error a possible solution has on a
134 * plane respect the back-projected plane of a line using estimated camera
135 * Residuals to determine whether planes are inliers or not are computed by
136 * comparing two planes algebraically (e.g. doing the dot product of their
137 * parameters).
138 * A residual of 0 indicates that dot product was 1 or -1 and planes were
139 * equal.
140 * A residual of 1 indicates that dot product was 0 and planes were
141 * orthogonal.
142 * If dot product between planes is -1, then although their director vectors
143 * are opposed, planes are considered equal, since sign changes are not
144 * taken into account and their residuals will be 0.
145 *
146 * @return threshold to determine whether matched planes are inliers or not.
147 */
148 public double getThreshold() {
149 return threshold;
150 }
151
152 /**
153 * Sets threshold to determine whether planes are inliers or not when
154 * testing possible estimation solutions.
155 * The threshold refers to the amount of error a possible solution has on a
156 * plane respect the back-projected plane of a line using estimated camera
157 * Residuals to determine whether planes are inliers or not are computed by
158 * comparing two planes algebraically (e.g. doing the dot product of their
159 * parameters).
160 * A residual of 0 indicates that dot product was 1 or -1 and planes were
161 * equal.
162 * A residual of 1 indicates that dot product was 0 and planes were
163 * orthogonal.
164 * If dot product between planes is -1, then although their director vectors
165 * are opposed, planes are considered equal, since sign changes are not
166 * taken into account and their residuals will be 0.
167 *
168 * @param threshold threshold to determine whether matched planes are
169 * inliers or not.
170 * @throws IllegalArgumentException if provided value is equal or less than
171 * zero.
172 * @throws LockedException if robust estimator is locked because an
173 * estimation is already in progress.
174 */
175 public void setThreshold(final double threshold) throws LockedException {
176 if (isLocked()) {
177 throw new LockedException();
178 }
179 if (threshold <= MIN_THRESHOLD) {
180 throw new IllegalArgumentException();
181 }
182 this.threshold = threshold;
183 }
184
185 /**
186 * Estimates a pinhole camera using a robust estimator and
187 * the best set of matched 2D line/3D plane correspondences found using the
188 * robust estimator.
189 *
190 * @return a pinhole camera.
191 * @throws LockedException if robust estimator is locked because an
192 * estimation is already in progress.
193 * @throws NotReadyException if provided input data is not enough to start
194 * the estimation.
195 * @throws RobustEstimatorException if estimation fails for any reason
196 * (i.e. numerical instability, no solution available, etc).
197 */
198 @Override
199 public PinholeCamera estimate() throws LockedException, NotReadyException, RobustEstimatorException {
200 if (isLocked()) {
201 throw new LockedException();
202 }
203 if (!isReady()) {
204 throw new NotReadyException();
205 }
206
207 // pinhole camera estimator using DLT (Direct Linear Transform) algorithm
208 final var nonRobustEstimator = new DLTLinePlaneCorrespondencePinholeCameraEstimator();
209
210 nonRobustEstimator.setLMSESolutionAllowed(false);
211
212 // suggestions
213 nonRobustEstimator.setSuggestSkewnessValueEnabled(isSuggestSkewnessValueEnabled());
214 nonRobustEstimator.setSuggestedSkewnessValue(getSuggestedSkewnessValue());
215 nonRobustEstimator.setSuggestHorizontalFocalLengthEnabled(isSuggestHorizontalFocalLengthEnabled());
216 nonRobustEstimator.setSuggestedHorizontalFocalLengthValue(getSuggestedHorizontalFocalLengthValue());
217 nonRobustEstimator.setSuggestVerticalFocalLengthEnabled(isSuggestVerticalFocalLengthEnabled());
218 nonRobustEstimator.setSuggestedVerticalFocalLengthValue(getSuggestedVerticalFocalLengthValue());
219 nonRobustEstimator.setSuggestAspectRatioEnabled(isSuggestAspectRatioEnabled());
220 nonRobustEstimator.setSuggestedAspectRatioValue(getSuggestedAspectRatioValue());
221 nonRobustEstimator.setSuggestPrincipalPointEnabled(isSuggestPrincipalPointEnabled());
222 nonRobustEstimator.setSuggestedPrincipalPointValue(getSuggestedPrincipalPointValue());
223 nonRobustEstimator.setSuggestRotationEnabled(isSuggestRotationEnabled());
224 nonRobustEstimator.setSuggestedRotationValue(getSuggestedRotationValue());
225 nonRobustEstimator.setSuggestCenterEnabled(isSuggestCenterEnabled());
226 nonRobustEstimator.setSuggestedCenterValue(getSuggestedCenterValue());
227
228 final var innerEstimator = new MSACRobustEstimator<>(new MSACRobustEstimatorListener<PinholeCamera>() {
229
230 // 3D planes for a subset of samples
231 private final List<Plane> subsetPlanes = new ArrayList<>();
232
233 // 2D lines for a subset of samples
234 private final List<Line2D> subsetLines = new ArrayList<>();
235
236 @Override
237 public double getThreshold() {
238 return threshold;
239 }
240
241 @Override
242 public int getTotalSamples() {
243 return planes.size();
244 }
245
246 @Override
247 public int getSubsetSize() {
248 return LinePlaneCorrespondencePinholeCameraEstimator.MIN_NUMBER_OF_LINE_PLANE_CORRESPONDENCES;
249 }
250
251 @Override
252 public void estimatePreliminarSolutions(final int[] samplesIndices, final List<PinholeCamera> solutions) {
253 subsetPlanes.clear();
254 subsetPlanes.add(planes.get(samplesIndices[0]));
255 subsetPlanes.add(planes.get(samplesIndices[1]));
256 subsetPlanes.add(planes.get(samplesIndices[2]));
257 subsetPlanes.add(planes.get(samplesIndices[3]));
258
259 subsetLines.clear();
260 subsetLines.add(lines.get(samplesIndices[0]));
261 subsetLines.add(lines.get(samplesIndices[1]));
262 subsetLines.add(lines.get(samplesIndices[2]));
263 subsetLines.add(lines.get(samplesIndices[3]));
264
265 try {
266 nonRobustEstimator.setLists(subsetPlanes, subsetLines);
267
268 final var cam = nonRobustEstimator.estimate();
269 solutions.add(cam);
270 } catch (final Exception e) {
271 // if lines/planes configuration is degenerate, no solution
272 // is added
273 }
274 }
275
276 @Override
277 public double computeResidual(final PinholeCamera currentEstimation, final int i) {
278 final var inputLine = lines.get(i);
279 final var inputPlane = planes.get(i);
280
281 return singleBackprojectionResidual(currentEstimation, inputLine, inputPlane);
282 }
283
284 @Override
285 public boolean isReady() {
286 return MSACDLTLinePlaneCorrespondencePinholeCameraRobustEstimator.this.isReady();
287 }
288
289 @Override
290 public void onEstimateStart(final RobustEstimator<PinholeCamera> estimator) {
291 if (listener != null) {
292 listener.onEstimateStart(MSACDLTLinePlaneCorrespondencePinholeCameraRobustEstimator.this);
293 }
294 }
295
296 @Override
297 public void onEstimateEnd(final RobustEstimator<PinholeCamera> estimator) {
298 if (listener != null) {
299 listener.onEstimateEnd(MSACDLTLinePlaneCorrespondencePinholeCameraRobustEstimator.this);
300 }
301 }
302
303 @Override
304 public void onEstimateNextIteration(final RobustEstimator<PinholeCamera> estimator, final int iteration) {
305 if (listener != null) {
306 listener.onEstimateNextIteration(
307 MSACDLTLinePlaneCorrespondencePinholeCameraRobustEstimator.this, iteration);
308 }
309 }
310
311 @Override
312 public void onEstimateProgressChange(final RobustEstimator<PinholeCamera> estimator, final float progress) {
313 if (listener != null) {
314 listener.onEstimateProgressChange(
315 MSACDLTLinePlaneCorrespondencePinholeCameraRobustEstimator.this, progress);
316 }
317 }
318 });
319
320 try {
321 locked = true;
322 inliersData = null;
323 innerEstimator.setConfidence(confidence);
324 innerEstimator.setMaxIterations(maxIterations);
325 innerEstimator.setProgressDelta(progressDelta);
326 final var result = innerEstimator.estimate();
327 inliersData = innerEstimator.getInliersData();
328 return attemptRefine(result, nonRobustEstimator.getMaxSuggestionWeight());
329
330 } catch (final com.irurueta.numerical.LockedException e) {
331 throw new LockedException(e);
332 } catch (final com.irurueta.numerical.NotReadyException e) {
333 throw new NotReadyException(e);
334 } finally {
335 locked = false;
336 }
337 }
338
339 /**
340 * Returns method being used for robust estimation.
341 *
342 * @return method being used for robust estimation.
343 */
344 @Override
345 public RobustEstimatorMethod getMethod() {
346 return RobustEstimatorMethod.MSAC;
347 }
348
349 /**
350 * Gets standard deviation used for Levenberg-Marquardt fitting during
351 * refinement.
352 * Returned value gives an indication of how much variance each residual
353 * has.
354 * Typically, this value is related to the threshold used on each robust
355 * estimation, since residuals of found inliers are within the range of
356 * such threshold.
357 *
358 * @return standard deviation used for refinement.
359 */
360 @Override
361 protected double getRefinementStandardDeviation() {
362 return threshold;
363 }
364 }