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.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 --&lt; 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 }