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; 17 18 import com.irurueta.ar.calibration.estimators.ImageOfAbsoluteConicRobustEstimator; 19 import com.irurueta.ar.calibration.estimators.ImageOfAbsoluteConicRobustEstimatorListener; 20 import com.irurueta.ar.calibration.estimators.LMedSImageOfAbsoluteConicRobustEstimator; 21 import com.irurueta.ar.calibration.estimators.MSACImageOfAbsoluteConicRobustEstimator; 22 import com.irurueta.ar.calibration.estimators.PROMedSImageOfAbsoluteConicRobustEstimator; 23 import com.irurueta.ar.calibration.estimators.PROSACImageOfAbsoluteConicRobustEstimator; 24 import com.irurueta.ar.calibration.estimators.RANSACImageOfAbsoluteConicRobustEstimator; 25 import com.irurueta.geometry.GeometryException; 26 import com.irurueta.geometry.HomogeneousPoint2D; 27 import com.irurueta.geometry.PinholeCameraIntrinsicParameters; 28 import com.irurueta.geometry.Point2D; 29 import com.irurueta.geometry.Transformation2D; 30 import com.irurueta.geometry.estimators.*; 31 import com.irurueta.numerical.NumericalException; 32 import com.irurueta.numerical.robust.RobustEstimatorMethod; 33 34 import java.util.ArrayList; 35 import java.util.Arrays; 36 import java.util.List; 37 import java.util.logging.Level; 38 import java.util.logging.Logger; 39 40 /** 41 * Calibrates a camera in order to find its intrinsic parameters and other 42 * parameters such as radial distortion. 43 */ 44 public abstract class CameraCalibrator { 45 46 /** 47 * Default robust estimator method to be used for homography estimations. 48 */ 49 public static final RobustEstimatorMethod DEFAULT_HOMOGRAPHY_METHOD = RobustEstimatorMethod.PROSAC; 50 51 /** 52 * Default robust estimator method to be used for IAC estimation. 53 */ 54 public static final RobustEstimatorMethod DEFAULT_IAC_METHOD = RobustEstimatorMethod.PROSAC; 55 56 /** 57 * Indicates whether radial distortion must be estimated or not by default. 58 */ 59 public static final boolean DEFAULT_ESTIMATE_RADIAL_DISTORTION = true; 60 61 /** 62 * Default amount of progress variation before notifying a change in 63 * estimation progress. By default, this is set to 5%. 64 */ 65 public static final float DEFAULT_PROGRESS_DELTA = 0.05f; 66 67 /** 68 * Minimum allowed value for progress delta. 69 */ 70 public static final float MIN_PROGRESS_DELTA = 0.0f; 71 72 /** 73 * Maximum allowed value for progress delta. 74 */ 75 public static final float MAX_PROGRESS_DELTA = 1.0f; 76 77 /** 78 * Default method used for camera calibration. 79 * The default method uses an alternating technique where first intrinsic 80 * parameters and camera pose are estimated without accounting for 81 * distortion and then the results are used to obtain an initial guess 82 * for distortion, which is then used to correct initially sampled points 83 * and repeat the whole process until convergence is achieved. 84 */ 85 public static final CameraCalibratorMethod DEFAULT_METHOD = CameraCalibratorMethod.ERROR_OPTIMIZATION; 86 87 /** 88 * Pattern used for camera calibration. Each pattern contains a unique 89 * combination of 2D points that must be sampled using the camera to be 90 * calibrated. 91 */ 92 protected Pattern2D pattern; 93 94 /** 95 * List of samples obtained from different pictures using the same camera 96 * device (or same camera model). Several samples can be used to calibrate 97 * the camera. The more samples are used, typically the better the results. 98 */ 99 protected List<CameraCalibratorSample> samples; 100 101 /** 102 * Quality scores for samples. This can be used on certain 103 * robust estimation methods of the IAC such as PROSAC and PROMedS. 104 * If not provided, homography quality scores will be estimated based on 105 * re-projection error and this value will be ignored. 106 * Typically, this will not be provided, but it can be used in case that it 107 * can be assured by some means that one sample is better than another. 108 */ 109 protected double[] samplesQualityScores; 110 111 /** 112 * Estimated homographies from provided list of samples respect to provided 113 * pattern. 114 */ 115 protected List<Transformation2D> homographies; 116 117 /** 118 * Quality scores for estimated homographies to be used during IAC 119 * estimation when PROSAC or PROMedS robust method is used. This value 120 * is only computed when no samples quality scores are provided. 121 * Homography quality scores are obtained based on re-projection error of 122 * marker coordinates. 123 */ 124 protected double[] homographyQualityScores; 125 126 /** 127 * Indicates whether homography quality scores need to be estimated if 128 * samples quality scores are not provided. 129 */ 130 protected boolean homographyQualityScoresRequired; 131 132 /** 133 * Estimated image of absolute conic. This can be used to obtain intrinsic 134 * pinhole camera intrinsic parameters. 135 */ 136 protected ImageOfAbsoluteConic iac; 137 138 /** 139 * Estimated intrinsic pinhole camera parameters. Intrinsic parameters 140 * contain data related to the camera sensor such as focal length, 141 * skewness or principal point. Except focal length, typically intrinsic 142 * parameters are fixed, and even in some situations such as when camera 143 * lens is fixed, focal length also remains constant. Because the latter is 144 * true in most phone cameras, it can be considered that intrinsic 145 * parameters remain constant for all phones of the same maker and model, 146 * and for that reason a calibrator can be used with pictures taken from 147 * different phones as long as they are the same phone model. 148 */ 149 protected PinholeCameraIntrinsicParameters intrinsic; 150 151 /** 152 * Estimated radial distortion. Radial distortion is inherent to the camera 153 * lens, and remains constant as long as the lens doesn't change. 154 * Because in most phone cameras the lens remains constant, lens distortion 155 * can be modeled once for each phone model, and for that reason a single 156 * calibrator can be used with pictures taken from different phones as long 157 * as they are the same phone model. 158 */ 159 protected RadialDistortion distortion; 160 161 /** 162 * Indicates whether radial distortion must be estimated or not. 163 */ 164 protected boolean estimateRadialDistortion; 165 166 /** 167 * Robust estimator method to be used during homography estimation. 168 * This will only be taken into account if more than 4 markers are detected 169 * on a single sample, otherwise no robust method is used and a single LMSE 170 * solution for the homography is found. 171 */ 172 protected RobustEstimatorMethod homographyMethod; 173 174 /** 175 * Robust estimator method to be used during IAC estimation. 176 * This will only be taken into account if more than 1 sample is provided, 177 * otherwise no robust method is used and a single LMSE solution for the 178 * IAC is found. 179 */ 180 protected RobustEstimatorMethod imageOfAbsoluteConicMethod; 181 182 /** 183 * Robust estimator of homographies between sampled markers and ideal 184 * pattern markers. 185 */ 186 protected PointCorrespondenceProjectiveTransformation2DRobustEstimator homographyEstimator; 187 188 /** 189 * Robust estimator of the Image of Absolute Conic (IAC). 190 */ 191 protected ImageOfAbsoluteConicRobustEstimator iacEstimator; 192 193 /** 194 * Listener for homography estimator. 195 */ 196 protected ProjectiveTransformation2DRobustEstimatorListener homographyEstimatorListener; 197 198 /** 199 * Listener for image of absolute conic estimator. 200 */ 201 protected ImageOfAbsoluteConicRobustEstimatorListener iacEstimatorListener; 202 203 /** 204 * Indicates whether this instance is locked because calibration is in 205 * progress. 206 */ 207 protected volatile boolean locked; 208 209 /** 210 * Amount of progress variation before notifying a progress change during 211 * estimation. 212 */ 213 protected float progressDelta; 214 215 /** 216 * Listener to notify when calibration starts, finishes or its progress 217 * significantly changes. 218 */ 219 protected CameraCalibratorListener listener; 220 221 /** 222 * Indicates progress of homography estimation. 223 */ 224 protected float homographyProgress; 225 226 /** 227 * Indicates progress of homography estimation for all samples. 228 */ 229 protected float sampleProgress; 230 231 /** 232 * Indicates progress of IAC estimation. 233 */ 234 protected float iacProgress; 235 236 /** 237 * Indicates progress of estimation of intrinsic parameters. 238 */ 239 protected float intrinsicProgress; 240 241 /** 242 * Constructor. 243 */ 244 protected CameraCalibrator() { 245 pattern = null; 246 homographies = null; 247 homographyQualityScores = null; 248 iac = null; 249 intrinsic = null; 250 distortion = null; 251 estimateRadialDistortion = DEFAULT_ESTIMATE_RADIAL_DISTORTION; 252 253 internalSetHomographyMethod(DEFAULT_HOMOGRAPHY_METHOD); 254 internalSetImageOfAbsoluteConicMethod(DEFAULT_IAC_METHOD); 255 256 samples = null; 257 samplesQualityScores = null; 258 259 progressDelta = DEFAULT_PROGRESS_DELTA; 260 } 261 262 /** 263 * Constructor. 264 * 265 * @param pattern 2D pattern to use for calibration. 266 * @param samples samples of the pattern taken with the camera to calibrate. 267 * @throws IllegalArgumentException if not enough samples are provided. 268 */ 269 protected CameraCalibrator(final Pattern2D pattern, final List<CameraCalibratorSample> samples) { 270 this.pattern = pattern; 271 homographies = null; 272 homographyQualityScores = null; 273 iac = null; 274 intrinsic = null; 275 distortion = null; 276 estimateRadialDistortion = DEFAULT_ESTIMATE_RADIAL_DISTORTION; 277 278 internalSetHomographyMethod(DEFAULT_HOMOGRAPHY_METHOD); 279 internalSetImageOfAbsoluteConicMethod(DEFAULT_IAC_METHOD); 280 281 internalSetSamples(samples); 282 samplesQualityScores = null; 283 284 progressDelta = DEFAULT_PROGRESS_DELTA; 285 } 286 287 /** 288 * Constructor. 289 * 290 * @param pattern 2D pattern to use for calibration. 291 * @param samples samples of the pattern taken with the camera to calibrate. 292 * @param samplesQualityScores quality scores for each sample. 293 * @throws IllegalArgumentException if not enough samples are provided or if 294 * both samples and quality scores do not have the same size. 295 */ 296 protected CameraCalibrator(final Pattern2D pattern, final List<CameraCalibratorSample> samples, 297 final double[] samplesQualityScores) { 298 if (samples.size() != samplesQualityScores.length) { 299 throw new IllegalArgumentException(); 300 } 301 302 this.pattern = pattern; 303 homographies = null; 304 homographyQualityScores = null; 305 iac = null; 306 intrinsic = null; 307 distortion = null; 308 estimateRadialDistortion = DEFAULT_ESTIMATE_RADIAL_DISTORTION; 309 310 internalSetHomographyMethod(DEFAULT_HOMOGRAPHY_METHOD); 311 internalSetImageOfAbsoluteConicMethod(DEFAULT_IAC_METHOD); 312 313 internalSetSamples(samples); 314 internalSetSamplesQualityScores(samplesQualityScores); 315 316 progressDelta = DEFAULT_PROGRESS_DELTA; 317 } 318 319 /** 320 * Returns pattern used for camera calibration. Each pattern contain a 321 * unique combination of 2D points that must be sampled using the camera to 322 * be calibrated. 323 * 324 * @return pattern used for camera calibration. 325 */ 326 public Pattern2D getPattern() { 327 return pattern; 328 } 329 330 /** 331 * Sets pattern used for camera calibration. Each pattern contains a unique 332 * combination of 2D points that must be sampled using the camera to be 333 * calibrated. 334 * 335 * @param pattern pattern used for camera calibration. 336 * @throws LockedException if this instance is locked. 337 */ 338 public void setPattern(final Pattern2D pattern) throws LockedException { 339 if (isLocked()) { 340 throw new LockedException(); 341 } 342 343 this.pattern = pattern; 344 } 345 346 /** 347 * Returns list of samples obtained from different pictures using the same 348 * camera device (or same camera model). Several samples can be used to 349 * calibrate the camera (a pinhole camera can be estimated for each sample). 350 * The more samples are used, typically the better the results. 351 * 352 * @return list of samples. 353 */ 354 public List<CameraCalibratorSample> getSamples() { 355 return samples; 356 } 357 358 /** 359 * Sets list of samples obtained from different pictures using the same 360 * camera device (or same camera model). Several samples can be used to 361 * calibrate the camera (a pinhole camera can be estimated for each sample). 362 * The more samples are used, typically the better the results. 363 * 364 * @param samples list of samples. 365 * @throws LockedException if this instance is locked. 366 * @throws IllegalArgumentException if not enough samples are provided to 367 * estimate the intrinsic parameters. By default, the minimum is 1, 368 * but depending on the settings at least 3 samples might be required. 369 */ 370 public void setSamples(final List<CameraCalibratorSample> samples) throws LockedException { 371 if (isLocked()) { 372 throw new LockedException(); 373 } 374 internalSetSamples(samples); 375 } 376 377 /** 378 * Returns quality scores assigned to each provided sample. This can be used 379 * on certain robust estimation methods of the IAC such as PROSAC and 380 * PROMedS. If not provided, homography quality scores will be estimated 381 * based on re-projection error and this value will be ignored. 382 * Typically, this will not be provided, but it can be used in case that it 383 * can be assured by some means that one sample is better than another 384 * 385 * @return quality scores assigned to each provided sample. 386 */ 387 public double[] getSamplesQualityScores() { 388 return samplesQualityScores; 389 } 390 391 /** 392 * Sets quality scores assigned to each provided sample. This can be used on 393 * certain robust estimation methods of the IAC such as PROSAC and PROMedS. 394 * If not provided, homography quality scores will be estimated based on 395 * re-projection error and this value will be ignored. 396 * 397 * @param samplesQualityScores quality scores assigned to each provided 398 * sample. 399 * @throws LockedException if this instance is locked. 400 * @throws IllegalArgumentException if not enough quality scores are 401 * provided for the corresponding samples to estimate the intrinsic 402 * parameters. By default, the minimum is 1, but depending on the 403 * settings at least 3 samples might be required. 404 */ 405 public void setSamplesQualityScores(final double[] samplesQualityScores) throws LockedException { 406 if (isLocked()) { 407 throw new LockedException(); 408 } 409 internalSetSamplesQualityScores(samplesQualityScores); 410 } 411 412 /** 413 * Returns quality scores for estimated homographies. 414 * If samples quality scores were provided, these will be equal 415 * to those provided. If no samples quality scores were provided, 416 * these scores will be related to the estimated homography 417 * estimation error based on sampled data and ideal data. 418 * This should rarely be used. It can be used for debugging purposes 419 * teo determine whether homographies used for calibration are 420 * reliable or not. 421 * 422 * @return estimated quality scores for homographies. 423 */ 424 public double[] getHomographyQualityScores() { 425 return homographyQualityScores; 426 } 427 428 /** 429 * Returns estimated image of absolute conic. This can be used to obtain 430 * intrinsic pinhole camera intrinsic parameters. 431 * 432 * @return estimated image of absolute conic or null if estimation has not 433 * been completed. 434 */ 435 public ImageOfAbsoluteConic getEstimatedImageOfAbsoluteConic() { 436 return iac; 437 } 438 439 /** 440 * Returns estimated pinhole camera intrinsic parameters. 441 * 442 * @return estimated pinhole camera intrinsic parameters or null if 443 * estimation has not been completed. 444 */ 445 public PinholeCameraIntrinsicParameters getEstimatedIntrinsicParameters() { 446 return intrinsic; 447 } 448 449 /** 450 * Returns estimated radial distortion due to camera lens. 451 * 452 * @return estimated radial distortion or null if estimation has not 453 * completed or radial distortion was not requested. 454 */ 455 public RadialDistortion getDistortion() { 456 return distortion; 457 } 458 459 /** 460 * Returns boolean indicating whether radial distortion must be estimated or 461 * not during calibration. 462 * 463 * @return true if radial distortion must be estimated, false otherwise. 464 */ 465 public boolean getEstimateRadialDistortion() { 466 return estimateRadialDistortion; 467 } 468 469 /** 470 * Sets boolean indicating whether radial distortion must be estimated or 471 * not during calibration. 472 * 473 * @param estimateRadialDistortion true if radial distortion must be 474 * estimated, false otherwise. 475 * @throws LockedException if this instance is locked. 476 */ 477 public void setEstimateRadialDistortion(final boolean estimateRadialDistortion) throws LockedException { 478 if (isLocked()) { 479 throw new LockedException(); 480 } 481 482 this.estimateRadialDistortion = estimateRadialDistortion; 483 } 484 485 /** 486 * Returns robust estimator method to be used during homography estimation. 487 * This will only be taken into account if more than 4 markers are detected 488 * on a single sample, otherwise no robust method is used and a single LMSE 489 * solution for the homography is found. 490 * 491 * @return robust estimator method to be used during homography estimation. 492 */ 493 public RobustEstimatorMethod getHomographyMethod() { 494 return homographyMethod; 495 } 496 497 /** 498 * Sets robust estimator method to be used during homography estimation. 499 * This will only be taken into account if more than 4 markers are detected 500 * on a single sample, otherwise no robust method is used and a single LMSE 501 * solution for the homography is found. 502 * 503 * @param homographyMethod robust estimator method to be used during 504 * homography estimation. 505 * @throws LockedException if this instance is locked. 506 */ 507 public void setHomographyMethod(final RobustEstimatorMethod homographyMethod) throws LockedException { 508 if (isLocked()) { 509 throw new LockedException(); 510 } 511 internalSetHomographyMethod(homographyMethod); 512 } 513 514 /** 515 * Returns robust estimator method to be used during IAC estimation. 516 * This will only be taken into account if more than 1 sample is provided, 517 * otherwise no robust method is used and a single LMSE solution for the 518 * IAC is found 519 * 520 * @return robust estimator method to be used during IAC estimation 521 */ 522 public RobustEstimatorMethod getImageOfAbsoluteConicMethod() { 523 return imageOfAbsoluteConicMethod; 524 } 525 526 /** 527 * Sets robust estimator method to be used during IAC estimation. 528 * This will only be taken into account if more than 1 sample is provided, 529 * otherwise no robust method is used and a single LMSE solution for the 530 * IAC is found. 531 * 532 * @param imageOfAbsoluteConicMethod robust estimator method to be used 533 * during IAC estimation. 534 * @throws LockedException if this instance is locked. 535 */ 536 public void setImageOfAbsoluteConicMethod(final RobustEstimatorMethod imageOfAbsoluteConicMethod) 537 throws LockedException { 538 if (isLocked()) { 539 throw new LockedException(); 540 } 541 internalSetImageOfAbsoluteConicMethod(imageOfAbsoluteConicMethod); 542 } 543 544 /** 545 * Returns homography estimator, which can be retrieved in case that some 546 * additional parameter needed to be adjusted. 547 * It is discouraged to directly access the homography estimator during 548 * camera calibration, as it might interfere with the results. 549 * 550 * @return homography estimator. 551 */ 552 public PointCorrespondenceProjectiveTransformation2DRobustEstimator getHomographyEstimator() { 553 return homographyEstimator; 554 } 555 556 /** 557 * Returns IAC estimator, which can be retrieved in case that some 558 * additional parameter needed to be adjusted. 559 * It is discouraged to directly access the homography estimator during 560 * camera calibration, as it might interfere with the results. 561 * 562 * @return IAC estimator. 563 */ 564 public ImageOfAbsoluteConicRobustEstimator getIACEstimator() { 565 return iacEstimator; 566 } 567 568 /** 569 * Returns boolean indicating whether camera skewness is assumed to be zero 570 * or not. 571 * Skewness determines whether LCD sensor cells are properly aligned or not, 572 * where zero indicates perfect alignment. 573 * Typically, skewness is a value equal or very close to zero. 574 * 575 * @return true if camera skewness is assumed to be zero, otherwise camera 576 * skewness is estimated. 577 */ 578 public boolean isZeroSkewness() { 579 return iacEstimator.isZeroSkewness(); 580 } 581 582 /** 583 * Sets boolean indicating whether camera skewness is assumed to be zero or 584 * not. 585 * Skewness determines whether LCD sensor cells are properly aligned or not, 586 * where zero indicates perfect alignment. 587 * Typically, skewness is a value equal or very close to zero. 588 * 589 * @param zeroSkewness true if camera skewness is assumed to be zero, 590 * otherwise camera skewness is estimated. 591 * @throws LockedException if this instance is locked. 592 */ 593 public void setZeroSkewness(final boolean zeroSkewness) throws LockedException { 594 if (isLocked()) { 595 throw new LockedException(); 596 } 597 598 iacEstimator.setZeroSkewness(zeroSkewness); 599 } 600 601 /** 602 * Returns boolean indicating whether principal point is assumed to be at 603 * origin of coordinates or not. 604 * Typically principal point is located at image center (origin of 605 * coordinates), and usually matches the center of radial distortion if it 606 * is taken into account. 607 * 608 * @return true if principal point is assumed to be at origin of 609 * coordinates, false if principal point must be estimated. 610 */ 611 public boolean isPrincipalPointAtOrigin() { 612 return iacEstimator.isPrincipalPointAtOrigin(); 613 } 614 615 /** 616 * Sets boolean indicating whether principal point is assumed to be at 617 * origin of coordinates or not. 618 * Typically principal point is located at image center (origin of 619 * coordinates), and usually matches the center of radial distortion if it 620 * is taken into account. 621 * 622 * @param principalPointAtOrigin true if principal point is assumed to bet 623 * at origin of coordinates, false if principal point must be estimated. 624 * @throws LockedException if estimator is locked. 625 */ 626 public void setPrincipalPointAtOrigin(final boolean principalPointAtOrigin) throws LockedException { 627 if (isLocked()) { 628 throw new LockedException(); 629 } 630 631 iacEstimator.setPrincipalPointAtOrigin(principalPointAtOrigin); 632 } 633 634 /** 635 * Returns boolean indicating whether aspect ratio of focal distances (i.e. 636 * vertical focal distance divided by horizontal focal distance) is known or 637 * not. 638 * Notice that focal distance aspect ratio is not related to image size 639 * aspect ratio. Typically, LCD sensor cells are square and hence aspect 640 * ratio of focal distances is known and equal to 1. 641 * This value is only taken into account if skewness is assumed to be zero, 642 * otherwise it is ignored. 643 * 644 * @return true if focal distance aspect ratio is known, false otherwise. 645 */ 646 public boolean isFocalDistanceAspectRatioKnown() { 647 return iacEstimator.isFocalDistanceAspectRatioKnown(); 648 } 649 650 /** 651 * Sets boolean indicating whether aspect ratio of focal distances (i.e. 652 * vertical focal distance divided by horizontal focal distance) is known or 653 * not. 654 * Notice that focal distance aspect ratio is not related to image size 655 * aspect ratio. Typically, LCD sensor cells are square and hence aspect 656 * ratio of focal distances is known and equal to 1. 657 * This value is only taken into account if skewness is assumed to be zero, 658 * otherwise it is ignored. 659 * 660 * @param focalDistanceAspectRatioKnown true if focal distance aspect ratio 661 * is known, false otherwise. 662 * @throws LockedException if estimator is locked. 663 */ 664 public void setFocalDistanceAspectRatioKnown(final boolean focalDistanceAspectRatioKnown) throws LockedException { 665 if (isLocked()) { 666 throw new LockedException(); 667 } 668 669 iacEstimator.setFocalDistanceAspectRatioKnown(focalDistanceAspectRatioKnown); 670 } 671 672 /** 673 * Returns aspect ratio of focal distances (i.e. vertical focal distance 674 * divided by horizontal focal distance). 675 * This value is only taken into account if skewness is assumed to be zero 676 * and focal distance aspect ratio is marked as known, otherwise it is 677 * ignored. 678 * By default, this is 1.0, since it is taken into account that typically 679 * LCD sensor cells are square and hence aspect ratio focal distances is 680 * known and equal to 1. 681 * Notice that focal distance aspect ratio is not related to image size 682 * aspect ratio. 683 * Notice that a negative aspect ratio indicates that vertical axis is 684 * reversed. This can be useful in some situations where image vertical 685 * coordinates are reversed respect to the physical world (i.e. in computer 686 * graphics typically image vertical coordinates go downwards, while in 687 * physical world they go upwards). 688 * 689 * @return aspect ratio of focal distances. 690 */ 691 public double getFocalDistanceAspectRatio() { 692 return iacEstimator.getFocalDistanceAspectRatio(); 693 } 694 695 /** 696 * Sets aspect ratio of focal distances (i.e. vertical focal distance 697 * divided by horizontal focal distance). 698 * This value is only taken into account if skewness is assumed to be zero 699 * and focal distance aspect ratio is marked as known, otherwise it is 700 * ignored. 701 * By default, this is 1.0, since it is taken into account that typically 702 * LCD sensor cells are square and hence aspect ratio focal distances is 703 * known and equal to 1. 704 * Notice that focal distance aspect ratio is not related to image size 705 * aspect ratio. 706 * Notice that a negative aspect ratio indicates that vertical axis is 707 * reversed. This can be useful in some situations where image vertical 708 * coordinates are reversed respect to the physical world (i.e. in computer 709 * graphics typically image vertical coordinates go downwards, while in 710 * physical world they go upwards). 711 * 712 * @param focalDistanceAspectRatio aspect ratio of focal distances to be set. 713 * @throws LockedException if estimator is locked. 714 * @throws IllegalArgumentException if focal distance aspect ratio is too 715 * close to zero, as it might produce numerical instabilities. 716 */ 717 public void setFocalDistanceAspectRatio(final double focalDistanceAspectRatio) throws LockedException { 718 if (isLocked()) { 719 throw new LockedException(); 720 } 721 722 iacEstimator.setFocalDistanceAspectRatio(focalDistanceAspectRatio); 723 } 724 725 /** 726 * Indicates whether this instance is locked because calibration is in 727 * progress. 728 * 729 * @return true if this instance, false otherwise. 730 */ 731 public boolean isLocked() { 732 return locked; 733 } 734 735 /** 736 * Returns amount of progress variation before notifying a progress change 737 * during estimation. 738 * 739 * @return amount of progress variation before notifying a progress change 740 * during estimation. 741 */ 742 public float getProgressDelta() { 743 return progressDelta; 744 } 745 746 /** 747 * Sets amount of progress variation before notifying a progress change 748 * during estimation. 749 * 750 * @param progressDelta amount of progress variation before notifying a 751 * progress change during estimation. 752 * @throws IllegalArgumentException if progress delta is less than zero or 753 * greater than 1. 754 * @throws LockedException if this estimator is locked because an estimation 755 * is being computed. 756 */ 757 public void setProgressDelta(final float progressDelta) throws LockedException { 758 if (isLocked()) { 759 throw new LockedException(); 760 } 761 if (progressDelta < MIN_PROGRESS_DELTA || progressDelta > MAX_PROGRESS_DELTA) { 762 throw new IllegalArgumentException(); 763 } 764 this.progressDelta = progressDelta; 765 } 766 767 /** 768 * Indicates whether this instance is ready to start camera calibration 769 * 770 * @return true if this instance has enough data to start camera 771 * calibration, false otherwise. 772 */ 773 public boolean isReady() { 774 return pattern != null && samples != null 775 && samples.size() >= iacEstimator.getMinNumberOfRequiredHomographies(); 776 } 777 778 /** 779 * Returns threshold to robustly estimate homographies between ideal pattern 780 * markers and sampled pattern markers. 781 * Usually the default value is good enough for most situations, but this 782 * setting can be changed for finer adjustments 783 * 784 * @return threshold to robustly estimate homographies between ideal pattern 785 * markers and sampled pattern markers. 786 */ 787 public double getHomographyEstimatorThreshold() { 788 return switch (homographyEstimator.getMethod()) { 789 case LMEDS -> ((LMedSPointCorrespondenceProjectiveTransformation2DRobustEstimator) homographyEstimator) 790 .getStopThreshold(); 791 case MSAC -> ((MSACPointCorrespondenceProjectiveTransformation2DRobustEstimator) homographyEstimator) 792 .getThreshold(); 793 case PROSAC -> ((PROSACPointCorrespondenceProjectiveTransformation2DRobustEstimator) homographyEstimator) 794 .getThreshold(); 795 case PROMEDS -> ((PROMedSPointCorrespondenceProjectiveTransformation2DRobustEstimator) homographyEstimator) 796 .getStopThreshold(); 797 default -> ((RANSACPointCorrespondenceProjectiveTransformation2DRobustEstimator) homographyEstimator) 798 .getThreshold(); 799 }; 800 } 801 802 /** 803 * Sets threshold to robustly estimate homographies between ideal pattern 804 * markers and sampled pattern markers. 805 * Usually the default value is good enough for most situations, but this 806 * setting can be changed for finer adjustments. 807 * 808 * @param homographyEstimatorThreshold threshold to robustly estimate 809 * homographies between ideal pattern markers and sampled pattern markers. 810 * @throws LockedException if this instance is locked. 811 * @throws IllegalArgumentException if provided value is zero or negative. 812 */ 813 public void setHomographyEstimatorThreshold(final double homographyEstimatorThreshold) throws LockedException { 814 if (isLocked()) { 815 throw new LockedException(); 816 } 817 818 switch (homographyEstimator.getMethod()) { 819 case LMEDS: 820 ((LMedSPointCorrespondenceProjectiveTransformation2DRobustEstimator) homographyEstimator). 821 setStopThreshold(homographyEstimatorThreshold); 822 break; 823 case MSAC: 824 ((MSACPointCorrespondenceProjectiveTransformation2DRobustEstimator) homographyEstimator). 825 setThreshold(homographyEstimatorThreshold); 826 break; 827 case PROSAC: 828 ((PROSACPointCorrespondenceProjectiveTransformation2DRobustEstimator) homographyEstimator). 829 setThreshold(homographyEstimatorThreshold); 830 break; 831 case PROMEDS: 832 ((PROMedSPointCorrespondenceProjectiveTransformation2DRobustEstimator) homographyEstimator). 833 setStopThreshold(homographyEstimatorThreshold); 834 break; 835 case RANSAC: 836 default: 837 ((RANSACPointCorrespondenceProjectiveTransformation2DRobustEstimator) homographyEstimator). 838 setThreshold(homographyEstimatorThreshold); 839 break; 840 } 841 } 842 843 /** 844 * Returns confidence to robustly estimate homographies between ideal 845 * pattern markers and sampled pattern markers. 846 * Usually the default value is good enough for most situations, but this 847 * setting can be changed for finer adjustments. 848 * Confidence is expressed as a value between 0.0 (0%) and 1.0 (100%). The 849 * amount of confidence indicates the probability that the estimated 850 * homography is correct (i.e. no outliers were used for the estimation, 851 * because they were successfully discarded). 852 * Typically, this value will be close to 1.0, but not exactly 1.0, because 853 * a 100% confidence would require an infinite number of iterations. 854 * Usually the default value is good enough for most situations, but this 855 * setting can be changed for finer adjustments. 856 * 857 * @return confidence to robustly estimate homographies. 858 */ 859 public double getHomographyEstimatorConfidence() { 860 return homographyEstimator.getConfidence(); 861 } 862 863 /** 864 * Sets confidence to robustly estimate homographies between ideal pattern 865 * markers and sampled pattern markers. 866 * Usually the default value is good enough for most situations, but this 867 * setting can be changed for finer adjustments. 868 * Confidence is expressed as a value between 0.0 (0%) and 1.0 (100%). The 869 * amount of confidence indicates the probability that the estimated 870 * homography is correct (i.e. no outliers were used for the estimation, 871 * because they were successfully discarded). 872 * Typically, this value will be close to 1.0, but not exactly 1.0, because 873 * a 100% confidence would require an infinite number of iterations. 874 * Usually the default value is good enough for most situations, but this 875 * setting can be changed for finer adjustments. 876 * 877 * @param homographyEstimatorConfidence confidence to robustly estimate 878 * homographies. 879 * @throws LockedException if this instance is locked. 880 * @throws IllegalArgumentException if provided value is not between 0.0 and 881 * 1.0. 882 */ 883 public void setHomographyEstimatorConfidence(final double homographyEstimatorConfidence) throws LockedException { 884 if (isLocked()) { 885 throw new LockedException(); 886 } 887 888 homographyEstimator.setConfidence(homographyEstimatorConfidence); 889 } 890 891 /** 892 * Returns the maximum number of iterations to be done when estimating 893 * the homographies between ideal pattern markers and sampled pattern 894 * markers. 895 * If the maximum allowed number of iterations is reached, resulting 896 * estimation might not have desired confidence. 897 * Usually the default value is good enough for most situations, but this 898 * setting can be changed for finer adjustments. 899 * 900 * @return maximum number of iterations to be done when estimating the 901 * homographies. 902 */ 903 public int getHomographyEstimatorMaxIterations() { 904 return homographyEstimator.getMaxIterations(); 905 } 906 907 /** 908 * Sets the maximum number of iterations to be done when estimating the 909 * homographies between ideal pattern markers and sampled pattern markers. 910 * If the maximum allowed number of iterations is reached, resulting 911 * estimation might not have desired confidence. 912 * Usually the default value is good enough for most situations, but this 913 * setting can be changed for finer adjustments. 914 * 915 * @param homographyEstimatorMaxIterations maximum number of iterations to 916 * be done when estimating the homographies between ideal 917 * pattern markers and sampled pattern markers. 918 * @throws LockedException if this instance is locked. 919 * @throws IllegalArgumentException if provided value is negative or zero. 920 */ 921 public void setHomographyEstimatorMaxIterations(final int homographyEstimatorMaxIterations) throws LockedException { 922 if (isLocked()) { 923 throw new LockedException(); 924 } 925 926 homographyEstimator.setMaxIterations(homographyEstimatorMaxIterations); 927 } 928 929 /** 930 * Returns threshold to robustly estimate the image of absolute conic. 931 * Usually the default value is good enough for most situations, but this 932 * setting can be changed for finer adjustments. 933 * 934 * @return threshold to robustly estimate the image of absolute conic. 935 */ 936 public double getIACEstimatorThreshold() { 937 return switch (iacEstimator.getMethod()) { 938 case LMEDS -> ((LMedSImageOfAbsoluteConicRobustEstimator) iacEstimator).getStopThreshold(); 939 case MSAC -> ((MSACImageOfAbsoluteConicRobustEstimator) iacEstimator).getThreshold(); 940 case PROSAC -> ((PROSACImageOfAbsoluteConicRobustEstimator) iacEstimator).getThreshold(); 941 case PROMEDS -> ((PROMedSImageOfAbsoluteConicRobustEstimator) iacEstimator).getStopThreshold(); 942 default -> ((RANSACImageOfAbsoluteConicRobustEstimator) iacEstimator).getThreshold(); 943 }; 944 } 945 946 /** 947 * Sets threshold to robustly estimate the image of absolute conic. 948 * Usually the default value is good enough for most situations, but this 949 * setting can be changed for finer adjustments. 950 * 951 * @param iacEstimatorThreshold threshold to robustly estimate the image of 952 * absolute conic. 953 * @throws LockedException if this instance is locked. 954 * @throws IllegalArgumentException if provided value is zero or negative. 955 */ 956 public void setIACEstimatorThreshold(final double iacEstimatorThreshold) throws LockedException { 957 if (isLocked()) { 958 throw new LockedException(); 959 } 960 961 switch (iacEstimator.getMethod()) { 962 case LMEDS: 963 ((LMedSImageOfAbsoluteConicRobustEstimator) iacEstimator).setStopThreshold(iacEstimatorThreshold); 964 break; 965 case MSAC: 966 ((MSACImageOfAbsoluteConicRobustEstimator) iacEstimator).setThreshold(iacEstimatorThreshold); 967 break; 968 case PROSAC: 969 ((PROSACImageOfAbsoluteConicRobustEstimator) iacEstimator).setThreshold(iacEstimatorThreshold); 970 break; 971 case PROMEDS: 972 ((PROMedSImageOfAbsoluteConicRobustEstimator) iacEstimator).setStopThreshold(iacEstimatorThreshold); 973 break; 974 case RANSAC: 975 default: 976 ((RANSACImageOfAbsoluteConicRobustEstimator) iacEstimator).setThreshold(iacEstimatorThreshold); 977 break; 978 } 979 } 980 981 /** 982 * Returns confidence to robustly estimate image of absolute conic 983 * Usually the default value is good enough for most situations, but this 984 * setting can be changed for finer adjustments. 985 * Confidence is expressed as a value between 0.0 (0%) and 1.0 (100%). The 986 * amount of confidence indicates the probability that the estimated 987 * homography is correct (i.e. no outliers were used for the estimation, 988 * because they were successfully discarded). 989 * Typically, this value will be close to 1.0, but not exactly 1.0, because 990 * a 100% confidence would require an infinite number of iterations. 991 * Usually the default value is good enough for most situations, but this 992 * setting can be changed for finer adjustments. 993 * 994 * @return confidence to robustly estimate the IAC. 995 */ 996 public double getIACEstimatorConfidence() { 997 return iacEstimator.getConfidence(); 998 } 999 1000 /** 1001 * Sets confidence to robustly estimate image of absolute conic. 1002 * Usually the default value is good enough for most situations, but this 1003 * setting can be changed for finer adjustments. 1004 * Confidence is expressed as a value between 0.0 (0%) and 1.0 (100%). The 1005 * amount of confidence indicates the probability that the estimated 1006 * homography is correct (i.e. no outliers were used for the estimation, 1007 * because they were successfully discarded). 1008 * Typically, this value will be close to 1.0, but not exactly 1.0, because 1009 * a 100% confidence would require an infinite number of iterations. 1010 * Usually the default value is good enough for most situations, but this 1011 * setting can be changed for finer adjustments. 1012 * 1013 * @param iacEstimatorConfidence confidence to robustly estimate the IAC. 1014 * @throws LockedException if this instance is locked. 1015 * @throws IllegalArgumentException if provided value is not between 0.0 and 1016 * 1.0. 1017 */ 1018 public void setIACEstimatorConfidence(final double iacEstimatorConfidence) throws LockedException { 1019 if (isLocked()) { 1020 throw new LockedException(); 1021 } 1022 1023 iacEstimator.setConfidence(iacEstimatorConfidence); 1024 } 1025 1026 /** 1027 * Returns the maximum number of iterations to be done when estimating 1028 * the image of absolute conic. 1029 * If the maximum allowed number of iterations is reached, resulting 1030 * estimation might not have desired confidence. 1031 * Usually the default value is good enough for most situations, but this 1032 * setting can be changed for finer adjustments. 1033 * 1034 * @return maximum number of iterations to be done when estimating the 1035 * image of absolute conic. 1036 */ 1037 public int getIACEstimatorMaxIterations() { 1038 return iacEstimator.getMaxIterations(); 1039 } 1040 1041 /** 1042 * Sets the maximum number of iterations to be done when estimating the 1043 * image of absolute conic. 1044 * If the maximum allowed number of iterations is reached, resulting 1045 * estimation might not have desired confidence. 1046 * Usually the default value is good enough for most situations, but this 1047 * setting can be changed for finer adjustments. 1048 * 1049 * @param iacEstimatorMaxIterations maximum number of iterations to be done 1050 * when estimating the image of absolute conic. 1051 * @throws LockedException if this instance is locked. 1052 * @throws IllegalArgumentException if provided value is negative or zero. 1053 */ 1054 public void setIACEstimatorMaxIterations(final int iacEstimatorMaxIterations) throws LockedException { 1055 if (isLocked()) { 1056 throw new LockedException(); 1057 } 1058 1059 iacEstimator.setMaxIterations(iacEstimatorMaxIterations); 1060 } 1061 1062 /** 1063 * Returns listener to notify when calibration starts, finishes or its 1064 * progress significantly changes. 1065 * 1066 * @return listener to notify when calibration starts, finishes or its 1067 * progress significantly changes. 1068 */ 1069 public CameraCalibratorListener getListener() { 1070 return listener; 1071 } 1072 1073 /** 1074 * Sets listener to notify when calibration starts, finishes or its progress 1075 * significantly changes. 1076 * 1077 * @param listener listener to notify when calibration starts, finishes or 1078 * its progress significantly changes. 1079 * @throws LockedException if this instance is locked. 1080 */ 1081 public void setListener(final CameraCalibratorListener listener) throws LockedException { 1082 if (isLocked()) { 1083 throw new LockedException(); 1084 } 1085 1086 this.listener = listener; 1087 } 1088 1089 /** 1090 * Creates a camera calibrator using provided method. 1091 * 1092 * @param method a camera calibrator method. 1093 * @return a camera calibrator. 1094 */ 1095 public static CameraCalibrator create(final CameraCalibratorMethod method) { 1096 return method == CameraCalibratorMethod.ERROR_OPTIMIZATION 1097 ? new ErrorOptimizationCameraCalibrator() : new AlternatingCameraCalibrator(); 1098 } 1099 1100 /** 1101 * Creates a camera calibrator using provided pattern, samples and 1102 * method. 1103 * 1104 * @param pattern a 2D pattern to use for calibration. 1105 * @param samples samples of the 2D pattern taken with the camera to be 1106 * calibrated. 1107 * @param method a camera calibrator method. 1108 * @return a camera calibrator. 1109 * @throws IllegalArgumentException if not enough samples are provided. 1110 */ 1111 public static CameraCalibrator create(final Pattern2D pattern, final List<CameraCalibratorSample> samples, 1112 final CameraCalibratorMethod method) { 1113 return method == CameraCalibratorMethod.ERROR_OPTIMIZATION 1114 ? new ErrorOptimizationCameraCalibrator(pattern, samples) 1115 : new AlternatingCameraCalibrator(pattern, samples); 1116 } 1117 1118 /** 1119 * Creates a camera calibrator using provided pattern, samples and method. 1120 * 1121 * @param pattern a 2D pattern to use for calibration. 1122 * @param samples samples of the 2D pattern taken with the camera to be 1123 * calibrated. 1124 * @param samplesQualityScores quality scores for each sample. 1125 * @param method a camera calibrator method. 1126 * @return a camera calibrator. 1127 * @throws IllegalArgumentException if not enough samples are provided. 1128 */ 1129 public static CameraCalibrator create( 1130 final Pattern2D pattern, final List<CameraCalibratorSample> samples, final double[] samplesQualityScores, 1131 final CameraCalibratorMethod method) { 1132 return method == CameraCalibratorMethod.ERROR_OPTIMIZATION 1133 ? new ErrorOptimizationCameraCalibrator(pattern, samples, samplesQualityScores) 1134 : new AlternatingCameraCalibrator(pattern, samples, samplesQualityScores); 1135 } 1136 1137 /** 1138 * Creates a camera calibrator using default method. 1139 * 1140 * @return a camera calibrator. 1141 */ 1142 public static CameraCalibrator create() { 1143 return create(DEFAULT_METHOD); 1144 } 1145 1146 /** 1147 * Creates a camera calibrator using provided pattern, samples and 1148 * default method. 1149 * 1150 * @param pattern a 2D pattern to use for calibration. 1151 * @param samples samples of the 2D pattern taken with the camera to be 1152 * calibrated. 1153 * @return a camera calibrator. 1154 * @throws IllegalArgumentException if not enough samples are provided. 1155 */ 1156 public static CameraCalibrator create(final Pattern2D pattern, final List<CameraCalibratorSample> samples) { 1157 return create(pattern, samples, DEFAULT_METHOD); 1158 } 1159 1160 /** 1161 * Creates a camera calibrator using provided pattern, samples and default 1162 * method. 1163 * 1164 * @param pattern a 2D pattern to use for calibration. 1165 * @param samples samples of the 2D pattern taken with the camera to be 1166 * calibrated. 1167 * @param samplesQualityScores quality scores for each sample. 1168 * @return a camera calibrator. 1169 * @throws IllegalArgumentException if not enough samples are provided. 1170 */ 1171 public static CameraCalibrator create( 1172 final Pattern2D pattern, final List<CameraCalibratorSample> samples, final double[] samplesQualityScores) { 1173 return create(pattern, samples, samplesQualityScores, DEFAULT_METHOD); 1174 } 1175 1176 /** 1177 * Starts the calibration process. 1178 * Depending on the settings the following will be estimated: 1179 * intrinsic pinhole camera parameters, radial distortion of lens, 1180 * camera pose (rotation and translation) for each sample, and the 1181 * associated homobraphy of sampled points respect to the ideal pattern 1182 * samples. 1183 * 1184 * @throws CalibrationException if calibration fails for some reason. 1185 * @throws LockedException if this instance is locked because calibration is 1186 * already in progress. 1187 * @throws NotReadyException if this instance does not have enough data to 1188 * start camera calibration. 1189 */ 1190 public abstract void calibrate() throws CalibrationException, LockedException, NotReadyException; 1191 1192 /** 1193 * Returns the camera calibrator method used by this instance. 1194 * 1195 * @return the camera calibrator method. 1196 */ 1197 public abstract CameraCalibratorMethod getMethod(); 1198 1199 /** 1200 * Notifies progress to current listener, if needed. 1201 */ 1202 protected abstract void notifyProgress(); 1203 1204 /** 1205 * Computes intrinsic estimation progress. 1206 */ 1207 protected void computeIntrinsicProgress() { 1208 final var lambda = 1.0f / samples.size(); 1209 intrinsicProgress = 0.5f * (sampleProgress + lambda * homographyProgress) + 0.5f * iacProgress; 1210 } 1211 1212 /** 1213 * Resets estimated value to their initial values. 1214 */ 1215 protected void reset() { 1216 homographies = null; 1217 for (final var sample : samples) { 1218 sample.setUndistortedMarkers(null); 1219 sample.setHomography(null); 1220 sample.setRotation(null); 1221 sample.setCameraCenter(null); 1222 sample.setCamera(null); 1223 } 1224 homographyQualityScores = null; 1225 iac = null; 1226 intrinsic = null; 1227 distortion = null; 1228 } 1229 1230 /** 1231 * Estimates pinhole camera intrinsic parameters without accounting for 1232 * lens radial distortion. 1233 * 1234 * @param idealFallbackPatternMarkers ideal pattern markers coordinates used 1235 * as fallback if pattern is not provided for a given sample. 1236 * @throws CalibrationException if calibration fails for some reason. 1237 */ 1238 protected void estimateIntrinsicParameters(final List<Point2D> idealFallbackPatternMarkers) 1239 throws CalibrationException { 1240 1241 homographyProgress = sampleProgress = iacProgress = intrinsicProgress = 0.0f; 1242 1243 if (listener != null) { 1244 listener.onIntrinsicParametersEstimationStarts(this); 1245 } 1246 1247 // for each sample estimate the homography between the ideal pattern 1248 // markers and the sampled ones 1249 List<Point2D> sampledPatternMarkers; 1250 final var sampleSize = samples.size(); 1251 homographies = new ArrayList<>(sampleSize); 1252 final var tmpHomographyQualityScores = new double[sampleSize]; 1253 var index = 0; 1254 var counter = 0; 1255 double error; 1256 for (final var sample : samples) { 1257 try { 1258 // reset homography before estimation in case that estimation 1259 // fails on current iteration 1260 sample.setHomography(null); 1261 final List<Point2D> idealPatternMarkers; 1262 if (sample.getPattern() != null) { 1263 // use sample pattern for homography estimation 1264 idealPatternMarkers = sample.getPattern().getIdealPoints(); 1265 } else { 1266 // use fallback pattern common to all samples 1267 idealPatternMarkers = idealFallbackPatternMarkers; 1268 } 1269 final var homography = sample.estimateHomography(homographyEstimator, idealPatternMarkers); 1270 sampledPatternMarkers = sample.getUndistortedMarkers() != null 1271 ? sample.getUndistortedMarkers() : sample.getSampledMarkers(); 1272 sample.setHomography(homography); 1273 homographies.add(homography); 1274 1275 if (iacEstimator.getMethod() == RobustEstimatorMethod.PROSAC 1276 || iacEstimator.getMethod() == RobustEstimatorMethod.PROMEDS 1277 || homographyQualityScoresRequired) { 1278 if (samplesQualityScores != null) { 1279 // pick corresponding quality score 1280 tmpHomographyQualityScores[counter] = samplesQualityScores[index]; 1281 } else { 1282 // compute re-projection error 1283 error = homographyTransformationError(homography, idealPatternMarkers, sampledPatternMarkers); 1284 tmpHomographyQualityScores[counter] = 1.0 / (1.0 + error); 1285 } 1286 } 1287 // counter of homography estimation successes 1288 counter++; 1289 } catch (final NumericalException | GeometryException ignore) { 1290 // homographies are attempted to be estimated. It's ok if some fail 1291 } 1292 1293 // position index (regardless of homography estimation success) 1294 index++; 1295 1296 homographyProgress = 0.0f; 1297 sampleProgress = (float) index / (float) sampleSize; 1298 computeIntrinsicProgress(); 1299 notifyProgress(); 1300 } 1301 1302 if (iacEstimator.getMethod() == RobustEstimatorMethod.PROSAC 1303 || iacEstimator.getMethod() == RobustEstimatorMethod.PROMEDS || homographyQualityScoresRequired) { 1304 1305 // truncate tmpHomographyQualityScores to contain only actual number of 1306 // successfully estimated homographies 1307 homographyQualityScores = Arrays.copyOf(tmpHomographyQualityScores, counter); 1308 } 1309 1310 // estimate IAC using estimated homographies 1311 try { 1312 iacEstimator.setHomographies(homographies); 1313 iacEstimator.setQualityScores(homographyQualityScores); 1314 iac = iacEstimator.estimate(); 1315 1316 intrinsic = iac.getIntrinsicParameters(); 1317 } catch (final GeometryException | NumericalException e) { 1318 throw new CalibrationException(e); 1319 } 1320 1321 if (listener != null) { 1322 listener.onIntrinsicParametersEstimationEnds(this, intrinsic); 1323 } 1324 } 1325 1326 /** 1327 * Computes average transformation error as a result of comparing sampled 1328 * pattern markers against the transformation of ideal pattern markers using 1329 * the estimated homography. 1330 * 1331 * @param homography estimated homography. 1332 * @param idealPatternMarkers ideal pattern markers. 1333 * @param sampledPatternMarkers sampled pattern markers. 1334 * @return average re-projection error. 1335 */ 1336 protected static double homographyTransformationError( 1337 final Transformation2D homography, final List<Point2D> idealPatternMarkers, 1338 final List<Point2D> sampledPatternMarkers) { 1339 1340 final var transformedPoint = new HomogeneousPoint2D(); 1341 Point2D idealPatternMarker; 1342 Point2D sampledPatternMarker; 1343 var avgError = 0.0; 1344 double distance; 1345 final var size = sampledPatternMarkers.size(); 1346 for (var i = 0; i < size; i++) { 1347 idealPatternMarker = idealPatternMarkers.get(i); 1348 sampledPatternMarker = sampledPatternMarkers.get(i); 1349 1350 homography.transform(idealPatternMarker, transformedPoint); 1351 distance = transformedPoint.distanceTo(sampledPatternMarker); 1352 avgError += distance; 1353 } 1354 1355 avgError /= size; 1356 1357 return avgError; 1358 } 1359 1360 /** 1361 * Refreshes listener of homography estimator. 1362 */ 1363 protected void refreshHomographyEstimatorListener() { 1364 if (homographyEstimatorListener == null) { 1365 homographyEstimatorListener = new ProjectiveTransformation2DRobustEstimatorListener() { 1366 1367 @Override 1368 public void onEstimateStart(final ProjectiveTransformation2DRobustEstimator estimator) { 1369 homographyProgress = 0.0f; 1370 computeIntrinsicProgress(); 1371 notifyProgress(); 1372 } 1373 1374 @Override 1375 public void onEstimateEnd(final ProjectiveTransformation2DRobustEstimator estimator) { 1376 homographyProgress = 1.0f; 1377 computeIntrinsicProgress(); 1378 notifyProgress(); 1379 } 1380 1381 @Override 1382 public void onEstimateNextIteration( 1383 final ProjectiveTransformation2DRobustEstimator estimator, int iteration) { 1384 // not used 1385 } 1386 1387 @Override 1388 public void onEstimateProgressChange( 1389 final ProjectiveTransformation2DRobustEstimator estimator, final float progress) { 1390 homographyProgress = progress; 1391 computeIntrinsicProgress(); 1392 notifyProgress(); 1393 } 1394 }; 1395 } 1396 1397 try { 1398 homographyEstimator.setListener(homographyEstimatorListener); 1399 } catch (final LockedException e) { 1400 Logger.getLogger(CameraCalibrator.class.getName()).log(Level.WARNING, 1401 "Could not set homography estimator listener", e); 1402 } 1403 } 1404 1405 /** 1406 * Refreshes listener of IAC estimator. 1407 */ 1408 protected void refreshIACEstimatorListener() { 1409 if (iacEstimatorListener == null) { 1410 iacEstimatorListener = new ImageOfAbsoluteConicRobustEstimatorListener() { 1411 1412 @Override 1413 public void onEstimateStart(final ImageOfAbsoluteConicRobustEstimator estimator) { 1414 iacProgress = 0.0f; 1415 computeIntrinsicProgress(); 1416 notifyProgress(); 1417 } 1418 1419 @Override 1420 public void onEstimateEnd(final ImageOfAbsoluteConicRobustEstimator estimator) { 1421 iacProgress = 1.0f; 1422 computeIntrinsicProgress(); 1423 notifyProgress(); 1424 } 1425 1426 @Override 1427 public void onEstimateNextIteration( 1428 final ImageOfAbsoluteConicRobustEstimator estimator, final int iteration) { 1429 // not used 1430 } 1431 1432 @Override 1433 public void onEstimateProgressChange( 1434 final ImageOfAbsoluteConicRobustEstimator estimator, final float progress) { 1435 iacProgress = progress; 1436 computeIntrinsicProgress(); 1437 notifyProgress(); 1438 } 1439 }; 1440 } 1441 1442 try { 1443 iacEstimator.setListener(iacEstimatorListener); 1444 } catch (final LockedException e) { 1445 Logger.getLogger(CameraCalibrator.class.getName()).log(Level.WARNING, 1446 "Could not set IAC estimator listener", e); 1447 } 1448 } 1449 1450 /** 1451 * Internal method to set list of samples obtained from different pictures 1452 * using the same camera device (or same camera model). Several samples can 1453 * be used to calibrate the camera (a pinhole camera can be estimated for 1454 * each sample). The more samples are used, typically the better the 1455 * results. 1456 * This method is for internal use only and does not check whether this 1457 * instance is locked or not. 1458 * 1459 * @param samples list of samples. 1460 * @throws IllegalArgumentException if not enough samples are provided to 1461 * estimate the intrinsic parameters. By default, the minimum is 1, 1462 * but depending on the settings at least 3 samples might be required. 1463 */ 1464 private void internalSetSamples(final List<CameraCalibratorSample> samples) { 1465 if (samples.size() < iacEstimator.getMinNumberOfRequiredHomographies()) { 1466 throw new IllegalArgumentException(); 1467 } 1468 1469 this.samples = samples; 1470 } 1471 1472 /** 1473 * Sets quality scores assigned to each provided sample. This can be used on 1474 * certain robust estimation methods of the IAC such as PROSAC and PROMedS. 1475 * If not provided, homography quality scores will be estimated based on 1476 * re-projection error and this value will be ignored. 1477 * This method is for internal use only and does not check whether this 1478 * instance is locked or not. 1479 * 1480 * @param samplesQualityScores quality scores assigned to each provided 1481 * sample. 1482 * @throws IllegalArgumentException if not enough quality scores are 1483 * provided for the corresponding samples to estimate the intrinsic 1484 * parameters. By default, the minimum is 1, but depending on the 1485 * settings at least 3 samples might be required. 1486 */ 1487 private void internalSetSamplesQualityScores(final double[] samplesQualityScores) { 1488 if (samplesQualityScores.length < iacEstimator.getMinNumberOfRequiredHomographies()) { 1489 throw new IllegalArgumentException(); 1490 } 1491 1492 this.samplesQualityScores = samplesQualityScores; 1493 } 1494 1495 /** 1496 * Sets robust homography estimation method. 1497 * If method changes, then a new homography robust estimator is created and 1498 * configured. 1499 * 1500 * @param homographyMethod robust homography estimation method to be set. 1501 */ 1502 private void internalSetHomographyMethod(final RobustEstimatorMethod homographyMethod) { 1503 // if method changes, homography estimator must be recreated 1504 if (homographyMethod != this.homographyMethod) { 1505 final var previousAvailable = homographyEstimator != null; 1506 var threshold = 0.0; 1507 var confidence = 0.0; 1508 var maxIterations = 0; 1509 if (previousAvailable) { 1510 threshold = getHomographyEstimatorThreshold(); 1511 confidence = getHomographyEstimatorConfidence(); 1512 maxIterations = getHomographyEstimatorMaxIterations(); 1513 } 1514 1515 homographyEstimator = PointCorrespondenceProjectiveTransformation2DRobustEstimator.create(homographyMethod); 1516 1517 // configure new estimator 1518 refreshHomographyEstimatorListener(); 1519 if (previousAvailable) { 1520 try { 1521 setHomographyEstimatorThreshold(threshold); 1522 setHomographyEstimatorConfidence(confidence); 1523 setHomographyEstimatorMaxIterations(maxIterations); 1524 } catch (final LockedException e) { 1525 Logger.getLogger(CameraCalibrator.class.getName()).log(Level.WARNING, 1526 "Could not reconfigure homography estimator", e); 1527 } 1528 } 1529 } 1530 this.homographyMethod = homographyMethod; 1531 } 1532 1533 /** 1534 * Sets robust IAC estimation method. 1535 * IF method changes, then a new IAC robust estimator is created and 1536 * configured 1537 * 1538 * @param imageOfAbsoluteConicMethod robust IAC estimation method to be set 1539 */ 1540 private void internalSetImageOfAbsoluteConicMethod(final RobustEstimatorMethod imageOfAbsoluteConicMethod) { 1541 // if method changes, iac estimator must be recreated 1542 if (imageOfAbsoluteConicMethod != this.imageOfAbsoluteConicMethod) { 1543 final var previousAvailable = iacEstimator != null; 1544 var threshold = 0.0; 1545 var confidence = 0.0; 1546 var maxIterations = 0; 1547 var zeroSkewness = false; 1548 var principalPointAtOrigin = false; 1549 var focalDistanceAspectRatioKnown = false; 1550 var focalDistanceAspectRatio = 0.0; 1551 if (previousAvailable) { 1552 threshold = getIACEstimatorThreshold(); 1553 confidence = getIACEstimatorConfidence(); 1554 maxIterations = getIACEstimatorMaxIterations(); 1555 zeroSkewness = isZeroSkewness(); 1556 principalPointAtOrigin = isPrincipalPointAtOrigin(); 1557 focalDistanceAspectRatioKnown = isFocalDistanceAspectRatioKnown(); 1558 focalDistanceAspectRatio = getFocalDistanceAspectRatio(); 1559 } 1560 1561 iacEstimator = ImageOfAbsoluteConicRobustEstimator.create(imageOfAbsoluteConicMethod); 1562 1563 // configure new estimator 1564 refreshIACEstimatorListener(); 1565 if (previousAvailable) { 1566 try { 1567 setIACEstimatorThreshold(threshold); 1568 setIACEstimatorConfidence(confidence); 1569 setIACEstimatorMaxIterations(maxIterations); 1570 setZeroSkewness(zeroSkewness); 1571 setPrincipalPointAtOrigin(principalPointAtOrigin); 1572 setFocalDistanceAspectRatioKnown(focalDistanceAspectRatioKnown); 1573 setFocalDistanceAspectRatio(focalDistanceAspectRatio); 1574 } catch (final LockedException e) { 1575 Logger.getLogger(CameraCalibrator.class.getName()).log(Level.WARNING, 1576 "Could not reconfigure IAC estimator", e); 1577 } 1578 } 1579 } 1580 this.imageOfAbsoluteConicMethod = imageOfAbsoluteConicMethod; 1581 } 1582 }