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 }