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.LMedSRobustEstimator; 24 import com.irurueta.numerical.robust.LMedSRobustEstimatorListener; 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 collection of 2D points using 36 * LMedS algorithm 37 */ 38 public class LMedSRadialDistortionRobustEstimator extends RadialDistortionRobustEstimator { 39 40 /** 41 * Default value to be used for stop threshold. Stop threshold can be used 42 * to keep the algorithm iterating in case that best estimated threshold 43 * using median of residuals is not small enough. Once a solution is found 44 * that generates a threshold below this value, the algorithm will stop. 45 * The stop threshold can be used to prevent the LMedS algorithm iterating 46 * too many times in cases where samples have a very similar accuracy. 47 * For instance, in cases where proportion of outliers is very small (close 48 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would 49 * iterate for a long time trying to find the best solution when indeed 50 * there is no need to do that if a reasonable threshold has already been 51 * reached. 52 * Because of this behaviour the stop threshold can be set to a value much 53 * lower than the one typically used in RANSAC, and yet the algorithm could 54 * still produce even smaller thresholds in estimated results. 55 */ 56 public static final double DEFAULT_STOP_THRESHOLD = 1e-3; 57 58 /** 59 * Minimum allowed stop threshold value. 60 */ 61 public static final double MIN_STOP_THRESHOLD = 0.0; 62 63 /** 64 * Threshold to be used to keep the algorithm iterating in case that best 65 * estimated threshold using median of residuals is not small enough. Once 66 * a solution is found that generates a threshold below this value, the 67 * algorithm will stop. 68 * The stop threshold can be used to prevent the LMedS algorithm iterating 69 * too many times in cases where samples have a very similar accuracy. 70 * For instance, in cases where proportion of outliers is very small (close 71 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would 72 * iterate for a long time trying to find the best solution when indeed 73 * there is no need to do that if a reasonable threshold has already been 74 * reached. 75 * Because of this behaviour the stop threshold can be set to a value much 76 * lower than the one typically used in RANSAC, and yet the algorithm could 77 * still produce even smaller thresholds in estimated results. 78 */ 79 private double stopThreshold; 80 81 /** 82 * Constructor. 83 */ 84 public LMedSRadialDistortionRobustEstimator() { 85 super(); 86 stopThreshold = DEFAULT_STOP_THRESHOLD; 87 } 88 89 /** 90 * Constructor. 91 * 92 * @param listener listener to be notified of events such as when 93 * estimation starts, ends or its progress significantly changes. 94 */ 95 public LMedSRadialDistortionRobustEstimator(final RadialDistortionRobustEstimatorListener listener) { 96 super(listener); 97 stopThreshold = DEFAULT_STOP_THRESHOLD; 98 } 99 100 /** 101 * Constructor. 102 * 103 * @param distortedPoints list of distorted points. Distorted points are 104 * obtained after radial distortion is applied to an undistorted point. 105 * @param undistortedPoints list of undistorted points. 106 * @throws IllegalArgumentException if provided lists of points don't have 107 * the same size or their size is smaller than MIN_NUMBER_OF_POINTS. 108 */ 109 public LMedSRadialDistortionRobustEstimator( 110 final List<Point2D> distortedPoints, final List<Point2D> undistortedPoints) { 111 super(distortedPoints, undistortedPoints); 112 stopThreshold = DEFAULT_STOP_THRESHOLD; 113 } 114 115 /** 116 * Constructor. 117 * 118 * @param distortedPoints list of distorted points. Distorted points are 119 * obtained after radial distortion is applied to an undistorted point. 120 * @param undistortedPoints list of undistorted points. 121 * @param listener listener to be notified of events such as when 122 * estimation starts, ends or its progress significantly changes. 123 * @throws IllegalArgumentException if provided lists of points don't have 124 * the same size or their size is smaller than MIN_NUMBER_OF_POINTS. 125 */ 126 public LMedSRadialDistortionRobustEstimator(final List<Point2D> distortedPoints, 127 final List<Point2D> undistortedPoints, 128 final RadialDistortionRobustEstimatorListener listener) { 129 super(distortedPoints, undistortedPoints, listener); 130 stopThreshold = DEFAULT_STOP_THRESHOLD; 131 } 132 133 /** 134 * Constructor. 135 * 136 * @param distortedPoints list of distorted points. Distorted points are 137 * obtained after radial distortion is applied to an undistorted point. 138 * @param undistortedPoints list of undistorted points. 139 * @param distortionCenter radial distortion center. If null it is assumed 140 * to be the origin of coordinates, otherwise this is typically equal to 141 * the camera principal point. 142 * @throws IllegalArgumentException if provided lists of points don't have 143 * the same size or their size is smaller than MIN_NUMBER_OF_POINTS. 144 */ 145 public LMedSRadialDistortionRobustEstimator(final List<Point2D> distortedPoints, 146 final List<Point2D> undistortedPoints, 147 final Point2D distortionCenter) { 148 super(distortedPoints, undistortedPoints, distortionCenter); 149 stopThreshold = DEFAULT_STOP_THRESHOLD; 150 } 151 152 /** 153 * Constructor. 154 * 155 * @param distortedPoints list of distorted points. Distorted points are 156 * obtained after radial distortion is applied to an undistorted point. 157 * @param undistortedPoints list of undistorted points. 158 * @param distortionCenter radial distortion center. If null it is assumed 159 * to be the origin of coordinates, otherwise this is typically equal to 160 * the camera principal point. 161 * @param listener listener to be notified of events such as when 162 * estimation starts, ends or its progress significantly changes. 163 * @throws IllegalArgumentException if provided lists of points don't have 164 * the same size or their size is smaller than MIN_NUMBER_OF_POINTS. 165 */ 166 public LMedSRadialDistortionRobustEstimator(final List<Point2D> distortedPoints, 167 final List<Point2D> undistortedPoints, 168 final Point2D distortionCenter, 169 final RadialDistortionRobustEstimatorListener listener) { 170 super(distortedPoints, undistortedPoints, distortionCenter, listener); 171 stopThreshold = DEFAULT_STOP_THRESHOLD; 172 } 173 174 /** 175 * Returns threshold to be used to keep the algorithm iterating in case that 176 * best estimated threshold using median of residuals is not small enough. 177 * Once a solution is found that generates a threshold below this value, the 178 * algorithm will stop. 179 * The stop threshold can be used to prevent the LMedS algorithm iterating 180 * too many times in cases where samples have a very similar accuracy. 181 * For instance, in cases where proportion of outliers is very small (close 182 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would 183 * iterate for a long time trying to find the best solution when indeed 184 * there is no need to do that if a reasonable threshold has already been 185 * reached. 186 * Because of this behaviour the stop threshold can be set to a value much 187 * lower than the one typically used in RANSAC, and yet the algorithm could 188 * still produce even smaller thresholds in estimated results. 189 * 190 * @return stop threshold to stop the algorithm prematurely when a certain 191 * accuracy has been reached. 192 */ 193 public double getStopThreshold() { 194 return stopThreshold; 195 } 196 197 /** 198 * Sets threshold to be used to keep the algorithm iterating in case that 199 * best estimated threshold using median of residuals is not small enough. 200 * Once a solution is found that generates a threshold below this value, the 201 * algorithm will stop. 202 * The stop threshold can be used to prevent the LMedS algorithm iterating 203 * too many times in cases where samples have a very similar accuracy. 204 * For instance, in cases where proportion of outliers is very small (close 205 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would 206 * iterate for a long time trying to find the best solution when indeed 207 * there is no need to do that if a reasonable threshold has already been 208 * reached. 209 * Because of this behaviour the stop threshold can be set to a value much 210 * lower than the one typically used in RANSAC, and yet the algorithm could 211 * still produce even smaller thresholds in estimated results. 212 * 213 * @param stopThreshold stop threshold to stop the algorithm prematurely 214 * when a certain accuracy has been reached. 215 * @throws IllegalArgumentException if provided value is zero or negative. 216 * @throws LockedException if robust estimator is locked because an 217 * estimation is already in progress. 218 */ 219 public void setStopThreshold(final double stopThreshold) throws LockedException { 220 if (isLocked()) { 221 throw new LockedException(); 222 } 223 if (stopThreshold <= MIN_STOP_THRESHOLD) { 224 throw new IllegalArgumentException(); 225 } 226 227 this.stopThreshold = stopThreshold; 228 } 229 230 /** 231 * Estimates a radial distortion using a robust estimator and 232 * the best set of matched 2D points found using the robust estimator. 233 * 234 * @return a radial distortion. 235 * @throws LockedException if robust estimator is locked because an 236 * estimation is already in progress. 237 * @throws NotReadyException if provided input data is not enough to start 238 * the estimation. 239 * @throws RobustEstimatorException if estimation fails for any reason 240 * (i.e. numerical instability, no solution available, etc). 241 */ 242 @SuppressWarnings("DuplicatedCode") 243 @Override 244 public RadialDistortion estimate() throws LockedException, NotReadyException, RobustEstimatorException { 245 if (isLocked()) { 246 throw new LockedException(); 247 } 248 if (!isReady()) { 249 throw new NotReadyException(); 250 } 251 252 final var innerEstimator = new LMedSRobustEstimator<RadialDistortion>(new LMedSRobustEstimatorListener<>() { 253 254 // point to be reused when computing residuals 255 private final Point2D testPoint = Point2D.create(CoordinatesType.INHOMOGENEOUS_COORDINATES); 256 257 // non-robust radial distortion estimator 258 private final LMSERadialDistortionEstimator radialDistortionEstimator = new LMSERadialDistortionEstimator(); 259 260 // subset of distorted (i.e. measured) points 261 private final List<Point2D> subsetDistorted = new ArrayList<>(); 262 263 // subset of undistorted (i.e. ideal) points 264 private final List<Point2D> subsetUndistorted = new ArrayList<>(); 265 266 @Override 267 public int getTotalSamples() { 268 return distortedPoints.size(); 269 } 270 271 @Override 272 public int getSubsetSize() { 273 return RadialDistortionRobustEstimator.MIN_NUMBER_OF_POINTS; 274 } 275 276 @Override 277 public void estimatePreliminarSolutions( 278 final int[] samplesIndices, final List<RadialDistortion> solutions) { 279 subsetDistorted.clear(); 280 subsetDistorted.add(distortedPoints.get(samplesIndices[0])); 281 subsetDistorted.add(distortedPoints.get(samplesIndices[1])); 282 283 subsetUndistorted.clear(); 284 subsetUndistorted.add(undistortedPoints.get(samplesIndices[0])); 285 subsetUndistorted.add(undistortedPoints.get(samplesIndices[1])); 286 287 try { 288 radialDistortionEstimator.setPoints(distortedPoints, undistortedPoints); 289 radialDistortionEstimator.setPoints(subsetDistorted, subsetUndistorted); 290 291 final var distortion = radialDistortionEstimator.estimate(); 292 solutions.add(distortion); 293 } catch (final Exception e) { 294 // if anything fails, no solution is added 295 } 296 } 297 298 @Override 299 public double computeResidual(final RadialDistortion currentEstimation, final int i) { 300 final var distortedPoint = distortedPoints.get(i); 301 final var undistortedPoint = undistortedPoints.get(i); 302 303 currentEstimation.distort(undistortedPoint, testPoint); 304 305 return testPoint.distanceTo(distortedPoint); 306 } 307 308 @Override 309 public boolean isReady() { 310 return LMedSRadialDistortionRobustEstimator.this.isReady(); 311 } 312 313 @Override 314 public void onEstimateStart(final RobustEstimator<RadialDistortion> estimator) { 315 try { 316 radialDistortionEstimator.setLMSESolutionAllowed(false); 317 radialDistortionEstimator.setIntrinsic(getIntrinsic()); 318 } catch (final Exception e) { 319 Logger.getLogger(LMedSRadialDistortionRobustEstimator.class.getName()).log(Level.WARNING, 320 "Could not set intrinsic parameters on radial distortion estimator", e); 321 } 322 323 if (listener != null) { 324 listener.onEstimateStart(LMedSRadialDistortionRobustEstimator.this); 325 } 326 } 327 328 @Override 329 public void onEstimateEnd(final RobustEstimator<RadialDistortion> estimator) { 330 if (listener != null) { 331 listener.onEstimateEnd(LMedSRadialDistortionRobustEstimator.this); 332 } 333 } 334 335 @Override 336 public void onEstimateNextIteration( 337 final RobustEstimator<RadialDistortion> estimator, final int iteration) { 338 if (listener != null) { 339 listener.onEstimateNextIteration(LMedSRadialDistortionRobustEstimator.this, iteration); 340 } 341 } 342 343 @Override 344 public void onEstimateProgressChange( 345 final RobustEstimator<RadialDistortion> estimator, final float progress) { 346 if (listener != null) { 347 listener.onEstimateProgressChange(LMedSRadialDistortionRobustEstimator.this, progress); 348 } 349 } 350 }); 351 352 try { 353 locked = true; 354 innerEstimator.setConfidence(confidence); 355 innerEstimator.setMaxIterations(maxIterations); 356 innerEstimator.setProgressDelta(progressDelta); 357 innerEstimator.setStopThreshold(stopThreshold); 358 return innerEstimator.estimate(); 359 } catch (final com.irurueta.numerical.LockedException e) { 360 throw new LockedException(e); 361 } catch (final com.irurueta.numerical.NotReadyException e) { 362 throw new NotReadyException(e); 363 } finally { 364 locked = false; 365 } 366 } 367 368 /** 369 * Returns method being used for robust estimation 370 * 371 * @return method being used for robust estimation 372 */ 373 @Override 374 public RobustEstimatorMethod getMethod() { 375 return RobustEstimatorMethod.LMEDS; 376 } 377 }