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.ar.sfm;
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.geometry.estimators.LockedException;
23  import com.irurueta.geometry.estimators.NotReadyException;
24  import com.irurueta.numerical.robust.MSACRobustEstimator;
25  import com.irurueta.numerical.robust.MSACRobustEstimatorListener;
26  import com.irurueta.numerical.robust.RobustEstimator;
27  import com.irurueta.numerical.robust.RobustEstimatorException;
28  import com.irurueta.numerical.robust.RobustEstimatorMethod;
29  
30  import java.util.ArrayList;
31  import java.util.List;
32  
33  /**
34   * Robustly triangulates 3D points from matched 2D points and their
35   * corresponding cameras on several views using MSAC algorithm.
36   */
37  public class MSACRobustSinglePoint3DTriangulator extends RobustSinglePoint3DTriangulator {
38  
39      /**
40       * Constant defining default threshold to determine whether samples are
41       * inliers or not.
42       * By default, 1.0 is considered a good value for cases where 2D point
43       * measures are done on pixels, since typically the minimum resolution is 1
44       * pixel.
45       */
46      public static final double DEFAULT_THRESHOLD = 1.0;
47  
48      /**
49       * Minimum value that can be set as threshold.
50       * Threshold must be strictly greater than 0.0.
51       */
52      public static final double MIN_THRESHOLD = 0.0;
53  
54      /**
55       * Threshold to determine whether samples are inliers or not when testing
56       * possible estimation solutions.
57       * The threshold refers to the amount of projection error (i.e. distance of
58       * projected solution using each camera).
59       */
60      private double threshold;
61  
62      /**
63       * Constructor.
64       */
65      public MSACRobustSinglePoint3DTriangulator() {
66          super();
67          threshold = DEFAULT_THRESHOLD;
68      }
69  
70      /**
71       * Constructor.
72       *
73       * @param listener listener to be notified of events such as when estimation
74       *                 starts, ends or its progress significantly changes.
75       */
76      public MSACRobustSinglePoint3DTriangulator(final RobustSinglePoint3DTriangulatorListener listener) {
77          super(listener);
78          threshold = DEFAULT_THRESHOLD;
79      }
80  
81      /**
82       * Constructor.
83       *
84       * @param points  Matched 2D points. Each point in the list is assumed to be
85       *                projected by the corresponding camera in the list.
86       * @param cameras List of cameras associated to the matched 2D point on the
87       *                same position as the camera on the list.
88       * @throws IllegalArgumentException if provided lists don't have the same
89       *                                  length or their length is less than 2 views, which is the minimum
90       *                                  required to compute triangulation.
91       */
92      public MSACRobustSinglePoint3DTriangulator(final List<Point2D> points, final List<PinholeCamera> cameras) {
93          super(points, cameras);
94          threshold = DEFAULT_THRESHOLD;
95      }
96  
97      /**
98       * Constructor.
99       *
100      * @param points   Matched 2D points. Each point in the list is assumed to be
101      *                 projected by the corresponding camera in the list.
102      * @param cameras  List of cameras associated to the matched 2D point on the
103      *                 same position as the camera on the list.
104      * @param listener listener to be notified of events such as when estimation
105      *                 starts, ends or its progress significantly changes.
106      * @throws IllegalArgumentException if provided lists don't have the same
107      *                                  length or their length is less than 2 views, which is the minimum
108      *                                  required to compute triangulation.
109      */
110     public MSACRobustSinglePoint3DTriangulator(
111             final List<Point2D> points, final List<PinholeCamera> cameras,
112             final RobustSinglePoint3DTriangulatorListener listener) {
113         super(points, cameras, listener);
114         threshold = DEFAULT_THRESHOLD;
115     }
116 
117     /**
118      * Returns threshold to determine whether points are inliers or not when
119      * testing possible estimation solutions.
120      * The threshold refers to the amount of error (i.e. Euclidean distance) a
121      * possible solution has on projected 2D points.
122      *
123      * @return threshold to determine whether points are inliers or not when
124      * testing possible estimation solutions.
125      */
126     public double getThreshold() {
127         return threshold;
128     }
129 
130     /**
131      * Sets threshold to determine whether points are inliers or not when
132      * testing possible estimation solutions.
133      * The threshold refers to the amount of error (i.e. Euclidean distance) a
134      * possible solution has on projected 2D points.
135      *
136      * @param threshold threshold to be set.
137      * @throws IllegalArgumentException if provided value is equal or less than
138      *                                  zero.
139      * @throws LockedException          if robust estimator is locked because an
140      *                                  estimation is already in progress.
141      */
142     public void setThreshold(final double threshold) throws LockedException {
143         if (isLocked()) {
144             throw new LockedException();
145         }
146         if (threshold <= MIN_THRESHOLD) {
147             throw new IllegalArgumentException();
148         }
149         this.threshold = threshold;
150     }
151 
152     /**
153      * Triangulates provided matched 2D points being projected by each
154      * corresponding camera into a single 3D point.
155      * At least 2 matched 2D points and their corresponding 2 cameras are
156      * required to compute triangulation. If more views are provided, an
157      * averaged solution can be found.
158      *
159      * @return computed triangulated 3D point.
160      * @throws LockedException          if this instance is locked.
161      * @throws NotReadyException        if lists of points and cameras don't have the
162      *                                  same length or less than 2 views are provided.
163      * @throws RobustEstimatorException if estimation fails for any reason
164      *                                  (i.e. numerical instability, no solution available, etc).
165      */
166     @SuppressWarnings("DuplicatedCode")
167     @Override
168     public Point3D triangulate() throws LockedException, NotReadyException, RobustEstimatorException {
169         if (isLocked()) {
170             throw new LockedException();
171         }
172         if (!isReady()) {
173             throw new NotReadyException();
174         }
175 
176         final var innerEstimator = new MSACRobustEstimator<Point3D>(new MSACRobustEstimatorListener<>() {
177 
178             // point to be reused when computing residuals
179             private final Point2D testPoint = Point2D.create(CoordinatesType.HOMOGENEOUS_COORDINATES);
180 
181             // non-robust 3D point triangulator
182             private final SinglePoint3DTriangulator triangulator = SinglePoint3DTriangulator.create(
183                     useHomogeneousSolution ? Point3DTriangulatorType.LMSE_HOMOGENEOUS_TRIANGULATOR
184                             : Point3DTriangulatorType.LMSE_INHOMOGENEOUS_TRIANGULATOR);
185 
186             // subset of 2D points
187             private final List<Point2D> subsetPoints = new ArrayList<>();
188 
189             // subst of cameras
190             private final List<PinholeCamera> subsetCameras = new ArrayList<>();
191 
192             @Override
193             public double getThreshold() {
194                 return threshold;
195             }
196 
197             @Override
198             public int getTotalSamples() {
199                 return points2D.size();
200             }
201 
202             @Override
203             public int getSubsetSize() {
204                 return MIN_REQUIRED_VIEWS;
205             }
206 
207             @Override
208             public void estimatePreliminarSolutions(final int[] samplesIndices, final List<Point3D> solutions) {
209                 subsetPoints.clear();
210                 subsetPoints.add(points2D.get(samplesIndices[0]));
211                 subsetPoints.add(points2D.get(samplesIndices[1]));
212 
213                 subsetCameras.clear();
214                 subsetCameras.add(cameras.get(samplesIndices[0]));
215                 subsetCameras.add(cameras.get(samplesIndices[1]));
216 
217                 try {
218                     triangulator.setPointsAndCameras(subsetPoints, subsetCameras);
219                     final var triangulated = triangulator.triangulate();
220                     solutions.add(triangulated);
221                 } catch (final Exception e) {
222                     // if anything fails, no solution is added
223                 }
224             }
225 
226             @Override
227             public double computeResidual(final Point3D currentEstimation, final int i) {
228                 final var point2D = points2D.get(i);
229                 final var camera = cameras.get(i);
230 
231                 // project estimated point with camera
232                 camera.project(currentEstimation, testPoint);
233 
234                 // return distance of projected point respect to the original one
235                 // as a residual
236                 return testPoint.distanceTo(point2D);
237             }
238 
239             @Override
240             public boolean isReady() {
241                 return MSACRobustSinglePoint3DTriangulator.this.isReady();
242             }
243 
244             @Override
245             public void onEstimateStart(final RobustEstimator<Point3D> estimator) {
246                 if (listener != null) {
247                     listener.onTriangulateStart(MSACRobustSinglePoint3DTriangulator.this);
248                 }
249             }
250 
251             @Override
252             public void onEstimateEnd(final RobustEstimator<Point3D> estimator) {
253                 if (listener != null) {
254                     listener.onTriangulateEnd(MSACRobustSinglePoint3DTriangulator.this);
255                 }
256             }
257 
258             @Override
259             public void onEstimateNextIteration(final RobustEstimator<Point3D> estimator, final int iteration) {
260                 if (listener != null) {
261                     listener.onTriangulateNextIteration(MSACRobustSinglePoint3DTriangulator.this, iteration);
262                 }
263             }
264 
265             @Override
266             public void onEstimateProgressChange(final RobustEstimator<Point3D> estimator, final float progress) {
267                 if (listener != null) {
268                     listener.onTriangulateProgressChange(MSACRobustSinglePoint3DTriangulator.this, progress);
269                 }
270             }
271         });
272 
273         try {
274             locked = true;
275             innerEstimator.setConfidence(confidence);
276             innerEstimator.setMaxIterations(maxIterations);
277             innerEstimator.setProgressDelta(progressDelta);
278             return innerEstimator.estimate();
279         } catch (final com.irurueta.numerical.LockedException e) {
280             throw new LockedException(e);
281         } catch (final com.irurueta.numerical.NotReadyException e) {
282             throw new NotReadyException(e);
283         } finally {
284             locked = false;
285         }
286     }
287 
288     /**
289      * Returns method being used for robust estimation.
290      *
291      * @return method being used for robust estimation.
292      */
293     @Override
294     public RobustEstimatorMethod getMethod() {
295         return RobustEstimatorMethod.MSAC;
296     }
297 }