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.AffineTransformation2D;
19  import com.irurueta.geometry.CoincidentPointsException;
20  import com.irurueta.geometry.CoordinatesType;
21  import com.irurueta.geometry.Point2D;
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 affine 2D transformation for provided collections of matched
32   * 2D points using MSAC algorithm.
33   */
34  @SuppressWarnings("DuplicatedCode")
35  public class MSACPointCorrespondenceAffineTransformation2DRobustEstimator
36          extends PointCorrespondenceAffineTransformation2DRobustEstimator {
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 MSACPointCorrespondenceAffineTransformation2DRobustEstimator() {
64          super();
65          threshold = DEFAULT_THRESHOLD;
66      }
67  
68      /**
69       * Constructor with lists of points to be used to estimate an affine 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 an
76       *                     affine 2D transformation.
77       * @param outputPoints list of output points to be used to estimate an
78       *                     affine 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 MSACPointCorrespondenceAffineTransformation2DRobustEstimator(
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 MSACPointCorrespondenceAffineTransformation2DRobustEstimator(
95              final AffineTransformation2DRobustEstimatorListener listener) {
96          super(listener);
97          threshold = DEFAULT_THRESHOLD;
98      }
99  
100     /**
101      * Constructor with listener and lists of points to be used to estimate an
102      * affine 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 an
110      *                     affine 2D transformation.
111      * @param outputPoints list of output points to be used to estimate an
112      *                     affine 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 MSACPointCorrespondenceAffineTransformation2DRobustEstimator(
117             final AffineTransformation2DRobustEstimatorListener 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
143      *                  not.
144      * @throws IllegalArgumentException if provided values is equal or less than
145      *                                  zero.
146      * @throws LockedException          if robust estimator is locked because an
147      *                                  estimation is already in progress.
148      */
149     public void setThreshold(final double threshold) throws LockedException {
150         if (isLocked()) {
151             throw new LockedException();
152         }
153         if (threshold <= MIN_THRESHOLD) {
154             throw new IllegalArgumentException();
155         }
156         this.threshold = threshold;
157     }
158 
159     /**
160      * Estimates an affine 2D transformation using a robust estimator and
161      * the best set of matched 2D point correspondences found using the robust
162      * estimator.
163      *
164      * @return an affine 2D transformation.
165      * @throws LockedException          if robust estimator is locked because an
166      *                                  estimation is already in progress.
167      * @throws NotReadyException        if provided input data is not enough to start
168      *                                  the estimation.
169      * @throws RobustEstimatorException if estimation fails for any reason
170      *                                  (i.e. numerical instability, no solution available, etc).
171      */
172     @Override
173     public AffineTransformation2D estimate() throws LockedException, NotReadyException, RobustEstimatorException {
174         if (isLocked()) {
175             throw new LockedException();
176         }
177         if (!isReady()) {
178             throw new NotReadyException();
179         }
180 
181         final var innerEstimator = new MSACRobustEstimator<>(new MSACRobustEstimatorListener<AffineTransformation2D>() {
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 AffineTransformation2DRobustEstimator.MINIMUM_SIZE;
199             }
200 
201             @Override
202             public void estimatePreliminarSolutions(
203                     final int[] samplesIndices, final List<AffineTransformation2D> 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 
208                 final var outputPoint1 = outputPoints.get(samplesIndices[0]);
209                 final var outputPoint2 = outputPoints.get(samplesIndices[1]);
210                 final var outputPoint3 = outputPoints.get(samplesIndices[2]);
211 
212                 try {
213                     final var transformation = new AffineTransformation2D(inputPoint1, inputPoint2, inputPoint3,
214                             outputPoint1, outputPoint2, outputPoint3);
215                     solutions.add(transformation);
216                 } catch (final CoincidentPointsException e) {
217                     // if points are coincident, no solution is added
218                 }
219             }
220 
221             @Override
222             public double computeResidual(final AffineTransformation2D currentEstimation, final int i) {
223                 final var inputPoint = inputPoints.get(i);
224                 final var outputPoint = outputPoints.get(i);
225 
226                 // transform input point and store result in mTestPoint
227                 currentEstimation.transform(inputPoint, testPoint);
228 
229                 return outputPoint.distanceTo(testPoint);
230             }
231 
232             @Override
233             public boolean isReady() {
234                 return MSACPointCorrespondenceAffineTransformation2DRobustEstimator.this.isReady();
235             }
236 
237             @Override
238             public void onEstimateStart(final RobustEstimator<AffineTransformation2D> estimator) {
239                 if (mListener != null) {
240                     mListener.onEstimateStart(
241                             MSACPointCorrespondenceAffineTransformation2DRobustEstimator.this);
242                 }
243             }
244 
245             @Override
246             public void onEstimateEnd(final RobustEstimator<AffineTransformation2D> estimator) {
247                 if (mListener != null) {
248                     mListener.onEstimateEnd(MSACPointCorrespondenceAffineTransformation2DRobustEstimator.this);
249                 }
250             }
251 
252             @Override
253             public void onEstimateNextIteration(
254                     final RobustEstimator<AffineTransformation2D> estimator, final int iteration) {
255                 if (mListener != null) {
256                     mListener.onEstimateNextIteration(
257                             MSACPointCorrespondenceAffineTransformation2DRobustEstimator.this, iteration);
258                 }
259             }
260 
261             @Override
262             public void onEstimateProgressChange(
263                     final RobustEstimator<AffineTransformation2D> estimator, final float progress) {
264                 if (mListener != null) {
265                     mListener.onEstimateProgressChange(
266                             MSACPointCorrespondenceAffineTransformation2DRobustEstimator.this, progress);
267                 }
268             }
269         });
270 
271         try {
272             locked = true;
273             inliersData = null;
274             innerEstimator.setConfidence(confidence);
275             innerEstimator.setMaxIterations(maxIterations);
276             innerEstimator.setProgressDelta(progressDelta);
277             final var transformation = innerEstimator.estimate();
278             inliersData = innerEstimator.getInliersData();
279             return attemptRefine(transformation);
280         } catch (final com.irurueta.numerical.LockedException e) {
281             throw new LockedException(e);
282         } catch (final com.irurueta.numerical.NotReadyException e) {
283             throw new NotReadyException(e);
284         } finally {
285             locked = false;
286         }
287     }
288 
289     /**
290      * Returns method being used for robust estimation.
291      *
292      * @return method being used for robust estimation.
293      */
294     @Override
295     public RobustEstimatorMethod getMethod() {
296         return RobustEstimatorMethod.MSAC;
297     }
298 
299     /**
300      * Gets standard deviation used for Levenberg-Marquardt fitting during
301      * refinement.
302      * Returned value gives an indication of how much variance each residual
303      * has.
304      * Typically, this value is related to the threshold used on each robust
305      * estimation, since residuals of found inliers are within the range of
306      * such threshold.
307      *
308      * @return standard deviation used for refinement.
309      */
310     @Override
311     protected double getRefinementStandardDeviation() {
312         return threshold;
313     }
314 }