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.calibration.estimators; 17 18 import com.irurueta.algebra.AlgebraException; 19 import com.irurueta.algebra.ArrayUtils; 20 import com.irurueta.algebra.Matrix; 21 import com.irurueta.ar.calibration.ImageOfAbsoluteConic; 22 import com.irurueta.geometry.ProjectiveTransformation2D; 23 import com.irurueta.geometry.Transformation2D; 24 import com.irurueta.geometry.estimators.LockedException; 25 import com.irurueta.geometry.estimators.NotReadyException; 26 import com.irurueta.numerical.robust.RobustEstimatorException; 27 import com.irurueta.numerical.robust.RobustEstimatorMethod; 28 29 import java.util.List; 30 31 /** 32 * This is an abstract class for algorithms to robustly find the best 33 * ImageOfAbsoluteConic (IAC) for provided collection of 2D homographies. 34 * Implementions of this class should be able to detect and discard outliers 35 * in order to find the best solution. 36 */ 37 public abstract class ImageOfAbsoluteConicRobustEstimator { 38 /** 39 * Default robust estimator method when none is provided. 40 * In general for IAC estimation is best to use PROSAC or RANSAC than 41 * any other method, as it provides more robust results. 42 */ 43 public static final RobustEstimatorMethod DEFAULT_ROBUST_METHOD = RobustEstimatorMethod.PROSAC; 44 45 /** 46 * Default amount of progress variation before notifying a change in 47 * estimation progress. By default, this is set to 5%. 48 */ 49 public static final float DEFAULT_PROGRESS_DELTA = 0.05f; 50 51 /** 52 * Minimum allowed value for progress delta. 53 */ 54 public static final float MIN_PROGRESS_DELTA = 0.0f; 55 56 /** 57 * Maximum allowed value for progress delta. 58 */ 59 public static final float MAX_PROGRESS_DELTA = 1.0f; 60 61 /** 62 * Constant defining default confidence of the estimated result, which is 63 * 99%. This means that with a probability of 99% estimation will be 64 * accurate because chosen sub-samples will be inliers. 65 */ 66 public static final double DEFAULT_CONFIDENCE = 0.99; 67 68 /** 69 * Default maximum allowed number of iterations. 70 */ 71 public static final int DEFAULT_MAX_ITERATIONS = 5000; 72 73 /** 74 * Minimum allowed confidence value. 75 */ 76 public static final double MIN_CONFIDENCE = 0.0; 77 78 /** 79 * Maximum allowed confidence value. 80 */ 81 public static final double MAX_CONFIDENCE = 1.0; 82 83 /** 84 * Minimum allowed number of iterations. 85 */ 86 public static final int MIN_ITERATIONS = 1; 87 88 /** 89 * Homographies to estimate image of absolute conic (IAC). 90 */ 91 protected List<Transformation2D> homographies; 92 93 /** 94 * Internal non-robust estimator of IAC. 95 */ 96 protected final LMSEImageOfAbsoluteConicEstimator iacEstimator; 97 98 /** 99 * Listener to be notified of events such as when estimation starts, ends or 100 * its progress significantly changes. 101 */ 102 protected ImageOfAbsoluteConicRobustEstimatorListener listener; 103 104 /** 105 * Indicates if this estimator is locked because an estimation is being 106 * computed. 107 */ 108 protected boolean locked; 109 110 /** 111 * Amount of progress variation before notifying a progress change during 112 * estimation. 113 */ 114 protected float progressDelta; 115 116 /** 117 * Amount of confidence expressed as a value between 0.0 and 1.0 (which is 118 * equivalent to 100%). The amount of confidence indicates the probability 119 * that the estimated result is correct. Usually this value will be close 120 * to 1.0, but not exactly 1.0. 121 */ 122 protected double confidence; 123 124 /** 125 * Maximum allowed number of iterations. When the maximum number of 126 * iterations is exceeded, result will not be available, however an 127 * approximate result will be available for retrieval. 128 */ 129 protected int maxIterations; 130 131 // internal members to compute residuals 132 133 /** 134 * IAC matrix for one iteration of the robust estimator. 135 * This is used during residuals estimation. 136 * This instance is reused for performance reasons. 137 */ 138 private Matrix iacMatrix; 139 140 /** 141 * Matrix representation of an homography. 142 * This is used during residuals estimation. 143 * This instance is reused for performance reasons. 144 */ 145 private Matrix homMatrix; 146 147 /** 148 * Sub-matrix of homography used as the left term on a matrix multiplication. 149 * This is used during residuals estimation. 150 * This instance is reused for performance reasons. 151 */ 152 private double[] subMatrixLeft; 153 154 /** 155 * Sub-matrix of homography used as the right term on a matrix 156 * multiplication. 157 * This is used during residuals estimation. 158 * This instance is reused for performance reasons. 159 */ 160 private Matrix subMatrixRight; 161 162 /** 163 * Product multiplication of IAC by the right term. 164 * This is used during residuals estimation. 165 * This instance is reused for performance reasons. 166 */ 167 private Matrix mult1; 168 169 /** 170 * Constructor. 171 */ 172 protected ImageOfAbsoluteConicRobustEstimator() { 173 progressDelta = DEFAULT_PROGRESS_DELTA; 174 confidence = DEFAULT_CONFIDENCE; 175 maxIterations = DEFAULT_MAX_ITERATIONS; 176 iacEstimator = new LMSEImageOfAbsoluteConicEstimator(); 177 } 178 179 /** 180 * Constructor. 181 * 182 * @param listener listener to be notified of events such as when 183 * estimation starts, ends or its progress significantly changes. 184 */ 185 protected ImageOfAbsoluteConicRobustEstimator(final ImageOfAbsoluteConicRobustEstimatorListener listener) { 186 this(); 187 this.listener = listener; 188 } 189 190 /** 191 * Constructor. 192 * 193 * @param homographies list of homographies (2D transformations) used to 194 * estimate the image of absolute conic (IAC), which can be used to obtain 195 * pinhole camera intrinsic parameters. 196 * @throws IllegalArgumentException if not enough homographies are provided 197 * for default settings. Hence, at least 1 homography must be provided. 198 */ 199 protected ImageOfAbsoluteConicRobustEstimator(final List<Transformation2D> homographies) { 200 this(); 201 internalSetHomographies(homographies); 202 } 203 204 /** 205 * Constructor. 206 * 207 * @param homographies list of homographies (2D transformations) used to 208 * estimate the image of absolute conic (IAC), which can be used to obtain 209 * pinhole camera intrinsic parameters. 210 * @param listener listener to be notified of events such as when estimation 211 * starts, ends or estimation progress changes. 212 * @throws IllegalArgumentException if not enough homographies are provided 213 * for default settings. Hence, at least 1 homography must be provided. 214 */ 215 protected ImageOfAbsoluteConicRobustEstimator( 216 final List<Transformation2D> homographies, final ImageOfAbsoluteConicRobustEstimatorListener listener) { 217 this(listener); 218 internalSetHomographies(homographies); 219 } 220 221 /** 222 * Returns boolean indicating whether camera skewness is assumed to be zero 223 * or not. 224 * Skewness determines whether LCD sensor cells are properly aligned or not, 225 * where zero indicates perfect alignment. 226 * Typically, skewness is a value equal or very close to zero. 227 * 228 * @return true if camera skewness is assumed to be zero, otherwise camera 229 * skewness is estimated. 230 */ 231 public boolean isZeroSkewness() { 232 return iacEstimator.isZeroSkewness(); 233 } 234 235 /** 236 * Sets boolean indicating whether camera skewness is assumed to be zero or 237 * not. 238 * Skewness determines whether LCD sensor cells are properly aligned or not, 239 * where zero indicates perfect alignment. 240 * Typically, skewness is a value equal or very close to zero. 241 * 242 * @param zeroSkewness true if camera skewness is assumed to be zero, 243 * otherwise camera skewness is estimated. 244 * @throws LockedException if estimator is locked. 245 */ 246 public void setZeroSkewness(final boolean zeroSkewness) throws LockedException { 247 if (isLocked()) { 248 throw new LockedException(); 249 } 250 251 iacEstimator.setZeroSkewness(zeroSkewness); 252 } 253 254 /** 255 * Returns boolean indicating whether principal point is assumed to be at 256 * origin of coordinates or not. 257 * Typically principal point is located at image center (origin of 258 * coordinates), and usually matches the center of radial distortion if 259 * it is taken into account. 260 * 261 * @return true if principal point is assumed to be at origin of 262 * coordinates, false if principal point must be estimated. 263 */ 264 public boolean isPrincipalPointAtOrigin() { 265 return iacEstimator.isPrincipalPointAtOrigin(); 266 } 267 268 /** 269 * Sets boolean indicating whether principal point is assumed to be at 270 * origin of coordinates or not. 271 * Typically principal point is located at image center (origin of 272 * coordinates), and usually matches the center of radial distortion if it 273 * is taken into account. 274 * 275 * @param principalPointAtOrigin true if principal point is assumed to bet 276 * at origin of coordinates, false if principal point must be estimated 277 * @throws LockedException if estimator is locked. 278 */ 279 public void setPrincipalPointAtOrigin(final boolean principalPointAtOrigin) throws LockedException { 280 if (isLocked()) { 281 throw new LockedException(); 282 } 283 284 iacEstimator.setPrincipalPointAtOrigin(principalPointAtOrigin); 285 } 286 287 /** 288 * Returns boolean indicating whether aspect ratio of focal distances (i.e. 289 * vertical focal distance divided by horizontal focal distance) is known or 290 * not. 291 * Notice that focal distance aspect ratio is not related to image size 292 * aspect ratio. Typically, LCD sensor cells are square and hence aspect 293 * ratio of focal distances is known and equal to 1. 294 * This value is only taken into account if skewness is assumed to be zero, 295 * otherwise it is ignored. 296 * 297 * @return true if focal distance aspect ratio is known, false otherwise. 298 */ 299 public boolean isFocalDistanceAspectRatioKnown() { 300 return iacEstimator.isFocalDistanceAspectRatioKnown(); 301 } 302 303 /** 304 * Sets boolean indicating whether aspect ratio of focal distances (i.e. 305 * vertical focal distance divided by horizontal focal distance) is known or 306 * not. 307 * Notice that focal distance aspect ratio is not related to image size 308 * aspect ratio. Typically, LCD sensor cells are square and hence aspect 309 * ratio of focal distances is known and equal to 1. 310 * This value is only taken into account if skewness is assumed to be zero, 311 * otherwise it is ignored. 312 * 313 * @param focalDistanceAspectRatioKnown true if focal distance aspect ratio 314 * is known, false otherwise. 315 * @throws LockedException if estimator is locked. 316 */ 317 public void setFocalDistanceAspectRatioKnown(final boolean focalDistanceAspectRatioKnown) throws LockedException { 318 if (isLocked()) { 319 throw new LockedException(); 320 } 321 322 iacEstimator.setFocalDistanceAspectRatioKnown(focalDistanceAspectRatioKnown); 323 } 324 325 /** 326 * Returns aspect ratio of focal distances (i.e. vertical focal distance 327 * divided by horizontal focal distance). 328 * This value is only taken into account if skewness is assumed to be zero 329 * and focal distance aspect ratio is marked as known, otherwise it is 330 * ignored. 331 * By default, this is 1.0, since it is taken into account that typically 332 * LCD sensor cells are square and hence aspect ratio focal distances is 333 * known and equal to 1. 334 * Notice that focal distance aspect ratio is not related to image size 335 * aspect ratio. 336 * Notice that a negative aspect ratio indicates that vertical axis is 337 * reversed. This can be useful in some situations where image vertical 338 * coordinates are reversed respect to the physical world (i.e. in computer 339 * graphics typically image vertical coordinates go downwards, while in 340 * physical world they go upwards). 341 * 342 * @return aspect ratio of focal distances. 343 */ 344 public double getFocalDistanceAspectRatio() { 345 return iacEstimator.getFocalDistanceAspectRatio(); 346 } 347 348 /** 349 * Sets aspect ratio of focal distances (i.e. vertical focal distance 350 * divided by horizontal focal distance). 351 * This value is only taken into account if skewness is assumed to be zero 352 * and focal distance aspect ratio is marked as known, otherwise it is 353 * ignored. 354 * By default, this is 1.0, since it is taken into account that typically 355 * LCD sensor cells are square and hence aspect ratio focal distances is 356 * known and equal to 1. 357 * Notice that focal distance aspect ratio is not related to image size 358 * aspect ratio. 359 * Notice that a negative aspect ratio indicates that vertical axis is 360 * reversed. This can be useful in some situations where image vertical 361 * coordinates are reversed respect to the physical world (i.e. in computer 362 * graphics typically image vertical coordinates go downwards, while in 363 * physical world they go upwards). 364 * 365 * @param focalDistanceAspectRatio aspect ratio of focal distances to be set 366 * @throws LockedException if estimator is locked. 367 * @throws IllegalArgumentException if focal distance aspect ratio is too 368 * close to zero, as it might produce numerical instabilities. 369 */ 370 public void setFocalDistanceAspectRatio(final double focalDistanceAspectRatio) throws LockedException { 371 if (isLocked()) { 372 throw new LockedException(); 373 } 374 375 iacEstimator.setFocalDistanceAspectRatio(focalDistanceAspectRatio); 376 } 377 378 /** 379 * Returns reference to listener to be notified of events such as when 380 * estimation starts, ends or its progress significantly changes. 381 * 382 * @return listener to be notified of events. 383 */ 384 public ImageOfAbsoluteConicRobustEstimatorListener getListener() { 385 return listener; 386 } 387 388 /** 389 * Sets listener to be notified of events such as when estimation starts, 390 * ends or its progress significantly changes. 391 * 392 * @param listener listener to be notified of events. 393 * @throws LockedException if robust estimator is locked. 394 */ 395 public void setListener(final ImageOfAbsoluteConicRobustEstimatorListener listener) throws LockedException { 396 if (isLocked()) { 397 throw new LockedException(); 398 } 399 this.listener = listener; 400 } 401 402 /** 403 * Indicates whether listener has been provided and is available for 404 * retrieval. 405 * 406 * @return true if available, false otherwise. 407 */ 408 public boolean isListenerAvailable() { 409 return listener != null; 410 } 411 412 /** 413 * Indicates if this instance is locked because estimation is being 414 * computed. 415 * 416 * @return true if locked, false otherwise. 417 */ 418 public boolean isLocked() { 419 return locked; 420 } 421 422 /** 423 * Returns amount of progress variation before notifying a progress change 424 * during estimation. 425 * 426 * @return amount of progress variation before notifying a progress change 427 * during estimation. 428 */ 429 public float getProgressDelta() { 430 return progressDelta; 431 } 432 433 /** 434 * Sets amount of progress variation before notifying a progress change 435 * during estimation. 436 * 437 * @param progressDelta amount of progress variation before notifying a 438 * progress change during estimation. 439 * @throws IllegalArgumentException if progress delta is less than zero or 440 * greater than 1. 441 * @throws LockedException if this estimator is locked because an estimation 442 * is being computed. 443 */ 444 public void setProgressDelta(final float progressDelta) throws LockedException { 445 if (isLocked()) { 446 throw new LockedException(); 447 } 448 if (progressDelta < MIN_PROGRESS_DELTA || progressDelta > MAX_PROGRESS_DELTA) { 449 throw new IllegalArgumentException(); 450 } 451 this.progressDelta = progressDelta; 452 } 453 454 /** 455 * Returns amount of confidence expressed as a value between 0.0 and 1.0 456 * (which is equivalent to 100%). The amount of confidence indicates the 457 * probability that the estimated result is correct. Usually this value will 458 * be close to 1.0, but not exactly 1.0. 459 * 460 * @return amount of confidence as a value between 0.0 and 1.0. 461 */ 462 public double getConfidence() { 463 return confidence; 464 } 465 466 /** 467 * Sets amount of confidence expressed as a value between 0.0 and 1.0 (which 468 * is equivalent to 100%). The amount of confidence indicates the 469 * probability that the estimated result is correct. Usually this value will 470 * be close to 1.0, but not exactly 1.0. 471 * 472 * @param confidence confidence to be set as a value between 0.0 and 1.0. 473 * @throws IllegalArgumentException if provided value is not between 0.0 and 474 * 1.0. 475 * @throws LockedException if this estimator is locked because an estimator 476 * is being computed. 477 */ 478 public void setConfidence(final double confidence) throws LockedException { 479 if (isLocked()) { 480 throw new LockedException(); 481 } 482 if (confidence < MIN_CONFIDENCE || confidence > MAX_CONFIDENCE) { 483 throw new IllegalArgumentException(); 484 } 485 this.confidence = confidence; 486 } 487 488 /** 489 * Returns maximum allowed number of iterations. If maximum allowed number 490 * of iterations is achieved without converging to a result when calling 491 * estimate(), a RobustEstimatorException will be raised. 492 * 493 * @return maximum allowed number of iterations. 494 */ 495 public int getMaxIterations() { 496 return maxIterations; 497 } 498 499 /** 500 * Sets maximum allowed number of iterations. When the maximum number of 501 * iterations is exceeded, result will not be available, however an 502 * approximate result will be available for retrieval. 503 * 504 * @param maxIterations maximum allowed number of iterations to be set. 505 * @throws IllegalArgumentException if provided value is less than 1. 506 * @throws LockedException if this estimator is locked because an estimation 507 * is being computed. 508 */ 509 public void setMaxIterations(final int maxIterations) throws LockedException { 510 if (isLocked()) { 511 throw new LockedException(); 512 } 513 if (maxIterations < MIN_ITERATIONS) { 514 throw new IllegalArgumentException(); 515 } 516 this.maxIterations = maxIterations; 517 } 518 519 /** 520 * Gets list of homographies to estimate IAC. 521 * 522 * @return list of homographies to estimate IAC. 523 */ 524 public List<Transformation2D> getHomographies() { 525 return homographies; 526 } 527 528 /** 529 * Sets list of homographies to estimate IAC. 530 * 531 * @param homographies list of homographies to estimate IAC. 532 * @throws LockedException if estimator is locked. 533 * @throws IllegalArgumentException if provided list of homographies does 534 * not contain enough elements to estimate the DIAC using current 535 * settings. 536 */ 537 public void setHomographies(final List<Transformation2D> homographies) throws LockedException { 538 if (isLocked()) { 539 throw new LockedException(); 540 } 541 internalSetHomographies(homographies); 542 } 543 544 /** 545 * Returns minimum number of required homographies needed to estimate the 546 * Imag eof Absolute Conic (IAC). 547 * If no constraints are imposed, then at least 3 homographies are required. 548 * For each constraint imposed, one less equation will be required, hence 549 * if skewness is assumed to be known (by using its typical value of zero), 550 * then only 4 equations will be needed. 551 * If also the horizontal and vertical coordinates of the principal point 552 * are assumed to be known (by being placed at the 2D origin of coordinates, 553 * which is a typical value), then only 2 equations will be needed 554 * (1 homography). 555 * 556 * @return minimum number of required homographies required to estimate the 557 * IAC. 558 */ 559 public int getMinNumberOfRequiredHomographies() { 560 return iacEstimator.getMinNumberOfRequiredHomographies(); 561 } 562 563 /** 564 * Returns value indicating whether required data has been provided so that 565 * IAC estimation can start. 566 * If true, estimator is ready to compute the IAC, otherwise more data needs 567 * to be provided. 568 * 569 * @return true if estimator is ready, false otherwise. 570 */ 571 public boolean isReady() { 572 return homographies != null && homographies.size() >= getMinNumberOfRequiredHomographies(); 573 } 574 575 /** 576 * Returns quality scores corresponding to each homography. 577 * The larger the score value the better the quality of the homography. 578 * This implementation always returns null. 579 * Subclasses using quality scores must implement proper behaviour. 580 * 581 * @return quality scores corresponding to each homography. 582 */ 583 public double[] getQualityScores() { 584 return null; 585 } 586 587 /** 588 * Sets quality scores corresponding to each homography. 589 * The larger the score value the better the quality of the homography. 590 * This implementation makes no action. 591 * Subclasses using quality scores must implement proper behaviour. 592 * 593 * @param qualityScores quality scores corresponding to each homography. 594 * @throws LockedException if robust estimator is locked because an 595 * estimation is already in progress. 596 * @throws IllegalArgumentException if provided quality scores length is 597 * smaller than minimum required number of homographies. 598 */ 599 public void setQualityScores(final double[] qualityScores) throws LockedException { 600 } 601 602 /** 603 * Estimates Image of Absolute Conic (IAC). 604 * 605 * @return estimated IAC. 606 * @throws LockedException if robust estimator is locked because an 607 * estimation is already in progress. 608 * @throws NotReadyException if provided input data is not enough to start 609 * the estimation. 610 * @throws RobustEstimatorException if estimation fails for any reason 611 * (i.e. numerical instability, no solution available, etc). 612 */ 613 public abstract ImageOfAbsoluteConic estimate() throws LockedException, NotReadyException, 614 RobustEstimatorException; 615 616 /** 617 * Returns method being used for robust estimation. 618 * 619 * @return method being used for robust estimation. 620 */ 621 public abstract RobustEstimatorMethod getMethod(); 622 623 /** 624 * Creates an image of absolute conic robust estimator using provided 625 * method. 626 * 627 * @param method method of a robust estimator algorithm to estimate best 628 * IAC. 629 * @return an instance of an image of absolute conic robust estimator. 630 */ 631 public static ImageOfAbsoluteConicRobustEstimator create(final RobustEstimatorMethod method) { 632 return switch (method) { 633 case LMEDS -> new LMedSImageOfAbsoluteConicRobustEstimator(); 634 case MSAC -> new MSACImageOfAbsoluteConicRobustEstimator(); 635 case PROSAC -> new PROSACImageOfAbsoluteConicRobustEstimator(); 636 case PROMEDS -> new PROMedSImageOfAbsoluteConicRobustEstimator(); 637 default -> new RANSACImageOfAbsoluteConicRobustEstimator(); 638 }; 639 } 640 641 /** 642 * Creates an image of absolute conic robust estimator using provided 643 * homographies. 644 * 645 * @param homographies list of homographies. 646 * @param qualityScores quality scores corresponding to each homography. 647 * @param method method of a robust estimator algorithm to estimate 648 * best IAC. 649 * @return an instance of an image of absolute conic robust estimator. 650 * @throws IllegalArgumentException if provided list of homographies and 651 * quality scores don't have the same size or size is too short. 652 */ 653 public static ImageOfAbsoluteConicRobustEstimator create( 654 final List<Transformation2D> homographies, final double[] qualityScores, 655 final RobustEstimatorMethod method) { 656 return switch (method) { 657 case LMEDS -> new LMedSImageOfAbsoluteConicRobustEstimator(homographies); 658 case MSAC -> new MSACImageOfAbsoluteConicRobustEstimator(homographies); 659 case PROSAC -> new PROSACImageOfAbsoluteConicRobustEstimator(homographies, qualityScores); 660 case PROMEDS -> new PROMedSImageOfAbsoluteConicRobustEstimator(homographies, qualityScores); 661 default -> new RANSACImageOfAbsoluteConicRobustEstimator(homographies); 662 }; 663 } 664 665 /** 666 * Creates an image of absolute conic robust estimator using provided 667 * homographies. 668 * 669 * @param homographies list of homographies. 670 * @param method method of a robust estimator algorithm to estimate 671 * best IAC. 672 * @return an instance of an image of absolute conic robust estimator. 673 * @throws IllegalArgumentException if provided list of homographies is too 674 * short. 675 */ 676 public static ImageOfAbsoluteConicRobustEstimator create( 677 final List<Transformation2D> homographies, final RobustEstimatorMethod method) { 678 return switch (method) { 679 case LMEDS -> new LMedSImageOfAbsoluteConicRobustEstimator(homographies); 680 case MSAC -> new MSACImageOfAbsoluteConicRobustEstimator(homographies); 681 case PROSAC -> new PROSACImageOfAbsoluteConicRobustEstimator(homographies); 682 case PROMEDS -> new PROMedSImageOfAbsoluteConicRobustEstimator(homographies); 683 default -> new RANSACImageOfAbsoluteConicRobustEstimator(homographies); 684 }; 685 } 686 687 /** 688 * Creates an image of absolute conic robust estimator using default 689 * method. 690 * 691 * @return an instance of an image of absolute conic robust estimator. 692 */ 693 public static ImageOfAbsoluteConicRobustEstimator create() { 694 return create(DEFAULT_ROBUST_METHOD); 695 } 696 697 /** 698 * Creates an image of absolute conic robust estimator using provided 699 * homographies. 700 * 701 * @param homographies list of homographies. 702 * @param qualityScores quality scores corresponding to each point. 703 * @return an instance of an image of absolute conic robust estimator. 704 * @throws IllegalArgumentException if provided list of homographies and 705 * quality scores don't have the same size or size is too short. 706 */ 707 public static ImageOfAbsoluteConicRobustEstimator create( 708 final List<Transformation2D> homographies, final double[] qualityScores) { 709 return create(homographies, qualityScores, DEFAULT_ROBUST_METHOD); 710 } 711 712 /** 713 * Creates an image of absolute conic robust estimator using provided 714 * homographies. 715 * 716 * @param homographies list of homographies. 717 * @return an instance of an image of absolute conic robust estimator. 718 * @throws IllegalArgumentException if provided list of homographies and 719 * is too short. 720 */ 721 public static ImageOfAbsoluteConicRobustEstimator create(final List<Transformation2D> homographies) { 722 return create(homographies, DEFAULT_ROBUST_METHOD); 723 } 724 725 /** 726 * Computes the residual between an image of absolute conic (IAC) and 727 * a 2D transformation (homography). 728 * Residual is derived from the fact that rotation matrices are 729 * orthonormal (i.e. h1'*IAC*h2 = 0 and 730 * h1'*IAC*h1 = h2'*IAC*h2 --< h1'*IAC*h1 - h2'*IAC*h2 = 0). 731 * The residual will be the average error of those two equations. 732 * 733 * @param iac the matrix of an image of absolute conic (IAC) 734 * @param homography 2D transformation (homography) 735 * @return residual. 736 */ 737 protected double residual(final ImageOfAbsoluteConic iac, final Transformation2D homography) { 738 739 iac.normalize(); 740 if (homography instanceof ProjectiveTransformation2D projectiveTransformation2D) { 741 projectiveTransformation2D.normalize(); 742 } 743 744 try { 745 if (iacMatrix == null) { 746 iacMatrix = iac.asMatrix(); 747 } else { 748 iac.asMatrix(iacMatrix); 749 } 750 751 if (homMatrix == null) { 752 homMatrix = homography.asMatrix(); 753 } else { 754 homography.asMatrix(homMatrix); 755 } 756 757 if (subMatrixLeft == null) { 758 subMatrixLeft = new double[ProjectiveTransformation2D.HOM_COORDS]; 759 } 760 if (subMatrixRight == null) { 761 subMatrixRight = new Matrix(ProjectiveTransformation2D.HOM_COORDS, 1); 762 } 763 if (mult1 == null) { 764 mult1 = new Matrix(ProjectiveTransformation2D.HOM_COORDS, 1); 765 } 766 767 // 1st equation h1'*IAC*h2 = 0 768 769 // 1st column of homography 770 homMatrix.getSubmatrixAsArray(0, 0, 771 ProjectiveTransformation2D.HOM_COORDS - 1, 0, subMatrixLeft); 772 // 2nd column of homography 773 homMatrix.getSubmatrix(0, 1, 774 ProjectiveTransformation2D.HOM_COORDS - 1, 1, subMatrixRight); 775 776 // IAC * h2 777 iacMatrix.multiply(subMatrixRight, mult1); 778 779 // h1' * (IAC * h2) 780 final var error1 = Math.abs(ArrayUtils.dotProduct(subMatrixLeft, mult1.getBuffer())); 781 782 // 2nd equation h1'*IAC*h1 - h2'*IAC*h2 = 0 783 784 // 1st column of homography 785 homMatrix.getSubmatrixAsArray(0, 0, 786 ProjectiveTransformation2D.HOM_COORDS - 1, 0, subMatrixLeft); 787 homMatrix.getSubmatrix(0, 0, 788 ProjectiveTransformation2D.HOM_COORDS - 1, 0, subMatrixRight); 789 790 // IAC * h1 791 iacMatrix.multiply(subMatrixRight, mult1); 792 793 // h1' * (IAC * h1) 794 final var error2a = ArrayUtils.dotProduct(subMatrixLeft, mult1.getBuffer()); 795 796 // 2nd column of homography 797 homMatrix.getSubmatrixAsArray(0, 1, 798 ProjectiveTransformation2D.HOM_COORDS - 1, 1, subMatrixLeft); 799 homMatrix.getSubmatrix(0, 1, 800 ProjectiveTransformation2D.HOM_COORDS - 1, 1, subMatrixRight); 801 802 // IAC * h2 803 iacMatrix.multiply(subMatrixRight, mult1); 804 805 // h2' * (IAC * h1) 806 final var error2b = ArrayUtils.dotProduct(subMatrixLeft, mult1.getBuffer()); 807 808 final var error2 = Math.abs(error2a - error2b); 809 810 return 0.5 * (error1 + error2); 811 } catch (final AlgebraException e) { 812 return Double.MAX_VALUE; 813 } 814 } 815 816 /** 817 * Sets list of homographies. 818 * This method does not check whether estimator is locked. 819 * 820 * @param homographies list of homographies to estimate IAC. 821 * @throws IllegalArgumentException if provided list of homographies is null 822 * or too small. 823 */ 824 private void internalSetHomographies(final List<Transformation2D> homographies) { 825 if (homographies == null || homographies.size() < getMinNumberOfRequiredHomographies()) { 826 throw new IllegalArgumentException(); 827 } 828 829 this.homographies = homographies; 830 } 831 }