View Javadoc
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 }