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.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.RANSACRobustEstimator;
23  import com.irurueta.numerical.robust.RANSACRobustEstimatorListener;
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 RANSAC algorithm.
34   */
35  public class RANSACFundamentalMatrixRobustEstimator extends FundamentalMatrixRobustEstimator {
36  
37      /**
38       * Constant defining default threshold to determine whether points are
39       * inliers or not.
40       * By default, 1.0 is considered a good value for cases where measures are
41       * done in pixels, since typically the minimum resolution is 1 pixel.
42       */
43      public static final double DEFAULT_THRESHOLD = 1.0;
44  
45      /**
46       * Minimum value that can be set as threshold.
47       * Threshold must be strictly greater than 0.0.
48       */
49      public static final double MIN_THRESHOLD = 0.0;
50  
51      /**
52       * Indicates that by default inliers will only be computed but not kept.
53       */
54      public static final boolean DEFAULT_COMPUTE_AND_KEEP_INLIERS = false;
55  
56      /**
57       * Indicates that by default residuals will only be computed but not kept.
58       */
59      public static final boolean DEFAULT_COMPUTE_AND_KEEP_RESIDUALS = false;
60  
61      /**
62       * Threshold to determine whether pairs of matched points are inliers or not
63       * when testing possible estimation solutions.
64       * The threshold refers to the amount of error (i.e. distance) a given
65       * point has respect to the epipolar line generated by its matched point.
66       */
67      private double threshold;
68  
69      /**
70       * Indicates whether inliers must be computed and kept.
71       */
72      private boolean computeAndKeepInliers;
73  
74      /**
75       * Indicates whether residuals must be computed and kept.
76       */
77      private boolean computeAndKeepResiduals;
78  
79      /**
80       * Constructor.
81       *
82       * @param fundMatrixEstimatorMethod method for non-robust fundamental matrix
83       *                                  estimator.
84       */
85      public RANSACFundamentalMatrixRobustEstimator(final FundamentalMatrixEstimatorMethod fundMatrixEstimatorMethod) {
86          super(fundMatrixEstimatorMethod);
87          threshold = DEFAULT_THRESHOLD;
88          computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
89          computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
90      }
91  
92      /**
93       * Constructor.
94       *
95       * @param fundMatrixEstimatorMethod method for non-robust fundamental matrix
96       *                                  estimator.
97       * @param listener                  listener to be notified of events such as when
98       *                                  estimation starts, ends or its progress significantly changes.
99       */
100     public RANSACFundamentalMatrixRobustEstimator(
101             final FundamentalMatrixEstimatorMethod fundMatrixEstimatorMethod,
102             final FundamentalMatrixRobustEstimatorListener listener) {
103         super(fundMatrixEstimatorMethod, listener);
104         threshold = DEFAULT_THRESHOLD;
105         computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
106         computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
107     }
108 
109     /**
110      * Constructor.
111      *
112      * @param fundMatrixEstimatorMethod method for non-robust fundamental matrix
113      *                                  estimator.
114      * @param leftPoints                2D points on left view.
115      * @param rightPoints               2D points on right view.
116      * @throws IllegalArgumentException if provided list of points do not have
117      *                                  the same length or their length is less than 7 points.
118      */
119     public RANSACFundamentalMatrixRobustEstimator(
120             final FundamentalMatrixEstimatorMethod fundMatrixEstimatorMethod,
121             final List<Point2D> leftPoints, final List<Point2D> rightPoints) {
122         super(fundMatrixEstimatorMethod, leftPoints, rightPoints);
123         threshold = DEFAULT_THRESHOLD;
124         computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
125         computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
126     }
127 
128     /**
129      * Constructor.
130      *
131      * @param fundMatrixEstimatorMethod method for non-robust fundamental matrix
132      *                                  estimator.
133      * @param leftPoints                2D points on left view.
134      * @param rightPoints               2D points on right view.
135      * @param listener                  listener to be notified of events such as when estimation
136      *                                  starts, ends or its progress significantly changes.
137      * @throws IllegalArgumentException if provided list of points do not have
138      *                                  the same length or their length is less than 7 points.
139      */
140     public RANSACFundamentalMatrixRobustEstimator(
141             final FundamentalMatrixEstimatorMethod fundMatrixEstimatorMethod,
142             final List<Point2D> leftPoints, final List<Point2D> rightPoints,
143             final FundamentalMatrixRobustEstimatorListener listener) {
144         super(fundMatrixEstimatorMethod, leftPoints, rightPoints, listener);
145         threshold = DEFAULT_THRESHOLD;
146         computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
147         computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
148     }
149 
150     /**
151      * Constructor.
152      */
153     public RANSACFundamentalMatrixRobustEstimator() {
154         this(DEFAULT_FUNDAMENTAL_MATRIX_ESTIMATOR_METHOD);
155     }
156 
157     /**
158      * Constructor.
159      *
160      * @param listener listener to be notified of events such as when
161      *                 estimation starts, ends or its progress significantly changes.
162      */
163     public RANSACFundamentalMatrixRobustEstimator(final FundamentalMatrixRobustEstimatorListener listener) {
164         this(DEFAULT_FUNDAMENTAL_MATRIX_ESTIMATOR_METHOD, listener);
165     }
166 
167     /**
168      * Constructor.
169      *
170      * @param leftPoints  2D points on left view.
171      * @param rightPoints 2D points on right view.
172      * @throws IllegalArgumentException if provided list of points do not have
173      *                                  the same length or their length is less than 7 points.
174      */
175     public RANSACFundamentalMatrixRobustEstimator(final List<Point2D> leftPoints, final List<Point2D> rightPoints) {
176         this(DEFAULT_FUNDAMENTAL_MATRIX_ESTIMATOR_METHOD, leftPoints, rightPoints);
177     }
178 
179     /**
180      * Constructor.
181      *
182      * @param leftPoints  2D points on left view.
183      * @param rightPoints 2D points on right view.
184      * @param listener    listener to be notified of events such as when estimation
185      *                    starts, ends or its progress significantly changes.
186      * @throws IllegalArgumentException if provided list of points do not have
187      *                                  the same length or their length is less than 7 points.
188      */
189     public RANSACFundamentalMatrixRobustEstimator(
190             final List<Point2D> leftPoints, final List<Point2D> rightPoints,
191             final FundamentalMatrixRobustEstimatorListener listener) {
192         this(DEFAULT_FUNDAMENTAL_MATRIX_ESTIMATOR_METHOD, leftPoints, rightPoints, listener);
193     }
194 
195     /**
196      * Returns threshold to determine whether matched pairs of points are
197      * inliers or not when testing possible estimation solutions.
198      * The threshold refers to the amount of error (i.e. distance) a given
199      * point has respect to the epipolar line generated by its matched point.
200      *
201      * @return threshold to determine whether matched pairs of points are
202      * inliers or not.
203      */
204     public double getThreshold() {
205         return threshold;
206     }
207 
208     /**
209      * Sets threshold to determine whether matched pairs of points are inliers
210      * or not when testing possible estimation solutions.
211      *
212      * @param threshold threshold to be set.
213      * @throws IllegalArgumentException if provided value is equal or less than
214      *                                  zero.
215      * @throws LockedException          if robust estimator is locked because an
216      *                                  estimation is already in progress.
217      */
218     public void setThreshold(final double threshold) throws LockedException {
219         if (isLocked()) {
220             throw new LockedException();
221         }
222         if (threshold <= MIN_THRESHOLD) {
223             throw new IllegalArgumentException();
224         }
225         this.threshold = threshold;
226     }
227 
228     /**
229      * Indicates whether inliers must be computed and kept.
230      *
231      * @return true if inliers must be computed and kept, false if inliers
232      * only need to be computed but not kept.
233      */
234     public boolean isComputeAndKeepInliersEnabled() {
235         return computeAndKeepInliers;
236     }
237 
238     /**
239      * Specifies whether inliers must be computed and kept.
240      *
241      * @param computeAndKeepInliers true if inliers must be computed and kept,
242      *                              false if inliers only need to be computed but not kept.
243      * @throws LockedException if estimator is locked.
244      */
245     public void setComputeAndKeepInliersEnabled(final boolean computeAndKeepInliers) throws LockedException {
246         if (isLocked()) {
247             throw new LockedException();
248         }
249         this.computeAndKeepInliers = computeAndKeepInliers;
250     }
251 
252     /**
253      * Indicates whether residuals must be computed and kept.
254      *
255      * @return true if residuals must be computed and kept, false if residuals
256      * only need to be computed but not kept.
257      */
258     public boolean isComputeAndKeepResidualsEnabled() {
259         return computeAndKeepResiduals;
260     }
261 
262     /**
263      * Specifies whether residuals must be computed and kept.
264      *
265      * @param computeAndKeepResiduals true if residuals must be computed and
266      *                                kept, false if residuals only need to be computed but not kept.
267      * @throws LockedException if estimator is locked.
268      */
269     public void setComputeAndKeepResidualsEnabled(final boolean computeAndKeepResiduals) throws LockedException {
270         if (isLocked()) {
271             throw new LockedException();
272         }
273         this.computeAndKeepResiduals = computeAndKeepResiduals;
274     }
275 
276     /**
277      * Estimates a radial distortion using a robust estimator and
278      * the best set of matched 2D points found using the robust estimator.
279      *
280      * @return a radial distortion.
281      * @throws LockedException          if robust estimator is locked because an
282      *                                  estimation is already in progress.
283      * @throws NotReadyException        if provided input data is not enough to start
284      *                                  the estimation.
285      * @throws RobustEstimatorException if estimation fails for any reason
286      *                                  (i.e. numerical instability, no solution available, etc).
287      */
288     @SuppressWarnings("DuplicatedCode")
289     @Override
290     public FundamentalMatrix estimate() throws LockedException, NotReadyException, RobustEstimatorException {
291         if (isLocked()) {
292             throw new LockedException();
293         }
294         if (!isReady()) {
295             throw new NotReadyException();
296         }
297 
298         final var innerEstimator = new RANSACRobustEstimator<FundamentalMatrix>(new RANSACRobustEstimatorListener<>() {
299 
300             // subset of left points
301             private final List<Point2D> subsetLeftPoints = new ArrayList<>();
302 
303             // subset of right points
304             private final List<Point2D> subsetRightPoints = new ArrayList<>();
305 
306             @Override
307             public double getThreshold() {
308                 return threshold;
309             }
310 
311             @Override
312             public int getTotalSamples() {
313                 return leftPoints.size();
314             }
315 
316             @Override
317             public int getSubsetSize() {
318                 return getMinRequiredPoints();
319             }
320 
321             @Override
322             public void estimatePreliminarSolutions(
323                     final int[] samplesIndices, final List<FundamentalMatrix> solutions) {
324 
325                 subsetLeftPoints.clear();
326                 subsetRightPoints.clear();
327                 for (final var samplesIndex : samplesIndices) {
328                     subsetLeftPoints.add(leftPoints.get(samplesIndex));
329                     subsetRightPoints.add(rightPoints.get(samplesIndex));
330                 }
331 
332                 nonRobustEstimate(solutions, subsetLeftPoints, subsetRightPoints);
333             }
334 
335             @Override
336             public double computeResidual(final FundamentalMatrix currentEstimation, int i) {
337                 final var leftPoint = leftPoints.get(i);
338                 final var rightPoint = rightPoints.get(i);
339                 return residual(currentEstimation, leftPoint, rightPoint);
340             }
341 
342             @Override
343             public boolean isReady() {
344                 return RANSACFundamentalMatrixRobustEstimator.this.isReady();
345             }
346 
347             @Override
348             public void onEstimateStart(final RobustEstimator<FundamentalMatrix> estimator) {
349                 if (listener != null) {
350                     listener.onEstimateStart(RANSACFundamentalMatrixRobustEstimator.this);
351                 }
352             }
353 
354             @Override
355             public void onEstimateEnd(final RobustEstimator<FundamentalMatrix> estimator) {
356                 if (listener != null) {
357                     listener.onEstimateEnd(RANSACFundamentalMatrixRobustEstimator.this);
358                 }
359             }
360 
361             @Override
362             public void onEstimateNextIteration(
363                     final RobustEstimator<FundamentalMatrix> estimator, final int iteration) {
364                 if (listener != null) {
365                     listener.onEstimateNextIteration(RANSACFundamentalMatrixRobustEstimator.this, iteration);
366                 }
367             }
368 
369             @Override
370             public void onEstimateProgressChange(
371                     final RobustEstimator<FundamentalMatrix> estimator, final float progress) {
372                 if (listener != null) {
373                     listener.onEstimateProgressChange(RANSACFundamentalMatrixRobustEstimator.this, progress);
374                 }
375             }
376         });
377 
378         try {
379             locked = true;
380             inliersData = null;
381             innerEstimator.setComputeAndKeepInliersEnabled(computeAndKeepInliers || refineResult);
382             innerEstimator.setComputeAndKeepResidualsEnabled(computeAndKeepResiduals || refineResult);
383             innerEstimator.setConfidence(confidence);
384             innerEstimator.setMaxIterations(maxIterations);
385             innerEstimator.setProgressDelta(progressDelta);
386             final var result = innerEstimator.estimate();
387             inliersData = innerEstimator.getInliersData();
388             return attemptRefine(result);
389         } catch (final com.irurueta.numerical.LockedException e) {
390             throw new LockedException(e);
391         } catch (final com.irurueta.numerical.NotReadyException e) {
392             throw new NotReadyException(e);
393         } finally {
394             locked = false;
395         }
396     }
397 
398     /**
399      * Returns method being used for robust estimation.
400      *
401      * @return method being used for robust estimation.
402      */
403     @Override
404     public RobustEstimatorMethod getMethod() {
405         return RobustEstimatorMethod.RANSAC;
406     }
407 
408     /**
409      * Gets standard deviation used for Levenberg-Marquardt fitting during
410      * refinement.
411      * Returned value gives an indication of how much variance each residual
412      * has.
413      * Typically, this value is related to the threshold used on each robust
414      * estimation, since residuals of found inliers are within the range of
415      * such threshold.
416      *
417      * @return standard deviation used for refinement.
418      */
419     @Override
420     protected double getRefinementStandardDeviation() {
421         return threshold;
422     }
423 }