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