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.ar.calibration.RadialDistortion;
19  import com.irurueta.geometry.CoordinatesType;
20  import com.irurueta.geometry.Point2D;
21  import com.irurueta.geometry.estimators.LockedException;
22  import com.irurueta.geometry.estimators.NotReadyException;
23  import com.irurueta.numerical.robust.PROSACRobustEstimator;
24  import com.irurueta.numerical.robust.PROSACRobustEstimatorListener;
25  import com.irurueta.numerical.robust.RobustEstimator;
26  import com.irurueta.numerical.robust.RobustEstimatorException;
27  import com.irurueta.numerical.robust.RobustEstimatorMethod;
28  
29  import java.util.ArrayList;
30  import java.util.List;
31  import java.util.logging.Level;
32  import java.util.logging.Logger;
33  
34  /**
35   * Finds the best radial distortion for provided collections of 2D points using
36   * PROSAC algorithm.
37   */
38  public class PROSACRadialDistortionRobustEstimator extends RadialDistortionRobustEstimator {
39  
40      /**
41       * Constant defining default threshold to determine whether points are
42       * inliers or not.
43       * By default, 1.0 is considered a good value for cases where measures are
44       * done on pixels, since typically the minimum resolution is 1 pixel.
45       */
46      public static final double DEFAULT_THRESHOLD = 1.0;
47  
48      /**
49       * Minimum value that can be set as threshold.
50       * Threshold must be strictly greater than 0.0.
51       */
52      public static final double MIN_THRESHOLD = 0.0;
53  
54      /**
55       * Threshold to determine whether points are inliers or not when testing
56       * possible estimation solutions.
57       * The threshold refers to the amount of error (i.e. distance) a possible
58       * solution has on a matched pair of points.
59       */
60      private double threshold;
61  
62      /**
63       * Quality scores corresponding to each provided point.
64       * The larger the score value the better the quality of the sample.
65       */
66      private double[] qualityScores;
67  
68      /**
69       * Constructor.
70       */
71      public PROSACRadialDistortionRobustEstimator() {
72          super();
73          threshold = DEFAULT_THRESHOLD;
74      }
75  
76      /**
77       * Constructor.
78       *
79       * @param listener listener to be notified of events such as when
80       *                 estimation starts, ends or its progress significantly changes.
81       */
82      public PROSACRadialDistortionRobustEstimator(final RadialDistortionRobustEstimatorListener listener) {
83          super(listener);
84          threshold = DEFAULT_THRESHOLD;
85      }
86  
87      /**
88       * Constructor.
89       *
90       * @param distortedPoints   list of distorted points. Distorted points are
91       *                          obtained after radial distortion is applied to an undistorted point.
92       * @param undistortedPoints list of undistorted points.
93       * @throws IllegalArgumentException if provided lists of points don't have
94       *                                  the same size or their size is smaller than MIN_NUMBER_OF_POINTS.
95       */
96      public PROSACRadialDistortionRobustEstimator(
97              final List<Point2D> distortedPoints, final List<Point2D> undistortedPoints) {
98          super(distortedPoints, undistortedPoints);
99          threshold = DEFAULT_THRESHOLD;
100     }
101 
102     /**
103      * Constructor.
104      *
105      * @param distortedPoints   list of distorted points. Distorted points are
106      *                          obtained after radial distortion is applied to an undistorted point.
107      * @param undistortedPoints list of undistorted points.
108      * @param listener          listener to be notified of events such as when
109      *                          estimation starts, ends or its progress significantly changes.
110      * @throws IllegalArgumentException if provided lists of points don't have
111      *                                  the same size or their size is smaller than MIN_NUMBER_OF_POINTS.
112      */
113     public PROSACRadialDistortionRobustEstimator(final List<Point2D> distortedPoints,
114                                                  final List<Point2D> undistortedPoints,
115                                                  final RadialDistortionRobustEstimatorListener listener) {
116         super(distortedPoints, undistortedPoints, listener);
117         threshold = DEFAULT_THRESHOLD;
118     }
119 
120     /**
121      * Constructor.
122      *
123      * @param distortedPoints   list of distorted points. Distorted points are
124      *                          obtained after radial distortion is applied to an undistorted point.
125      * @param undistortedPoints list of undistorted points.
126      * @param distortionCenter  radial distortion center. If null it is assumed
127      *                          to be the origin of coordinates, otherwise this is typically equal to
128      *                          the camera principal point.
129      * @throws IllegalArgumentException if provided lists of points don't have
130      *                                  the same size or their size is smaller than MIN_NUMBER_OF_POINTS.
131      */
132     public PROSACRadialDistortionRobustEstimator(final List<Point2D> distortedPoints,
133                                                  final List<Point2D> undistortedPoints,
134                                                  final Point2D distortionCenter) {
135         super(distortedPoints, undistortedPoints, distortionCenter);
136         threshold = DEFAULT_THRESHOLD;
137     }
138 
139     /**
140      * Constructor.
141      *
142      * @param distortedPoints   list of distorted points. Distorted points are
143      *                          obtained after radial distortion is applied to an undistorted point.
144      * @param undistortedPoints list of undistorted points.
145      * @param distortionCenter  radial distortion center. If null it is assumed
146      *                          to be the origin of coordinates, otherwise this is typically equal to
147      *                          the camera principal point.
148      * @param listener          listener to be notified of events such as when
149      *                          estimation starts, ends or its progress significantly changes.
150      * @throws IllegalArgumentException if provided lists of points don't have
151      *                                  the same size or their size is smaller than MIN_NUMBER_OF_POINTS.
152      */
153     public PROSACRadialDistortionRobustEstimator(final List<Point2D> distortedPoints,
154                                                  final List<Point2D> undistortedPoints,
155                                                  final Point2D distortionCenter,
156                                                  final RadialDistortionRobustEstimatorListener listener) {
157         super(distortedPoints, undistortedPoints, distortionCenter, listener);
158         threshold = DEFAULT_THRESHOLD;
159     }
160 
161     /**
162      * Constructor.
163      *
164      * @param qualityScores quality scores corresponding to each provided point.
165      * @throws IllegalArgumentException if provided quality scores length is
166      *                                  smaller than required size (i.e. 2 points).
167      */
168     public PROSACRadialDistortionRobustEstimator(final double[] qualityScores) {
169         this();
170         internalSetQualityScores(qualityScores);
171     }
172 
173     /**
174      * Constructor.
175      *
176      * @param qualityScores quality scores corresponding to each provided point.
177      * @param listener      listener to be notified of events such as when
178      *                      estimation starts, ends or its progress significantly changes.
179      * @throws IllegalArgumentException if provided quality scores length is
180      *                                  smaller than required size (i.e. 2 points).
181      */
182     public PROSACRadialDistortionRobustEstimator(
183             final double[] qualityScores, final RadialDistortionRobustEstimatorListener listener) {
184         this(listener);
185         internalSetQualityScores(qualityScores);
186     }
187 
188     /**
189      * Constructor.
190      *
191      * @param distortedPoints   list of distorted points. Distorted points are
192      *                          obtained after radial distortion is applied to an undistorted point.
193      * @param undistortedPoints list of undistorted points.
194      * @param qualityScores     quality scores corresponding to each provided point.
195      * @throws IllegalArgumentException if provided lists of points and quality
196      *                                  scores don't have the same size or their size is smaller than
197      *                                  MIN_NUMBER_OF_POINTS (i.e. 2 points).
198      */
199     public PROSACRadialDistortionRobustEstimator(final List<Point2D> distortedPoints,
200                                                  final List<Point2D> undistortedPoints,
201                                                  final double[] qualityScores) {
202         this(distortedPoints, undistortedPoints);
203         internalSetQualityScores(qualityScores);
204     }
205 
206     /**
207      * Constructor.
208      *
209      * @param distortedPoints   list of distorted points. Distorted points are
210      *                          obtained after radial distortion is applied to an undistorted point.
211      * @param undistortedPoints list of undistorted points.
212      * @param qualityScores     quality scores corresponding to each provided point.
213      * @param listener          listener to be notified of events such as when
214      *                          estimation starts, ends or its progress significantly changes.
215      * @throws IllegalArgumentException if provided lists of points or quality
216      *                                  scores don't have the same size or their size is smaller than
217      *                                  MIN_NUMBER_OF_POINTS (i.e. 2 points).
218      */
219     public PROSACRadialDistortionRobustEstimator(final List<Point2D> distortedPoints,
220                                                  final List<Point2D> undistortedPoints,
221                                                  final double[] qualityScores,
222                                                  final RadialDistortionRobustEstimatorListener listener) {
223         this(distortedPoints, undistortedPoints, listener);
224         internalSetQualityScores(qualityScores);
225     }
226 
227     /**
228      * Constructor.
229      *
230      * @param distortedPoints   list of distorted points. Distorted points are
231      *                          obtained after radial distortion is applied to an undistorted point.
232      * @param undistortedPoints list of undistorted points.
233      * @param qualityScores     quality scores corresponding to each provided point.
234      * @param distortionCenter  radial distortion center. If null it is assumed
235      *                          to be the origin of coordinates, otherwise this is typically equal to
236      *                          the camera principal point.
237      * @throws IllegalArgumentException if provided lists of points or quality
238      *                                  scores don't have the same size or their size is smaller than
239      *                                  MIN_NUMBER_OF_POINTS (i.e. 2 points).
240      */
241     public PROSACRadialDistortionRobustEstimator(final List<Point2D> distortedPoints,
242                                                  final List<Point2D> undistortedPoints,
243                                                  final double[] qualityScores,
244                                                  final Point2D distortionCenter) {
245         this(distortedPoints, undistortedPoints, distortionCenter);
246         internalSetQualityScores(qualityScores);
247     }
248 
249     /**
250      * Constructor.
251      *
252      * @param distortedPoints   list of distorted points. Distorted points are
253      *                          obtained after radial distortion is applied to an undistorted point.
254      * @param undistortedPoints list of undistorted points.
255      * @param qualityScores     quality scores corresponding to each provided point.
256      * @param distortionCenter  radial distortion center. If null it is assumed
257      *                          to be the origin of coordinates, otherwise this is typically equal to
258      *                          the camera principal point.
259      * @param listener          listener to be notified of events such as when
260      *                          estimation starts, ends or its progress significantly changes.
261      * @throws IllegalArgumentException if provided lists of points or quality
262      *                                  scores don't have the same size or their size is smaller than
263      *                                  MIN_NUMBER_OF_POINTS (i.e. 2 points).
264      */
265     public PROSACRadialDistortionRobustEstimator(final List<Point2D> distortedPoints,
266                                                  final List<Point2D> undistortedPoints,
267                                                  final double[] qualityScores,
268                                                  final Point2D distortionCenter,
269                                                  final RadialDistortionRobustEstimatorListener listener) {
270         this(distortedPoints, undistortedPoints, distortionCenter, listener);
271         internalSetQualityScores(qualityScores);
272     }
273 
274     /**
275      * Returns threshold to determine whether points are inliers or not when
276      * testing possible estimation solutions.
277      * The threshold refers to the amount of error (i.e. Euclidean distance) a
278      * possible solution has on projected 2D points.
279      *
280      * @return threshold to determine whether points are inliers or not when
281      * testing possible estimation solutions.
282      */
283     public double getThreshold() {
284         return threshold;
285     }
286 
287     /**
288      * Sets threshold to determine whether points are inliers or not when
289      * testing possible estimation solutions.
290      * The threshold refers to the amount of error (i.e. Euclidean distance) a
291      * possible solution has on projected 2D points.
292      *
293      * @param threshold threshold to be set.
294      * @throws IllegalArgumentException if provided value is equal or less than
295      *                                  zero.
296      * @throws LockedException          if robust estimator is locked because an
297      *                                  estimation is already in progress.
298      */
299     public void setThreshold(final double threshold) throws LockedException {
300         if (isLocked()) {
301             throw new LockedException();
302         }
303         if (threshold <= MIN_THRESHOLD) {
304             throw new IllegalArgumentException();
305         }
306         this.threshold = threshold;
307     }
308 
309     /**
310      * Returns quality scores corresponding to each provided point.
311      * The larger the score value the better the quality of the sampled point.
312      *
313      * @return quality scores corresponding to each point.
314      */
315     @Override
316     public double[] getQualityScores() {
317         return qualityScores;
318     }
319 
320     /**
321      * Sets quality scores corresponding to each provided point.
322      * The larger the score value the better the quality of the sampled point.
323      *
324      * @param qualityScores quality scores corresponding to each point.
325      * @throws LockedException          if robust estimator is locked because an
326      *                                  estimation is already in progress.
327      * @throws IllegalArgumentException if provided quality scores length is
328      *                                  smaller than MINIMUM_SIZE (i.e. 2 samples).
329      */
330     @Override
331     public void setQualityScores(final double[] qualityScores) throws LockedException {
332         if (isLocked()) {
333             throw new LockedException();
334         }
335         internalSetQualityScores(qualityScores);
336     }
337 
338     /**
339      * Indicates if estimator is ready to start the radial distortion
340      * estimation.
341      * This is true when input data (i.e. 2D points and quality scores) are
342      * provided and a minimum of 2 points are available.
343      *
344      * @return true if estimator is ready, false otherwise.
345      */
346     @Override
347     public boolean isReady() {
348         return super.isReady() && qualityScores != null && qualityScores.length == distortedPoints.size();
349     }
350 
351     /**
352      * Estimates a radial distortion using a robust estimator and
353      * the best set of matched 2D points found using the robust estimator.
354      *
355      * @return a radial distortion.
356      * @throws LockedException          if robust estimator is locked because an
357      *                                  estimation is already in progress.
358      * @throws NotReadyException        if provided input data is not enough to start
359      *                                  the estimation.
360      * @throws RobustEstimatorException if estimation fails for any reason
361      *                                  (i.e. numerical instability, no solution available, etc).
362      */
363     @SuppressWarnings("DuplicatedCode")
364     @Override
365     public RadialDistortion estimate() throws LockedException, NotReadyException, RobustEstimatorException {
366         if (isLocked()) {
367             throw new LockedException();
368         }
369         if (!isReady()) {
370             throw new NotReadyException();
371         }
372 
373         final var innerEstimator = new PROSACRobustEstimator<RadialDistortion>(new PROSACRobustEstimatorListener<>() {
374 
375             // point to be reused when computing residuals
376             private final Point2D testPoint = Point2D.create(CoordinatesType.INHOMOGENEOUS_COORDINATES);
377 
378             // non-robust radial distortion estimator
379             private final LMSERadialDistortionEstimator radialDistortionEstimator = new LMSERadialDistortionEstimator();
380 
381             // subset of distorted (i.e. measured) points
382             private final List<Point2D> subsetDistorted = new ArrayList<>();
383 
384             // subset of undistorted (i.e. ideal) points
385             private final List<Point2D> subsetUndistorted = new ArrayList<>();
386 
387             @Override
388             public double getThreshold() {
389                 return threshold;
390             }
391 
392             @Override
393             public int getTotalSamples() {
394                 return distortedPoints.size();
395             }
396 
397             @Override
398             public int getSubsetSize() {
399                 return RadialDistortionRobustEstimator.MIN_NUMBER_OF_POINTS;
400             }
401 
402             @Override
403             public void estimatePreliminarSolutions(
404                     final int[] samplesIndices, final List<RadialDistortion> solutions) {
405                 subsetDistorted.clear();
406                 subsetDistorted.add(distortedPoints.get(samplesIndices[0]));
407                 subsetDistorted.add(distortedPoints.get(samplesIndices[1]));
408 
409                 subsetUndistorted.clear();
410                 subsetUndistorted.add(undistortedPoints.get(samplesIndices[0]));
411                 subsetUndistorted.add(undistortedPoints.get(samplesIndices[1]));
412 
413                 try {
414                     radialDistortionEstimator.setPoints(distortedPoints, undistortedPoints);
415                     radialDistortionEstimator.setPoints(subsetDistorted, subsetUndistorted);
416 
417                     final var distortion = radialDistortionEstimator.estimate();
418                     solutions.add(distortion);
419                 } catch (final Exception e) {
420                     // if anything fails, no solution is added
421                 }
422             }
423 
424             @Override
425             public double computeResidual(final RadialDistortion currentEstimation, final int i) {
426                 final var distortedPoint = distortedPoints.get(i);
427                 final var undistortedPoint = undistortedPoints.get(i);
428 
429                 currentEstimation.distort(undistortedPoint, testPoint);
430 
431                 return testPoint.distanceTo(distortedPoint);
432             }
433 
434             @Override
435             public boolean isReady() {
436                 return PROSACRadialDistortionRobustEstimator.this.isReady();
437             }
438 
439             @Override
440             public void onEstimateStart(final RobustEstimator<RadialDistortion> estimator) {
441                 try {
442                     radialDistortionEstimator.setLMSESolutionAllowed(false);
443                     radialDistortionEstimator.setIntrinsic(getIntrinsic());
444                 } catch (final Exception e) {
445                     Logger.getLogger(PROSACRadialDistortionRobustEstimator.class.getName()).log(Level.WARNING,
446                             "Could not set intrinsic parameters on radial distortion estimator", e);
447                 }
448 
449                 if (listener != null) {
450                     listener.onEstimateStart(PROSACRadialDistortionRobustEstimator.this);
451                 }
452             }
453 
454             @Override
455             public void onEstimateEnd(final RobustEstimator<RadialDistortion> estimator) {
456                 if (listener != null) {
457                     listener.onEstimateEnd(PROSACRadialDistortionRobustEstimator.this);
458                 }
459             }
460 
461             @Override
462             public void onEstimateNextIteration(
463                     final RobustEstimator<RadialDistortion> estimator, final int iteration) {
464                 if (listener != null) {
465                     listener.onEstimateNextIteration(PROSACRadialDistortionRobustEstimator.this, iteration);
466                 }
467             }
468 
469             @Override
470             public void onEstimateProgressChange(
471                     final RobustEstimator<RadialDistortion> estimator, final float progress) {
472                 if (listener != null) {
473                     listener.onEstimateProgressChange(PROSACRadialDistortionRobustEstimator.this, progress);
474                 }
475             }
476 
477             @Override
478             public double[] getQualityScores() {
479                 return qualityScores;
480             }
481         });
482 
483         try {
484             locked = true;
485             innerEstimator.setConfidence(confidence);
486             innerEstimator.setMaxIterations(maxIterations);
487             innerEstimator.setProgressDelta(progressDelta);
488             return innerEstimator.estimate();
489         } catch (final com.irurueta.numerical.LockedException e) {
490             throw new LockedException(e);
491         } catch (final com.irurueta.numerical.NotReadyException e) {
492             throw new NotReadyException(e);
493         } finally {
494             locked = false;
495         }
496     }
497 
498     /**
499      * Returns method being used for robust estimation
500      *
501      * @return method being used for robust estimation
502      */
503     @Override
504     public RobustEstimatorMethod getMethod() {
505         return RobustEstimatorMethod.PROSAC;
506     }
507 
508     /**
509      * Sets quality scores corresponding to each provided point.
510      * This method is used internally and does not check whether instance is
511      * locked or not
512      *
513      * @param qualityScores quality scores to be set
514      * @throws IllegalArgumentException if provided quality scores length is
515      *                                  smaller than MINIMUM_SIZE
516      */
517     private void internalSetQualityScores(final double[] qualityScores) {
518         if (qualityScores.length < MIN_NUMBER_OF_POINTS) {
519             throw new IllegalArgumentException();
520         }
521 
522         this.qualityScores = qualityScores;
523     }
524 }