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.RANSACRobustEstimator;
25  import com.irurueta.numerical.robust.RANSACRobustEstimatorListener;
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 RANSAC algorithm.
36   */
37  public class RANSACRobustSinglePoint3DTriangulator extends
38          RobustSinglePoint3DTriangulator {
39  
40      /**
41       * Constant defining default threshold to determine whether samples are
42       * inliers or not.
43       * By default, 1.0 is considered a good value for cases where 2D point
44       * measures are done on pixels, since typically the minimum resolution is 1
45       * pixel.
46       */
47      public static final double DEFAULT_THRESHOLD = 1.0;
48  
49      /**
50       * Minimum value that can be set as threshold.
51       * Threshold must be strictly greater than 0.0.
52       */
53      public static final double MIN_THRESHOLD = 0.0;
54  
55      /**
56       * Threshold to determine whether samples are inliers or not when testing
57       * possible estimation solutions.
58       * The threshold refers to the amount of projection error (i.e. distance of
59       * projected solution using each camera).
60       */
61      private double threshold;
62  
63      /**
64       * Constructor.
65       */
66      public RANSACRobustSinglePoint3DTriangulator() {
67          super();
68          threshold = DEFAULT_THRESHOLD;
69      }
70  
71      /**
72       * Constructor.
73       *
74       * @param listener listener to be notified of events such as when estimation
75       *                 starts, ends or its progress significantly changes.
76       */
77      public RANSACRobustSinglePoint3DTriangulator(final RobustSinglePoint3DTriangulatorListener listener) {
78          super(listener);
79          threshold = DEFAULT_THRESHOLD;
80      }
81  
82      /**
83       * Constructor.
84       *
85       * @param points  Matched 2D points. Each point in the list is assumed to be
86       *                projected by the corresponding camera in the list.
87       * @param cameras List of cameras associated to the matched 2D point on the
88       *                same position as the camera on the list.
89       * @throws IllegalArgumentException if provided lists don't have the same
90       *                                  length or their length is less than 2 views, which is the minimum
91       *                                  required to compute triangulation.
92       */
93      public RANSACRobustSinglePoint3DTriangulator(final List<Point2D> points, final List<PinholeCamera> cameras) {
94          super(points, cameras);
95          threshold = DEFAULT_THRESHOLD;
96      }
97  
98      /**
99       * Constructor.
100      *
101      * @param points   Matched 2D points. Each point in the list is assumed to be
102      *                 projected by the corresponding camera in the list.
103      * @param cameras  List of cameras associated to the matched 2D point on the
104      *                 same position as the camera on the list.
105      * @param listener listener to be notified of events such as when estimation
106      *                 starts, ends or its progress significantly changes.
107      * @throws IllegalArgumentException if provided lists don't have the same
108      *                                  length or their length is less than 2 views, which is the minimum
109      *                                  required to compute triangulation.
110      */
111     public RANSACRobustSinglePoint3DTriangulator(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 RANSACRobustEstimator<Point3D>(new RANSACRobustEstimatorListener<>() {
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 RANSACRobustSinglePoint3DTriangulator.this.isReady();
242             }
243 
244             @Override
245             public void onEstimateStart(final RobustEstimator<Point3D> estimator) {
246                 if (listener != null) {
247                     listener.onTriangulateStart(RANSACRobustSinglePoint3DTriangulator.this);
248                 }
249             }
250 
251             @Override
252             public void onEstimateEnd(final RobustEstimator<Point3D> estimator) {
253                 if (listener != null) {
254                     listener.onTriangulateEnd(RANSACRobustSinglePoint3DTriangulator.this);
255                 }
256             }
257 
258             @Override
259             public void onEstimateNextIteration(final RobustEstimator<Point3D> estimator, final int iteration) {
260                 if (listener != null) {
261                     listener.onTriangulateNextIteration(
262                             RANSACRobustSinglePoint3DTriangulator.this, iteration);
263                 }
264             }
265 
266             @Override
267             public void onEstimateProgressChange(final RobustEstimator<Point3D> estimator, final float progress) {
268                 if (listener != null) {
269                     listener.onTriangulateProgressChange(
270                             RANSACRobustSinglePoint3DTriangulator.this, progress);
271                 }
272             }
273         });
274 
275         try {
276             locked = true;
277             innerEstimator.setConfidence(confidence);
278             innerEstimator.setMaxIterations(maxIterations);
279             innerEstimator.setProgressDelta(progressDelta);
280             return innerEstimator.estimate();
281         } catch (final com.irurueta.numerical.LockedException e) {
282             throw new LockedException(e);
283         } catch (final com.irurueta.numerical.NotReadyException e) {
284             throw new NotReadyException(e);
285         } finally {
286             locked = false;
287         }
288     }
289 
290     /**
291      * Returns method being used for robust estimation.
292      *
293      * @return method being used for robust estimation.
294      */
295     @Override
296     public RobustEstimatorMethod getMethod() {
297         return RobustEstimatorMethod.RANSAC;
298     }
299 }