1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package com.irurueta.ar.calibration;
17
18 import com.irurueta.algebra.Matrix;
19 import com.irurueta.algebra.WrongSizeException;
20 import com.irurueta.ar.calibration.estimators.LMedSRadialDistortionRobustEstimator;
21 import com.irurueta.ar.calibration.estimators.MSACRadialDistortionRobustEstimator;
22 import com.irurueta.ar.calibration.estimators.PROMedSRadialDistortionRobustEstimator;
23 import com.irurueta.ar.calibration.estimators.PROSACRadialDistortionRobustEstimator;
24 import com.irurueta.ar.calibration.estimators.RANSACRadialDistortionRobustEstimator;
25 import com.irurueta.ar.calibration.estimators.RadialDistortionRobustEstimator;
26 import com.irurueta.ar.calibration.estimators.RadialDistortionRobustEstimatorListener;
27 import com.irurueta.geometry.AxisRotation3D;
28 import com.irurueta.geometry.HomogeneousPoint2D;
29 import com.irurueta.geometry.HomogeneousPoint3D;
30 import com.irurueta.geometry.PinholeCamera;
31 import com.irurueta.geometry.Point2D;
32 import com.irurueta.geometry.Point3D;
33 import com.irurueta.geometry.Rotation3DType;
34 import com.irurueta.geometry.estimators.LockedException;
35 import com.irurueta.geometry.estimators.NotReadyException;
36 import com.irurueta.numerical.EvaluationException;
37 import com.irurueta.numerical.JacobianEstimator;
38 import com.irurueta.numerical.MultiVariateFunctionEvaluatorListener;
39 import com.irurueta.numerical.fitting.LevenbergMarquardtMultiVariateFitter;
40 import com.irurueta.numerical.fitting.LevenbergMarquardtMultiVariateFunctionEvaluator;
41 import com.irurueta.numerical.robust.RobustEstimatorMethod;
42
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.List;
46 import java.util.logging.Level;
47 import java.util.logging.Logger;
48
49
50
51
52
53
54
55
56
57
58
59
60 @SuppressWarnings("DuplicatedCode")
61 public class ErrorOptimizationCameraCalibrator extends CameraCalibrator {
62
63
64
65
66
67 public static final RobustEstimatorMethod DEFAULT_RADIAL_DISTORTION_METHOD = RobustEstimatorMethod.PROSAC;
68
69
70
71
72
73
74 public static final boolean DEFAULT_ESTIMATE_INITIAL_RADIAL_DISTORTION = true;
75
76
77
78
79
80 public static final int DEFAULT_LEVENBERG_MARQUARDT_MAX_ITERS = 1000;
81
82
83
84
85
86 public static final double DEFAULT_LEVENBERG_MARQUARDT_TOLERANCE = 1e-12;
87
88
89
90
91
92 private int levenbergMarquardtMaxIters;
93
94
95
96
97
98 private double levenbergMarquardtTolerance;
99
100
101
102
103 private RobustEstimatorMethod distortionMethod;
104
105
106
107
108
109
110 private final boolean estimateInitialRadialDistortion;
111
112
113
114
115 private RadialDistortionRobustEstimator distortionEstimator;
116
117
118
119
120 private RadialDistortionRobustEstimatorListener distortionEstimatorListener;
121
122
123
124
125 private float radialDistortionProgress;
126
127
128
129
130 private float fittingProgress;
131
132
133
134
135 private float previousNotifiedProgress;
136
137
138
139
140
141 private int[] indexToView;
142
143
144
145
146
147 public ErrorOptimizationCameraCalibrator() {
148 super();
149 levenbergMarquardtMaxIters = DEFAULT_LEVENBERG_MARQUARDT_MAX_ITERS;
150 levenbergMarquardtTolerance = DEFAULT_LEVENBERG_MARQUARDT_TOLERANCE;
151 internalSetDistortionMethod(DEFAULT_RADIAL_DISTORTION_METHOD);
152 estimateInitialRadialDistortion = DEFAULT_ESTIMATE_INITIAL_RADIAL_DISTORTION;
153 }
154
155
156
157
158
159
160
161
162 public ErrorOptimizationCameraCalibrator(final Pattern2D pattern, final List<CameraCalibratorSample> samples) {
163 super(pattern, samples);
164 levenbergMarquardtMaxIters = DEFAULT_LEVENBERG_MARQUARDT_MAX_ITERS;
165 levenbergMarquardtTolerance = DEFAULT_LEVENBERG_MARQUARDT_TOLERANCE;
166 internalSetDistortionMethod(DEFAULT_RADIAL_DISTORTION_METHOD);
167 estimateInitialRadialDistortion = DEFAULT_ESTIMATE_INITIAL_RADIAL_DISTORTION;
168 }
169
170
171
172
173
174
175
176
177
178
179 public ErrorOptimizationCameraCalibrator(
180 final Pattern2D pattern, final List<CameraCalibratorSample> samples, final double[] samplesQualityScores) {
181 super(pattern, samples, samplesQualityScores);
182 levenbergMarquardtMaxIters = DEFAULT_LEVENBERG_MARQUARDT_MAX_ITERS;
183 levenbergMarquardtTolerance = DEFAULT_LEVENBERG_MARQUARDT_TOLERANCE;
184 internalSetDistortionMethod(DEFAULT_RADIAL_DISTORTION_METHOD);
185 estimateInitialRadialDistortion = DEFAULT_ESTIMATE_INITIAL_RADIAL_DISTORTION;
186 }
187
188
189
190
191
192
193
194
195 public int getLevenbergMarquardtMaxIters() {
196 return levenbergMarquardtMaxIters;
197 }
198
199
200
201
202
203
204
205
206
207
208 public void setLevenbergMarquardtMaxIters(final int levenbergMarquardtMaxIters) throws LockedException {
209 if (isLocked()) {
210 throw new LockedException();
211 }
212 if (levenbergMarquardtMaxIters <= 0) {
213 throw new IllegalArgumentException();
214 }
215 this.levenbergMarquardtMaxIters = levenbergMarquardtMaxIters;
216 }
217
218
219
220
221
222
223
224
225 public double getLevenbergMarquardtTolerance() {
226 return levenbergMarquardtTolerance;
227 }
228
229
230
231
232
233
234
235
236
237
238
239 public void setLevenbergMarquardtTolerance(final double levenbergMarquardtTolerance) throws LockedException {
240 if (isLocked()) {
241 throw new LockedException();
242 }
243 if (levenbergMarquardtTolerance <= 0.0) {
244 throw new IllegalArgumentException();
245 }
246 this.levenbergMarquardtTolerance = levenbergMarquardtTolerance;
247 }
248
249
250
251
252
253
254
255
256 public RobustEstimatorMethod getDistortionMethod() {
257 return distortionMethod;
258 }
259
260
261
262
263
264
265
266
267
268 public void setDistortionMethod(final RobustEstimatorMethod distortionMethod) throws LockedException {
269 if (isLocked()) {
270 throw new LockedException();
271 }
272 internalSetDistortionMethod(distortionMethod);
273 }
274
275
276
277
278
279
280
281
282
283 public RadialDistortionRobustEstimator getDistortionEstimator() {
284 return distortionEstimator;
285 }
286
287
288
289
290
291
292
293
294 public double getDistortionEstimatorThreshold() {
295 return switch (distortionEstimator.getMethod()) {
296 case LMEDS -> ((LMedSRadialDistortionRobustEstimator) distortionEstimator).getStopThreshold();
297 case MSAC -> ((MSACRadialDistortionRobustEstimator) distortionEstimator).getThreshold();
298 case PROSAC -> ((PROSACRadialDistortionRobustEstimator) distortionEstimator).getThreshold();
299 case PROMEDS -> ((PROMedSRadialDistortionRobustEstimator) distortionEstimator).getStopThreshold();
300 default -> ((RANSACRadialDistortionRobustEstimator) distortionEstimator).getThreshold();
301 };
302 }
303
304
305
306
307
308
309
310
311
312
313
314 public void setDistortionEstimatorThreshold(final double distortionEstimatorThreshold) throws LockedException {
315 if (isLocked()) {
316 throw new LockedException();
317 }
318
319 switch (distortionEstimator.getMethod()) {
320 case LMEDS:
321 ((LMedSRadialDistortionRobustEstimator) distortionEstimator).setStopThreshold(
322 distortionEstimatorThreshold);
323 break;
324 case MSAC:
325 ((MSACRadialDistortionRobustEstimator) distortionEstimator).setThreshold(distortionEstimatorThreshold);
326 break;
327 case PROSAC:
328 ((PROSACRadialDistortionRobustEstimator) distortionEstimator).setThreshold(
329 distortionEstimatorThreshold);
330 break;
331 case PROMEDS:
332 ((PROMedSRadialDistortionRobustEstimator) distortionEstimator).setStopThreshold(
333 distortionEstimatorThreshold);
334 break;
335 case RANSAC:
336 default:
337 ((RANSACRadialDistortionRobustEstimator) distortionEstimator).setThreshold(
338 distortionEstimatorThreshold);
339 break;
340 }
341 }
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358 public double getDistortionEstimatorConfidence() {
359 return distortionEstimator.getConfidence();
360 }
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381 public void setDistortionEstimatorConfidence(final double distortionEstimatorConfidence) throws LockedException {
382 if (isLocked()) {
383 throw new LockedException();
384 }
385
386 distortionEstimator.setConfidence(distortionEstimatorConfidence);
387 }
388
389
390
391
392
393
394
395
396
397
398
399
400 public int getDistortionEstimatorMaxIterations() {
401 return distortionEstimator.getMaxIterations();
402 }
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417 public void setDistortionEstimatorMaxIterations(final int distortionEstimatorMaxIterations) throws LockedException {
418 if (isLocked()) {
419 throw new LockedException();
420 }
421
422 distortionEstimator.setMaxIterations(distortionEstimatorMaxIterations);
423 }
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439 @Override
440 public void calibrate() throws CalibrationException, LockedException, NotReadyException {
441
442 if (isLocked()) {
443 throw new LockedException();
444 }
445 if (!isReady()) {
446 throw new NotReadyException();
447 }
448
449 locked = true;
450
451 homographyQualityScoresRequired = (distortionEstimator.getMethod() == RobustEstimatorMethod.PROSAC
452 || distortionEstimator.getMethod() == RobustEstimatorMethod.PROMEDS);
453
454 if (listener != null) {
455 listener.onCalibrateStart(this);
456 }
457
458 reset();
459 radialDistortionProgress = fittingProgress = previousNotifiedProgress = 0.0f;
460
461 final var idealFallbackPatternMarkers = pattern.getIdealPoints();
462
463 try {
464
465 estimateIntrinsicParameters(idealFallbackPatternMarkers);
466
467 if (estimateRadialDistortion) {
468
469 estimateRadialDistortion(idealFallbackPatternMarkers);
470 }
471
472 if (listener != null) {
473 listener.onCalibrateEnd(this);
474 }
475 } finally {
476 locked = false;
477 }
478 }
479
480
481
482
483
484
485 @Override
486 public CameraCalibratorMethod getMethod() {
487 return CameraCalibratorMethod.ERROR_OPTIMIZATION;
488 }
489
490
491
492
493 @Override
494 protected void notifyProgress() {
495 final float progress;
496 if (estimateInitialRadialDistortion) {
497 progress = (radialDistortionProgress + intrinsicProgress + fittingProgress) / 3.0f;
498 } else {
499 progress = 0.5f * intrinsicProgress + 0.5f * fittingProgress;
500 }
501
502 if (listener != null && (progress - previousNotifiedProgress) > progressDelta) {
503 listener.onCalibrateProgressChange(this, progress);
504 previousNotifiedProgress = progress;
505 }
506 }
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525 protected double estimateRadialDistortion(final List<Point2D> idealFallbackPatternMarkers)
526 throws CalibrationException {
527
528 radialDistortionProgress = 0.0f;
529
530 if (listener != null) {
531 listener.onRadialDistortionEstimationStarts(this);
532 }
533
534
535 var totalPoints = 0;
536 var totalHomographies = 0;
537 for (final var sample : samples) {
538 if (sample.getHomography() != null) {
539 totalPoints += sample.getSampledMarkers().size();
540 totalHomographies++;
541 }
542 }
543
544 indexToView = new int[totalPoints];
545
546 if (estimateInitialRadialDistortion) {
547 final var distortedPoints = new ArrayList<Point2D>();
548 final var undistortedPoints = new ArrayList<Point2D>();
549
550 double[] qualityScores = null;
551 if (distortionMethod == RobustEstimatorMethod.PROSAC || distortionMethod == RobustEstimatorMethod.PROMEDS) {
552 qualityScores = new double[totalPoints];
553 }
554
555
556 var pointCounter = 0;
557 var sampleCounter = 0;
558 for (final var sample : samples) {
559 if (sample.getHomography() == null) {
560
561
562 continue;
563 }
564 sample.computeCameraPose(intrinsic);
565
566
567 final List<Point2D> idealPatternMarkers;
568 if (sample.getPattern() != null) {
569
570 idealPatternMarkers = sample.getPattern().getIdealPoints();
571 } else {
572
573 idealPatternMarkers = idealFallbackPatternMarkers;
574 }
575
576 final var transformedIdealPatternMarkers = sample.getHomography().transformPointsAndReturnNew(
577 idealPatternMarkers);
578
579 distortedPoints.addAll(sample.getSampledMarkers());
580 undistortedPoints.addAll(transformedIdealPatternMarkers);
581
582 final var markersSize = transformedIdealPatternMarkers.size();
583
584
585
586 Arrays.fill(indexToView, pointCounter, pointCounter + markersSize, sampleCounter);
587
588
589 if (qualityScores != null && (distortionMethod == RobustEstimatorMethod.PROSAC
590 || distortionMethod == RobustEstimatorMethod.PROMEDS)) {
591
592 final var sampleQuality = homographyQualityScores[sampleCounter];
593
594
595
596 for (var i = pointCounter; i < pointCounter + markersSize; i++) {
597 qualityScores[i] = sampleQuality;
598 }
599
600 pointCounter += markersSize;
601 sampleCounter++;
602 }
603 }
604
605
606 try {
607 distortionEstimator.setIntrinsic(intrinsic);
608 distortionEstimator.setPoints(distortedPoints, undistortedPoints);
609 distortionEstimator.setQualityScores(qualityScores);
610
611 distortion = distortionEstimator.estimate();
612 } catch (final Exception e) {
613 throw new CalibrationException(e);
614 }
615 } else {
616
617
618 for (final var sample : samples) {
619 if (sample.getHomography() == null) {
620
621
622 continue;
623 }
624 sample.computeCameraPose(intrinsic);
625 }
626
627
628 distortion = new RadialDistortion(0.0, 0.0);
629 }
630
631
632
633 try {
634
635
636 final var initParams = new double[numParameters(totalHomographies)];
637 paramsFromData(initParams);
638
639 final var x = dataXToMatrix(idealFallbackPatternMarkers);
640
641 final var y = dataYToMatrix();
642
643
644
645
646
647 final var evaluator = new LevenbergMarquardtMultiVariateFunctionEvaluator() {
648
649
650 private int i;
651
652
653 private double[] point;
654
655
656
657
658
659 private final JacobianEstimator jacobianEstimator = new JacobianEstimator(
660 new MultiVariateFunctionEvaluatorListener() {
661
662
663
664 @Override
665 public void evaluate(final double[] params, final double[] result) {
666 evaluateFunction(i, point, params, result);
667 }
668
669
670
671 @Override
672 public int getNumberOfVariables() {
673 return Point2D.POINT2D_INHOMOGENEOUS_COORDINATES_LENGTH;
674 }
675 });
676
677
678
679 @Override
680 public int getNumberOfDimensions() {
681 return Point2D.POINT2D_INHOMOGENEOUS_COORDINATES_LENGTH;
682 }
683
684
685
686 @Override
687 public int getNumberOfVariables() {
688 return Point2D.POINT2D_INHOMOGENEOUS_COORDINATES_LENGTH;
689 }
690
691
692
693
694 @Override
695 public double[] createInitialParametersArray() {
696 return initParams;
697 }
698
699
700 @Override
701 public void evaluate(final int i, final double[] point, final double[] result, final double[] params,
702 final Matrix jacobian) throws EvaluationException {
703 this.i = i;
704 this.point = point;
705 evaluateFunction(this.i, this.point, params, result);
706 jacobianEstimator.jacobian(params, jacobian);
707 }
708 };
709
710
711 final var sigma = 1.0;
712 final var fitter = new LevenbergMarquardtMultiVariateFitter(evaluator, x, y, sigma);
713 fitter.setItmax(levenbergMarquardtMaxIters);
714 fitter.setTol(levenbergMarquardtTolerance);
715
716 final var estimatedParams = fitter.getA();
717
718 dataFromParams(estimatedParams);
719
720
721
722 final var error = computeReprojectionError(idealFallbackPatternMarkers);
723
724 if (listener != null) {
725 listener.onRadialDistortionEstimationEnds(this, distortion);
726 }
727
728 return error;
729
730 } catch (Exception e) {
731 throw new CalibrationException(e);
732 }
733 }
734
735
736
737
738
739 protected void refreshDistortionEstimatorListener() {
740 if (distortionEstimatorListener == null) {
741 distortionEstimatorListener = new RadialDistortionRobustEstimatorListener() {
742
743 @Override
744 public void onEstimateStart(final RadialDistortionRobustEstimator estimator) {
745 radialDistortionProgress = 0.0f;
746 notifyProgress();
747 }
748
749 @Override
750 public void onEstimateEnd(final RadialDistortionRobustEstimator estimator) {
751 radialDistortionProgress = 1.0f;
752 notifyProgress();
753 }
754
755 @Override
756 public void onEstimateNextIteration(
757 final RadialDistortionRobustEstimator estimator, final int iteration) {
758
759 }
760
761 @Override
762 public void onEstimateProgressChange(
763 final RadialDistortionRobustEstimator estimator, final float progress) {
764 radialDistortionProgress = progress;
765 notifyProgress();
766 }
767 };
768 }
769
770 try {
771 distortionEstimator.setListener(distortionEstimatorListener);
772 } catch (final LockedException e) {
773 Logger.getLogger(AlternatingCameraCalibrator.class.getName()).log(Level.WARNING,
774 "Could not set radial distortion estimator listener", e);
775 }
776 }
777
778
779
780
781
782
783
784
785
786
787
788 private void evaluateFunction(final int i, final double[] point, final double[] params, final double[] result) {
789
790
791 dataFromParams(params);
792
793 final var numView = indexToView[i];
794
795
796 final var sample = samples.get(numView);
797 final var camera = sample.getCamera();
798
799 final var idealPoint3D = new HomogeneousPoint3D();
800
801 idealPoint3D.setInhomogeneousCoordinates(point[0], point[1], 0.0);
802
803
804 final var undistortedPoint = camera.project(idealPoint3D);
805
806
807 final var distortedPoint = new HomogeneousPoint2D();
808 distortion.distort(undistortedPoint, distortedPoint);
809
810 result[0] = distortedPoint.getInhomX();
811 result[1] = distortedPoint.getInhomY();
812 }
813
814
815
816
817
818
819
820
821
822
823
824
825 private Matrix dataXToMatrix(final List<Point2D> idealFallbackPatternMarkers) throws WrongSizeException {
826 final var idealPoints = new ArrayList<Point2D>();
827 for (final var sample : samples) {
828 final List<Point2D> idealPatternMarkers;
829 if (sample.getPattern() != null) {
830
831 idealPatternMarkers = sample.getPattern().getIdealPoints();
832 } else {
833
834 idealPatternMarkers = idealFallbackPatternMarkers;
835 }
836
837 idealPoints.addAll(idealPatternMarkers);
838 }
839
840 final var nPoints = idealPoints.size();
841
842 final var m = new Matrix(nPoints, Point2D.POINT2D_INHOMOGENEOUS_COORDINATES_LENGTH);
843 var i = 0;
844 for (final var sample : samples) {
845 final List<Point2D> idealPatternMarkers;
846 if (sample.getPattern() != null) {
847
848 idealPatternMarkers = sample.getPattern().getIdealPoints();
849 } else {
850
851 idealPatternMarkers = idealFallbackPatternMarkers;
852 }
853
854 for (final var point : idealPatternMarkers) {
855 m.setElementAt(i, 0, point.getInhomX());
856 m.setElementAt(i, 1, point.getInhomY());
857 i++;
858 }
859 }
860
861 return m;
862 }
863
864
865
866
867
868
869
870
871 private Matrix dataYToMatrix() throws WrongSizeException {
872 var nPoints = 0;
873 for (final var sample : samples) {
874 nPoints += sample.getSampledMarkers().size();
875 }
876
877 final var m = new Matrix(nPoints, Point2D.POINT2D_INHOMOGENEOUS_COORDINATES_LENGTH);
878 var i = 0;
879 for (final var sample : samples) {
880 for (final var point : sample.getSampledMarkers()) {
881 m.setElementAt(i, 0, point.getInhomX());
882 m.setElementAt(i, 1, point.getInhomY());
883 i++;
884 }
885 }
886
887 return m;
888 }
889
890
891
892
893
894
895
896
897
898 private void paramsFromData(final double[] params) {
899 var pos = 0;
900
901
902
903
904
905 if (!isZeroSkewness()) {
906
907
908 params[pos] = intrinsic.getHorizontalFocalLength();
909 pos++;
910 params[pos] = intrinsic.getVerticalFocalLength();
911 pos++;
912 params[pos] = intrinsic.getSkewness();
913 pos++;
914 } else {
915
916 params[pos] = intrinsic.getHorizontalFocalLength();
917 pos++;
918
919 if (!isFocalDistanceAspectRatioKnown()) {
920
921
922 params[pos] = intrinsic.getVerticalFocalLength();
923 pos++;
924 }
925 }
926
927 if (!isPrincipalPointAtOrigin()) {
928
929 params[pos] = intrinsic.getHorizontalPrincipalPoint();
930 pos++;
931 params[pos] = intrinsic.getVerticalPrincipalPoint();
932 pos++;
933 }
934
935
936 final var kParams = distortion.getKParams();
937 for (final var kParam : kParams) {
938 params[pos] = kParam;
939 pos++;
940 }
941
942
943
944 for (final var sample : samples) {
945 if (sample.getHomography() == null) {
946 continue;
947 }
948
949
950 AxisRotation3D rot;
951 if (sample.getRotation().getType() == Rotation3DType.AXIS_ROTATION3D) {
952 rot = (AxisRotation3D) sample.getRotation();
953 } else {
954 rot = new AxisRotation3D(sample.getRotation());
955 }
956
957 params[pos] = rot.getRotationAngle();
958 pos++;
959 params[pos] = rot.getAxisX();
960 pos++;
961 params[pos] = rot.getAxisY();
962 pos++;
963 params[pos] = rot.getAxisZ();
964 pos++;
965
966
967 final var center = sample.getCameraCenter();
968 params[pos] = center.getInhomX();
969 pos++;
970 params[pos] = center.getInhomY();
971 pos++;
972 params[pos] = center.getInhomZ();
973 pos++;
974 }
975 }
976
977
978
979
980
981
982
983 private void dataFromParams(final double[] params) {
984 var pos = 0;
985
986
987 double horizontalFocalLength;
988 double verticalFocalLength;
989 double skewness;
990 double horizontalPrincipalPoint;
991 double verticalPrincipalPoint;
992 if (!isZeroSkewness()) {
993
994
995 horizontalFocalLength = params[pos];
996 pos++;
997 verticalFocalLength = params[pos];
998 pos++;
999 skewness = params[pos];
1000 pos++;
1001 } else {
1002
1003 skewness = 0.0;
1004 horizontalFocalLength = params[pos];
1005 pos++;
1006
1007 if (!isFocalDistanceAspectRatioKnown()) {
1008
1009 verticalFocalLength = params[pos];
1010 pos++;
1011 } else {
1012
1013
1014 verticalFocalLength = horizontalFocalLength * getFocalDistanceAspectRatio();
1015 }
1016 }
1017
1018 if (!isPrincipalPointAtOrigin()) {
1019
1020 horizontalPrincipalPoint = params[pos];
1021 pos++;
1022 verticalPrincipalPoint = params[pos];
1023 pos++;
1024 } else {
1025
1026 horizontalPrincipalPoint = verticalPrincipalPoint = 0.0;
1027 }
1028
1029
1030 intrinsic.setHorizontalFocalLength(horizontalFocalLength);
1031 intrinsic.setVerticalFocalLength(verticalFocalLength);
1032 intrinsic.setSkewness(skewness);
1033 intrinsic.setHorizontalPrincipalPoint(horizontalPrincipalPoint);
1034 intrinsic.setVerticalPrincipalPoint(verticalPrincipalPoint);
1035
1036
1037 final var kParams = distortion.getKParams();
1038 for (var i = 0; i < kParams.length; i++) {
1039 kParams[i] = params[pos];
1040 pos++;
1041 }
1042
1043
1044 for (final var sample : samples) {
1045 if (sample.getHomography() == null) {
1046 continue;
1047 }
1048
1049
1050 final AxisRotation3D rot;
1051 if (sample.getRotation().getType() == Rotation3DType.AXIS_ROTATION3D) {
1052 rot = (AxisRotation3D) sample.getRotation();
1053 } else {
1054 rot = new AxisRotation3D();
1055
1056 sample.setRotation(rot);
1057 }
1058
1059 final var rotAngle = params[pos];
1060 pos++;
1061 final var axisX = params[pos];
1062 pos++;
1063 final var axisY = params[pos];
1064 pos++;
1065 final var axisZ = params[pos];
1066 pos++;
1067
1068 rot.setAxisAndRotation(axisX, axisY, axisZ, rotAngle);
1069
1070
1071 final var inhomX = params[pos];
1072 pos++;
1073 final var inhomY = params[pos];
1074 pos++;
1075 final var inhomZ = params[pos];
1076 pos++;
1077 final var center = sample.getCameraCenter();
1078 center.setInhomogeneousCoordinates(inhomX, inhomY, inhomZ);
1079
1080
1081 final var camera = sample.getCamera();
1082 camera.setIntrinsicAndExtrinsicParameters(intrinsic, rot, center);
1083 }
1084 }
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096 private double computeReprojectionError(final List<Point2D> idealFallbackPatternMarkers) {
1097
1098
1099
1100 PinholeCamera camera;
1101 Point2D marker2D;
1102 final var marker3D = Point3D.create();
1103 final var undistortedPoint = Point2D.create();
1104 var totalPoints = 0;
1105 final var distortedPoint = Point2D.create();
1106 Point2D sampledPoint;
1107 var avgError = 0.0;
1108 for (final var sample : samples) {
1109 camera = sample.getCamera();
1110 if (camera == null) {
1111 continue;
1112 }
1113
1114 final List<Point2D> idealPatternMarkers;
1115 if (sample.getPattern() != null) {
1116 idealPatternMarkers = sample.getPattern().getIdealPoints();
1117 } else {
1118 idealPatternMarkers = idealFallbackPatternMarkers;
1119 }
1120
1121 final var pointsPerSample = idealPatternMarkers.size();
1122 for (var i = 0; i < pointsPerSample; i++) {
1123 marker2D = idealPatternMarkers.get(i);
1124 sampledPoint = sample.getSampledMarkers().get(i);
1125
1126
1127
1128 marker3D.setInhomogeneousCoordinates(marker2D.getInhomX(), marker2D.getInhomY(), 0.0);
1129
1130
1131
1132 camera.project(marker3D, undistortedPoint);
1133
1134
1135 distortion.distort(undistortedPoint, distortedPoint);
1136
1137
1138
1139 avgError += sampledPoint.distanceTo(distortedPoint);
1140 totalPoints++;
1141 }
1142 }
1143
1144 if (totalPoints == 0) {
1145 avgError = Double.MAX_VALUE;
1146 } else {
1147 avgError /= totalPoints;
1148 }
1149
1150 return avgError;
1151 }
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161 private void internalSetDistortionMethod(RobustEstimatorMethod distortionMethod) {
1162
1163 if (distortionMethod != this.distortionMethod) {
1164 final var previousAvailable = this.distortionMethod != null;
1165 var threshold = 0.0;
1166 var confidence = 0.0;
1167 var maxIterations = 0;
1168 if (previousAvailable) {
1169 threshold = getDistortionEstimatorThreshold();
1170 confidence = getDistortionEstimatorConfidence();
1171 maxIterations = getDistortionEstimatorMaxIterations();
1172 }
1173
1174 distortionEstimator = RadialDistortionRobustEstimator.create(distortionMethod);
1175
1176
1177 refreshDistortionEstimatorListener();
1178 if (previousAvailable) {
1179 try {
1180 setDistortionEstimatorThreshold(threshold);
1181 setDistortionEstimatorConfidence(confidence);
1182 setDistortionEstimatorMaxIterations(maxIterations);
1183 } catch (final LockedException e) {
1184 Logger.getLogger(AlternatingCameraCalibrator.class.getName()).log(Level.WARNING,
1185 "Could not reconfigure distortion estimator", e);
1186 }
1187 }
1188 }
1189
1190 this.distortionMethod = distortionMethod;
1191 }
1192
1193
1194
1195
1196
1197
1198
1199 private int numParameters(final int numHomographies) {
1200
1201
1202
1203
1204
1205
1206 return 7 * numHomographies + numIntrinsicParameters() + distortion.getKParams().length;
1207 }
1208
1209
1210
1211
1212
1213
1214
1215
1216 private int numIntrinsicParameters() {
1217
1218
1219
1220 var num = 5;
1221 if (isZeroSkewness()) {
1222 if (isFocalDistanceAspectRatioKnown()) {
1223 num--;
1224 }
1225 num--;
1226 }
1227 if (isPrincipalPointAtOrigin()) {
1228 num -= 2;
1229 }
1230
1231 return num;
1232 }
1233 }