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.Point2D;
24  import com.irurueta.geometry.estimators.LockedException;
25  import com.irurueta.geometry.estimators.NotReadyException;
26  import com.irurueta.numerical.robust.WeightSelection;
27  import com.irurueta.sorting.SortingException;
28  
29  import java.util.List;
30  
31  /**
32   * This class implements a radial distortion estimator using a weighted 
33   * algorithm and correspondences.
34   * Weights can be used so that correspondences assumed to have a better quality
35   * are considered to be more relevant.
36   */
37  public class WeightedRadialDistortionEstimator extends RadialDistortionEstimator {
38      /**
39       * Default number of points (i.e. correspondences) to be weighted and taken
40       * into account.
41       */
42      public static final int DEFAULT_MAX_POINTS = 50;
43      
44      /**
45       * Indicates if weights are sorted by default so that largest weighted
46       * correspondences are used first.
47       */
48      public static final boolean DEFAULT_SORT_WEIGHTS = true;
49      
50      /**
51       * Maximum number of points (i.e. correspondences) to be weighted and taken
52       * into account.
53       */
54      private int maxPoints;
55      
56      /**
57       * Indicates if weights are sorted by default so that largest weighted
58       * correspondences are used first.
59       */
60      private boolean sortWeights;
61      
62      /**
63       * Array containing weights for all point correspondences.
64       */
65      private double[] weights;
66      
67      /**
68       * Constructor.
69       */
70      public WeightedRadialDistortionEstimator() {
71          super();
72          maxPoints = DEFAULT_MAX_POINTS;
73          sortWeights = DEFAULT_SORT_WEIGHTS;
74      }
75      
76      /**
77       * Constructor with listener.
78       * @param listener listener to be notified of events such as when estimation
79       * starts, ends or estimation progress changes.
80       */
81      public WeightedRadialDistortionEstimator(final RadialDistortionEstimatorListener listener) {
82          super(listener);
83          maxPoints = DEFAULT_MAX_POINTS;
84          sortWeights = DEFAULT_SORT_WEIGHTS;
85      }
86      
87      /**
88       * Constructor.
89       * @param distortedPoints list of distorted points. Distorted points are
90       * obtained after radial distortion is applied to an undistorted point.
91       * @param undistortedPoints list of undistorted points.
92       * @param weights array containing a weight amount for each correspondence.
93       * The larger the value of a weight, the most significant the
94       * correspondence will be.
95       * @throws IllegalArgumentException if provided lists of points don't have 
96       * the same size or their size is smaller than 
97       * MIN_NUMBER_OF_POINT_CORRESPONDENCES.
98       */
99      public WeightedRadialDistortionEstimator(final List<Point2D> distortedPoints, final List<Point2D> undistortedPoints,
100                                              final double[] weights) {
101         super();
102         internalSetPointsAndWeights(distortedPoints, undistortedPoints, weights);
103         maxPoints = DEFAULT_MAX_POINTS;
104         sortWeights = DEFAULT_SORT_WEIGHTS;
105     }
106    
107     /**
108      * Constructor.
109      * @param distortedPoints list of distorted points. Distorted points are
110      * obtained after radial distortion is applied to an undistorted point.
111      * @param undistortedPoints list of undistorted points.
112      * @param weights array containing a weight amount for each correspondence.
113      * The larger the value of a weight, the most significant the
114      * correspondence will be.
115      * @param listener listener to be notified of events such as when estimation
116      * starts, ends or estimation progress changes.
117      * @throws IllegalArgumentException if provided lists of points don't have 
118      * the same size or their size is smaller than
119      * MIN_NUMBER_OF_POINT_CORRESPONDENCES.
120      */
121     
122     public WeightedRadialDistortionEstimator(final List<Point2D> distortedPoints, final List<Point2D> undistortedPoints,
123                                              final double[] weights, final RadialDistortionEstimatorListener listener) {
124         super(listener);
125         internalSetPointsAndWeights(distortedPoints, undistortedPoints, weights);
126         maxPoints = DEFAULT_MAX_POINTS;
127         sortWeights = DEFAULT_SORT_WEIGHTS;
128     }
129 
130     /**
131      * Constructor with distortion center.
132      * @param distortionCenter Distortion center. This is usually equal to the 
133      * principal point of an estimated camera. If not set it is assumed to be at
134      * the origin of coordinates (0,0).
135      */
136     public WeightedRadialDistortionEstimator(final Point2D distortionCenter) {
137         super(distortionCenter);
138         maxPoints = DEFAULT_MAX_POINTS;
139         sortWeights = DEFAULT_SORT_WEIGHTS;
140     }
141     
142     /**
143      * Constructor with listener and distortion center.
144      * @param distortionCenter Distortion center. This is usually equal to the 
145      * principal point of an estimated camera. If not set it is assumed to be at
146      * the origin of coordinates (0,0).
147      * @param listener listener to be notified of events such as when estimation
148      * starts, ends or estimation progress changes.
149      */
150     public WeightedRadialDistortionEstimator(final Point2D distortionCenter,
151                                              final RadialDistortionEstimatorListener listener) {
152         super(distortionCenter, listener);
153         maxPoints = DEFAULT_MAX_POINTS;
154         sortWeights = DEFAULT_SORT_WEIGHTS;
155     }
156     
157     /**
158      * Constructor with points and distortion center.
159      * @param distortedPoints list of distorted points. Distorted points are
160      * obtained after radial distortion is applied to an undistorted point.
161      * @param undistortedPoints list of undistorted points.
162      * @param weights array containing a weight amount for each correspondence.
163      * The larger the value of a weight, the most significant the
164      * correspondence will be.
165      * @param distortionCenter Distortion center. This is usually equal to the 
166      * principal point of an estimated camera. If not set it is assumed to be at
167      * the origin of coordinates (0,0).
168      * @throws IllegalArgumentException if provided lists of points don't have 
169      * the same size or their size is smaller than 
170      * MIN_NUMBER_OF_POINT_CORRESPONDENCES.
171      */
172     public WeightedRadialDistortionEstimator(final List<Point2D> distortedPoints, final List<Point2D> undistortedPoints,
173                                              final double[] weights, final Point2D distortionCenter) {
174         super(distortionCenter);
175         internalSetPointsAndWeights(distortedPoints, undistortedPoints, weights);
176         maxPoints = DEFAULT_MAX_POINTS;
177         sortWeights = DEFAULT_SORT_WEIGHTS;
178     }
179    
180     /**
181      * Constructor.
182      * @param distortedPoints list of distorted points. Distorted points are
183      * obtained after radial distortion is applied to an undistorted point.
184      * @param undistortedPoints list of undistorted points.
185      * @param weights array containing a weight amount for each correspondence.
186      * The larger the value of a weight, the most significant the
187      * correspondence will be.
188      * @param distortionCenter Distortion center. This is usually equal to the 
189      * principal point of an estimated camera. If not set it is assumed to be at
190      * the origin of coordinates (0,0).
191      * @param listener listener to be notified of events such as when estimation
192      * starts, ends or estimation progress changes.
193      * @throws IllegalArgumentException if provided lists of points don't have 
194      * the same size or their size is smaller than
195      * MIN_NUMBER_OF_POINT_CORRESPONDENCES.
196      */ 
197     public WeightedRadialDistortionEstimator(final List<Point2D> distortedPoints, final List<Point2D> undistortedPoints,
198                                              final double[] weights, final Point2D distortionCenter,
199                                              final RadialDistortionEstimatorListener listener) {
200         super(distortionCenter, listener);
201         internalSetPointsAndWeights(distortedPoints, undistortedPoints, weights);
202         maxPoints = DEFAULT_MAX_POINTS;
203         sortWeights = DEFAULT_SORT_WEIGHTS;
204     }
205     
206     /**
207      * Sets lists of corresponding distorted and undistorted points.
208      * @param distortedPoints list of distorted points. Distorted points are
209      * obtained after radial distortion is applied to an undistorted point.
210      * @param undistortedPoints list of undistorted points.
211      * @param weights array containing a weight amount for each correspondence.
212      * The larger the value of a weight, the most significant the
213      * correspondence will be.
214      * @throws LockedException if estimator is locked.
215      * @throws IllegalArgumentException if any of the lists or arrays are null 
216      * or if provided lists of points don't have the same size and enough points 
217      * or if the length of the weights array is not equal to the number of point 
218      * correspondences.
219      */
220     public void setPointsAndWeights(final List<Point2D> distortedPoints, final List<Point2D> undistortedPoints,
221                                     final double[] weights) throws LockedException {
222         if (isLocked()) {
223             throw new LockedException();
224         }
225         
226         internalSetPointsAndWeights(distortedPoints, undistortedPoints, weights);
227     }       
228     
229     /**
230      * Indicates if lists of corresponding distorted and undistorted points are
231      * valid.
232      * Lists are considered valid if they have the same number of points and
233      * both have more than the required minimum of correspondences (which is 2).
234      * @param distortedPoints list of distorted points. Distorted points are
235      * obtained after radial distortion is applied to an undistorted point.
236      * @param undistortedPoints list of undistorted points.
237      * @param weights array containing a weight amount for each correspondence.
238      * The larger the value of a weight, the most significant the
239      * correspondence will be.
240      * @return true if lists of points are valid, false otherwise.
241      */
242     public boolean areValidPointsAndWeights(final List<Point2D> distortedPoints, final List<Point2D> undistortedPoints,
243                                             final double[] weights){
244         if (distortedPoints == null || undistortedPoints == null || weights == null) {
245             return false;
246         }
247         
248         return distortedPoints.size() == undistortedPoints.size() && distortedPoints.size() == weights.length
249                 && distortedPoints.size() >= getMinNumberOfMatchedPoints();
250     }    
251     
252     /**
253      * Returns array containing a weight amount for each correspondence.
254      * The larger the value of a weight, the most significant the
255      * correspondence will be.
256      * @return array containing weights for each correspondence.
257      */
258     public double[] getWeights() {
259         return weights;
260     }
261     
262     /**
263      * Returns boolean indicating whether weights have been provided and are
264      * available for retrieval.
265      * @return true if weights are available, false otherwise.
266      */
267     public boolean areWeightsAvailable() {
268         return weights != null;
269     }    
270     
271     /**
272      * Returns maximum number of points (i.e. correspondences) to be weighted 
273      * and taken into account.
274      * @return maximum number of points to be weighted.
275      */
276     public int getMaxPoints() {
277         return maxPoints;
278     }
279     
280     /**
281      * Sets maximum number of points (i.e. correspondences) to be weighted and
282      * taken into account.
283      * @param maxPoints maximum number of points to be weighted.
284      * @throws IllegalArgumentException if provided value is less than the 
285      * minimum allowed number of point correspondences.
286      * @throws LockedException if this instance is locked.
287      */
288     public void setMaxPoints(final int maxPoints) throws LockedException {
289         if (isLocked()) {
290             throw new LockedException();
291         }
292         if (maxPoints < getMinNumberOfMatchedPoints()) {
293             throw new IllegalArgumentException();
294         }
295         
296         this.maxPoints = maxPoints;
297     }
298     
299     /**
300      * Indicates if weights are sorted by so that largest weighted
301      * correspondences are used first.
302      * @return true if weights are sorted, false otherwise.
303      */
304     public boolean isSortWeightsEnabled() {
305         return sortWeights;
306     }
307     
308     /**
309      * Specifies whether weights are sorted by so that largest weighted
310      * correspondences are used first.
311      * @param sortWeights true if weights are sorted, false otherwise.
312      * @throws LockedException if this instance is locked.
313      */
314     public void setSortWeightsEnabled(final boolean sortWeights) throws LockedException {
315         if (isLocked()) {
316             throw new LockedException();
317         }
318         
319         this.sortWeights = sortWeights;
320     }
321     
322     /**
323      * Indicates if this estimator is ready to start the estimation.
324      * Estimator will be ready once both lists and weights are available.
325      * @return true if estimator is ready, false otherwise.
326      */
327     @Override
328     public boolean isReady() {
329         return arePointsAvailable() && areWeightsAvailable();
330     }    
331     
332     /**
333      * Estimates a radial distortion.
334      * @return estimated radial distortion.
335      * @throws LockedException if estimator is locked.
336      * @throws NotReadyException if input has not yet been provided.
337      * @throws RadialDistortionEstimatorException if an error occurs during
338      * estimation, usually because input data is not valid.
339      */    
340     @SuppressWarnings("DuplicatedCode")
341     @Override
342     public RadialDistortion estimate() throws LockedException, NotReadyException, RadialDistortionEstimatorException {
343         if (isLocked()) {
344             throw new LockedException();
345         }
346         if (!isReady()) {
347             throw new NotReadyException();
348         }
349         
350         try {
351             locked = true;
352             if (listener != null) {
353                 listener.onEstimateStart(this);
354             }
355             
356             final var selection = WeightSelection.selectWeights(weights, sortWeights, maxPoints);
357             final var selected = selection.getSelected();
358             
359             final var nPoints = distortedPoints.size();
360             
361             final var aMatrix = new Matrix(2 * nPoints, numKParams);
362             final var b = new double[2 * nPoints];
363             
364             final var iteratorDistorted = distortedPoints.iterator();
365             final var iteratorUndistorted = undistortedPoints.iterator();
366             
367             Point2D distorted;
368             Point2D undistorted;
369             var index = 0;
370             var counter = 0;
371             
372             // undistorted normalized homogeneous coordinates
373             double uNormHomX;
374             double uNormHomY;
375             double uNormHomW;
376             // undistorted normalized inhomogeneous coordinates
377             double uNormInhomX;
378             double uNormInhomY;
379             // undistorted denormalized homogeneous coordinates
380             double uDenormHomX;
381             double uDenormHomY;
382             double uDenormHomW;
383             // undistorted denormalized inhomogeneous coordinates
384             double uDenormInhomX;
385             double uDenormInhomY;
386             // distorted inhomogeneous coordinates
387             double dInhomX;
388             double dInhomY;
389             double rowNormX;
390             double rowNormY;
391             
392             // radial distortion center
393             var centerX = 0.0;
394             var centerY = 0.0;
395             if (distortionCenter != null) {
396                 centerX = distortionCenter.getInhomX();
397                 centerY = distortionCenter.getInhomY();
398             }
399             
400             // radial distance of undistorted normalized (calibration independent)
401             // coordinates
402             double r2; 
403             double a;
404             double value;
405             double weight;
406 
407             while (iteratorDistorted.hasNext() && iteratorUndistorted.hasNext()) {
408                 distorted = iteratorDistorted.next();
409                 undistorted = iteratorUndistorted.next();                
410                 
411                 if (selected[index]) {
412                     undistorted.normalize();                    
413                     
414                     weight = weights[index];
415                     
416                     uDenormHomX = undistorted.getHomX();
417                     uDenormHomY = undistorted.getHomY();
418                     uDenormHomW = undistorted.getHomW();
419                 
420                     uDenormInhomX = uDenormHomX / uDenormHomW;
421                     uDenormInhomY = uDenormHomY / uDenormHomW;
422                 
423                     // multiply intrinsic parameters by undistorted point
424                     uNormHomX = kInv.getElementAt(0, 0) * uDenormHomX
425                             + kInv.getElementAt(0, 1) * uDenormHomY
426                             + kInv.getElementAt(0, 2) * uDenormHomW;
427                     uNormHomY = kInv.getElementAt(1, 0) * uDenormHomX
428                             + kInv.getElementAt(1, 1) * uDenormHomY
429                             + kInv.getElementAt(1, 2) * uDenormHomW;
430                     uNormHomW = kInv.getElementAt(2, 0) * uDenormHomX
431                             + kInv.getElementAt(2, 1) * uDenormHomY
432                             + kInv.getElementAt(2, 2) * uDenormHomW;
433                 
434                     uNormInhomX = uNormHomX / uNormHomW;
435                     uNormInhomY = uNormHomY / uNormHomW;
436                 
437                     r2 = uNormInhomX * uNormInhomX + uNormInhomY * uNormInhomY;
438                 
439                     dInhomX = distorted.getInhomX();
440                     dInhomY = distorted.getInhomY();
441                     
442                     a = 1.0;
443                     rowNormX = rowNormY = 0.0;
444                     for (var i = 0; i < numKParams; i++) {
445                         a *= r2;
446                         
447                         // x and y coordinates generate linear dependent equations, for
448                         // that reason we need more than one point
449                     
450                         // x coordinates
451                         value = (uDenormInhomX - centerX) * a * weight;
452                         aMatrix.setElementAt(counter * 2, i, value);
453                     
454                         rowNormX += Math.pow(value, 2.0);
455                     
456                         // y coordinates
457                         value = (uDenormInhomY - centerY) * a * weight;
458                         aMatrix.setElementAt(counter * 2 + 1, i, value);
459                     
460                         rowNormY += Math.pow(value, 2.0);                        
461                     }
462                     
463                     // x coordinates
464                     value = (dInhomX - uDenormInhomX) * weight;
465                     b[counter * 2] = value;
466                 
467                     rowNormX += Math.pow(value, 2.0);
468                 
469                     // y coordinates
470                     value = (dInhomY - uDenormInhomY) * weight;
471                     b[counter * 2 + 1] = value;
472                 
473                     rowNormY += Math.pow(value, 2.0);
474                     
475                     // normalize rows to increase accuracy
476                     for (var i = 0; i < numKParams; i++) {
477                         aMatrix.setElementAt(counter * 2, i,
478                                 aMatrix.getElementAt(counter * 2, i) / rowNormX);
479                         aMatrix.setElementAt(counter * 2 + 1, i,
480                                 aMatrix.getElementAt(counter * 2 + 1, i) / rowNormY);
481                     }
482                 
483                     b[counter * 2] /= rowNormX;
484                     b[counter * 2 +1] /= rowNormY;
485                                 
486                     counter++;                                                            
487                 }
488 
489                 index++;
490             }
491             
492             final var params = Utils.solve(aMatrix, b);
493             
494             final var distortion = new RadialDistortion(params, distortionCenter, horizontalFocalLength,
495                     verticalFocalLength, skew);
496             
497             if (listener != null) {
498                 listener.onEstimateEnd(this);
499             }
500             
501             return distortion;
502         } catch (final AlgebraException | SortingException | RadialDistortionException e) {
503             throw new RadialDistortionEstimatorException(e);
504         } finally {
505             locked = false;
506         }
507     }    
508     
509     /**
510      * Returns type of radial distortion estimator.
511      * @return type of radial distortion estimator.
512      */    
513     @Override
514     public RadialDistortionEstimatorType getType() {
515         return RadialDistortionEstimatorType.WEIGHTED_RADIAL_DISTORTION_ESTIMATOR;
516     }  
517             
518     /**
519      * Internal method to set list of corresponding points (it does not check
520      * if estimator is locked).
521      * @param distortedPoints list of distorted points. Distorted points are
522      * obtained after radial distortion is applied to an undistorted point.
523      * @param undistortedPoints list of undistorted points.
524      * @param weights array containing a weight amount for each correspondence.
525      * The larger the value of a weight, the most significant the
526      * correspondence will be.
527      * @throws IllegalArgumentException if any of the lists or arrays are null 
528      * or if provided lists of points don't have the same size and enough points 
529      * or if the length of the weights array is not equal to the number of point 
530      * correspondences.
531      */
532     private void internalSetPointsAndWeights(final List<Point2D> distortedPoints, final List<Point2D> undistortedPoints,
533                                              final double[] weights) {
534         
535         if (distortedPoints == null || undistortedPoints == null || weights == null) {
536             throw new IllegalArgumentException();
537         }
538         
539         if (!areValidPointsAndWeights(distortedPoints, undistortedPoints, weights)) {
540             throw new IllegalArgumentException();
541         }
542         
543         this.distortedPoints = distortedPoints;
544         this.undistortedPoints = undistortedPoints;
545         this.weights = weights;
546     }        
547 }