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.epipolar.estimators; 17 18 import com.irurueta.ar.epipolar.FundamentalMatrix; 19 import com.irurueta.geometry.Point2D; 20 import com.irurueta.geometry.estimators.LockedException; 21 import com.irurueta.geometry.estimators.NotReadyException; 22 import com.irurueta.numerical.robust.LMedSRobustEstimator; 23 import com.irurueta.numerical.robust.LMedSRobustEstimatorListener; 24 import com.irurueta.numerical.robust.RobustEstimator; 25 import com.irurueta.numerical.robust.RobustEstimatorException; 26 import com.irurueta.numerical.robust.RobustEstimatorMethod; 27 28 import java.util.ArrayList; 29 import java.util.List; 30 31 /** 32 * Finds the best fundamental matrix for provided collections of matched 2D 33 * points using LMedS algorithm. 34 */ 35 public class LMedSFundamentalMatrixRobustEstimator extends FundamentalMatrixRobustEstimator { 36 37 /** 38 * Default value to be used for stop threshold. Stop threshold can be used 39 * to keep the algorithm iterating in case that best estimated threshold 40 * using median of residuals is not small enough. Once a solution is found 41 * that generates a threshold below this value, the algorithm will stop. 42 * The stop threshold can be used to prevent the LMedS algorithm iterating 43 * too many times in cases where samples have a very similar accuracy. 44 * For instance, in cases where proportion of outliers is very small (close 45 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would 46 * iterate for a long time trying to find the best solution when indeed 47 * there is no need to do that if a reasonable threshold has already been 48 * reached. 49 * Because of this behaviour the stop threshold can be set to a value much 50 * lower than the one typically used in RANSAC, and yet the algorithm could 51 * still produce even smaller thresholds in estimated results. 52 */ 53 public static final double DEFAULT_STOP_THRESHOLD = 1e-3; 54 55 /** 56 * Minimum allowed stop threshold value. 57 */ 58 public static final double MIN_STOP_THRESHOLD = 0.0; 59 60 /** 61 * Threshold to be used to keep the algorithm iterating in case that best 62 * estimated threshold using median of residuals is not small enough. Once 63 * a solution is found that generates a threshold below this value, the 64 * algorithm will stop. 65 * The stop threshold can be used to prevent the LMedS algorithm iterating 66 * too many times in cases where samples have a very similar accuracy. 67 * For instance, in cases where proportion of outliers is very small (close 68 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would 69 * iterate for a long time trying to find the best solution when indeed 70 * there is no need to do that if a reasonable threshold has already been 71 * reached. 72 * Because of this behaviour the stop threshold can be set to a value much 73 * lower than the one typically used in RANSAC, and yet the algorithm could 74 * still produce even smaller thresholds in estimated results. 75 */ 76 private double stopThreshold; 77 78 /** 79 * Constructor. 80 * 81 * @param fundMatrixEstimatorMethod method for non-robust fundamental matrix 82 * estimator. 83 */ 84 public LMedSFundamentalMatrixRobustEstimator(final FundamentalMatrixEstimatorMethod fundMatrixEstimatorMethod) { 85 super(fundMatrixEstimatorMethod); 86 stopThreshold = DEFAULT_STOP_THRESHOLD; 87 } 88 89 /** 90 * Constructor. 91 * 92 * @param fundMatrixEstimatorMethod method for non-robust fundamental matrix 93 * estimator. 94 * @param listener listener to be notified of events such as when estimation 95 * starts, ends or its progress significantly changes. 96 */ 97 public LMedSFundamentalMatrixRobustEstimator( 98 final FundamentalMatrixEstimatorMethod fundMatrixEstimatorMethod, 99 final FundamentalMatrixRobustEstimatorListener listener) { 100 super(fundMatrixEstimatorMethod, listener); 101 stopThreshold = DEFAULT_STOP_THRESHOLD; 102 } 103 104 /** 105 * Constructor. 106 * 107 * @param fundMatrixEstimatorMethod method for non-robust fundamental matrix 108 * estimator. 109 * @param leftPoints 2D points on left view. 110 * @param rightPoints 2D points on right view. 111 * @throws IllegalArgumentException if provided list of points do not have 112 * the same length or their length is less than 7 points. 113 */ 114 public LMedSFundamentalMatrixRobustEstimator( 115 final FundamentalMatrixEstimatorMethod fundMatrixEstimatorMethod, 116 final List<Point2D> leftPoints, final List<Point2D> rightPoints) { 117 super(fundMatrixEstimatorMethod, leftPoints, rightPoints); 118 stopThreshold = DEFAULT_STOP_THRESHOLD; 119 } 120 121 /** 122 * Constructor. 123 * 124 * @param fundMatrixEstimatorMethod method for non-robust fundamental matrix 125 * estimator. 126 * @param leftPoints 2D points on left view. 127 * @param rightPoints 2D points on right view. 128 * @param listener listener to be notified of events such as when estimation 129 * starts, ends or its progress significantly changes. 130 * @throws IllegalArgumentException if provided list of points do not have 131 * the same length or their length is less than 7 points. 132 */ 133 public LMedSFundamentalMatrixRobustEstimator( 134 final FundamentalMatrixEstimatorMethod fundMatrixEstimatorMethod, 135 final List<Point2D> leftPoints, final List<Point2D> rightPoints, 136 final FundamentalMatrixRobustEstimatorListener listener) { 137 super(fundMatrixEstimatorMethod, leftPoints, rightPoints, listener); 138 stopThreshold = DEFAULT_STOP_THRESHOLD; 139 } 140 141 /** 142 * Constructor. 143 */ 144 public LMedSFundamentalMatrixRobustEstimator() { 145 this(DEFAULT_FUNDAMENTAL_MATRIX_ESTIMATOR_METHOD); 146 } 147 148 /** 149 * Constructor. 150 * 151 * @param listener listener to be notified of events such as when estimation 152 * starts, ends or its progress significantly changes. 153 */ 154 public LMedSFundamentalMatrixRobustEstimator(final FundamentalMatrixRobustEstimatorListener listener) { 155 this(DEFAULT_FUNDAMENTAL_MATRIX_ESTIMATOR_METHOD, listener); 156 } 157 158 /** 159 * Constructor. 160 * 161 * @param leftPoints 2D points on left view. 162 * @param rightPoints 2D points on right view. 163 * @throws IllegalArgumentException if provided list of points do not have 164 * the same length or their length is less than 7 points. 165 */ 166 public LMedSFundamentalMatrixRobustEstimator(final List<Point2D> leftPoints, final List<Point2D> rightPoints) { 167 this(DEFAULT_FUNDAMENTAL_MATRIX_ESTIMATOR_METHOD, leftPoints, rightPoints); 168 } 169 170 /** 171 * Constructor. 172 * 173 * @param leftPoints 2D points on left view. 174 * @param rightPoints 2D points on right view. 175 * @param listener listener to be notified of events such as when estimation 176 * starts, ends or its progress significantly changes. 177 * @throws IllegalArgumentException if provided list of points do not have 178 * the same length or their length is less than 7 points. 179 */ 180 public LMedSFundamentalMatrixRobustEstimator(final List<Point2D> leftPoints, final List<Point2D> rightPoints, 181 final FundamentalMatrixRobustEstimatorListener listener) { 182 this(DEFAULT_FUNDAMENTAL_MATRIX_ESTIMATOR_METHOD, leftPoints, rightPoints, listener); 183 } 184 185 /** 186 * Returns threshold to be used to keep the algorithm iterating in case that 187 * best estimated threshold using median of residuals is not small enough. 188 * Once a solution is found that generates a threshold below this value, the 189 * algorithm will stop. 190 * The stop threshold can be used to prevent the LMedS algorithm iterating 191 * too many times in cases where samples have a very similar accuracy. 192 * For instance, in cases where proportion of outliers is very small (close 193 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would 194 * iterate for a long time trying to find the best solution when indeed 195 * there is no need to do that if a reasonable threshold has already been 196 * reached. 197 * Because of this behaviour the stop threshold can be set to a value much 198 * lower than the one typically used in RANSAC, and yet the algorithm could 199 * still produce even smaller thresholds in estimated results. 200 * 201 * @return stop threshold to stop the algorithm prematurely when a certain 202 * accuracy has been reached. 203 */ 204 public double getStopThreshold() { 205 return stopThreshold; 206 } 207 208 /** 209 * Sets threshold to be used to keep the algorithm iterating in case that 210 * best estimated threshold using median of residuals is not small enough. 211 * Once a solution is found that generates a threshold below this value, the 212 * algorithm will stop. 213 * The stop threshold can be used to prevent the LMedS algorithm iterating 214 * too many times in cases where samples have a very similar accuracy. 215 * For instance, in cases where proportion of outliers is very small (close 216 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would 217 * iterate for a long time trying to find the best solution when indeed 218 * there is no need to do that if a reasonable threshold has already been 219 * reached. 220 * Because of this behaviour the stop threshold can be set to a value much 221 * lower than the one typically used in RANSAC, and yet the algorithm could 222 * still produce even smaller thresholds in estimated results. 223 * 224 * @param stopThreshold stop threshold to stop the algorithm prematurely 225 * when a certain accuracy has been reached. 226 * @throws IllegalArgumentException if provided value is zero or negative. 227 * @throws LockedException if robust estimator is locked because an 228 * estimation is already in progress. 229 */ 230 public void setStopThreshold(final double stopThreshold) throws LockedException { 231 if (isLocked()) { 232 throw new LockedException(); 233 } 234 if (stopThreshold <= MIN_STOP_THRESHOLD) { 235 throw new IllegalArgumentException(); 236 } 237 238 this.stopThreshold = stopThreshold; 239 } 240 241 /** 242 * Estimates a radial distortion using a robust estimator and 243 * the best set of matched 2D points found using the robust estimator. 244 * 245 * @return a radial distortion. 246 * @throws LockedException if robust estimator is locked because an 247 * estimation is already in progress. 248 * @throws NotReadyException if provided input data is not enough to start 249 * the estimation. 250 * @throws RobustEstimatorException if estimation fails for any reason 251 * (i.e. numerical instability, no solution available, etc). 252 */ 253 @Override 254 public FundamentalMatrix estimate() throws LockedException, NotReadyException, RobustEstimatorException { 255 if (isLocked()) { 256 throw new LockedException(); 257 } 258 if (!isReady()) { 259 throw new NotReadyException(); 260 } 261 262 final var innerEstimator = new LMedSRobustEstimator<FundamentalMatrix>(new LMedSRobustEstimatorListener<>() { 263 264 // subset of left points 265 private final List<Point2D> subsetLeftPoints = new ArrayList<>(); 266 267 // subset of right points 268 private final List<Point2D> subsetRightPoints = new ArrayList<>(); 269 270 @Override 271 public int getTotalSamples() { 272 return leftPoints.size(); 273 } 274 275 @Override 276 public int getSubsetSize() { 277 return getMinRequiredPoints(); 278 } 279 280 @Override 281 public void estimatePreliminarSolutions( 282 final int[] samplesIndices, final List<FundamentalMatrix> solutions) { 283 284 subsetLeftPoints.clear(); 285 subsetRightPoints.clear(); 286 for (final var samplesIndex : samplesIndices) { 287 subsetLeftPoints.add(leftPoints.get(samplesIndex)); 288 subsetRightPoints.add(rightPoints.get(samplesIndex)); 289 } 290 291 nonRobustEstimate(solutions, subsetLeftPoints, subsetRightPoints); 292 } 293 294 @Override 295 public double computeResidual(final FundamentalMatrix currentEstimation, final int i) { 296 final var leftPoint = leftPoints.get(i); 297 final var rightPoint = rightPoints.get(i); 298 return residual(currentEstimation, leftPoint, rightPoint); 299 } 300 301 @Override 302 public boolean isReady() { 303 return LMedSFundamentalMatrixRobustEstimator.this.isReady(); 304 } 305 306 @Override 307 public void onEstimateStart(final RobustEstimator<FundamentalMatrix> estimator) { 308 if (listener != null) { 309 listener.onEstimateStart(LMedSFundamentalMatrixRobustEstimator.this); 310 } 311 } 312 313 @Override 314 public void onEstimateEnd(final RobustEstimator<FundamentalMatrix> estimator) { 315 if (listener != null) { 316 listener.onEstimateEnd(LMedSFundamentalMatrixRobustEstimator.this); 317 } 318 } 319 320 @Override 321 public void onEstimateNextIteration( 322 final RobustEstimator<FundamentalMatrix> estimator, final int iteration) { 323 if (listener != null) { 324 listener.onEstimateNextIteration(LMedSFundamentalMatrixRobustEstimator.this, iteration); 325 } 326 } 327 328 @Override 329 public void onEstimateProgressChange( 330 final RobustEstimator<FundamentalMatrix> estimator, final float progress) { 331 if (listener != null) { 332 listener.onEstimateProgressChange(LMedSFundamentalMatrixRobustEstimator.this, progress); 333 } 334 } 335 }); 336 337 try { 338 locked = true; 339 inliersData = null; 340 innerEstimator.setConfidence(confidence); 341 innerEstimator.setMaxIterations(maxIterations); 342 innerEstimator.setProgressDelta(progressDelta); 343 innerEstimator.setStopThreshold(stopThreshold); 344 final var result = innerEstimator.estimate(); 345 inliersData = innerEstimator.getInliersData(); 346 return attemptRefine(result); 347 } catch (final com.irurueta.numerical.LockedException e) { 348 throw new LockedException(e); 349 } catch (final com.irurueta.numerical.NotReadyException e) { 350 throw new NotReadyException(e); 351 } finally { 352 locked = false; 353 } 354 } 355 356 /** 357 * Returns method being used for robust estimation. 358 * 359 * @return method being used for robust estimation. 360 */ 361 @Override 362 public RobustEstimatorMethod getMethod() { 363 return RobustEstimatorMethod.LMEDS; 364 } 365 366 /** 367 * Gets standard deviation used for Levenberg-Marquardt fitting during 368 * refinement. 369 * Returned value gives an indication of how much variance each residual 370 * has. 371 * Typically, this value is related to the threshold used on each robust 372 * estimation, since residuals of found inliers are within the range of 373 * such threshold. 374 * 375 * @return standard deviation used for refinement. 376 */ 377 @Override 378 protected double getRefinementStandardDeviation() { 379 final var inliersData = (LMedSRobustEstimator.LMedSInliersData) getInliersData(); 380 return inliersData.getEstimatedThreshold(); 381 } 382 }