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.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 }