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.PinholeCamera;
19 import com.irurueta.geometry.Point2D;
20 import com.irurueta.geometry.Point3D;
21 import com.irurueta.geometry.estimators.LockedException;
22 import com.irurueta.geometry.estimators.NotReadyException;
23 import com.irurueta.numerical.robust.RobustEstimatorException;
24 import com.irurueta.numerical.robust.RobustEstimatorMethod;
25
26 import java.util.List;
27
28 /**
29 * Abstract class for algorithms to robustly triangulate 3D points from matched
30 * 2D points and their corresponding cameras on several views.
31 * Robust estimators can be used to estimate a more precise triangulation when
32 * there are more than 2 views where points have been matched.
33 */
34 public abstract class RobustSinglePoint3DTriangulator {
35
36 /**
37 * Default robust estimator method when none is provided.
38 */
39 public static final RobustEstimatorMethod DEFAULT_ROBUST_METHOD = RobustEstimatorMethod.PROMEDS;
40
41 /**
42 * Default amount of progress variation before notifying a change in
43 * estimation progress. By default, this is set to 5%.
44 */
45 public static final float DEFAULT_PROGRESS_DELTA = 0.05f;
46
47 /**
48 * Minimum allowed value for progress delta.
49 */
50 public static final float MIN_PROGRESS_DELTA = 0.0f;
51
52 /**
53 * Maximum allowed value for progress delta.
54 */
55 public static final float MAX_PROGRESS_DELTA = 1.0f;
56
57 /**
58 * Constant defining default confidence of the estimated result, which is
59 * 99%. This means that with a probability of 99% estimation will be
60 * accurate because chose sub-samples will be inliers.
61 */
62 public static final double DEFAULT_CONFIDENCE = 0.99;
63
64 /**
65 * Default maximum allowed number of iterations.
66 */
67 public static final int DEFAULT_MAX_ITERATIONS = 5000;
68
69 /**
70 * Minimum allowed confidence value.
71 */
72 public static final double MIN_CONFIDENCE = 0.0;
73
74 /**
75 * Maximum allowed confidence value.
76 */
77 public static final double MAX_CONFIDENCE = 1.0;
78
79 /**
80 * Minimum allowed number of iterations.
81 */
82 public static final int MIN_ITERATIONS = 1;
83
84 /**
85 * Minimum required number of views to triangulate 3D points.
86 */
87 public static final int MIN_REQUIRED_VIEWS = 2;
88
89 /**
90 * Indicates whether by default a solution to an homogeneous system of
91 * equations should be found.
92 */
93 public static final boolean DEFAULT_USE_HOMOGENEOUS_SOLUTION = true;
94
95 /**
96 * Matched 2D points. Each point in the list is assumed to be projected by
97 * the corresponding camera in the list.
98 */
99 protected List<Point2D> points2D;
100
101 /**
102 * List of cameras associated to the matched 2D point on the same position
103 * as the camera on the list.
104 */
105 protected List<PinholeCamera> cameras;
106
107 /**
108 * Listener to be notified of events such as when estimation starts, ends or
109 * its progress significantly changes.
110 */
111 protected RobustSinglePoint3DTriangulatorListener listener;
112
113 /**
114 * Indicates whether a solution to an homogeneous system of equations should
115 * be found. Typically, this should be true, since even points and cameras
116 * at infinity can be used. If points are close and geometry is
117 * well-defined, false can be used to solve an inhomogeneous system of equations
118 * and obtain a slightly better accuracy.
119 */
120 protected boolean useHomogeneousSolution;
121
122 /**
123 * Indicates if this estimator is locked because an estimation is being
124 * computed.
125 */
126 protected volatile boolean locked;
127
128 /**
129 * Amount of progress variation before notifying a progress change during
130 * estimation.
131 */
132 protected float progressDelta;
133
134 /**
135 * Amount of confidence expressed as a value between 0.0 and 1.0 (which is
136 * equivalent to 100%). The amount of confidence indicates the probability
137 * that the estimated result is correct. Usually this value will be close
138 * to 1.0, but not exactly 1.0.
139 */
140 protected double confidence;
141
142 /**
143 * Maximum allowed number of iterations. When the maximum number of
144 * iterations is exceeded, result will not be available, however an
145 * approximate result will be available for retrieval.
146 */
147 protected int maxIterations;
148
149 /**
150 * Constructor.
151 */
152 protected RobustSinglePoint3DTriangulator() {
153 useHomogeneousSolution = DEFAULT_USE_HOMOGENEOUS_SOLUTION;
154 progressDelta = DEFAULT_PROGRESS_DELTA;
155 confidence = DEFAULT_CONFIDENCE;
156 maxIterations = DEFAULT_MAX_ITERATIONS;
157 }
158
159 /**
160 * Constructor.
161 *
162 * @param listener listener to be notified of events such as when estimation
163 * starts, ends or its progress significantly changes.
164 */
165 protected RobustSinglePoint3DTriangulator(
166 final RobustSinglePoint3DTriangulatorListener listener) {
167 this();
168 this.listener = listener;
169 }
170
171 /**
172 * Constructor.
173 *
174 * @param points Matched 2D points. Each point in the list is assumed to be
175 * projected by the corresponding camera in the list.
176 * @param cameras List of cameras associated to the matched 2D point on the
177 * same position as the camera on the list.
178 * @throws IllegalArgumentException if provided lists don't have the same
179 * length or their length is less than 2 views, which is the minimum
180 * required to compute triangulation.
181 */
182 protected RobustSinglePoint3DTriangulator(final List<Point2D> points, final List<PinholeCamera> cameras) {
183 this();
184 internalSetPointsAndCameras(points, cameras);
185 }
186
187 /**
188 * Constructor.
189 *
190 * @param points Matched 2D points. Each point in the list is assumed to be
191 * projected by the corresponding camera in the list.
192 * @param cameras List of cameras associated to the matched 2D point on the
193 * same position as the camera on the list.
194 * @param listener listener to be notified of events such as when estimation
195 * starts, ends or its progress significantly changes.
196 * @throws IllegalArgumentException if provided lists don't have the same
197 * length or their length is less than 2 views, which is the minimum
198 * required to compute triangulation.
199 */
200 protected RobustSinglePoint3DTriangulator(final List<Point2D> points,
201 final List<PinholeCamera> cameras,
202 final RobustSinglePoint3DTriangulatorListener listener) {
203 this(points, cameras);
204 this.listener = listener;
205 }
206
207 /**
208 * Returns reference to listener to be notified of events such as when
209 * estimation starts, ends or its progress significantly changes.
210 *
211 * @return listener to be notified of events.
212 */
213 public RobustSinglePoint3DTriangulatorListener getListener() {
214 return listener;
215 }
216
217 /**
218 * Sets listener to be notified of events such as when estimation starts,
219 * ends or its progress significantly changes.
220 *
221 * @param listener listener to be notified of events.
222 * @throws LockedException if robust estimator is locked.
223 */
224 public void setListener(final RobustSinglePoint3DTriangulatorListener listener) throws LockedException {
225 if (isLocked()) {
226 throw new LockedException();
227 }
228 this.listener = listener;
229 }
230
231 /**
232 * Indicates whether listener has been provided and is available for
233 * retrieval.
234 *
235 * @return true if available, false otherwise.
236 */
237 public boolean isListenerAvailable() {
238 return listener != null;
239 }
240
241 /**
242 * Indicates whether a solution to an homogeneous system of equations should
243 * be found. Typically, this should be true, since even points and cameras
244 * at infinity can be used. If points are close and geometry is
245 * well-defined, false can be used to solve an inhomogeneous system of equations
246 * and obtain a slightly better accuracy.
247 *
248 * @return true if an homogeneous solution must be found (default value),
249 * false otherwise.
250 */
251 public boolean isUseHomogeneousSolution() {
252 return useHomogeneousSolution;
253 }
254
255 /**
256 * Sets boolean indicating whether a solution to an homogeneous system of
257 * equations should be found. Typically, this should be true, since even
258 * points and cameras at infinity can be used. If points are close and
259 * geometry is well-defined, false can be used to solve an inhomogeneous
260 * system of equations and obtain a slightly better accuracy.
261 *
262 * @param useHomogeneousSolution true if an homogeneous solution will be
263 * found, false if an inhomogeneous solution will be found instead.
264 * @throws LockedException if this instance is locked.
265 */
266 public void setUseHomogeneousSolution(final boolean useHomogeneousSolution) throws LockedException {
267 if (isLocked()) {
268 throw new LockedException();
269 }
270 this.useHomogeneousSolution = useHomogeneousSolution;
271 }
272
273 /**
274 * Indicates if this instance is locked because triangulation is being
275 * computed.
276 *
277 * @return true if locked, false otherwise.
278 */
279 public boolean isLocked() {
280 return locked;
281 }
282
283 /**
284 * Returns amount of progress variation before notifying a progress change
285 * during estimation.
286 *
287 * @return amount of progress variation before notifying a progress change
288 * during estimation.
289 */
290 public float getProgressDelta() {
291 return progressDelta;
292 }
293
294 /**
295 * Sets amount of progress variation before notifying a progress change
296 * during estimation.
297 *
298 * @param progressDelta amount of progress variation before notifying a
299 * progress change during estimation.
300 * @throws IllegalArgumentException if progress delta is less than zero or
301 * greater than 1.
302 * @throws LockedException if this estimator is locked because an estimation
303 * is being computed.
304 */
305 public void setProgressDelta(final float progressDelta) throws LockedException {
306 if (isLocked()) {
307 throw new LockedException();
308 }
309 if (progressDelta < MIN_PROGRESS_DELTA || progressDelta > MAX_PROGRESS_DELTA) {
310 throw new IllegalArgumentException();
311 }
312 this.progressDelta = progressDelta;
313 }
314
315 /**
316 * Returns amount of confidence expressed as a value between 0.0 and 1.0
317 * (which is equivalent to 100%). The amount of confidence indicates the
318 * probability that the estimated result is correct. Usually this value will
319 * be close to 1.0, but not exactly 1.0.
320 *
321 * @return amount of confidence as a value between 0.0 and 1.0.
322 */
323 public double getConfidence() {
324 return confidence;
325 }
326
327 /**
328 * Sets amount of confidence expressed as a value between 0.0 and 1.0 (which
329 * is equivalent to 100%). The amount of confidence indicates the
330 * probability that the estimated result is correct. Usually this value will
331 * be close to 1.0, but not exactly 1.0.
332 *
333 * @param confidence confidence to be set as a value between 0.0 and 1.0.
334 * @throws IllegalArgumentException if provided value is not between 0.0 and
335 * 1.0.
336 * @throws LockedException if this estimator is locked because an estimator
337 * is being computed.
338 */
339 public void setConfidence(final double confidence) throws LockedException {
340 if (isLocked()) {
341 throw new LockedException();
342 }
343 if (confidence < MIN_CONFIDENCE || confidence > MAX_CONFIDENCE) {
344 throw new IllegalArgumentException();
345 }
346 this.confidence = confidence;
347 }
348
349 /**
350 * Returns maximum allowed number of iterations. If maximum allowed number
351 * of iterations is achieved without converging to a result when calling
352 * estimate(), a RobustEstimatorException will be raised.
353 *
354 * @return maximum allowed number of iterations.
355 */
356 public int getMaxIterations() {
357 return maxIterations;
358 }
359
360 /**
361 * Sets maximum allowed number of iterations. When the maximum number of
362 * iterations is exceeded, result will not be available, however an
363 * approximate result will be available for retrieval.
364 *
365 * @param maxIterations maximum allowed number of iterations to be set.
366 * @throws IllegalArgumentException if provided value is less than 1.
367 * @throws LockedException if this estimator is locked because an estimation
368 * is being computed.
369 */
370 public void setMaxIterations(final int maxIterations) throws LockedException {
371 if (isLocked()) {
372 throw new LockedException();
373 }
374 if (maxIterations < MIN_ITERATIONS) {
375 throw new IllegalArgumentException();
376 }
377 this.maxIterations = maxIterations;
378 }
379
380 /**
381 * Sets list of matched 2D points for each view and their corresponding
382 * cameras used to project them.
383 *
384 * @param points2D list of matched 2D points on each view. Each point in the
385 * list is assumed to be projected by the corresponding camera in the list.
386 * @param cameras cameras for each view where 2D points are represented.
387 * @throws LockedException if this instance is locked.
388 * @throws IllegalArgumentException if provided lists don't have the same
389 * length or their length is less than 2 views, which is the minimum
390 * required to compute triangulation.
391 */
392 public void setPointsAndCameras(final List<Point2D> points2D, final List<PinholeCamera> cameras)
393 throws LockedException {
394 if (isLocked()) {
395 throw new LockedException();
396 }
397 internalSetPointsAndCameras(points2D, cameras);
398 }
399
400 /**
401 * Returns list of matched 2D points on each view. Each point in the list is
402 * assumed to be projected by the corresponding camera.
403 *
404 * @return list of matched 2D points on each view.
405 */
406 public List<Point2D> getPoints2D() {
407 return points2D;
408 }
409
410 /**
411 * Returns cameras for each view where 2D points are represented.
412 *
413 * @return cameras for each view where 2D points are represented.
414 */
415 public List<PinholeCamera> getCameras() {
416 return cameras;
417 }
418
419 /**
420 * Returns quality scores corresponding to each view.
421 * The larger the score value the better the quality of the view measure.
422 * This implementation always returns null.
423 * Subclasses using quality scores must implement proper behaviour.
424 *
425 * @return quality scores corresponding to each view.
426 */
427 public double[] getQualityScores() {
428 return null;
429 }
430
431 /**
432 * Sets quality scores corresponding to each view.
433 * The larger the score value the better the quality of the view sample.
434 * This implementation makes no action.
435 * Subclasses using quality scores must implement proper behaviour.
436 *
437 * @param qualityScores quality scores corresponding to each view.
438 * @throws LockedException if robust estimator is locked because an
439 * estimation is already in progress.
440 * @throws IllegalArgumentException if provided quality scores length is
441 * smaller than MIN_REQUIRED_VIEWS (i.e. 2 views).
442 */
443 public void setQualityScores(final double[] qualityScores) throws LockedException {
444 }
445
446
447 /**
448 * Indicates whether this instance is ready to start the triangulation.
449 * An instance is ready when both lists of 2D points and cameras are
450 * provided, both lists have the same length and at least data for 2 views
451 * is provided.
452 *
453 * @return true if this instance is ready, false otherwise.
454 */
455 public boolean isReady() {
456 return SinglePoint3DTriangulator.areValidPointsAndCameras(points2D, cameras);
457 }
458
459 /**
460 * Triangulates provided matched 2D points being projected by each
461 * corresponding camera into a single 3D point.
462 * At least 2 matched 2D points and their corresponding 2 cameras are
463 * required to compute triangulation. If more views are provided, an
464 * averaged solution can be found.
465 *
466 * @return computed triangulated 3D point.
467 * @throws LockedException if this instance is locked.
468 * @throws NotReadyException if lists of points and cameras don't have the
469 * same length or less than 2 views are provided.
470 * @throws RobustEstimatorException if estimation fails for any reason
471 * (i.e. numerical instability, no solution available, etc).
472 */
473 public abstract Point3D triangulate() throws LockedException, NotReadyException, RobustEstimatorException;
474
475 /**
476 * Returns method being used for robust estimation.
477 *
478 * @return method being used for robust estimation.
479 */
480 public abstract RobustEstimatorMethod getMethod();
481
482 /**
483 * Creates a robust single 3D point triangulator using provided robust
484 * method.
485 *
486 * @param method method of a robust estimator algorithm to estimate the best
487 * triangulation.
488 * @return an instance of a robust single 3D point triangulator.
489 */
490 public static RobustSinglePoint3DTriangulator create(final RobustEstimatorMethod method) {
491 return switch (method) {
492 case RANSAC -> new RANSACRobustSinglePoint3DTriangulator();
493 case LMEDS -> new LMedSRobustSinglePoint3DTriangulator();
494 case MSAC -> new MSACRobustSinglePoint3DTriangulator();
495 case PROSAC -> new PROSACRobustSinglePoint3DTriangulator();
496 default -> new PROMedSRobustSinglePoint3DTriangulator();
497 };
498 }
499
500 /**
501 * Creates a robust single 3D point triangulator using provided points,
502 * cameras and robust method.
503 *
504 * @param points matched 2D points. Each point in the list is assumed to
505 * be projected by the corresponding camera in the list.
506 * @param cameras list of cameras associated to the matched 2D point on the
507 * same position as the camera on the list.
508 * @param method method of a robust estimator algorithm to estimate the best
509 * triangulation.
510 * @return an instance of a robust single 3D point triangulator.
511 * @throws IllegalArgumentException if provided lists don't have the same
512 * length or their length is less than 2 views, which is the minimum
513 * required to compute triangulation.
514 */
515 public static RobustSinglePoint3DTriangulator create(
516 final List<Point2D> points, final List<PinholeCamera> cameras, final RobustEstimatorMethod method) {
517 return switch (method) {
518 case RANSAC -> new RANSACRobustSinglePoint3DTriangulator(points, cameras);
519 case LMEDS -> new LMedSRobustSinglePoint3DTriangulator(points, cameras);
520 case MSAC -> new MSACRobustSinglePoint3DTriangulator(points, cameras);
521 case PROSAC -> new PROSACRobustSinglePoint3DTriangulator(points, cameras);
522 default -> new PROMedSRobustSinglePoint3DTriangulator(points, cameras);
523 };
524 }
525
526 /**
527 * Creates a robust single 3D point triangulator using provided points,
528 * cameras and robust method.
529 *
530 * @param points matched 2D points. Each point in the list is assumed to
531 * be projected by the corresponding camera in the list.
532 * @param cameras list of cameras associated to the matched 2D point on the
533 * same position as the camera on the list.
534 * @param qualityScores quality scores corresponding to each point.
535 * @param method method of a robust estimator algorithm to estimate the best
536 * triangulation.
537 * @return an instance of a robust single 3D point triangulator.
538 * @throws IllegalArgumentException if provided lists or quality scores
539 * don't have the same length or their length is less than 2 views,
540 * which is the minimum required to compute triangulation.
541 */
542 public static RobustSinglePoint3DTriangulator create(
543 final List<Point2D> points, final List<PinholeCamera> cameras, final double[] qualityScores,
544 final RobustEstimatorMethod method) {
545 return switch (method) {
546 case RANSAC -> new RANSACRobustSinglePoint3DTriangulator(points, cameras);
547 case LMEDS -> new LMedSRobustSinglePoint3DTriangulator(points, cameras);
548 case MSAC -> new MSACRobustSinglePoint3DTriangulator(points, cameras);
549 case PROSAC -> new PROSACRobustSinglePoint3DTriangulator(points, cameras, qualityScores);
550 default -> new PROMedSRobustSinglePoint3DTriangulator(points, cameras, qualityScores);
551 };
552 }
553
554 /**
555 * Creates a robust single 3D point triangulator using default robust
556 * method.
557 *
558 * @return an instance of a robust single 3D point triangulator.
559 */
560 public static RobustSinglePoint3DTriangulator create() {
561 return create(DEFAULT_ROBUST_METHOD);
562 }
563
564 /**
565 * Creates a robust single 3D point triangulator using provided points,
566 * cameras and default robust method.
567 *
568 * @param points matched 2D points. Each point in the list is assumed to
569 * be projected by the corresponding camera in the list.
570 * @param cameras list of cameras associated to the matched 2D point on the
571 * same position as the camera on the list.
572 * @return an instance of a robust single 3D point triangulator.
573 * @throws IllegalArgumentException if provided lists don't have the same
574 * length or their length is less than 2 views, which is the minimum
575 * required to compute triangulation.
576 */
577 public static RobustSinglePoint3DTriangulator create(
578 final List<Point2D> points, final List<PinholeCamera> cameras) {
579 return create(points, cameras, DEFAULT_ROBUST_METHOD);
580 }
581
582 /**
583 * Creates a robust single 3D point triangulator using provided points,
584 * cameras and default robust method.
585 *
586 * @param points matched 2D points. Each point in the list is assumed to
587 * be projected by the corresponding camera in the list.
588 * @param cameras list of cameras associated to the matched 2D point on the
589 * same position as the camera on the list.
590 * @param qualityScores quality scores corresponding to each point.
591 * @return an instance of a robust single 3D point triangulator.
592 * @throws IllegalArgumentException if provided lists or quality scores
593 * don't have the same length or their length is less than 2 views,
594 * which is the minimum required to compute triangulation.
595 */
596 public static RobustSinglePoint3DTriangulator create(
597 final List<Point2D> points, final List<PinholeCamera> cameras, final double[] qualityScores) {
598 return create(points, cameras, qualityScores, DEFAULT_ROBUST_METHOD);
599 }
600
601 /**
602 * Internal method to sets list of matched 2D points for each view and their
603 * corresponding cameras used to project them.
604 * This method does not check whether instance is locked.
605 *
606 * @param points2D list of matched 2D points on each view. Each point in the
607 * list is assumed to be projected by the corresponding camera in the list.
608 * @param cameras cameras for each view where 2D points are represented.
609 * @throws IllegalArgumentException if provided lists don't have the same
610 * length or their length is less than 2 views, which is the minimum
611 * required to compute triangulation.
612 */
613 private void internalSetPointsAndCameras(
614 final List<Point2D> points2D, final List<PinholeCamera> cameras) {
615
616 if (!SinglePoint3DTriangulator.areValidPointsAndCameras(points2D, cameras)) {
617 throw new IllegalArgumentException();
618 }
619
620 this.points2D = points2D;
621 this.cameras = cameras;
622 }
623 }