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 }