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.algebra.AlgebraException;
19 import com.irurueta.algebra.ArrayUtils;
20 import com.irurueta.algebra.Matrix;
21 import com.irurueta.ar.calibration.ImageOfAbsoluteConic;
22 import com.irurueta.geometry.ProjectiveTransformation2D;
23 import com.irurueta.geometry.Transformation2D;
24 import com.irurueta.geometry.estimators.LockedException;
25 import com.irurueta.geometry.estimators.NotReadyException;
26 import com.irurueta.numerical.robust.RobustEstimatorException;
27 import com.irurueta.numerical.robust.RobustEstimatorMethod;
28
29 import java.util.List;
30
31 /**
32 * This is an abstract class for algorithms to robustly find the best
33 * ImageOfAbsoluteConic (IAC) for provided collection of 2D homographies.
34 * Implementions of this class should be able to detect and discard outliers
35 * in order to find the best solution.
36 */
37 public abstract class ImageOfAbsoluteConicRobustEstimator {
38 /**
39 * Default robust estimator method when none is provided.
40 * In general for IAC estimation is best to use PROSAC or RANSAC than
41 * any other method, as it provides more robust results.
42 */
43 public static final RobustEstimatorMethod DEFAULT_ROBUST_METHOD = RobustEstimatorMethod.PROSAC;
44
45 /**
46 * Default amount of progress variation before notifying a change in
47 * estimation progress. By default, this is set to 5%.
48 */
49 public static final float DEFAULT_PROGRESS_DELTA = 0.05f;
50
51 /**
52 * Minimum allowed value for progress delta.
53 */
54 public static final float MIN_PROGRESS_DELTA = 0.0f;
55
56 /**
57 * Maximum allowed value for progress delta.
58 */
59 public static final float MAX_PROGRESS_DELTA = 1.0f;
60
61 /**
62 * Constant defining default confidence of the estimated result, which is
63 * 99%. This means that with a probability of 99% estimation will be
64 * accurate because chosen sub-samples will be inliers.
65 */
66 public static final double DEFAULT_CONFIDENCE = 0.99;
67
68 /**
69 * Default maximum allowed number of iterations.
70 */
71 public static final int DEFAULT_MAX_ITERATIONS = 5000;
72
73 /**
74 * Minimum allowed confidence value.
75 */
76 public static final double MIN_CONFIDENCE = 0.0;
77
78 /**
79 * Maximum allowed confidence value.
80 */
81 public static final double MAX_CONFIDENCE = 1.0;
82
83 /**
84 * Minimum allowed number of iterations.
85 */
86 public static final int MIN_ITERATIONS = 1;
87
88 /**
89 * Homographies to estimate image of absolute conic (IAC).
90 */
91 protected List<Transformation2D> homographies;
92
93 /**
94 * Internal non-robust estimator of IAC.
95 */
96 protected final LMSEImageOfAbsoluteConicEstimator iacEstimator;
97
98 /**
99 * Listener to be notified of events such as when estimation starts, ends or
100 * its progress significantly changes.
101 */
102 protected ImageOfAbsoluteConicRobustEstimatorListener listener;
103
104 /**
105 * Indicates if this estimator is locked because an estimation is being
106 * computed.
107 */
108 protected boolean locked;
109
110 /**
111 * Amount of progress variation before notifying a progress change during
112 * estimation.
113 */
114 protected float progressDelta;
115
116 /**
117 * Amount of confidence expressed as a value between 0.0 and 1.0 (which is
118 * equivalent to 100%). The amount of confidence indicates the probability
119 * that the estimated result is correct. Usually this value will be close
120 * to 1.0, but not exactly 1.0.
121 */
122 protected double confidence;
123
124 /**
125 * Maximum allowed number of iterations. When the maximum number of
126 * iterations is exceeded, result will not be available, however an
127 * approximate result will be available for retrieval.
128 */
129 protected int maxIterations;
130
131 // internal members to compute residuals
132
133 /**
134 * IAC matrix for one iteration of the robust estimator.
135 * This is used during residuals estimation.
136 * This instance is reused for performance reasons.
137 */
138 private Matrix iacMatrix;
139
140 /**
141 * Matrix representation of an homography.
142 * This is used during residuals estimation.
143 * This instance is reused for performance reasons.
144 */
145 private Matrix homMatrix;
146
147 /**
148 * Sub-matrix of homography used as the left term on a matrix multiplication.
149 * This is used during residuals estimation.
150 * This instance is reused for performance reasons.
151 */
152 private double[] subMatrixLeft;
153
154 /**
155 * Sub-matrix of homography used as the right term on a matrix
156 * multiplication.
157 * This is used during residuals estimation.
158 * This instance is reused for performance reasons.
159 */
160 private Matrix subMatrixRight;
161
162 /**
163 * Product multiplication of IAC by the right term.
164 * This is used during residuals estimation.
165 * This instance is reused for performance reasons.
166 */
167 private Matrix mult1;
168
169 /**
170 * Constructor.
171 */
172 protected ImageOfAbsoluteConicRobustEstimator() {
173 progressDelta = DEFAULT_PROGRESS_DELTA;
174 confidence = DEFAULT_CONFIDENCE;
175 maxIterations = DEFAULT_MAX_ITERATIONS;
176 iacEstimator = new LMSEImageOfAbsoluteConicEstimator();
177 }
178
179 /**
180 * Constructor.
181 *
182 * @param listener listener to be notified of events such as when
183 * estimation starts, ends or its progress significantly changes.
184 */
185 protected ImageOfAbsoluteConicRobustEstimator(final ImageOfAbsoluteConicRobustEstimatorListener listener) {
186 this();
187 this.listener = listener;
188 }
189
190 /**
191 * Constructor.
192 *
193 * @param homographies list of homographies (2D transformations) used to
194 * estimate the image of absolute conic (IAC), which can be used to obtain
195 * pinhole camera intrinsic parameters.
196 * @throws IllegalArgumentException if not enough homographies are provided
197 * for default settings. Hence, at least 1 homography must be provided.
198 */
199 protected ImageOfAbsoluteConicRobustEstimator(final List<Transformation2D> homographies) {
200 this();
201 internalSetHomographies(homographies);
202 }
203
204 /**
205 * Constructor.
206 *
207 * @param homographies list of homographies (2D transformations) used to
208 * estimate the image of absolute conic (IAC), which can be used to obtain
209 * pinhole camera intrinsic parameters.
210 * @param listener listener to be notified of events such as when estimation
211 * starts, ends or estimation progress changes.
212 * @throws IllegalArgumentException if not enough homographies are provided
213 * for default settings. Hence, at least 1 homography must be provided.
214 */
215 protected ImageOfAbsoluteConicRobustEstimator(
216 final List<Transformation2D> homographies, final ImageOfAbsoluteConicRobustEstimatorListener listener) {
217 this(listener);
218 internalSetHomographies(homographies);
219 }
220
221 /**
222 * Returns boolean indicating whether camera skewness is assumed to be zero
223 * or not.
224 * Skewness determines whether LCD sensor cells are properly aligned or not,
225 * where zero indicates perfect alignment.
226 * Typically, skewness is a value equal or very close to zero.
227 *
228 * @return true if camera skewness is assumed to be zero, otherwise camera
229 * skewness is estimated.
230 */
231 public boolean isZeroSkewness() {
232 return iacEstimator.isZeroSkewness();
233 }
234
235 /**
236 * Sets boolean indicating whether camera skewness is assumed to be zero or
237 * not.
238 * Skewness determines whether LCD sensor cells are properly aligned or not,
239 * where zero indicates perfect alignment.
240 * Typically, skewness is a value equal or very close to zero.
241 *
242 * @param zeroSkewness true if camera skewness is assumed to be zero,
243 * otherwise camera skewness is estimated.
244 * @throws LockedException if estimator is locked.
245 */
246 public void setZeroSkewness(final boolean zeroSkewness) throws LockedException {
247 if (isLocked()) {
248 throw new LockedException();
249 }
250
251 iacEstimator.setZeroSkewness(zeroSkewness);
252 }
253
254 /**
255 * Returns boolean indicating whether principal point is assumed to be at
256 * origin of coordinates or not.
257 * Typically principal point is located at image center (origin of
258 * coordinates), and usually matches the center of radial distortion if
259 * it is taken into account.
260 *
261 * @return true if principal point is assumed to be at origin of
262 * coordinates, false if principal point must be estimated.
263 */
264 public boolean isPrincipalPointAtOrigin() {
265 return iacEstimator.isPrincipalPointAtOrigin();
266 }
267
268 /**
269 * Sets boolean indicating whether principal point is assumed to be at
270 * origin of coordinates or not.
271 * Typically principal point is located at image center (origin of
272 * coordinates), and usually matches the center of radial distortion if it
273 * is taken into account.
274 *
275 * @param principalPointAtOrigin true if principal point is assumed to bet
276 * at origin of coordinates, false if principal point must be estimated
277 * @throws LockedException if estimator is locked.
278 */
279 public void setPrincipalPointAtOrigin(final boolean principalPointAtOrigin) throws LockedException {
280 if (isLocked()) {
281 throw new LockedException();
282 }
283
284 iacEstimator.setPrincipalPointAtOrigin(principalPointAtOrigin);
285 }
286
287 /**
288 * Returns boolean indicating whether aspect ratio of focal distances (i.e.
289 * vertical focal distance divided by horizontal focal distance) is known or
290 * not.
291 * Notice that focal distance aspect ratio is not related to image size
292 * aspect ratio. Typically, LCD sensor cells are square and hence aspect
293 * ratio of focal distances is known and equal to 1.
294 * This value is only taken into account if skewness is assumed to be zero,
295 * otherwise it is ignored.
296 *
297 * @return true if focal distance aspect ratio is known, false otherwise.
298 */
299 public boolean isFocalDistanceAspectRatioKnown() {
300 return iacEstimator.isFocalDistanceAspectRatioKnown();
301 }
302
303 /**
304 * Sets boolean indicating whether aspect ratio of focal distances (i.e.
305 * vertical focal distance divided by horizontal focal distance) is known or
306 * not.
307 * Notice that focal distance aspect ratio is not related to image size
308 * aspect ratio. Typically, LCD sensor cells are square and hence aspect
309 * ratio of focal distances is known and equal to 1.
310 * This value is only taken into account if skewness is assumed to be zero,
311 * otherwise it is ignored.
312 *
313 * @param focalDistanceAspectRatioKnown true if focal distance aspect ratio
314 * is known, false otherwise.
315 * @throws LockedException if estimator is locked.
316 */
317 public void setFocalDistanceAspectRatioKnown(final boolean focalDistanceAspectRatioKnown) throws LockedException {
318 if (isLocked()) {
319 throw new LockedException();
320 }
321
322 iacEstimator.setFocalDistanceAspectRatioKnown(focalDistanceAspectRatioKnown);
323 }
324
325 /**
326 * Returns aspect ratio of focal distances (i.e. vertical focal distance
327 * divided by horizontal focal distance).
328 * This value is only taken into account if skewness is assumed to be zero
329 * and focal distance aspect ratio is marked as known, otherwise it is
330 * ignored.
331 * By default, this is 1.0, since it is taken into account that typically
332 * LCD sensor cells are square and hence aspect ratio focal distances is
333 * known and equal to 1.
334 * Notice that focal distance aspect ratio is not related to image size
335 * aspect ratio.
336 * Notice that a negative aspect ratio indicates that vertical axis is
337 * reversed. This can be useful in some situations where image vertical
338 * coordinates are reversed respect to the physical world (i.e. in computer
339 * graphics typically image vertical coordinates go downwards, while in
340 * physical world they go upwards).
341 *
342 * @return aspect ratio of focal distances.
343 */
344 public double getFocalDistanceAspectRatio() {
345 return iacEstimator.getFocalDistanceAspectRatio();
346 }
347
348 /**
349 * Sets aspect ratio of focal distances (i.e. vertical focal distance
350 * divided by horizontal focal distance).
351 * This value is only taken into account if skewness is assumed to be zero
352 * and focal distance aspect ratio is marked as known, otherwise it is
353 * ignored.
354 * By default, this is 1.0, since it is taken into account that typically
355 * LCD sensor cells are square and hence aspect ratio focal distances is
356 * known and equal to 1.
357 * Notice that focal distance aspect ratio is not related to image size
358 * aspect ratio.
359 * Notice that a negative aspect ratio indicates that vertical axis is
360 * reversed. This can be useful in some situations where image vertical
361 * coordinates are reversed respect to the physical world (i.e. in computer
362 * graphics typically image vertical coordinates go downwards, while in
363 * physical world they go upwards).
364 *
365 * @param focalDistanceAspectRatio aspect ratio of focal distances to be set
366 * @throws LockedException if estimator is locked.
367 * @throws IllegalArgumentException if focal distance aspect ratio is too
368 * close to zero, as it might produce numerical instabilities.
369 */
370 public void setFocalDistanceAspectRatio(final double focalDistanceAspectRatio) throws LockedException {
371 if (isLocked()) {
372 throw new LockedException();
373 }
374
375 iacEstimator.setFocalDistanceAspectRatio(focalDistanceAspectRatio);
376 }
377
378 /**
379 * Returns reference to listener to be notified of events such as when
380 * estimation starts, ends or its progress significantly changes.
381 *
382 * @return listener to be notified of events.
383 */
384 public ImageOfAbsoluteConicRobustEstimatorListener getListener() {
385 return listener;
386 }
387
388 /**
389 * Sets listener to be notified of events such as when estimation starts,
390 * ends or its progress significantly changes.
391 *
392 * @param listener listener to be notified of events.
393 * @throws LockedException if robust estimator is locked.
394 */
395 public void setListener(final ImageOfAbsoluteConicRobustEstimatorListener listener) throws LockedException {
396 if (isLocked()) {
397 throw new LockedException();
398 }
399 this.listener = listener;
400 }
401
402 /**
403 * Indicates whether listener has been provided and is available for
404 * retrieval.
405 *
406 * @return true if available, false otherwise.
407 */
408 public boolean isListenerAvailable() {
409 return listener != null;
410 }
411
412 /**
413 * Indicates if this instance is locked because estimation is being
414 * computed.
415 *
416 * @return true if locked, false otherwise.
417 */
418 public boolean isLocked() {
419 return locked;
420 }
421
422 /**
423 * Returns amount of progress variation before notifying a progress change
424 * during estimation.
425 *
426 * @return amount of progress variation before notifying a progress change
427 * during estimation.
428 */
429 public float getProgressDelta() {
430 return progressDelta;
431 }
432
433 /**
434 * Sets amount of progress variation before notifying a progress change
435 * during estimation.
436 *
437 * @param progressDelta amount of progress variation before notifying a
438 * progress change during estimation.
439 * @throws IllegalArgumentException if progress delta is less than zero or
440 * greater than 1.
441 * @throws LockedException if this estimator is locked because an estimation
442 * is being computed.
443 */
444 public void setProgressDelta(final float progressDelta) throws LockedException {
445 if (isLocked()) {
446 throw new LockedException();
447 }
448 if (progressDelta < MIN_PROGRESS_DELTA || progressDelta > MAX_PROGRESS_DELTA) {
449 throw new IllegalArgumentException();
450 }
451 this.progressDelta = progressDelta;
452 }
453
454 /**
455 * Returns amount of confidence expressed as a value between 0.0 and 1.0
456 * (which is equivalent to 100%). The amount of confidence indicates the
457 * probability that the estimated result is correct. Usually this value will
458 * be close to 1.0, but not exactly 1.0.
459 *
460 * @return amount of confidence as a value between 0.0 and 1.0.
461 */
462 public double getConfidence() {
463 return confidence;
464 }
465
466 /**
467 * Sets amount of confidence expressed as a value between 0.0 and 1.0 (which
468 * is equivalent to 100%). The amount of confidence indicates the
469 * probability that the estimated result is correct. Usually this value will
470 * be close to 1.0, but not exactly 1.0.
471 *
472 * @param confidence confidence to be set as a value between 0.0 and 1.0.
473 * @throws IllegalArgumentException if provided value is not between 0.0 and
474 * 1.0.
475 * @throws LockedException if this estimator is locked because an estimator
476 * is being computed.
477 */
478 public void setConfidence(final double confidence) throws LockedException {
479 if (isLocked()) {
480 throw new LockedException();
481 }
482 if (confidence < MIN_CONFIDENCE || confidence > MAX_CONFIDENCE) {
483 throw new IllegalArgumentException();
484 }
485 this.confidence = confidence;
486 }
487
488 /**
489 * Returns maximum allowed number of iterations. If maximum allowed number
490 * of iterations is achieved without converging to a result when calling
491 * estimate(), a RobustEstimatorException will be raised.
492 *
493 * @return maximum allowed number of iterations.
494 */
495 public int getMaxIterations() {
496 return maxIterations;
497 }
498
499 /**
500 * Sets maximum allowed number of iterations. When the maximum number of
501 * iterations is exceeded, result will not be available, however an
502 * approximate result will be available for retrieval.
503 *
504 * @param maxIterations maximum allowed number of iterations to be set.
505 * @throws IllegalArgumentException if provided value is less than 1.
506 * @throws LockedException if this estimator is locked because an estimation
507 * is being computed.
508 */
509 public void setMaxIterations(final int maxIterations) throws LockedException {
510 if (isLocked()) {
511 throw new LockedException();
512 }
513 if (maxIterations < MIN_ITERATIONS) {
514 throw new IllegalArgumentException();
515 }
516 this.maxIterations = maxIterations;
517 }
518
519 /**
520 * Gets list of homographies to estimate IAC.
521 *
522 * @return list of homographies to estimate IAC.
523 */
524 public List<Transformation2D> getHomographies() {
525 return homographies;
526 }
527
528 /**
529 * Sets list of homographies to estimate IAC.
530 *
531 * @param homographies list of homographies to estimate IAC.
532 * @throws LockedException if estimator is locked.
533 * @throws IllegalArgumentException if provided list of homographies does
534 * not contain enough elements to estimate the DIAC using current
535 * settings.
536 */
537 public void setHomographies(final List<Transformation2D> homographies) throws LockedException {
538 if (isLocked()) {
539 throw new LockedException();
540 }
541 internalSetHomographies(homographies);
542 }
543
544 /**
545 * Returns minimum number of required homographies needed to estimate the
546 * Imag eof Absolute Conic (IAC).
547 * If no constraints are imposed, then at least 3 homographies are required.
548 * For each constraint imposed, one less equation will be required, hence
549 * if skewness is assumed to be known (by using its typical value of zero),
550 * then only 4 equations will be needed.
551 * If also the horizontal and vertical coordinates of the principal point
552 * are assumed to be known (by being placed at the 2D origin of coordinates,
553 * which is a typical value), then only 2 equations will be needed
554 * (1 homography).
555 *
556 * @return minimum number of required homographies required to estimate the
557 * IAC.
558 */
559 public int getMinNumberOfRequiredHomographies() {
560 return iacEstimator.getMinNumberOfRequiredHomographies();
561 }
562
563 /**
564 * Returns value indicating whether required data has been provided so that
565 * IAC estimation can start.
566 * If true, estimator is ready to compute the IAC, otherwise more data needs
567 * to be provided.
568 *
569 * @return true if estimator is ready, false otherwise.
570 */
571 public boolean isReady() {
572 return homographies != null && homographies.size() >= getMinNumberOfRequiredHomographies();
573 }
574
575 /**
576 * Returns quality scores corresponding to each homography.
577 * The larger the score value the better the quality of the homography.
578 * This implementation always returns null.
579 * Subclasses using quality scores must implement proper behaviour.
580 *
581 * @return quality scores corresponding to each homography.
582 */
583 public double[] getQualityScores() {
584 return null;
585 }
586
587 /**
588 * Sets quality scores corresponding to each homography.
589 * The larger the score value the better the quality of the homography.
590 * This implementation makes no action.
591 * Subclasses using quality scores must implement proper behaviour.
592 *
593 * @param qualityScores quality scores corresponding to each homography.
594 * @throws LockedException if robust estimator is locked because an
595 * estimation is already in progress.
596 * @throws IllegalArgumentException if provided quality scores length is
597 * smaller than minimum required number of homographies.
598 */
599 public void setQualityScores(final double[] qualityScores) throws LockedException {
600 }
601
602 /**
603 * Estimates Image of Absolute Conic (IAC).
604 *
605 * @return estimated IAC.
606 * @throws LockedException if robust estimator is locked because an
607 * estimation is already in progress.
608 * @throws NotReadyException if provided input data is not enough to start
609 * the estimation.
610 * @throws RobustEstimatorException if estimation fails for any reason
611 * (i.e. numerical instability, no solution available, etc).
612 */
613 public abstract ImageOfAbsoluteConic estimate() throws LockedException, NotReadyException,
614 RobustEstimatorException;
615
616 /**
617 * Returns method being used for robust estimation.
618 *
619 * @return method being used for robust estimation.
620 */
621 public abstract RobustEstimatorMethod getMethod();
622
623 /**
624 * Creates an image of absolute conic robust estimator using provided
625 * method.
626 *
627 * @param method method of a robust estimator algorithm to estimate best
628 * IAC.
629 * @return an instance of an image of absolute conic robust estimator.
630 */
631 public static ImageOfAbsoluteConicRobustEstimator create(final RobustEstimatorMethod method) {
632 return switch (method) {
633 case LMEDS -> new LMedSImageOfAbsoluteConicRobustEstimator();
634 case MSAC -> new MSACImageOfAbsoluteConicRobustEstimator();
635 case PROSAC -> new PROSACImageOfAbsoluteConicRobustEstimator();
636 case PROMEDS -> new PROMedSImageOfAbsoluteConicRobustEstimator();
637 default -> new RANSACImageOfAbsoluteConicRobustEstimator();
638 };
639 }
640
641 /**
642 * Creates an image of absolute conic robust estimator using provided
643 * homographies.
644 *
645 * @param homographies list of homographies.
646 * @param qualityScores quality scores corresponding to each homography.
647 * @param method method of a robust estimator algorithm to estimate
648 * best IAC.
649 * @return an instance of an image of absolute conic robust estimator.
650 * @throws IllegalArgumentException if provided list of homographies and
651 * quality scores don't have the same size or size is too short.
652 */
653 public static ImageOfAbsoluteConicRobustEstimator create(
654 final List<Transformation2D> homographies, final double[] qualityScores,
655 final RobustEstimatorMethod method) {
656 return switch (method) {
657 case LMEDS -> new LMedSImageOfAbsoluteConicRobustEstimator(homographies);
658 case MSAC -> new MSACImageOfAbsoluteConicRobustEstimator(homographies);
659 case PROSAC -> new PROSACImageOfAbsoluteConicRobustEstimator(homographies, qualityScores);
660 case PROMEDS -> new PROMedSImageOfAbsoluteConicRobustEstimator(homographies, qualityScores);
661 default -> new RANSACImageOfAbsoluteConicRobustEstimator(homographies);
662 };
663 }
664
665 /**
666 * Creates an image of absolute conic robust estimator using provided
667 * homographies.
668 *
669 * @param homographies list of homographies.
670 * @param method method of a robust estimator algorithm to estimate
671 * best IAC.
672 * @return an instance of an image of absolute conic robust estimator.
673 * @throws IllegalArgumentException if provided list of homographies is too
674 * short.
675 */
676 public static ImageOfAbsoluteConicRobustEstimator create(
677 final List<Transformation2D> homographies, final RobustEstimatorMethod method) {
678 return switch (method) {
679 case LMEDS -> new LMedSImageOfAbsoluteConicRobustEstimator(homographies);
680 case MSAC -> new MSACImageOfAbsoluteConicRobustEstimator(homographies);
681 case PROSAC -> new PROSACImageOfAbsoluteConicRobustEstimator(homographies);
682 case PROMEDS -> new PROMedSImageOfAbsoluteConicRobustEstimator(homographies);
683 default -> new RANSACImageOfAbsoluteConicRobustEstimator(homographies);
684 };
685 }
686
687 /**
688 * Creates an image of absolute conic robust estimator using default
689 * method.
690 *
691 * @return an instance of an image of absolute conic robust estimator.
692 */
693 public static ImageOfAbsoluteConicRobustEstimator create() {
694 return create(DEFAULT_ROBUST_METHOD);
695 }
696
697 /**
698 * Creates an image of absolute conic robust estimator using provided
699 * homographies.
700 *
701 * @param homographies list of homographies.
702 * @param qualityScores quality scores corresponding to each point.
703 * @return an instance of an image of absolute conic robust estimator.
704 * @throws IllegalArgumentException if provided list of homographies and
705 * quality scores don't have the same size or size is too short.
706 */
707 public static ImageOfAbsoluteConicRobustEstimator create(
708 final List<Transformation2D> homographies, final double[] qualityScores) {
709 return create(homographies, qualityScores, DEFAULT_ROBUST_METHOD);
710 }
711
712 /**
713 * Creates an image of absolute conic robust estimator using provided
714 * homographies.
715 *
716 * @param homographies list of homographies.
717 * @return an instance of an image of absolute conic robust estimator.
718 * @throws IllegalArgumentException if provided list of homographies and
719 * is too short.
720 */
721 public static ImageOfAbsoluteConicRobustEstimator create(final List<Transformation2D> homographies) {
722 return create(homographies, DEFAULT_ROBUST_METHOD);
723 }
724
725 /**
726 * Computes the residual between an image of absolute conic (IAC) and
727 * a 2D transformation (homography).
728 * Residual is derived from the fact that rotation matrices are
729 * orthonormal (i.e. h1'*IAC*h2 = 0 and
730 * h1'*IAC*h1 = h2'*IAC*h2 --< h1'*IAC*h1 - h2'*IAC*h2 = 0).
731 * The residual will be the average error of those two equations.
732 *
733 * @param iac the matrix of an image of absolute conic (IAC)
734 * @param homography 2D transformation (homography)
735 * @return residual.
736 */
737 protected double residual(final ImageOfAbsoluteConic iac, final Transformation2D homography) {
738
739 iac.normalize();
740 if (homography instanceof ProjectiveTransformation2D projectiveTransformation2D) {
741 projectiveTransformation2D.normalize();
742 }
743
744 try {
745 if (iacMatrix == null) {
746 iacMatrix = iac.asMatrix();
747 } else {
748 iac.asMatrix(iacMatrix);
749 }
750
751 if (homMatrix == null) {
752 homMatrix = homography.asMatrix();
753 } else {
754 homography.asMatrix(homMatrix);
755 }
756
757 if (subMatrixLeft == null) {
758 subMatrixLeft = new double[ProjectiveTransformation2D.HOM_COORDS];
759 }
760 if (subMatrixRight == null) {
761 subMatrixRight = new Matrix(ProjectiveTransformation2D.HOM_COORDS, 1);
762 }
763 if (mult1 == null) {
764 mult1 = new Matrix(ProjectiveTransformation2D.HOM_COORDS, 1);
765 }
766
767 // 1st equation h1'*IAC*h2 = 0
768
769 // 1st column of homography
770 homMatrix.getSubmatrixAsArray(0, 0,
771 ProjectiveTransformation2D.HOM_COORDS - 1, 0, subMatrixLeft);
772 // 2nd column of homography
773 homMatrix.getSubmatrix(0, 1,
774 ProjectiveTransformation2D.HOM_COORDS - 1, 1, subMatrixRight);
775
776 // IAC * h2
777 iacMatrix.multiply(subMatrixRight, mult1);
778
779 // h1' * (IAC * h2)
780 final var error1 = Math.abs(ArrayUtils.dotProduct(subMatrixLeft, mult1.getBuffer()));
781
782 // 2nd equation h1'*IAC*h1 - h2'*IAC*h2 = 0
783
784 // 1st column of homography
785 homMatrix.getSubmatrixAsArray(0, 0,
786 ProjectiveTransformation2D.HOM_COORDS - 1, 0, subMatrixLeft);
787 homMatrix.getSubmatrix(0, 0,
788 ProjectiveTransformation2D.HOM_COORDS - 1, 0, subMatrixRight);
789
790 // IAC * h1
791 iacMatrix.multiply(subMatrixRight, mult1);
792
793 // h1' * (IAC * h1)
794 final var error2a = ArrayUtils.dotProduct(subMatrixLeft, mult1.getBuffer());
795
796 // 2nd column of homography
797 homMatrix.getSubmatrixAsArray(0, 1,
798 ProjectiveTransformation2D.HOM_COORDS - 1, 1, subMatrixLeft);
799 homMatrix.getSubmatrix(0, 1,
800 ProjectiveTransformation2D.HOM_COORDS - 1, 1, subMatrixRight);
801
802 // IAC * h2
803 iacMatrix.multiply(subMatrixRight, mult1);
804
805 // h2' * (IAC * h1)
806 final var error2b = ArrayUtils.dotProduct(subMatrixLeft, mult1.getBuffer());
807
808 final var error2 = Math.abs(error2a - error2b);
809
810 return 0.5 * (error1 + error2);
811 } catch (final AlgebraException e) {
812 return Double.MAX_VALUE;
813 }
814 }
815
816 /**
817 * Sets list of homographies.
818 * This method does not check whether estimator is locked.
819 *
820 * @param homographies list of homographies to estimate IAC.
821 * @throws IllegalArgumentException if provided list of homographies is null
822 * or too small.
823 */
824 private void internalSetHomographies(final List<Transformation2D> homographies) {
825 if (homographies == null || homographies.size() < getMinNumberOfRequiredHomographies()) {
826 throw new IllegalArgumentException();
827 }
828
829 this.homographies = homographies;
830 }
831 }