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;
17  
18  import com.irurueta.algebra.AlgebraException;
19  import com.irurueta.ar.calibration.estimators.CameraPoseEstimator;
20  import com.irurueta.geometry.*;
21  import com.irurueta.geometry.estimators.LockedException;
22  import com.irurueta.geometry.estimators.NotReadyException;
23  import com.irurueta.geometry.estimators.PointCorrespondenceProjectiveTransformation2DRobustEstimator;
24  import com.irurueta.numerical.robust.RobustEstimatorException;
25  import com.irurueta.numerical.robust.RobustEstimatorMethod;
26  
27  import java.util.List;
28  
29  /**
30   * Contains data obtained from a single picture using the camera.
31   * Several samples can be used to calibrate the camera. The more samples are
32   * used, typically the better the results.
33   */
34  public class CameraCalibratorSample {
35      /**
36       * Minimum number of sampled markers that must be provided to estimate
37       * an homography.
38       */
39      public static final int MIN_REQUIRED_SAMPLED_MARKERS = 4;
40  
41      /**
42       * Pattern used for camera calibration. Each pattern contains a unique
43       * combination of 2D points that must be sampled using the camera to be
44       * calibrated.
45       */
46      private Pattern2D pattern;
47  
48      /**
49       * Contains the sampled markers taken from a single picture using the
50       * camera.
51       */
52      private List<Point2D> sampledMarkers;
53  
54      /**
55       * Contains the sampled markers of the pattern but accounting for the
56       * distortion effect introduced by the camera lens.
57       */
58      private List<Point2D> undistortedMarkers;
59  
60      /**
61       * Quality scores of sampled markers. These can be used during
62       * homography estimation if a robust estimation method such as PROSAC or
63       * PROMedS is used.
64       */
65      private double[] sampledMarkersQualityScores;
66  
67      /**
68       * 2D homography estimated from the sampled pattern points respect to
69       * the ideal ones using a single picture.
70       */
71      private Transformation2D homography;
72  
73      /**
74       * Estimated camera rotation. This contains the amount of rotation
75       * respect to the plane formed by the pattern markers. This is obtained
76       * once the IAC of the camera is estimated.
77       */
78      private Rotation3D rotation;
79  
80      /**
81       * Estimated camera center. This determines the amount of translation
82       * of the camera respect to the plane formed by the pattern markers. This
83       * is obtained once the IAC of the camera is estimated.
84       */
85      private Point3D cameraCenter;
86  
87      /**
88       * Estimated camera. Estimated pinhole camera taking into account
89       * estimated intrinsic parameters and amount of rotation and translation
90       * respect to the plane formed by the pattern markers, but without
91       * taking into account any radial distortion introduced by the lens.
92       */
93      private PinholeCamera camera;
94  
95      /**
96       * Constructor.
97       */
98      public CameraCalibratorSample() {
99      }
100 
101     /**
102      * Constructor.
103      *
104      * @param sampledMarkers sampled markers of the pattern taken from a
105      *                       single picture using the camera.
106      * @throws IllegalArgumentException if provided number of sampled
107      *                                  markers is smaller than the required minimum (4) to estimate an
108      *                                  homography.
109      */
110     public CameraCalibratorSample(final List<Point2D> sampledMarkers) {
111         setSampledMarkers(sampledMarkers);
112     }
113 
114     /**
115      * Constructor.
116      *
117      * @param sampledMarkers              sampled markers of the pattern taken from a
118      *                                    single picture using the camera.
119      * @param sampledMarkersQualityScores quality scores associated to
120      *                                    each provided point for each sampled marker. The higher the value
121      *                                    the better the quality assigned to that point.
122      * @throws IllegalArgumentException if size of sampled markers or
123      *                                  quality scores is smaller than the required minimum (4) to estimate
124      *                                  an homography, or if their sizes do not match.
125      */
126     public CameraCalibratorSample(final List<Point2D> sampledMarkers, final double[] sampledMarkersQualityScores) {
127         if (sampledMarkers.size() != sampledMarkersQualityScores.length) {
128             throw new IllegalArgumentException();
129         }
130 
131         setSampledMarkers(sampledMarkers);
132         setSampledMarkersQualityScores(sampledMarkersQualityScores);
133     }
134 
135     /**
136      * Constructor.
137      *
138      * @param pattern        2D pattern to use for calibration.
139      * @param sampledMarkers sampled markers of the pattern taken from a
140      *                       single picture using the camera.
141      * @throws IllegalArgumentException if provided number of sampled
142      *                                  markers is smaller than the required minimum (4) to estimate an
143      *                                  homography.
144      */
145     public CameraCalibratorSample(final Pattern2D pattern, final List<Point2D> sampledMarkers) {
146         this.pattern = pattern;
147         setSampledMarkers(sampledMarkers);
148     }
149 
150     /**
151      * Constructor.
152      *
153      * @param pattern                     2D pattern to use for calibration.
154      * @param sampledMarkers              sampled markers of the pattern taken from a
155      *                                    single picture using the camera.
156      * @param sampledMarkersQualityScores quality scores associated to
157      *                                    each provided point for each sampled marker. The higher the value
158      *                                    the better the quality assigned to that point.
159      * @throws IllegalArgumentException if size of sampled markers or
160      *                                  quality scores is smaller than the required minimum (4) to estimate
161      *                                  an homography, or if their sizes do not match.
162      */
163     public CameraCalibratorSample(final Pattern2D pattern, final List<Point2D> sampledMarkers,
164                                   final double[] sampledMarkersQualityScores) {
165         if (sampledMarkers.size() != sampledMarkersQualityScores.length) {
166             throw new IllegalArgumentException();
167         }
168 
169         this.pattern = pattern;
170         setSampledMarkers(sampledMarkers);
171         setSampledMarkersQualityScores(sampledMarkersQualityScores);
172     }
173 
174     /**
175      * Returns pattern used for camera calibration. Each pattern contain a
176      * unique combination of 2D points that must be sampled using the camera to
177      * be calibrated.
178      *
179      * @return pattern used for camera calibration.
180      */
181     public Pattern2D getPattern() {
182         return pattern;
183     }
184 
185     /**
186      * Sets pattern used for camera calibration. Each pattern contains a unique
187      * combination of 2D points that must be sampled using the camera to be
188      * calibrated.
189      *
190      * @param pattern pattern used for camera calibration.
191      */
192     public void setPattern(final Pattern2D pattern) {
193         this.pattern = pattern;
194     }
195 
196     /**
197      * Obtains sampled markers of the pattern taken from a single picture
198      * using the camera.
199      *
200      * @return sampled markers of the pattern.
201      */
202     public List<Point2D> getSampledMarkers() {
203         return sampledMarkers;
204     }
205 
206     /**
207      * Sets sampled markers of the pattern taken from a single picture
208      * using the camera.
209      *
210      * @param sampledMarkers sampled markers of the pattern.
211      * @throws IllegalArgumentException if provided number of sampled
212      *                                  markers is smaller than the required minimum (4) to estimate an
213      *                                  homography.
214      */
215     public final void setSampledMarkers(final List<Point2D> sampledMarkers) {
216         if (sampledMarkers.size() < MIN_REQUIRED_SAMPLED_MARKERS) {
217             throw new IllegalArgumentException();
218         }
219 
220         this.sampledMarkers = sampledMarkers;
221     }
222 
223     /**
224      * Returns quality scores of sampled markers. The higher the quality
225      * score value the better the quality assigned to the associated 2D
226      * point of a sampled marker.
227      * Quality scores are only used if a robust estimation method such as
228      * PROSAC or PROMedS is used for homography estimation
229      *
230      * @return quality scores of sampled markers.
231      */
232     public double[] getSampledMarkersQualityScores() {
233         return sampledMarkersQualityScores;
234     }
235 
236     /**
237      * Sets quality scores of sampled markers. The higher the quality score
238      * value the better the quality assigned to the associated 2D point of
239      * a sampled marker.
240      * Quality scores are only used if a robust estimation method such as
241      * PROSAC or PROMedS is used for homography estimation.
242      *
243      * @param sampledMarkersQualityScores quality scores of sampled markers.
244      * @throws IllegalArgumentException if provided number of quality scores
245      *                                  is smaller than the required minimum (4) to estimate an homography.
246      */
247     public final void setSampledMarkersQualityScores(final double[] sampledMarkersQualityScores) {
248         if (sampledMarkersQualityScores.length < MIN_REQUIRED_SAMPLED_MARKERS) {
249             throw new IllegalArgumentException();
250         }
251 
252         this.sampledMarkersQualityScores = sampledMarkersQualityScores;
253     }
254 
255     /**
256      * Computes quality scores of sampled markers by taking into account
257      * distance to radial distortion center.
258      * Typically, the farther a sample is to the radial distortion, the more
259      * likely it is to be distorted, and hence, the less reliable will be.
260      *
261      * @param sampledMarkers         sampled markers of the pattern.
262      * @param radialDistortionCenter location where radial distortion center
263      *                               is assumed to be. If null, it is assumed that center is at origin
264      *                               of coordinates (i.e. center of image if principal point is also at
265      *                               center of image).
266      * @return quality scores of sampled markers.
267      */
268     public static double[] computeSampledMarkersQualityScores(
269             final List<Point2D> sampledMarkers, final Point2D radialDistortionCenter) {
270 
271         final Point2D center = radialDistortionCenter != null
272                 ? radialDistortionCenter : new InhomogeneousPoint2D(0.0, 0.0);
273 
274         final var qualityScores = new double[sampledMarkers.size()];
275 
276         var counter = 0;
277         double distance;
278         double qualityScore;
279         for (final var sampledMarker : sampledMarkers) {
280             distance = sampledMarker.distanceTo(center);
281             qualityScore = 1.0 / (1.0 + distance);
282 
283             qualityScores[counter] = qualityScore;
284             counter++;
285         }
286 
287         return qualityScores;
288     }
289 
290     /**
291      * Computes quality scores of sampled markers by taking into account
292      * distance to radial distortion center, which is assumed to be at
293      * origin of coordinates (i.e. center of image if principal point is
294      * also at center of image).
295      *
296      * @param sampledMarkers sampled markers of the pattern.
297      * @return quality scores of sampled markers.
298      */
299     public static double[] computeSampledMarkersQualityScores(final List<Point2D> sampledMarkers) {
300         return computeSampledMarkersQualityScores(sampledMarkers, null);
301     }
302 
303     /**
304      * Contains the sampled markers of the pattern but accounting for the
305      * distortion effect introduced by the camera lens, so that coordinates
306      * are undistorted and follow a pure pinhole camera model.
307      * Coordinates of undistorted markers might change during camera
308      * calibration while the radial distortion parameters are refined.
309      *
310      * @return sampled markers of the pattern accounting for lens radial
311      * distortion.
312      */
313     protected List<Point2D> getUndistortedMarkers() {
314         return undistortedMarkers;
315     }
316 
317     /**
318      * Sets sampled markers of the pattern but accounting for the distortion
319      * effect introduced by the camera lens, so that coordinates are
320      * undistorted and follow a pure pinhole camera model.
321      * This method is for internal purposes only, and it is called while
322      * the camera radial distortion parameters are being computed.
323      *
324      * @param undistortedMarkers sampled markers of the pattern accounting
325      *                           for lens radial distortion.
326      */
327     protected void setUndistortedMarkers(final List<Point2D> undistortedMarkers) {
328         this.undistortedMarkers = undistortedMarkers;
329     }
330 
331     /**
332      * Returns 2D homography estimated from the sampled pattern points
333      * respect to the ideal ones using a single picture.
334      *
335      * @return homography of the sampled pattern points respect to the ideal
336      * ones.
337      */
338     public Transformation2D getHomography() {
339         return homography;
340     }
341 
342     /**
343      * Sets 2D homography estimated from the sampled pattern points
344      * respect to the ideal ones using a single picture.
345      * This method is for internal purposes only, and it is called while
346      * the IAC is being estimated.
347      *
348      * @param homography homography to be set.
349      */
350     protected void setHomography(final Transformation2D homography) {
351         this.homography = homography;
352     }
353 
354     /**
355      * Returns estimated camera rotation for this sample. This contains
356      * the amount of rotation respect to the plane formed by the pattern
357      * markers for the picture associated to this sample. This is obtained
358      * once the IAC of the camera is estimated.
359      *
360      * @return estimated camera rotation for this sample.
361      */
362     public Rotation3D getRotation() {
363         return rotation;
364     }
365 
366     /**
367      * Sets estimated camera rotation for this sample. This contains
368      * the amount of rotation respect to the plane formed by the pattern
369      * markers for the picture associated to this sample. This is obtained
370      * once the IAC of the camera is estimated.
371      * This method is for internal purposes only and might only be called
372      * if camera rotation is required during radial distortion estimation,
373      * or if rotation is requested for some other purpose.
374      *
375      * @param rotation camera rotation for this sample.
376      */
377     protected void setRotation(final Rotation3D rotation) {
378         this.rotation = rotation;
379     }
380 
381     /**
382      * Returns estimated camera center. This determines the amount of
383      * translation of the camera respect to the plane formed by the pattern
384      * markers. This is obtained once the IAC of the camera is estimated.
385      *
386      * @return estimated camera center.
387      */
388     public Point3D getCameraCenter() {
389         return cameraCenter;
390     }
391 
392     /**
393      * Sets estimated camera center. This determines the amount of translation
394      * of the camera respect to the plane formed by the pattern markers. This is
395      * obtained once the IAC of the camera is estimated.
396      *
397      * @param cameraCenter estimated camera center.
398      */
399     protected void setCameraCenter(final Point3D cameraCenter) {
400         this.cameraCenter = cameraCenter;
401     }
402 
403     /**
404      * Returns estimated camera. Estimated pinhole camera taking into
405      * account estimated intrinsic parameters and amount of rotation and
406      * translation respect to the plane formed by the pattern markers, but
407      * without taking into account any radial distortion introduced by the
408      * lens.
409      *
410      * @return estimated camera.
411      */
412     public PinholeCamera getCamera() {
413         return camera;
414     }
415 
416     /**
417      * Sets estimated camera taking into account estimated intrinsic
418      * parameters, amount of rotation and translation respect to the plane
419      * formed by the pattern markers, but without taking into account any
420      * radial distortion introduced by the lens.
421      * This method is for internal purposes only and might only be called if
422      * camera is required during radial distortion estimation, or if camera
423      * is requested for some other purpose.
424      *
425      * @param camera estimated camera.
426      */
427     protected void setCamera(final PinholeCamera camera) {
428         this.camera = camera;
429     }
430 
431     /**
432      * Estimates homography of sampled points respect to the ideal pattern
433      * points. Undistorted sampled taking into account radial distortion
434      * will be taken into account whenever possible.
435      *
436      * @param estimator           a robust estimator for the homography. It will only
437      *                            be used if more than 4 markers are provided.
438      * @param idealPatternMarkers ideal marker coordinates of the pattern.
439      *                            This contains measures expressed in meters so that camera can be
440      *                            calibrated against real measures.
441      * @return an homography.
442      * @throws LockedException           if robust estimator is locked because
443      *                                   computations are already in progress.
444      * @throws NotReadyException         if provided data to compute homography is
445      *                                   not enough, or it is invalid.
446      * @throws RobustEstimatorException  if robust estimation of homography
447      *                                   failed. This typically happens when not enough inliers are found or
448      *                                   configuration of points to estimate homography is degenerate.
449      * @throws CoincidentPointsException if configuration of points to
450      *                                   estimate homography is degenerate.
451      */
452     protected Transformation2D estimateHomography(
453             final PointCorrespondenceProjectiveTransformation2DRobustEstimator estimator,
454             final List<Point2D> idealPatternMarkers) throws LockedException, NotReadyException,
455             RobustEstimatorException, CoincidentPointsException {
456 
457         final var markers = undistortedMarkers != null ? undistortedMarkers : sampledMarkers;
458 
459         if (markers.size() < MIN_REQUIRED_SAMPLED_MARKERS) {
460             throw new NotReadyException();
461         }
462         if (markers.size() != idealPatternMarkers.size()) {
463             throw new NotReadyException();
464         }
465 
466         if (markers.size() == MIN_REQUIRED_SAMPLED_MARKERS) {
467             // use non-robust projective transformation estimation since it
468             // is faster and will produce the same result as a robust
469             // estimator
470             return new ProjectiveTransformation2D(idealPatternMarkers.get(0), idealPatternMarkers.get(1),
471                     idealPatternMarkers.get(2), idealPatternMarkers.get(3), markers.get(0), markers.get(1),
472                     markers.get(2), markers.get(3));
473         } else {
474             // use robust projective transformation estimation
475             estimator.setPoints(idealPatternMarkers, markers);
476             if (estimator.getMethod() == RobustEstimatorMethod.PROSAC
477                     || estimator.getMethod() == RobustEstimatorMethod.PROMEDS) {
478                 if (sampledMarkersQualityScores == null) {
479                     // attempt to estimate quality scores based on distance of
480                     // samples to origin of coordinates (i.e. image center)
481                     sampledMarkersQualityScores = computeSampledMarkersQualityScores(markers);
482                 }
483                 estimator.setQualityScores(sampledMarkersQualityScores);
484             }
485 
486             return estimator.estimate();
487         }
488     }
489 
490     /**
491      * Computes camera pose using estimated homography and provided intrinsic
492      * pinhole camera parameters that have been estimated so far.
493      *
494      * @param intrinsic intrinsic pinhole camera parameters.
495      * @throws CalibrationException if something fails.
496      */
497     protected void computeCameraPose(final PinholeCameraIntrinsicParameters intrinsic) throws CalibrationException {
498         try {
499             // reset previous values
500             rotation = null;
501             cameraCenter = null;
502             camera = null;
503 
504             final var estimator = new CameraPoseEstimator();
505             estimator.estimate(intrinsic, homography);
506 
507             rotation = estimator.getRotation();
508             cameraCenter = estimator.getCameraCenter();
509             camera = estimator.getCamera();
510 
511         } catch (final AlgebraException | GeometryException e) {
512             throw new CalibrationException(e);
513         }
514     }
515 }