View Javadoc
1   /*
2    * Copyright (C) 2017 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.epipolar.estimators;
17  
18  import com.irurueta.algebra.AlgebraException;
19  import com.irurueta.algebra.Matrix;
20  import com.irurueta.algebra.SingularValueDecomposer;
21  import com.irurueta.geometry.EuclideanTransformation3D;
22  import com.irurueta.geometry.InvalidRotationMatrixException;
23  import com.irurueta.geometry.MatrixRotation3D;
24  import com.irurueta.geometry.PinholeCameraIntrinsicParameters;
25  import com.irurueta.geometry.Transformation2D;
26  import com.irurueta.geometry.estimators.LockedException;
27  import com.irurueta.geometry.estimators.NotReadyException;
28  
29  import java.util.ArrayList;
30  import java.util.List;
31  
32  /**
33   * Decomposes a 2D homography to extract its internal geometry structure. There
34   * are four possible solutions, with two that are physically possible. The
35   * physically possible solution can be found by imposing a positive depth
36   * constraint (reconstructed points must lie in front of the cameras).
37   * An homography matrix is defined as H = (R + (1/d)*T*N<sup>T</sup>), where R
38   * is a 3x3 rotation matrix, d is the distance of the plane, N is the plane's
39   * normal, T is the translation vector. The decomposition works by computing the
40   * SVD of H<sup>T</sup>H and then following the procedure defined in
41   * O. Faugeras, Motion and structure from motion in a piecewise planar
42   * environment.
43   */
44  @SuppressWarnings("DuplicatedCode")
45  public class HomographyDecomposer {
46  
47      /**
48       * Number of inhomogeneous coordinates in 3D.
49       */
50      public static final int NUM_COORDS_3D = 3;
51  
52      /**
53       * Threshold to determine that two singular values are equal.
54       */
55      public static final double EQUAL_SINGULAR_VALUE_THRESHOLD = 1e-12;
56  
57      /**
58       * 2D transformation relating two views (left view to right view).
59       */
60      private Transformation2D homography;
61  
62      /**
63       * Intrinsic parameters to be used on left view.
64       */
65      private PinholeCameraIntrinsicParameters leftIntrinsics;
66  
67      /**
68       * Intrinsic parameters to be used on right view.
69       */
70      private PinholeCameraIntrinsicParameters rightIntrinsics;
71  
72      /**
73       * Listener to handle events raised by this instance.
74       */
75      private HomographyDecomposerListener listener;
76  
77      /**
78       * Indicates whether decomposer is locked while computing decomposition.
79       */
80      private boolean locked;
81  
82      /**
83       * Constructor.
84       */
85      public HomographyDecomposer() {
86      }
87  
88      /**
89       * Constructor.
90       *
91       * @param homography      2D transformation relating two views (left view to
92       *                        right view).
93       * @param leftIntrinsics  intrinsic parameters to be used on left view.
94       * @param rightIntrinsics intrinsic parameters to be used on right view.
95       */
96      public HomographyDecomposer(final Transformation2D homography,
97                                  final PinholeCameraIntrinsicParameters leftIntrinsics,
98                                  final PinholeCameraIntrinsicParameters rightIntrinsics) {
99          this.homography = homography;
100         this.leftIntrinsics = leftIntrinsics;
101         this.rightIntrinsics = rightIntrinsics;
102     }
103 
104     /**
105      * Constructor.
106      *
107      * @param homography      2D transformation relating two views (left view to
108      *                        right view).
109      * @param leftIntrinsics  intrinsic parameters to be used on left view.
110      * @param rightIntrinsics intrinsic parameters to be used on right view.
111      * @param listener        listener to attend events generated by this instance.
112      */
113     public HomographyDecomposer(final Transformation2D homography,
114                                 final PinholeCameraIntrinsicParameters leftIntrinsics,
115                                 final PinholeCameraIntrinsicParameters rightIntrinsics,
116                                 final HomographyDecomposerListener listener) {
117         this(homography, leftIntrinsics, rightIntrinsics);
118         this.listener = listener;
119     }
120 
121     /**
122      * Gets 2D transformation relating two views (left view to right view).
123      *
124      * @return 2D transformation relating two views.
125      */
126     public Transformation2D getHomography() {
127         return homography;
128     }
129 
130     /**
131      * Sets 2D transformation relating two views (left view to right view).
132      *
133      * @param homography 2D transformation relating two views.
134      * @throws LockedException if estimator is locked.
135      */
136     public void setHomography(final Transformation2D homography) throws LockedException {
137         if (isLocked()) {
138             throw new LockedException();
139         }
140         this.homography = homography;
141     }
142 
143     /**
144      * Gets intrinsic parameters to be used on left view.
145      *
146      * @return intrinsic parameters to be used on left view.
147      */
148     public PinholeCameraIntrinsicParameters getLeftIntrinsics() {
149         return leftIntrinsics;
150     }
151 
152     /**
153      * Sets intrinsic parameters to be used on left view.
154      *
155      * @param leftIntrinsics intrinsic parameters to be used on left view.
156      * @throws LockedException if estimator is locked.
157      */
158     public void setLeftIntrinsics(final PinholeCameraIntrinsicParameters leftIntrinsics) throws LockedException {
159         if (isLocked()) {
160             throw new LockedException();
161         }
162         this.leftIntrinsics = leftIntrinsics;
163     }
164 
165     /**
166      * Gets intrinsic parameters to be used on right view.
167      *
168      * @return intrinsic parameters to be used on right view.
169      */
170     public PinholeCameraIntrinsicParameters getRightIntrinsics() {
171         return rightIntrinsics;
172     }
173 
174     /**
175      * Sets intrinsic parameters to be used on right view.
176      *
177      * @param rightIntrinsics intrinsic parameters to be used on right view.
178      * @throws LockedException if estimator is locked.
179      */
180     public void setRightIntrinsics(final PinholeCameraIntrinsicParameters rightIntrinsics) throws LockedException {
181         if (isLocked()) {
182             throw new LockedException();
183         }
184         this.rightIntrinsics = rightIntrinsics;
185     }
186 
187     /**
188      * Gets listener to handle events raised by this instance.
189      *
190      * @return listener to handle events raised by this instance.
191      */
192     public HomographyDecomposerListener getListener() {
193         return listener;
194     }
195 
196     /**
197      * Sets listener to handle events raised by this instance.
198      *
199      * @param listener listener to handle events raised by this instance.
200      */
201     public void setListener(final HomographyDecomposerListener listener) {
202         this.listener = listener;
203     }
204 
205     /**
206      * Indicates whether estimator is locked while computing decomposition.
207      *
208      * @return true if decomposer is locked, false otherwise.
209      */
210     public boolean isLocked() {
211         return locked;
212     }
213 
214     /**
215      * Indicates whether decomposer is ready to start the decomposition when all
216      * required data has been provided.
217      *
218      * @return true if decomposer is ready, false otherwise.
219      */
220     public boolean isReady() {
221         return homography != null && leftIntrinsics != null && rightIntrinsics != null;
222     }
223 
224     /**
225      * Decomposes homography into possible solutions containing possible 3D
226      * rotation, 3D translation and normal and distance of the plane relating
227      * two views via provided homography.
228      *
229      * @return possible solutions.
230      * @throws LockedException               if decomposer is locked.
231      * @throws NotReadyException             if decomposer is not ready.
232      * @throws HomographyDecomposerException if decomposition fails for some
233      *                                       other reason (i.e. numerical instabilities).
234      */
235     public List<HomographyDecomposition> decompose() throws LockedException, NotReadyException,
236             HomographyDecomposerException {
237         final var result = new ArrayList<HomographyDecomposition>();
238         decompose(result);
239         return result;
240     }
241 
242     /**
243      * Decomposes homography into possible solutions containing possible 3D
244      * rotation, 3D translation and normal and distance of the plane relating
245      * two views via provided homography.
246      *
247      * @param result instance where possible solutions will be stored.
248      * @throws LockedException               if decomposer is locked.
249      * @throws NotReadyException             if decomposer is not ready.
250      * @throws HomographyDecomposerException if decomposition fails for some
251      *                                       other reason (i.e. numerical instabilities).
252      */
253     public void decompose(final List<HomographyDecomposition> result) throws LockedException, NotReadyException,
254             HomographyDecomposerException {
255         if (isLocked()) {
256             throw new LockedException();
257         }
258 
259         if (!isReady()) {
260             throw new NotReadyException();
261         }
262 
263         try {
264             locked = true;
265             result.clear();
266 
267             if (listener != null) {
268                 listener.onDecomposeStart(this);
269             }
270 
271             final var h = computeNormalizedCoordinatesHomographyMatrix();
272 
273             // Homography matrix H can be expressed as:
274             // H = d*R + t*n^T
275             // where d is the distance to the plane used to relate both views, R
276             // is a rotation relating both views so that R*R^T = I and
277             // det(R) = 1, t is the translation relating both views and n is the
278             // normal of the plane relating both views.
279 
280             // By using SVD decomposition, we get:
281             // H = U*A'*V^T
282 
283             // where U and V are orthonormal matrices and A' is a diagonal matrix
284             // containing singular values in decreasing order d1 >= d2 >= d3
285 
286             // Hence A' can also be expressed similarly to H as:
287             // A' = d'*R' + t'*n'^T, and the relation to d, R, t and n is as
288             // follows:
289             // H = U*(d'*R' + t'*n'^T)*V^T = d'*U*R'*V^T + U*t'*n'^T*V^T
290             // d = d'
291             // R = U*R'*V^T
292             // t = U*t'
293             // n^T = n'T*V^T --> n = V*n'
294 
295             final var svdDecomposer = new SingularValueDecomposer(h);
296             svdDecomposer.decompose();
297             final var u = svdDecomposer.getU();
298             final var singularValues = svdDecomposer.getSingularValues();
299             final var v = svdDecomposer.getV();
300 
301             final var n = new ArrayList<double[]>();
302             final var r = new ArrayList<Matrix>();
303             final var t = new ArrayList<double[]>();
304             final var d = new ArrayList<Double>();
305             final var numSolutions = decomposeAllFromSingularValues(singularValues, n, r, t, d);
306 
307             final var transV = v.transposeAndReturnNew();
308             final var translationMatrix = new Matrix(NUM_COORDS_3D, 1);
309             final var planeNormalMatrix = new Matrix(NUM_COORDS_3D, 1);
310 
311             for (var i = 0; i < numSolutions; i++) {
312                 // undo U, V decomposition
313 
314                 // R = U*R'*V^T
315                 final var denormalizedR = new Matrix(u);
316                 denormalizedR.multiply(r.get(i));
317                 denormalizedR.multiply(transV);
318 
319                 // t = U*t'
320                 translationMatrix.fromArray(t.get(i), true);
321                 final var denormalizedTranslationMatrix = u.multiplyAndReturnNew(translationMatrix);
322 
323                 // n = V*n'
324                 planeNormalMatrix.fromArray(n.get(i));
325                 final var denormalizedPlaneNormalMatrix = v.multiplyAndReturnNew(planeNormalMatrix);
326 
327                 final var denormalizedPlaneDistance = d.get(i);
328 
329                 // rotation
330                 final var denormalizedRotation = new MatrixRotation3D(denormalizedR);
331                 final var denormalizedTranslation = denormalizedTranslationMatrix.getBuffer();
332                 final var denormalizedPlaneNormal = denormalizedPlaneNormalMatrix.getBuffer();
333 
334                 // set rotation and translation
335                 final var transformation = new EuclideanTransformation3D(denormalizedRotation, denormalizedTranslation);
336 
337                 result.add(new HomographyDecomposition(transformation, denormalizedPlaneNormal,
338                         denormalizedPlaneDistance));
339             }
340         } catch (final InvalidRotationMatrixException | AlgebraException e) {
341             throw new HomographyDecomposerException(e);
342         } finally {
343             if (listener != null) {
344                 listener.onDecomposeEnd(this, result);
345             }
346             locked = false;
347         }
348     }
349 
350     /**
351      * Decompose solutions from singular values.
352      *
353      * @param singularValues input singular values.
354      * @param n              list containing possible plane normals.
355      * @param r              list containing possible camera rotations.
356      * @param t              list containing possible camera translations.
357      * @param d              list of distances to plane.
358      * @return number of solutions.
359      * @throws HomographyDecomposerException if decomposition fails (i.e. numerical instabilities, etc).
360      */
361     private int decomposeAllFromSingularValues(
362             final double[] singularValues,
363             final List<double[]> n,
364             final List<Matrix> r,
365             final List<double[]> t,
366             final List<Double> d)
367             throws HomographyDecomposerException {
368 
369         n.clear();
370         r.clear();
371         t.clear();
372         d.clear();
373 
374         if (areThreeDifferentSingularValues(singularValues)) {
375             // Three different singular values
376             final var n1 = new double[NUM_COORDS_3D];
377             final var n2 = new double[NUM_COORDS_3D];
378             final var n3 = new double[NUM_COORDS_3D];
379             final var n4 = new double[NUM_COORDS_3D];
380             Matrix r1 = null;
381             Matrix r2 = null;
382             Matrix r3 = null;
383             Matrix r4 = null;
384             try {
385                 r1 = new Matrix(NUM_COORDS_3D, NUM_COORDS_3D);
386                 r2 = new Matrix(NUM_COORDS_3D, NUM_COORDS_3D);
387                 r3 = new Matrix(NUM_COORDS_3D, NUM_COORDS_3D);
388                 r4 = new Matrix(NUM_COORDS_3D, NUM_COORDS_3D);
389             } catch (final AlgebraException ignore) {
390                 // never thrown
391             }
392 
393             final var t1 = new double[NUM_COORDS_3D];
394             final var t2 = new double[NUM_COORDS_3D];
395             final var t3 = new double[NUM_COORDS_3D];
396             final var t4 = new double[NUM_COORDS_3D];
397 
398             final var planeDistance1 = decomposeFromSingularValues(singularValues, n1, r1, t1, true,
399                     true);
400             final var planeDistance2 = decomposeFromSingularValues(singularValues, n2, r2, t2, false,
401                     true);
402             final var planeDistance3 = decomposeFromSingularValues(singularValues, n3, r3, t3, true,
403                     false);
404             final var planeDistance4 = decomposeFromSingularValues(singularValues, n4, r4, t4, false,
405                     false);
406 
407             n.add(n1);
408             n.add(n2);
409             n.add(n3);
410             n.add(n4);
411 
412             r.add(r1);
413             r.add(r2);
414             r.add(r3);
415             r.add(r4);
416 
417             t.add(t1);
418             t.add(t2);
419             t.add(t3);
420             t.add(t4);
421 
422             d.add(planeDistance1);
423             d.add(planeDistance2);
424             d.add(planeDistance3);
425             d.add(planeDistance4);
426 
427             return n.size();
428 
429         } else if (areTwoEqualSingularValues(singularValues)) {
430             // Two different singular values
431             final var n1 = new double[NUM_COORDS_3D];
432             final var n2 = new double[NUM_COORDS_3D];
433             Matrix r1 = null;
434             Matrix r2 = null;
435             try {
436                 r1 = new Matrix(NUM_COORDS_3D, NUM_COORDS_3D);
437                 r2 = new Matrix(NUM_COORDS_3D, NUM_COORDS_3D);
438             } catch (final AlgebraException ignore) {
439                 // never thrown
440             }
441 
442             final var t1 = new double[NUM_COORDS_3D];
443             final var t2 = new double[NUM_COORDS_3D];
444 
445             final var planeDistance1 = decomposeFromSingularValues(singularValues, n1, r1, t1, true,
446                     true);
447             final var planeDistance2 = decomposeFromSingularValues(singularValues, n2, r2, t2, true,
448                     false);
449 
450             n.add(n1);
451             n.add(n2);
452 
453             r.add(r1);
454             r.add(r2);
455 
456             t.add(t1);
457             t.add(t2);
458 
459             d.add(planeDistance1);
460             d.add(planeDistance2);
461 
462             return n.size();
463 
464         } else {
465             // Three equal singular values
466             throw new HomographyDecomposerException("undefined plane normal");
467         }
468     }
469 
470     /**
471      * Determines whether there are three different singular values or not.
472      *
473      * @param singularValues singular values to be checked.
474      * @return true if there are three different singular values, false
475      * otherwise.
476      */
477     private static boolean areThreeDifferentSingularValues(final double[] singularValues) {
478         final var d1 = singularValues[0];
479         final var d2 = singularValues[1];
480         final var d3 = singularValues[2];
481 
482         return (Math.abs(d1 - d2) > EQUAL_SINGULAR_VALUE_THRESHOLD)
483                 && (Math.abs(d2 - d3) > EQUAL_SINGULAR_VALUE_THRESHOLD);
484     }
485 
486     /**
487      * Determines whether there are two equal singular values or not.
488      *
489      * @param singularValues singular values to be checked.
490      * @return true if there are two equal singular values, false otherwise.
491      */
492     private static boolean areTwoEqualSingularValues(final double[] singularValues) {
493         final var d1 = singularValues[0];
494         final var d2 = singularValues[1];
495         final var d3 = singularValues[2];
496 
497         return ((Math.abs(d1 - d2) <= EQUAL_SINGULAR_VALUE_THRESHOLD)
498                 && (Math.abs(d2 - d3) > EQUAL_SINGULAR_VALUE_THRESHOLD))
499                 || ((Math.abs(d1 - d2) > EQUAL_SINGULAR_VALUE_THRESHOLD)
500                 && (Math.abs(d2 - d3) <= EQUAL_SINGULAR_VALUE_THRESHOLD));
501     }
502 
503     /**
504      * Decomposes one possible solution using provided singular values and signs
505      *
506      * @param singularValues singular values to use for
507      * @param n              array where plane normal will be store.
508      * @param r              matrix where rotation will be stored.
509      * @param t              array where translation will be stored.
510      * @param positive1      sign of 1st coordinate of plane normal.
511      * @param positive3      sign of 2nd coordinate of plane normal.
512      * @return plane distance.
513      * @throws HomographyDecomposerException if decomposition is undetermined
514      *                                       when all three singular values are equal.
515      */
516     private double decomposeFromSingularValues(
517             final double[] singularValues, final double[] n, final Matrix r, final double[] t, final boolean positive1,
518             final boolean positive3) throws HomographyDecomposerException {
519         final var d1 = singularValues[0];
520         final var d2 = singularValues[1];
521         final var d3 = singularValues[2];
522 
523         if (areThreeDifferentSingularValues(singularValues)) {
524             // Three different singular values
525             if (d2 > 0.0) {
526                 // Three different singular values d1 != d2 != d3 and d'= d2 > 0
527                 return decomposeFromThreeDifferentSingularValuesPositive(d1, d2, d3, n, r, t, positive1, positive3);
528             } else {
529                 // Three different singular values and d' = d2 < 0
530                 return decomposeFromThreeDifferentSingularValuesNegative(d1, d2, d3, n, r, t, positive1, positive3);
531             }
532         }
533         if (areTwoEqualSingularValues(singularValues)) {
534             // Two different singular values
535             if (d2 > 0.0) {
536                 // Two different singular values d1 = d2 != d3 or d1 != d2 = d3
537                 // and d2 > 0
538                 return decomposeFromTwoDifferentSingularValuesPositive(d1, d2, d3, n, r, t, positive3);
539             } else {
540                 // Two different singular values d1 = d2 != d3 or d1 != d2 = d3
541                 // and d2 < 0
542                 return decomposeFromTwoDifferentSingularValuesNegative(d1, d2, d3, n, r, t, positive3);
543             }
544         } else {
545             // Three equal singular values
546             throw new HomographyDecomposerException("undefined plane normal");
547         }
548     }
549 
550     /**
551      * Generates one homography decomposition using provided singular values and
552      * assuming that there are two equal singular values and that d2 is
553      * negative.
554      *
555      * @param d1        1st singular value.
556      * @param d2        2nd singular value.
557      * @param d3        3rd singular value.
558      * @param n         plane normal.
559      * @param r         rotation.
560      * @param t         translation.
561      * @param positive3 true to assume positive x3, false otherwise.
562      * @return distance to plane.
563      */
564     private double decomposeFromTwoDifferentSingularValuesNegative(
565             final double d1, final double d2, final double d3, final double[] n, final Matrix r, final double[] t,
566             final boolean positive3) {
567 
568         // fill plane normal solution
569         n[0] = 0.0;
570         n[1] = 0.0;
571         n[2] = positive3 ? 1.0 : -1.0;
572 
573         // compute rotation
574 
575         // fill rotation matrix
576         r.setElementAt(0, 0, -1.0);
577         r.setElementAt(1, 0, 0.0);
578         r.setElementAt(2, 0, 0.0);
579 
580         r.setElementAt(0, 1, 0.0);
581         r.setElementAt(1, 1, -1.0);
582         r.setElementAt(2, 1, 0.0);
583 
584         r.setElementAt(0, 2, 0.0);
585         r.setElementAt(1, 2, 0.0);
586         r.setElementAt(2, 2, 1.0);
587 
588         // compute translation
589         final var sum = d3 + d1;
590         t[0] = 0.0;
591         t[1] = 0.0;
592         t[2] = sum * n[2];
593 
594         // plane distance
595         return d2;
596     }
597 
598     /**
599      * Generates one homography decomposition using provided singular values and
600      * assuming that there are two equal singular values and that d2 is
601      * positive.
602      *
603      * @param d1        1st singular value.
604      * @param d2        2nd singular value.
605      * @param d3        3rd singular value.
606      * @param n         plane normal.
607      * @param r         rotation.
608      * @param t         translation.
609      * @param positive3 true to assume positive x3, false otherwise.
610      * @return distance to plane.
611      */
612     private double decomposeFromTwoDifferentSingularValuesPositive(
613             final double d1, final double d2, final double d3, final double[] n, final Matrix r, final double[] t,
614             final boolean positive3) {
615 
616         // fill plane normal solution
617         n[0] = 0.0;
618         n[1] = 0.0;
619         n[2] = positive3 ? 1.0 : -1.0;
620 
621         // compute rotation
622 
623         //fill rotation matrix
624         r.setElementAt(0, 0, 1.0);
625         r.setElementAt(1, 0, 0.0);
626         r.setElementAt(2, 0, 0.0);
627 
628         r.setElementAt(0, 1, 0.0);
629         r.setElementAt(1, 1, 1.0);
630         r.setElementAt(2, 1, 0.0);
631 
632         r.setElementAt(0, 2, 0.0);
633         r.setElementAt(1, 2, 0.0);
634         r.setElementAt(2, 2, 1.0);
635 
636         // compute translation
637         final var diff = d1 - d3;
638         t[0] = 0.0;
639         t[1] = 0.0;
640         t[2] = -diff * n[2];
641 
642         // plane distance
643         return d2;
644     }
645 
646     /**
647      * Generates one homography decomposition using provided singular values and
648      * assuming that there are three different singular values and that d2 is
649      * negative.
650      *
651      * @param d1        1st singular value.
652      * @param d2        2nd singular value.
653      * @param d3        3rd singular value.
654      * @param n         plane normal.
655      * @param r         rotation.
656      * @param t         translation.
657      * @param positive1 true to assume positive x1, false otherwise.
658      * @param positive3 true to assume positive x3, false otherwise.
659      * @return distance to plane.
660      */
661     private double decomposeFromThreeDifferentSingularValuesNegative(
662             final double d1, final double d2, final double d3, final double[] n, final Matrix r, final double[] t,
663             final boolean positive1, final boolean positive3) {
664         final var d1Sqr = d1 * d1;
665         final var d2Sqr = d2 * d2;
666         final var d3Sqr = d3 * d3;
667 
668         // compute plane normal
669         final var denom = d1Sqr - d3Sqr;
670 
671         var x1 = Math.sqrt((d1Sqr - d2Sqr) / denom);
672         if (!positive1) {
673             x1 = -x1;
674         }
675 
676         final var x2 = 0.0;
677 
678         var x3 = Math.sqrt((d2Sqr - d3Sqr) / denom);
679         if (!positive3) {
680             x3 = -x3;
681         }
682 
683         // fill plane normal solution
684         n[0] = x1;
685         n[1] = x2;
686         n[2] = x3;
687 
688         // compute rotation
689         final var x1Sqr = x1 * x1;
690         final var x3Sqr = x3 * x3;
691 
692         final var sinTheta = (d1 + d3) * x1 * x3 / d2;
693         final var cosTheta = (d3 * x1Sqr - d1 * x3Sqr) / d2;
694 
695         // fill rotation matrix
696         r.setElementAt(0, 0, cosTheta);
697         r.setElementAt(1, 0, 0.0);
698         r.setElementAt(2, 0, sinTheta);
699 
700         r.setElementAt(0, 1, 0.0);
701         r.setElementAt(1, 1, 1.0);
702         r.setElementAt(2, 1, 0.0);
703 
704         r.setElementAt(0, 2, -sinTheta);
705         r.setElementAt(1, 2, 0.0);
706         r.setElementAt(2, 2, cosTheta);
707 
708         // compute translation
709         final var sum = d1 + d3;
710         t[0] = sum * x1;
711         t[1] = 0.0;
712         t[2] = sum * x3;
713 
714         // plane distance
715         return d2;
716     }
717 
718     /**
719      * Generates one homography decomposition using provided singular values and
720      * assuming that there are three different singular values and that d2 is
721      * positive.
722      *
723      * @param d1        1st singular value.
724      * @param d2        2nd singular value.
725      * @param d3        3rd singular value.
726      * @param n         plane normal.
727      * @param r         rotation.
728      * @param t         translation.
729      * @param positive1 true to assume positive x1, false otherwise.
730      * @param positive3 true to assume positive x3, false otherwise.
731      * @return distance to plane.
732      */
733     private double decomposeFromThreeDifferentSingularValuesPositive(
734             final double d1, final double d2, final double d3, final double[] n, final Matrix r, final double[] t,
735             final boolean positive1, final boolean positive3) {
736         final var d1Sqr = d1 * d1;
737         final var d2Sqr = d2 * d2;
738         final var d3Sqr = d3 * d3;
739 
740         // compute plane normal
741         final var denom = d1Sqr - d3Sqr;
742 
743         var x1 = Math.sqrt((d1Sqr - d2Sqr) / denom);
744         if (!positive1) {
745             x1 = -x1;
746         }
747 
748         final var x2 = 0.0;
749 
750         var x3 = Math.sqrt((d2Sqr - d3Sqr) / denom);
751         if (!positive3) {
752             x3 = -x3;
753         }
754 
755         // fill plane normal solution
756         n[0] = x1;
757         n[1] = x2;
758         n[2] = x3;
759 
760         // compute rotation
761         final var x1Sqr = x1 * x1;
762         final var x3Sqr = x3 * x3;
763 
764         final var sinTheta = (d1 - d3) * x1 * x3 / d2;
765         final var cosTheta = (d1 * x3Sqr + d3 * x1Sqr) / d2;
766 
767         // fill rotation matrix
768         r.setElementAt(0, 0, cosTheta);
769         r.setElementAt(1, 0, 0.0);
770         r.setElementAt(2, 0, sinTheta);
771 
772         r.setElementAt(0, 1, 0.0);
773         r.setElementAt(1, 1, 1.0);
774         r.setElementAt(2, 1, 0.0);
775 
776         r.setElementAt(0, 2, -sinTheta);
777         r.setElementAt(1, 2, 0.0);
778         r.setElementAt(2, 2, cosTheta);
779 
780 
781         // compute translation
782         final var diff = d1 - d3;
783         t[0] = diff * x1;
784         t[1] = 0.0;
785         t[2] = -diff * x3;
786 
787         // plane distance
788         return d2;
789     }
790 
791     /**
792      * Computes homography matrix in terms of normalized point coordinates by
793      * taking into account intrinsic camera parameters on left and right views.
794      *
795      * @return normalized homography matrix
796      * @throws AlgebraException if there are numerical instabilities.
797      */
798     private Matrix computeNormalizedCoordinatesHomographyMatrix() throws AlgebraException {
799         // we know that point p1 in the left view is related to point p2
800         // in the right view by homography G so that:
801         // p2 = G*p1
802         // where:
803         // p1 = K1*m1
804         // p2 = K2*m2
805         // where K1 and K2 are intrinsic parameters on left and right views
806         // and m1, m2 are normalized point coordinates on left and right
807         // views.
808         // Hence:
809         // K2*m2 = G*K1*m1 --> m2 = K2^-1*G*K1*m1
810         // and so we obtain:
811         // H = K2^-1*G*K1, which is an homography in normalized coordinates
812 
813         final var k1 = leftIntrinsics.getInternalMatrix();
814         final var invK2 = rightIntrinsics.getInverseInternalMatrix();
815         final var g = homography.asMatrix();
816 
817         // compute H = K2^-1*G*K1
818         g.multiply(k1);
819         invK2.multiply(g);
820         return invK2;
821     }
822 }