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.LMedSRobustEstimator;
25 import com.irurueta.numerical.robust.LMedSRobustEstimatorListener;
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 LMedS algorithm.
36 */
37 public class LMedSRobustSinglePoint3DTriangulator extends RobustSinglePoint3DTriangulator {
38
39 /**
40 * Default value to be used for stop threshold. Stop threshold can be used
41 * to keep the algorithm iterating in case that best estimated threshold
42 * using median of residuals is not small enough. Once a solution is found
43 * that generates a threshold below this value, the algorithm will stop.
44 * The stop threshold can be used to prevent the LMedS algorithm iterating
45 * too many times in cases where samples have a very similar accuracy.
46 * For instance, in cases where proportion of outliers is very small (close
47 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
48 * iterate for a long time trying to find the best solution when indeed
49 * there is no need to do that if a reasonable threshold has already been
50 * reached.
51 * Because of this behaviour the stop threshold can be set to a value much
52 * lower than the one typically used in RANSAC, and yet the algorithm could
53 * still produce even smaller thresholds in estimated results.
54 */
55 public static final double DEFAULT_STOP_THRESHOLD = 1e-3;
56
57 /**
58 * Minimum allowed stop threshold value.
59 */
60 public static final double MIN_STOP_THRESHOLD = 0.0;
61
62 /**
63 * Threshold to be used to keep the algorithm iterating in case that best
64 * estimated threshold using median of residuals is not small enough. Once
65 * a solution is found that generates a threshold below this value, the
66 * algorithm will stop.
67 * The stop threshold can be used to prevent the LMedS algorithm iterating
68 * too many times in cases where samples have a very similar accuracy.
69 * For instance, in cases where proportion of outliers is very small (close
70 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
71 * iterate for a long time trying to find the best solution when indeed
72 * there is no need to do that if a reasonable threshold has already been
73 * reached.
74 * Because of this behaviour the stop threshold can be set to a value much
75 * lower than the one typically used in RANSAC, and yet the algorithm could
76 * still produce even smaller thresholds in estimated results.
77 */
78 private double stopThreshold;
79
80 /**
81 * Constructor.
82 */
83 public LMedSRobustSinglePoint3DTriangulator() {
84 super();
85 stopThreshold = DEFAULT_STOP_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 LMedSRobustSinglePoint3DTriangulator(final RobustSinglePoint3DTriangulatorListener listener) {
95 super(listener);
96 stopThreshold = DEFAULT_STOP_THRESHOLD;
97 }
98
99 /**
100 * Constructor.
101 *
102 * @param points Matched 2D points. Each point in the list is assumed to be
103 * projected by the corresponding camera in the list.
104 * @param cameras List of cameras associated to the matched 2D point on the
105 * same position as the camera on the list.
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 LMedSRobustSinglePoint3DTriangulator(final List<Point2D> points, final List<PinholeCamera> cameras) {
111 super(points, cameras);
112 stopThreshold = DEFAULT_STOP_THRESHOLD;
113 }
114
115 /**
116 * Constructor.
117 *
118 * @param points Matched 2D points. Each point in the list is assumed to be
119 * projected by the corresponding camera in the list.
120 * @param cameras List of cameras associated to the matched 2D point on the
121 * same position as the camera on the list.
122 * @param listener listener to be notified of events such as when estimation
123 * starts, ends or its progress significantly changes.
124 * @throws IllegalArgumentException if provided lists don't have the same
125 * length or their length is less than 2 views, which is the minimum
126 * required to compute triangulation.
127 */
128 public LMedSRobustSinglePoint3DTriangulator(final List<Point2D> points,
129 final List<PinholeCamera> cameras,
130 final RobustSinglePoint3DTriangulatorListener listener) {
131 super(points, cameras, listener);
132 stopThreshold = DEFAULT_STOP_THRESHOLD;
133 }
134
135 /**
136 * Returns threshold to be used to keep the algorithm iterating in case that
137 * best estimated threshold using median of residuals is not small enough.
138 * Once a solution is found that generates a threshold below this value, the
139 * algorithm will stop.
140 * The stop threshold can be used to prevent the LMedS algorithm iterating
141 * too many times in cases where samples have a very similar accuracy.
142 * For instance, in cases where proportion of outliers is very small (close
143 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
144 * iterate for a long time trying to find the best solution when indeed
145 * there is no need to do that if a reasonable threshold has already been
146 * reached.
147 * Because of this behaviour the stop threshold can be set to a value much
148 * lower than the one typically used in RANSAC, and yet the algorithm could
149 * still produce even smaller thresholds in estimated results.
150 *
151 * @return stop threshold to stop the algorithm prematurely when a certain
152 * accuracy has been reached.
153 */
154 public double getStopThreshold() {
155 return stopThreshold;
156 }
157
158 /**
159 * Sets threshold to be used to keep the algorithm iterating in case that
160 * best estimated threshold using median of residuals is not small enough.
161 * Once a solution is found that generates a threshold below this value, the
162 * algorithm will stop.
163 * The stop threshold can be used to prevent the LMedS algorithm iterating
164 * too many times in cases where samples have a very similar accuracy.
165 * For instance, in cases where proportion of outliers is very small (close
166 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
167 * iterate for a long time trying to find the best solution when indeed
168 * there is no need to do that if a reasonable threshold has already been
169 * reached.
170 * Because of this behaviour the stop threshold can be set to a value much
171 * lower than the one typically used in RANSAC, and yet the algorithm could
172 * still produce even smaller thresholds in estimated results.
173 *
174 * @param stopThreshold stop threshold to stop the algorithm prematurely
175 * when a certain accuracy has been reached.
176 * @throws IllegalArgumentException if provided value is zero or negative.
177 * @throws LockedException if robust estimator is locked because an
178 * estimation is already in progress.
179 */
180 public void setStopThreshold(final double stopThreshold) throws LockedException {
181 if (isLocked()) {
182 throw new LockedException();
183 }
184 if (stopThreshold <= MIN_STOP_THRESHOLD) {
185 throw new IllegalArgumentException();
186 }
187
188 this.stopThreshold = stopThreshold;
189 }
190
191 /**
192 * Triangulates provided matched 2D points being projected by each
193 * corresponding camera into a single 3D point.
194 * At least 2 matched 2D points and their corresponding 2 cameras are
195 * required to compute triangulation. If more views are provided, an
196 * averaged solution can be found.
197 *
198 * @return computed triangulated 3D point.
199 * @throws LockedException if this instance is locked.
200 * @throws NotReadyException if lists of points and cameras don't have the
201 * same length or less than 2 views are provided.
202 * @throws RobustEstimatorException if estimation fails for any reason
203 * (i.e. numerical instability, no solution available, etc).
204 */
205 @SuppressWarnings("DuplicatedCode")
206 @Override
207 public Point3D triangulate() throws LockedException, NotReadyException, RobustEstimatorException {
208 if (isLocked()) {
209 throw new LockedException();
210 }
211 if (!isReady()) {
212 throw new NotReadyException();
213 }
214
215 final var innerEstimator = new LMedSRobustEstimator<Point3D>(new LMedSRobustEstimatorListener<>() {
216
217 // point to be reused when computing residuals
218 private final Point2D testPoint = Point2D.create(CoordinatesType.HOMOGENEOUS_COORDINATES);
219
220 // non-robust 3D point triangulator
221 private final SinglePoint3DTriangulator triangulator = SinglePoint3DTriangulator.create(
222 useHomogeneousSolution ? Point3DTriangulatorType.LMSE_HOMOGENEOUS_TRIANGULATOR
223 : Point3DTriangulatorType.LMSE_INHOMOGENEOUS_TRIANGULATOR);
224
225 // subset of 2D points
226 private final List<Point2D> subsetPoints = new ArrayList<>();
227
228 // subst of cameras
229 private final List<PinholeCamera> subsetCameras = new ArrayList<>();
230
231 @Override
232 public int getTotalSamples() {
233 return points2D.size();
234 }
235
236 @Override
237 public int getSubsetSize() {
238 return MIN_REQUIRED_VIEWS;
239 }
240
241 @Override
242 public void estimatePreliminarSolutions(final int[] samplesIndices, final List<Point3D> solutions) {
243 subsetPoints.clear();
244 subsetPoints.add(points2D.get(samplesIndices[0]));
245 subsetPoints.add(points2D.get(samplesIndices[1]));
246
247 subsetCameras.clear();
248 subsetCameras.add(cameras.get(samplesIndices[0]));
249 subsetCameras.add(cameras.get(samplesIndices[1]));
250
251 try {
252 triangulator.setPointsAndCameras(subsetPoints, subsetCameras);
253 final var triangulated = triangulator.triangulate();
254 solutions.add(triangulated);
255 } catch (final Exception e) {
256 // if anything fails, no solution is added
257 }
258 }
259
260 @Override
261 public double computeResidual(final Point3D currentEstimation, final int i) {
262 final var point2D = points2D.get(i);
263 final var camera = cameras.get(i);
264
265 // project estimated point with camera
266 camera.project(currentEstimation, testPoint);
267
268 // return distance of projected point respect to the original one
269 // as a residual
270 return testPoint.distanceTo(point2D);
271 }
272
273 @Override
274 public boolean isReady() {
275 return LMedSRobustSinglePoint3DTriangulator.this.isReady();
276 }
277
278 @Override
279 public void onEstimateStart(final RobustEstimator<Point3D> estimator) {
280 if (listener != null) {
281 listener.onTriangulateStart(LMedSRobustSinglePoint3DTriangulator.this);
282 }
283 }
284
285 @Override
286 public void onEstimateEnd(final RobustEstimator<Point3D> estimator) {
287 if (listener != null) {
288 listener.onTriangulateEnd(LMedSRobustSinglePoint3DTriangulator.this);
289 }
290 }
291
292 @Override
293 public void onEstimateNextIteration(final RobustEstimator<Point3D> estimator, final int iteration) {
294 if (listener != null) {
295 listener.onTriangulateNextIteration(
296 LMedSRobustSinglePoint3DTriangulator.this, iteration);
297 }
298 }
299
300 @Override
301 public void onEstimateProgressChange(final RobustEstimator<Point3D> estimator, final float progress) {
302 if (listener != null) {
303 listener.onTriangulateProgressChange(
304 LMedSRobustSinglePoint3DTriangulator.this, progress);
305 }
306 }
307 });
308
309 try {
310 locked = true;
311 innerEstimator.setConfidence(confidence);
312 innerEstimator.setMaxIterations(maxIterations);
313 innerEstimator.setProgressDelta(progressDelta);
314 innerEstimator.setStopThreshold(stopThreshold);
315 return innerEstimator.estimate();
316 } catch (final com.irurueta.numerical.LockedException e) {
317 throw new LockedException(e);
318 } catch (final com.irurueta.numerical.NotReadyException e) {
319 throw new NotReadyException(e);
320 } finally {
321 locked = false;
322 }
323 }
324
325 /**
326 * Returns method being used for robust estimation.
327 *
328 * @return method being used for robust estimation.
329 */
330 @Override
331 public RobustEstimatorMethod getMethod() {
332 return RobustEstimatorMethod.LMEDS;
333 }
334 }