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 }