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