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.PROSACRobustEstimator;
24 import com.irurueta.numerical.robust.PROSACRobustEstimatorListener;
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 collections of 2D points using
36 * PROSAC algorithm.
37 */
38 public class PROSACRadialDistortionRobustEstimator extends RadialDistortionRobustEstimator {
39
40 /**
41 * Constant defining default threshold to determine whether points are
42 * inliers or not.
43 * By default, 1.0 is considered a good value for cases where measures are
44 * done on pixels, since typically the minimum resolution is 1 pixel.
45 */
46 public static final double DEFAULT_THRESHOLD = 1.0;
47
48 /**
49 * Minimum value that can be set as threshold.
50 * Threshold must be strictly greater than 0.0.
51 */
52 public static final double MIN_THRESHOLD = 0.0;
53
54 /**
55 * Threshold to determine whether points are inliers or not when testing
56 * possible estimation solutions.
57 * The threshold refers to the amount of error (i.e. distance) a possible
58 * solution has on a matched pair of points.
59 */
60 private double threshold;
61
62 /**
63 * Quality scores corresponding to each provided point.
64 * The larger the score value the better the quality of the sample.
65 */
66 private double[] qualityScores;
67
68 /**
69 * Constructor.
70 */
71 public PROSACRadialDistortionRobustEstimator() {
72 super();
73 threshold = DEFAULT_THRESHOLD;
74 }
75
76 /**
77 * Constructor.
78 *
79 * @param listener listener to be notified of events such as when
80 * estimation starts, ends or its progress significantly changes.
81 */
82 public PROSACRadialDistortionRobustEstimator(final RadialDistortionRobustEstimatorListener listener) {
83 super(listener);
84 threshold = DEFAULT_THRESHOLD;
85 }
86
87 /**
88 * Constructor.
89 *
90 * @param distortedPoints list of distorted points. Distorted points are
91 * obtained after radial distortion is applied to an undistorted point.
92 * @param undistortedPoints list of undistorted points.
93 * @throws IllegalArgumentException if provided lists of points don't have
94 * the same size or their size is smaller than MIN_NUMBER_OF_POINTS.
95 */
96 public PROSACRadialDistortionRobustEstimator(
97 final List<Point2D> distortedPoints, final List<Point2D> undistortedPoints) {
98 super(distortedPoints, undistortedPoints);
99 threshold = DEFAULT_THRESHOLD;
100 }
101
102 /**
103 * Constructor.
104 *
105 * @param distortedPoints list of distorted points. Distorted points are
106 * obtained after radial distortion is applied to an undistorted point.
107 * @param undistortedPoints list of undistorted points.
108 * @param listener listener to be notified of events such as when
109 * estimation starts, ends or its progress significantly changes.
110 * @throws IllegalArgumentException if provided lists of points don't have
111 * the same size or their size is smaller than MIN_NUMBER_OF_POINTS.
112 */
113 public PROSACRadialDistortionRobustEstimator(final List<Point2D> distortedPoints,
114 final List<Point2D> undistortedPoints,
115 final RadialDistortionRobustEstimatorListener listener) {
116 super(distortedPoints, undistortedPoints, listener);
117 threshold = DEFAULT_THRESHOLD;
118 }
119
120 /**
121 * Constructor.
122 *
123 * @param distortedPoints list of distorted points. Distorted points are
124 * obtained after radial distortion is applied to an undistorted point.
125 * @param undistortedPoints list of undistorted points.
126 * @param distortionCenter radial distortion center. If null it is assumed
127 * to be the origin of coordinates, otherwise this is typically equal to
128 * the camera principal point.
129 * @throws IllegalArgumentException if provided lists of points don't have
130 * the same size or their size is smaller than MIN_NUMBER_OF_POINTS.
131 */
132 public PROSACRadialDistortionRobustEstimator(final List<Point2D> distortedPoints,
133 final List<Point2D> undistortedPoints,
134 final Point2D distortionCenter) {
135 super(distortedPoints, undistortedPoints, distortionCenter);
136 threshold = DEFAULT_THRESHOLD;
137 }
138
139 /**
140 * Constructor.
141 *
142 * @param distortedPoints list of distorted points. Distorted points are
143 * obtained after radial distortion is applied to an undistorted point.
144 * @param undistortedPoints list of undistorted points.
145 * @param distortionCenter radial distortion center. If null it is assumed
146 * to be the origin of coordinates, otherwise this is typically equal to
147 * the camera principal point.
148 * @param listener listener to be notified of events such as when
149 * estimation starts, ends or its progress significantly changes.
150 * @throws IllegalArgumentException if provided lists of points don't have
151 * the same size or their size is smaller than MIN_NUMBER_OF_POINTS.
152 */
153 public PROSACRadialDistortionRobustEstimator(final List<Point2D> distortedPoints,
154 final List<Point2D> undistortedPoints,
155 final Point2D distortionCenter,
156 final RadialDistortionRobustEstimatorListener listener) {
157 super(distortedPoints, undistortedPoints, distortionCenter, listener);
158 threshold = DEFAULT_THRESHOLD;
159 }
160
161 /**
162 * Constructor.
163 *
164 * @param qualityScores quality scores corresponding to each provided point.
165 * @throws IllegalArgumentException if provided quality scores length is
166 * smaller than required size (i.e. 2 points).
167 */
168 public PROSACRadialDistortionRobustEstimator(final double[] qualityScores) {
169 this();
170 internalSetQualityScores(qualityScores);
171 }
172
173 /**
174 * Constructor.
175 *
176 * @param qualityScores quality scores corresponding to each provided point.
177 * @param listener listener to be notified of events such as when
178 * estimation starts, ends or its progress significantly changes.
179 * @throws IllegalArgumentException if provided quality scores length is
180 * smaller than required size (i.e. 2 points).
181 */
182 public PROSACRadialDistortionRobustEstimator(
183 final double[] qualityScores, final RadialDistortionRobustEstimatorListener listener) {
184 this(listener);
185 internalSetQualityScores(qualityScores);
186 }
187
188 /**
189 * Constructor.
190 *
191 * @param distortedPoints list of distorted points. Distorted points are
192 * obtained after radial distortion is applied to an undistorted point.
193 * @param undistortedPoints list of undistorted points.
194 * @param qualityScores quality scores corresponding to each provided point.
195 * @throws IllegalArgumentException if provided lists of points and quality
196 * scores don't have the same size or their size is smaller than
197 * MIN_NUMBER_OF_POINTS (i.e. 2 points).
198 */
199 public PROSACRadialDistortionRobustEstimator(final List<Point2D> distortedPoints,
200 final List<Point2D> undistortedPoints,
201 final double[] qualityScores) {
202 this(distortedPoints, undistortedPoints);
203 internalSetQualityScores(qualityScores);
204 }
205
206 /**
207 * Constructor.
208 *
209 * @param distortedPoints list of distorted points. Distorted points are
210 * obtained after radial distortion is applied to an undistorted point.
211 * @param undistortedPoints list of undistorted points.
212 * @param qualityScores quality scores corresponding to each provided point.
213 * @param listener listener to be notified of events such as when
214 * estimation starts, ends or its progress significantly changes.
215 * @throws IllegalArgumentException if provided lists of points or quality
216 * scores don't have the same size or their size is smaller than
217 * MIN_NUMBER_OF_POINTS (i.e. 2 points).
218 */
219 public PROSACRadialDistortionRobustEstimator(final List<Point2D> distortedPoints,
220 final List<Point2D> undistortedPoints,
221 final double[] qualityScores,
222 final RadialDistortionRobustEstimatorListener listener) {
223 this(distortedPoints, undistortedPoints, listener);
224 internalSetQualityScores(qualityScores);
225 }
226
227 /**
228 * Constructor.
229 *
230 * @param distortedPoints list of distorted points. Distorted points are
231 * obtained after radial distortion is applied to an undistorted point.
232 * @param undistortedPoints list of undistorted points.
233 * @param qualityScores quality scores corresponding to each provided point.
234 * @param distortionCenter radial distortion center. If null it is assumed
235 * to be the origin of coordinates, otherwise this is typically equal to
236 * the camera principal point.
237 * @throws IllegalArgumentException if provided lists of points or quality
238 * scores don't have the same size or their size is smaller than
239 * MIN_NUMBER_OF_POINTS (i.e. 2 points).
240 */
241 public PROSACRadialDistortionRobustEstimator(final List<Point2D> distortedPoints,
242 final List<Point2D> undistortedPoints,
243 final double[] qualityScores,
244 final Point2D distortionCenter) {
245 this(distortedPoints, undistortedPoints, distortionCenter);
246 internalSetQualityScores(qualityScores);
247 }
248
249 /**
250 * Constructor.
251 *
252 * @param distortedPoints list of distorted points. Distorted points are
253 * obtained after radial distortion is applied to an undistorted point.
254 * @param undistortedPoints list of undistorted points.
255 * @param qualityScores quality scores corresponding to each provided point.
256 * @param distortionCenter radial distortion center. If null it is assumed
257 * to be the origin of coordinates, otherwise this is typically equal to
258 * the camera principal point.
259 * @param listener listener to be notified of events such as when
260 * estimation starts, ends or its progress significantly changes.
261 * @throws IllegalArgumentException if provided lists of points or quality
262 * scores don't have the same size or their size is smaller than
263 * MIN_NUMBER_OF_POINTS (i.e. 2 points).
264 */
265 public PROSACRadialDistortionRobustEstimator(final List<Point2D> distortedPoints,
266 final List<Point2D> undistortedPoints,
267 final double[] qualityScores,
268 final Point2D distortionCenter,
269 final RadialDistortionRobustEstimatorListener listener) {
270 this(distortedPoints, undistortedPoints, distortionCenter, listener);
271 internalSetQualityScores(qualityScores);
272 }
273
274 /**
275 * Returns threshold to determine whether points are inliers or not when
276 * testing possible estimation solutions.
277 * The threshold refers to the amount of error (i.e. Euclidean distance) a
278 * possible solution has on projected 2D points.
279 *
280 * @return threshold to determine whether points are inliers or not when
281 * testing possible estimation solutions.
282 */
283 public double getThreshold() {
284 return threshold;
285 }
286
287 /**
288 * Sets threshold to determine whether points are inliers or not when
289 * testing possible estimation solutions.
290 * The threshold refers to the amount of error (i.e. Euclidean distance) a
291 * possible solution has on projected 2D points.
292 *
293 * @param threshold threshold to be set.
294 * @throws IllegalArgumentException if provided value is equal or less than
295 * zero.
296 * @throws LockedException if robust estimator is locked because an
297 * estimation is already in progress.
298 */
299 public void setThreshold(final double threshold) throws LockedException {
300 if (isLocked()) {
301 throw new LockedException();
302 }
303 if (threshold <= MIN_THRESHOLD) {
304 throw new IllegalArgumentException();
305 }
306 this.threshold = threshold;
307 }
308
309 /**
310 * Returns quality scores corresponding to each provided point.
311 * The larger the score value the better the quality of the sampled point.
312 *
313 * @return quality scores corresponding to each point.
314 */
315 @Override
316 public double[] getQualityScores() {
317 return qualityScores;
318 }
319
320 /**
321 * Sets quality scores corresponding to each provided point.
322 * The larger the score value the better the quality of the sampled point.
323 *
324 * @param qualityScores quality scores corresponding to each point.
325 * @throws LockedException if robust estimator is locked because an
326 * estimation is already in progress.
327 * @throws IllegalArgumentException if provided quality scores length is
328 * smaller than MINIMUM_SIZE (i.e. 2 samples).
329 */
330 @Override
331 public void setQualityScores(final double[] qualityScores) throws LockedException {
332 if (isLocked()) {
333 throw new LockedException();
334 }
335 internalSetQualityScores(qualityScores);
336 }
337
338 /**
339 * Indicates if estimator is ready to start the radial distortion
340 * estimation.
341 * This is true when input data (i.e. 2D points and quality scores) are
342 * provided and a minimum of 2 points are available.
343 *
344 * @return true if estimator is ready, false otherwise.
345 */
346 @Override
347 public boolean isReady() {
348 return super.isReady() && qualityScores != null && qualityScores.length == distortedPoints.size();
349 }
350
351 /**
352 * Estimates a radial distortion using a robust estimator and
353 * the best set of matched 2D points found using the robust estimator.
354 *
355 * @return a radial distortion.
356 * @throws LockedException if robust estimator is locked because an
357 * estimation is already in progress.
358 * @throws NotReadyException if provided input data is not enough to start
359 * the estimation.
360 * @throws RobustEstimatorException if estimation fails for any reason
361 * (i.e. numerical instability, no solution available, etc).
362 */
363 @SuppressWarnings("DuplicatedCode")
364 @Override
365 public RadialDistortion estimate() throws LockedException, NotReadyException, RobustEstimatorException {
366 if (isLocked()) {
367 throw new LockedException();
368 }
369 if (!isReady()) {
370 throw new NotReadyException();
371 }
372
373 final var innerEstimator = new PROSACRobustEstimator<RadialDistortion>(new PROSACRobustEstimatorListener<>() {
374
375 // point to be reused when computing residuals
376 private final Point2D testPoint = Point2D.create(CoordinatesType.INHOMOGENEOUS_COORDINATES);
377
378 // non-robust radial distortion estimator
379 private final LMSERadialDistortionEstimator radialDistortionEstimator = new LMSERadialDistortionEstimator();
380
381 // subset of distorted (i.e. measured) points
382 private final List<Point2D> subsetDistorted = new ArrayList<>();
383
384 // subset of undistorted (i.e. ideal) points
385 private final List<Point2D> subsetUndistorted = new ArrayList<>();
386
387 @Override
388 public double getThreshold() {
389 return threshold;
390 }
391
392 @Override
393 public int getTotalSamples() {
394 return distortedPoints.size();
395 }
396
397 @Override
398 public int getSubsetSize() {
399 return RadialDistortionRobustEstimator.MIN_NUMBER_OF_POINTS;
400 }
401
402 @Override
403 public void estimatePreliminarSolutions(
404 final int[] samplesIndices, final List<RadialDistortion> solutions) {
405 subsetDistorted.clear();
406 subsetDistorted.add(distortedPoints.get(samplesIndices[0]));
407 subsetDistorted.add(distortedPoints.get(samplesIndices[1]));
408
409 subsetUndistorted.clear();
410 subsetUndistorted.add(undistortedPoints.get(samplesIndices[0]));
411 subsetUndistorted.add(undistortedPoints.get(samplesIndices[1]));
412
413 try {
414 radialDistortionEstimator.setPoints(distortedPoints, undistortedPoints);
415 radialDistortionEstimator.setPoints(subsetDistorted, subsetUndistorted);
416
417 final var distortion = radialDistortionEstimator.estimate();
418 solutions.add(distortion);
419 } catch (final Exception e) {
420 // if anything fails, no solution is added
421 }
422 }
423
424 @Override
425 public double computeResidual(final RadialDistortion currentEstimation, final int i) {
426 final var distortedPoint = distortedPoints.get(i);
427 final var undistortedPoint = undistortedPoints.get(i);
428
429 currentEstimation.distort(undistortedPoint, testPoint);
430
431 return testPoint.distanceTo(distortedPoint);
432 }
433
434 @Override
435 public boolean isReady() {
436 return PROSACRadialDistortionRobustEstimator.this.isReady();
437 }
438
439 @Override
440 public void onEstimateStart(final RobustEstimator<RadialDistortion> estimator) {
441 try {
442 radialDistortionEstimator.setLMSESolutionAllowed(false);
443 radialDistortionEstimator.setIntrinsic(getIntrinsic());
444 } catch (final Exception e) {
445 Logger.getLogger(PROSACRadialDistortionRobustEstimator.class.getName()).log(Level.WARNING,
446 "Could not set intrinsic parameters on radial distortion estimator", e);
447 }
448
449 if (listener != null) {
450 listener.onEstimateStart(PROSACRadialDistortionRobustEstimator.this);
451 }
452 }
453
454 @Override
455 public void onEstimateEnd(final RobustEstimator<RadialDistortion> estimator) {
456 if (listener != null) {
457 listener.onEstimateEnd(PROSACRadialDistortionRobustEstimator.this);
458 }
459 }
460
461 @Override
462 public void onEstimateNextIteration(
463 final RobustEstimator<RadialDistortion> estimator, final int iteration) {
464 if (listener != null) {
465 listener.onEstimateNextIteration(PROSACRadialDistortionRobustEstimator.this, iteration);
466 }
467 }
468
469 @Override
470 public void onEstimateProgressChange(
471 final RobustEstimator<RadialDistortion> estimator, final float progress) {
472 if (listener != null) {
473 listener.onEstimateProgressChange(PROSACRadialDistortionRobustEstimator.this, progress);
474 }
475 }
476
477 @Override
478 public double[] getQualityScores() {
479 return qualityScores;
480 }
481 });
482
483 try {
484 locked = true;
485 innerEstimator.setConfidence(confidence);
486 innerEstimator.setMaxIterations(maxIterations);
487 innerEstimator.setProgressDelta(progressDelta);
488 return innerEstimator.estimate();
489 } catch (final com.irurueta.numerical.LockedException e) {
490 throw new LockedException(e);
491 } catch (final com.irurueta.numerical.NotReadyException e) {
492 throw new NotReadyException(e);
493 } finally {
494 locked = false;
495 }
496 }
497
498 /**
499 * Returns method being used for robust estimation
500 *
501 * @return method being used for robust estimation
502 */
503 @Override
504 public RobustEstimatorMethod getMethod() {
505 return RobustEstimatorMethod.PROSAC;
506 }
507
508 /**
509 * Sets quality scores corresponding to each provided point.
510 * This method is used internally and does not check whether instance is
511 * locked or not
512 *
513 * @param qualityScores quality scores to be set
514 * @throws IllegalArgumentException if provided quality scores length is
515 * smaller than MINIMUM_SIZE
516 */
517 private void internalSetQualityScores(final double[] qualityScores) {
518 if (qualityScores.length < MIN_NUMBER_OF_POINTS) {
519 throw new IllegalArgumentException();
520 }
521
522 this.qualityScores = qualityScores;
523 }
524 }