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 }