View Javadoc
1   /*
2    * Copyright (C) 2015 Alberto Irurueta Carro (alberto@irurueta.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.irurueta.ar.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 }