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.geometry.estimators;
17  
18  import com.irurueta.geometry.CoincidentPointsException;
19  import com.irurueta.geometry.CoordinatesType;
20  import com.irurueta.geometry.Point2D;
21  import com.irurueta.geometry.ProjectiveTransformation2D;
22  import com.irurueta.numerical.robust.MSACRobustEstimator;
23  import com.irurueta.numerical.robust.MSACRobustEstimatorListener;
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.List;
29  
30  /**
31   * Finds the best projective 2D transformation for provided collections of matched
32   * 2D points using MSAC algorithm.
33   */
34  @SuppressWarnings("DuplicatedCode")
35  public class MSACPointCorrespondenceProjectiveTransformation2DRobustEstimator
36          extends PointCorrespondenceProjectiveTransformation2DRobustEstimator {
37  
38      /**
39       * Constant defining default threshold to determine whether points are
40       * inliers or not.
41       * By default, 1.0 is considered a good value for cases where measures are
42       * done on pixels, since typically the minimum resolution is 1 pixel.
43       */
44      public static final double DEFAULT_THRESHOLD = 1.0;
45  
46      /**
47       * Minimum value that can be set as threshold.
48       * Threshold must be strictly greater than 0.0.
49       */
50      public static final double MIN_THRESHOLD = 0.0;
51  
52      /**
53       * Threshold to determine whether points are inliers or not when testing
54       * possible estimation solutions.
55       * The threshold refers to the amount of error (i.e. distance) a possible
56       * solution has on a matched pair of points.
57       */
58      private double threshold;
59  
60      /**
61       * Constructor.
62       */
63      public MSACPointCorrespondenceProjectiveTransformation2DRobustEstimator() {
64          super();
65          threshold = DEFAULT_THRESHOLD;
66      }
67  
68      /**
69       * Constructor with lists of points to be used to estimate a projective 2D
70       * transformation.
71       * Points in the list located at the same position are considered to be
72       * matched. Hence, both lists must have the same size, and their size must
73       * be greater or equal than MINIMUM_SIZE.
74       *
75       * @param inputPoints  list of input points to be used to estimate a
76       *                     projective 2D transformation.
77       * @param outputPoints list of output points to be used to estimate a
78       *                     projective 2D transformation.
79       * @throws IllegalArgumentException if provided lists of points don't have
80       *                                  the same size or their size is smaller than MINIMUM_SIZE.
81       */
82      public MSACPointCorrespondenceProjectiveTransformation2DRobustEstimator(
83              final List<Point2D> inputPoints, final List<Point2D> outputPoints) {
84          super(inputPoints, outputPoints);
85          threshold = DEFAULT_THRESHOLD;
86      }
87  
88      /**
89       * Constructor.
90       *
91       * @param listener listener to be notified of events such as when estimation
92       *                 starts, ends or its progress significantly changes.
93       */
94      public MSACPointCorrespondenceProjectiveTransformation2DRobustEstimator(
95              final ProjectiveTransformation2DRobustEstimatorListener listener) {
96          super(listener);
97          threshold = DEFAULT_THRESHOLD;
98      }
99  
100     /**
101      * Constructor with listener and lists of points to be used to estimate a
102      * projective 2D transformation.
103      * Points in the list located at the same position are considered to be
104      * matched. Hence, both lists must have the same size, and their size must
105      * be greater or equal than MINIMUM_SIZE.
106      *
107      * @param listener     listener to be notified of events such as when estimation
108      *                     stars, ends or its progress significantly changes.
109      * @param inputPoints  list of input points to be used to estimate a
110      *                     projective 2D transformation.
111      * @param outputPoints list of output points to be used to estimate a
112      *                     projective 2D transformation.
113      * @throws IllegalArgumentException if provided lists of points don't have
114      *                                  the same size or their size is smaller than MINIMUM_SIZE.
115      */
116     public MSACPointCorrespondenceProjectiveTransformation2DRobustEstimator(
117             final ProjectiveTransformation2DRobustEstimatorListener listener,
118             final List<Point2D> inputPoints, final List<Point2D> outputPoints) {
119         super(listener, inputPoints, outputPoints);
120         threshold = DEFAULT_THRESHOLD;
121     }
122 
123     /**
124      * Returns threshold to determine whether points are inliers or not when
125      * testing possible estimation solutions.
126      * The threshold refers to the amount of error (i.e. Euclidean distance) a
127      * possible solution has on a matched pair of points.
128      *
129      * @return threshold to determine whether points are inliers or not when
130      * testing possible estimation solutions.
131      */
132     public double getThreshold() {
133         return threshold;
134     }
135 
136     /**
137      * Sets threshold to determine whether points are inliers or not when
138      * testing possible estimation solutions.
139      * The threshold refers to the amount of error (i.e. Euclidean distance) a
140      * possible solution has on a matched pair of points.
141      *
142      * @param threshold threshold to determine whether points are inliers or not.
143      * @throws IllegalArgumentException if provided values is equal or less than
144      *                                  zero.
145      * @throws LockedException          if robust estimator is locked because an
146      *                                  estimation is already in progress.
147      */
148     public void setThreshold(final double threshold) throws LockedException {
149         if (isLocked()) {
150             throw new LockedException();
151         }
152         if (threshold <= MIN_THRESHOLD) {
153             throw new IllegalArgumentException();
154         }
155         this.threshold = threshold;
156     }
157 
158     /**
159      * Estimates a projective 2D transformation using a robust estimator and
160      * the best set of matched 2D point correspondences found using the robust
161      * estimator.
162      *
163      * @return a projective 2D transformation.
164      * @throws LockedException          if robust estimator is locked because an
165      *                                  estimation is already in progress.
166      * @throws NotReadyException        if provided input data is not enough to start
167      *                                  the estimation.
168      * @throws RobustEstimatorException if estimation fails for any reason
169      *                                  (i.e. numerical instability, no solution available, etc).
170      */
171     @Override
172     public ProjectiveTransformation2D estimate() throws LockedException, NotReadyException, RobustEstimatorException {
173         if (isLocked()) {
174             throw new LockedException();
175         }
176         if (!isReady()) {
177             throw new NotReadyException();
178         }
179 
180         final var innerEstimator = new MSACRobustEstimator<>(
181                 new MSACRobustEstimatorListener<ProjectiveTransformation2D>() {
182 
183                     // point to be reused when computing residuals
184                     private final Point2D testPoint = Point2D.create(CoordinatesType.HOMOGENEOUS_COORDINATES);
185 
186                     @Override
187                     public double getThreshold() {
188                         return threshold;
189                     }
190 
191                     @Override
192                     public int getTotalSamples() {
193                         return inputPoints.size();
194                     }
195 
196                     @Override
197                     public int getSubsetSize() {
198                         return ProjectiveTransformation2DRobustEstimator.MINIMUM_SIZE;
199                     }
200 
201                     @Override
202                     public void estimatePreliminarSolutions(
203                             final int[] samplesIndices, final List<ProjectiveTransformation2D> solutions) {
204                         final var inputPoint1 = inputPoints.get(samplesIndices[0]);
205                         final var inputPoint2 = inputPoints.get(samplesIndices[1]);
206                         final var inputPoint3 = inputPoints.get(samplesIndices[2]);
207                         final var inputPoint4 = inputPoints.get(samplesIndices[3]);
208 
209                         final var outputPoint1 = outputPoints.get(samplesIndices[0]);
210                         final var outputPoint2 = outputPoints.get(samplesIndices[1]);
211                         final var outputPoint3 = outputPoints.get(samplesIndices[2]);
212                         final var outputPoint4 = outputPoints.get(samplesIndices[3]);
213 
214                         try {
215                             final var transformation = new ProjectiveTransformation2D(inputPoint1, inputPoint2,
216                                     inputPoint3, inputPoint4, outputPoint1, outputPoint2, outputPoint3, outputPoint4);
217                             solutions.add(transformation);
218                         } catch (final CoincidentPointsException e) {
219                             // if points are coincident, no solution is added
220                         }
221                     }
222 
223                     @Override
224                     public double computeResidual(final ProjectiveTransformation2D currentEstimation, final int i) {
225                         final var inputPoint = inputPoints.get(i);
226                         final var outputPoint = outputPoints.get(i);
227 
228                         // transform input point and store result in mTestPoint
229                         currentEstimation.transform(inputPoint, testPoint);
230 
231                         return outputPoint.distanceTo(testPoint);
232                     }
233 
234                     @Override
235                     public boolean isReady() {
236                         return MSACPointCorrespondenceProjectiveTransformation2DRobustEstimator.this.isReady();
237                     }
238 
239                     @Override
240                     public void onEstimateStart(final RobustEstimator<ProjectiveTransformation2D> estimator) {
241                         if (listener != null) {
242                             listener.onEstimateStart(
243                                     MSACPointCorrespondenceProjectiveTransformation2DRobustEstimator.this);
244                         }
245                     }
246 
247                     @Override
248                     public void onEstimateEnd(final RobustEstimator<ProjectiveTransformation2D> estimator) {
249                         if (listener != null) {
250                             listener.onEstimateEnd(
251                                     MSACPointCorrespondenceProjectiveTransformation2DRobustEstimator.this);
252                         }
253                     }
254 
255                     @Override
256                     public void onEstimateNextIteration(
257                             final RobustEstimator<ProjectiveTransformation2D> estimator, final int iteration) {
258                         if (listener != null) {
259                             listener.onEstimateNextIteration(
260                                     MSACPointCorrespondenceProjectiveTransformation2DRobustEstimator.this,
261                                     iteration);
262                         }
263                     }
264 
265                     @Override
266                     public void onEstimateProgressChange(
267                             final RobustEstimator<ProjectiveTransformation2D> estimator, final float progress) {
268                         if (listener != null) {
269                             listener.onEstimateProgressChange(
270                                     MSACPointCorrespondenceProjectiveTransformation2DRobustEstimator.this,
271                                     progress);
272                         }
273                     }
274                 });
275 
276         try {
277             locked = true;
278             inliersData = null;
279             innerEstimator.setConfidence(confidence);
280             innerEstimator.setMaxIterations(maxIterations);
281             innerEstimator.setProgressDelta(progressDelta);
282             final var transformation = innerEstimator.estimate();
283             inliersData = innerEstimator.getInliersData();
284             return attemptRefine(transformation);
285         } catch (final com.irurueta.numerical.LockedException e) {
286             throw new LockedException(e);
287         } catch (final com.irurueta.numerical.NotReadyException e) {
288             throw new NotReadyException(e);
289         } finally {
290             locked = false;
291         }
292     }
293 
294     /**
295      * Returns method being used for robust estimation.
296      *
297      * @return method being used for robust estimation.
298      */
299     @Override
300     public RobustEstimatorMethod getMethod() {
301         return RobustEstimatorMethod.MSAC;
302     }
303 
304     /**
305      * Gets standard deviation used for Levenberg-Marquardt fitting during
306      * refinement.
307      * Returned value gives an indication of how much variance each residual
308      * has.
309      * Typically, this value is related to the threshold used on each robust
310      * estimation, since residuals of found inliers are within the range of
311      * such threshold.
312      *
313      * @return standard deviation used for refinement.
314      */
315     @Override
316     protected double getRefinementStandardDeviation() {
317         return threshold;
318     }
319 }