1 /*
2 * Copyright (C) 2015 Alberto Irurueta Carro (alberto@irurueta.com)
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.irurueta.ar.calibration.estimators;
17
18 import com.irurueta.algebra.AlgebraException;
19 import com.irurueta.algebra.Matrix;
20 import com.irurueta.algebra.Utils;
21 import com.irurueta.ar.calibration.RadialDistortion;
22 import com.irurueta.ar.calibration.RadialDistortionException;
23 import com.irurueta.geometry.InhomogeneousPoint2D;
24 import com.irurueta.geometry.PinholeCameraIntrinsicParameters;
25 import com.irurueta.geometry.Point2D;
26 import com.irurueta.geometry.estimators.LockedException;
27 import com.irurueta.geometry.estimators.NotReadyException;
28
29 import java.util.List;
30
31 /**
32 * This class defines the interface for an estimator of radial distortion
33 */
34 public abstract class RadialDistortionEstimator {
35
36 /**
37 * Default number of radial distortion parameters.
38 */
39 public static final int DEFAULT_NUM_K_PARAMS = 2;
40
41 /**
42 * Minimum number of radial distortion parameters.
43 */
44 public static final int MIN_K_PARAMS = 1;
45
46 /**
47 * Defines default focal length if none is defined.
48 */
49 public static final double DEFAULT_FOCAL_LENGTH = 1.0;
50
51 /**
52 * Defines default skewness if none is defined.
53 */
54 public static final double DEFAULT_SKEW = 0.0;
55
56 /**
57 * Default estimator type.
58 */
59 public static final RadialDistortionEstimatorType DEFAULT_ESTIMATOR_TYPE =
60 RadialDistortionEstimatorType.LMSE_RADIAL_DISTORTION_ESTIMATOR;
61
62 /**
63 * True when estimator is estimating radial distortion.
64 */
65 protected boolean locked;
66
67 /**
68 * Listener to be notified of events such as when estimation starts, ends or
69 * estimation progress changes.
70 */
71 protected RadialDistortionEstimatorListener listener;
72
73 /**
74 * Distortion center. This is usually equal to the principal point
75 * of an estimated camera. If not set it is assumed to be at the origin of
76 * coordinates (0,0).
77 */
78 protected Point2D distortionCenter;
79
80 /**
81 * Horizontal focal length expressed in pixels.
82 */
83 protected double horizontalFocalLength;
84
85 /**
86 * Vertical focal length expressed in pixels.
87 */
88 protected double verticalFocalLength;
89
90 /**
91 * Skew in pixels.
92 */
93 protected double skew;
94
95 /**
96 * Intrinsic parameters matrix.
97 */
98 protected Matrix kInv;
99
100 /**
101 * List of distorted points. Distorted points are obtained after radial
102 * distortion is applied to an undistorted point.
103 */
104 protected List<Point2D> distortedPoints;
105
106 /**
107 * List of undistorted points.
108 */
109 protected List<Point2D> undistortedPoints;
110
111 /**
112 * Number of radial distortion parameters to estimate.
113 */
114 protected int numKParams;
115
116 /**
117 * Constructor.
118 */
119 protected RadialDistortionEstimator() {
120 locked = false;
121 listener = null;
122 numKParams = DEFAULT_NUM_K_PARAMS;
123 try {
124 setInternalIntrinsic(null, DEFAULT_FOCAL_LENGTH, DEFAULT_FOCAL_LENGTH, DEFAULT_SKEW);
125 } catch (final RadialDistortionException ignore) {
126 // never happens
127 }
128 }
129
130 /**
131 * Constructor with listener.
132 *
133 * @param listener listener to be notified of events such as when estimation
134 * starts, ends or estimation progress changes.
135 */
136 protected RadialDistortionEstimator(final RadialDistortionEstimatorListener listener) {
137 locked = false;
138 this.listener = listener;
139 numKParams = DEFAULT_NUM_K_PARAMS;
140 try {
141 setInternalIntrinsic(null, DEFAULT_FOCAL_LENGTH, DEFAULT_FOCAL_LENGTH, DEFAULT_SKEW);
142 } catch (final RadialDistortionException ignore) {
143 // never happens
144 }
145 }
146
147 /**
148 * Constructor.
149 *
150 * @param distortedPoints list of distorted points. Distorted points are
151 * obtained after radial distortion is applied to an undistorted point.
152 * @param undistortedPoints list of undistorted points.
153 * @throws IllegalArgumentException if provided lists of points don't have
154 * the same size.
155 */
156 protected RadialDistortionEstimator(final List<Point2D> distortedPoints, final List<Point2D> undistortedPoints) {
157 this();
158 internalSetPoints(distortedPoints, undistortedPoints);
159 }
160
161 /**
162 * Constructor.
163 *
164 * @param distortedPoints list of distorted points. Distorted points are
165 * obtained after radial distortion is applied to an undistorted point.
166 * @param undistortedPoints list of undistorted points.
167 * @param listener listener to be notified of events such as when estimation
168 * starts, ends or estimation progress changes.
169 * @throws IllegalArgumentException if provided lists of points don't have
170 * the same size.
171 */
172 protected RadialDistortionEstimator(final List<Point2D> distortedPoints, final List<Point2D> undistortedPoints,
173 final RadialDistortionEstimatorListener listener) {
174 this(listener);
175 internalSetPoints(distortedPoints, undistortedPoints);
176 }
177
178 /**
179 * Constructor with distortion center.
180 *
181 * @param distortionCenter Distortion center. This is usually equal to the
182 * principal point of an estimated camera. If not set it is assumed to be at
183 * the origin of coordinates (0,0).
184 */
185 protected RadialDistortionEstimator(final Point2D distortionCenter) {
186 locked = false;
187 listener = null;
188 numKParams = DEFAULT_NUM_K_PARAMS;
189 try {
190 setInternalIntrinsic(distortionCenter, DEFAULT_FOCAL_LENGTH, DEFAULT_FOCAL_LENGTH, DEFAULT_SKEW);
191 } catch (final RadialDistortionException ignore) {
192 // never happens
193 }
194 }
195
196 /**
197 * Constructor with listener and distortion center.
198 *
199 * @param distortionCenter Distortion center. This is usually equal to the
200 * principal point of an estimated camera. If not set it is assumed to be at
201 * the origin of coordinates (0,0).
202 * @param listener listener to be notified of events such as when estimation
203 * starts, ends or estimation progress changes.
204 */
205 protected RadialDistortionEstimator(
206 final Point2D distortionCenter, final RadialDistortionEstimatorListener listener) {
207 this(listener);
208 try {
209 setInternalIntrinsic(distortionCenter, DEFAULT_FOCAL_LENGTH, DEFAULT_FOCAL_LENGTH, DEFAULT_SKEW);
210 } catch (final RadialDistortionException ignore) {
211 // never happens
212 }
213 }
214
215 /**
216 * Constructor with points and distortion center.
217 *
218 * @param distortedPoints list of distorted points. Distorted points are
219 * obtained after radial distortion is applied to an undistorted point.
220 * @param undistortedPoints list of undistorted points.
221 * @param distortionCenter Distortion center. This is usually equal to the
222 * principal point of an estimated camera. If not set it is assumed to be at
223 * the origin of coordinates (0,0).
224 * @throws IllegalArgumentException if provided lists of points don't have
225 * the same size.
226 */
227 protected RadialDistortionEstimator(final List<Point2D> distortedPoints, final List<Point2D> undistortedPoints,
228 final Point2D distortionCenter) {
229 this(distortedPoints, undistortedPoints);
230 try {
231 setInternalIntrinsic(distortionCenter, DEFAULT_FOCAL_LENGTH, DEFAULT_FOCAL_LENGTH, DEFAULT_SKEW);
232 } catch (final RadialDistortionException ignore) {
233 // never happens
234 }
235 }
236
237 /**
238 * Constructor.
239 *
240 * @param distortedPoints list of distorted points. Distorted points are
241 * obtained after radial distortion is applied to an undistorted point.
242 * @param undistortedPoints list of undistorted points.
243 * @param distortionCenter Distortion center. This is usually equal to the
244 * principal point of an estimated camera. If not set it is assumed to be at
245 * the origin of coordinates (0,0).
246 * @param listener listener to be notified of events such as when estimation
247 * starts, ends or estimation progress changes.
248 * @throws IllegalArgumentException if provided lists of points don't have
249 * the same size.
250 */
251 protected RadialDistortionEstimator(
252 final List<Point2D> distortedPoints, final List<Point2D> undistortedPoints, final Point2D distortionCenter,
253 final RadialDistortionEstimatorListener listener) {
254 this(distortedPoints, undistortedPoints, listener);
255 try {
256 setInternalIntrinsic(distortionCenter, DEFAULT_FOCAL_LENGTH, DEFAULT_FOCAL_LENGTH, DEFAULT_SKEW);
257 } catch (final RadialDistortionException ignore) {
258 // never happens
259 }
260 }
261
262 /**
263 * Returns listener to be notified of events such as when estimation starts,
264 * ends or estimation progress changes.
265 *
266 * @return listener to be notified of events.
267 */
268 public RadialDistortionEstimatorListener getListener() {
269 return listener;
270 }
271
272 /**
273 * Sets listener to be notified of events such as when estimation starts,
274 * ends or estimation progress changes.
275 *
276 * @param listener listener to be notified of events.
277 * @throws LockedException if estimator is locked.
278 */
279 public void setListener(final RadialDistortionEstimatorListener listener) throws LockedException {
280 if (isLocked()) {
281 throw new LockedException();
282 }
283 this.listener = listener;
284 }
285
286 /**
287 * Indicates whether this instance is locked.
288 *
289 * @return true if this estimator is busy estimating a camera, false
290 * otherwise.
291 */
292 public boolean isLocked() {
293 return locked;
294 }
295
296 /**
297 * Sets list of corresponding distorted and undistorted points.
298 *
299 * @param distortedPoints list of distorted points. Distorted points are
300 * obtained after radial distortion is applied to an undistorted point.
301 * @param undistortedPoints list of undistorted points.
302 * @throws LockedException if estimator is locked.
303 * @throws IllegalArgumentException if provided lists of points don't have
304 * the same size.
305 */
306 public void setPoints(final List<Point2D> distortedPoints, final List<Point2D> undistortedPoints)
307 throws LockedException {
308 if (isLocked()) {
309 throw new LockedException();
310 }
311
312 internalSetPoints(distortedPoints, undistortedPoints);
313 }
314
315 /**
316 * Returns list of distorted points. Distorted points are obtained after
317 * radial distortion is applied to an undistorted point.
318 *
319 * @return list of distorted points.
320 */
321 public List<Point2D> getDistortedPoints() {
322 return distortedPoints;
323 }
324
325 /**
326 * Returns list of undistorted points.
327 *
328 * @return list of undistorted points.
329 */
330 public List<Point2D> getUndistortedPoints() {
331 return undistortedPoints;
332 }
333
334 /**
335 * Returns distortion center. This is usually equal to the principal point
336 * of an estimated camera. If not set it is assumed to be at the origin of
337 * coordinates (0,0).
338 *
339 * @return distortion center or null.
340 */
341 public Point2D getDistortionCenter() {
342 return distortionCenter;
343 }
344
345 /**
346 * Sets distortion center. This is usually equal to the principal point of
347 * an estimated camera. If not set it is assumed to be at the origin of
348 * coordinates (0,0).
349 *
350 * @param distortionCenter distortion center, or null if set at origin of
351 * coordinates.
352 * @throws LockedException if estimator is locked.
353 */
354 public void setDistortionCenter(final Point2D distortionCenter) throws LockedException {
355 if (isLocked()) {
356 throw new LockedException();
357 }
358
359 try {
360 setInternalIntrinsic(distortionCenter, horizontalFocalLength, verticalFocalLength, skew);
361 } catch (final RadialDistortionException ignore) {
362 // never happens
363 }
364 }
365
366 /**
367 * Returns horizontal focal length expressed in pixels.
368 *
369 * @return horizontal focal length expressed in pixels.
370 */
371 public double getHorizontalFocalLength() {
372 return horizontalFocalLength;
373 }
374
375 /**
376 * Sets horizontal focal length expressed in pixels.
377 *
378 * @param horizontalFocalLength horizontal focal length expressed in pixels.
379 * @throws LockedException if estimator is locked.
380 * @throws RadialDistortionException if provided value is degenerate (i.e.
381 * zero).
382 */
383 public void setHorizontalFocalLength(final double horizontalFocalLength) throws LockedException,
384 RadialDistortionException {
385 if (isLocked()) {
386 throw new LockedException();
387 }
388
389 setInternalIntrinsic(distortionCenter, horizontalFocalLength, verticalFocalLength, skew);
390 }
391
392 /**
393 * Returns vertical focal length expressed in pixels.
394 *
395 * @return vertical focal length expressed in pixels.
396 */
397 public double getVerticalFocalLength() {
398 return verticalFocalLength;
399 }
400
401 /**
402 * Sets vertical focal length expressed in pixels.
403 *
404 * @param verticalFocalLength vertical focal length expressed in pixels.
405 * @throws LockedException if estimator is locked.
406 * @throws RadialDistortionException if provided value is degenerate (i.e.
407 * zero).
408 */
409 public void setVerticalFocalLength(final double verticalFocalLength) throws LockedException,
410 RadialDistortionException {
411 if (isLocked()) {
412 throw new LockedException();
413 }
414
415 setInternalIntrinsic(distortionCenter, horizontalFocalLength, verticalFocalLength, skew);
416 }
417
418 /**
419 * Returns skew expressed in pixels.
420 *
421 * @return skew expressed in pixels.
422 */
423 public double getSkew() {
424 return skew;
425 }
426
427 /**
428 * Sets skew expressed in pixels.
429 *
430 * @param skew skew expressed in pixels.
431 * @throws LockedException if estimator is locked.
432 */
433 public void setSkew(final double skew) throws LockedException {
434 if (isLocked()) {
435 throw new LockedException();
436 }
437
438 try {
439 setInternalIntrinsic(distortionCenter, horizontalFocalLength, verticalFocalLength, skew);
440 } catch (final RadialDistortionException ignore) {
441 // never happens
442 }
443 }
444
445 /**
446 * Sets intrinsic parameters.
447 *
448 * @param distortionCenter radial distortion center.
449 * @param horizontalFocalLength horizontal focal length expressed in pixels.
450 * @param verticalFocalLength vertical focal length expressed in pixels.
451 * @param skew skew expressed in pixels.
452 * @throws LockedException if estimator is locked.
453 * @throws RadialDistortionException if focal length is degenerate (i.e.
454 * zero).
455 */
456 public final void setIntrinsic(
457 final Point2D distortionCenter, final double horizontalFocalLength, final double verticalFocalLength,
458 final double skew) throws LockedException, RadialDistortionException {
459 if (isLocked()) {
460 throw new LockedException();
461 }
462
463 setInternalIntrinsic(distortionCenter, horizontalFocalLength, verticalFocalLength, skew);
464 }
465
466 /**
467 * Returns pinhole camera intrinsic parameters associated to this estimator.
468 *
469 * @return pinhole camera intrinsic parameters associated to this estimator.
470 */
471 public PinholeCameraIntrinsicParameters getIntrinsic() {
472 return new PinholeCameraIntrinsicParameters(horizontalFocalLength, verticalFocalLength,
473 distortionCenter != null ? distortionCenter.getInhomX() : 0.0,
474 distortionCenter != null ? distortionCenter.getInhomY() : 0.0,
475 skew);
476 }
477
478 /**
479 * Sets intrinsic parameters for this estimator from pinhole camera
480 * intrinsic parameters.
481 *
482 * @param intrinsic intrinsic parameters.
483 * @throws LockedException if estimator is locked.
484 * @throws RadialDistortionException if focal length is degenerate (i.e.
485 * zero).
486 */
487 public void setIntrinsic(final PinholeCameraIntrinsicParameters intrinsic) throws LockedException,
488 RadialDistortionException {
489 setIntrinsic(new InhomogeneousPoint2D(
490 intrinsic.getHorizontalPrincipalPoint(), intrinsic.getVerticalPrincipalPoint()),
491 intrinsic.getHorizontalFocalLength(),
492 intrinsic.getVerticalFocalLength(),
493 intrinsic.getSkewness());
494 }
495
496 /**
497 * Returns number of radial distortion parameters to estimate.
498 *
499 * @return number of radial distortion parameters to estimate.
500 */
501 public int getNumKParams() {
502 return numKParams;
503 }
504
505 /**
506 * Sets number of radial distortion parameters to estimate.
507 *
508 * @param numKParams number of radial distortion parameters to estimate.
509 * @throws LockedException if estimator is locked.
510 * @throws IllegalArgumentException if number of parameters is less than 1.
511 */
512 public void setNumKParams(final int numKParams) throws LockedException {
513 if (isLocked()) {
514 throw new LockedException();
515 }
516 if (numKParams < MIN_K_PARAMS) {
517 throw new IllegalArgumentException();
518 }
519
520 this.numKParams = numKParams;
521 }
522
523 /**
524 * Returns minimum number of required matched points to compute the radial
525 * distortion. This is equal to the number of radial distortion parameters.
526 * The larger the number of radial distortion parameters, the larger the
527 * number of matched points required. Typically, 2 radial distortion
528 * parameters are enough, since the following terms can be safely neglected
529 *
530 * @return minimum number of required matched points to compute the radial
531 * distortion.
532 */
533 public int getMinNumberOfMatchedPoints() {
534 return numKParams;
535 }
536
537 /**
538 * Indicates if lists of corresponding distorted and undistorted points are
539 * valid.
540 * Lists are considered valid if they have the same number of points and
541 * both have more than the required minimum of correspondences (which is 2)
542 *
543 * @param distortedPoints list of distorted points. Distorted points are
544 * obtained after radial distortion is applied to an undistorted point.
545 * @param undistortedPoints list of undistorted points.
546 * @return true if lists of points are valid, false otherwise.
547 */
548 public boolean areValidPoints(final List<Point2D> distortedPoints, final List<Point2D> undistortedPoints) {
549 if (distortedPoints == null || undistortedPoints == null) {
550 return false;
551 }
552 return distortedPoints.size() == undistortedPoints.size()
553 && distortedPoints.size() >= getMinNumberOfMatchedPoints();
554 }
555
556 /**
557 * Indicates if lists of corresponding points have already been provided and
558 * are available for retrieval.
559 *
560 * @return true if available, false otherwise.
561 */
562 public boolean arePointsAvailable() {
563 return distortedPoints != null && undistortedPoints != null;
564 }
565
566 /**
567 * Indicates if this estimator is ready to start th estimation.
568 * This is true when lists of points are provided, having equal size and
569 * at least 2 points.
570 *
571 * @return true if estimator is ready, false otherwise.
572 */
573 public boolean isReady() {
574 return arePointsAvailable() && areValidPoints(distortedPoints, undistortedPoints);
575 }
576
577 /**
578 * Estimates a radial distortion.
579 *
580 * @return estimated radial distortion.
581 * @throws LockedException if estimator is locked.
582 * @throws NotReadyException if input has not yet been provided.
583 * @throws RadialDistortionEstimatorException if an error occurs during
584 * estimation, usually because input data is not valid.
585 */
586 public abstract RadialDistortion estimate() throws LockedException, NotReadyException,
587 RadialDistortionEstimatorException;
588
589 /**
590 * Returns type of radial distortion estimator.
591 *
592 * @return type of radial distortion estimator.
593 */
594 public abstract RadialDistortionEstimatorType getType();
595
596 /**
597 * Creates an instance of a radial distortion estimator using default
598 * type.
599 *
600 * @return an instance of a radial distortion estimator.
601 */
602 public static RadialDistortionEstimator create() {
603 return create(DEFAULT_ESTIMATOR_TYPE);
604 }
605
606 /**
607 * Creates an instance of a radial distortion estimator using provided
608 * type.
609 *
610 * @param type type of radial distortion estimator.
611 * @return an instance of a radial distortion estimator.
612 */
613 public static RadialDistortionEstimator create(final RadialDistortionEstimatorType type) {
614 return type == RadialDistortionEstimatorType.WEIGHTED_RADIAL_DISTORTION_ESTIMATOR
615 ? new WeightedRadialDistortionEstimator() : new LMSERadialDistortionEstimator();
616 }
617
618 /**
619 * Internally sets intrinsic parameters.
620 * This method does not check whether estimator is locked.
621 *
622 * @param distortionCenter radial distortion center.
623 * @param horizontalFocalLength horizontal focal length expressed in pixels.
624 * @param verticalFocalLength vertical focal length expressed in pixels.
625 * @param skew skew expressed in pixels.
626 * @throws RadialDistortionException if focal length is degenerate (i.e.
627 * zero).
628 */
629 @SuppressWarnings("DuplicatedCode")
630 private void setInternalIntrinsic(
631 final Point2D distortionCenter, final double horizontalFocalLength, final double verticalFocalLength,
632 final double skew) throws RadialDistortionException {
633 this.distortionCenter = distortionCenter;
634 this.horizontalFocalLength = horizontalFocalLength;
635 this.verticalFocalLength = verticalFocalLength;
636 this.skew = skew;
637
638 try {
639 if (kInv == null) {
640 kInv = new Matrix(3, 3);
641 }
642
643 // initially matrix is zero
644 final var k = new Matrix(3, 3);
645
646 k.setElementAt(0, 0, horizontalFocalLength);
647 k.setElementAt(1, 1, verticalFocalLength);
648 k.setElementAt(0, 1, skew);
649 // if center is not provided, values below are zero
650 if (this.distortionCenter != null) {
651 k.setElementAt(0, 2, this.distortionCenter.getInhomX());
652 k.setElementAt(1, 2, this.distortionCenter.getInhomY());
653 }
654 k.setElementAt(2, 2, 1.0);
655
656 Utils.inverse(k, kInv);
657 } catch (AlgebraException e) {
658 throw new RadialDistortionException(e);
659 }
660 }
661
662 /**
663 * Sets list of corresponding distorted and undistorted points.
664 * This method does not check whether estimator is locked.
665 *
666 * @param distortedPoints list of distorted points. Distorted points are
667 * obtained after radial distortion is applied to an undistorted point.
668 * @param undistortedPoints list of undistorted points.
669 * @throws IllegalArgumentException if provided lists of points don't have
670 * the same size.
671 */
672 private void internalSetPoints(final List<Point2D> distortedPoints, final List<Point2D> undistortedPoints) {
673
674 if (distortedPoints == null || undistortedPoints == null) {
675 throw new IllegalArgumentException();
676 }
677
678 if (!areValidPoints(distortedPoints, undistortedPoints)) {
679 throw new IllegalArgumentException();
680 }
681
682 this.distortedPoints = distortedPoints;
683 this.undistortedPoints = undistortedPoints;
684 }
685
686 }