View Javadoc
1   /*
2    * Copyright (C) 2012 Alberto Irurueta Carro (alberto@irurueta.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.irurueta.geometry;
17  
18  import com.irurueta.algebra.AlgebraException;
19  import com.irurueta.algebra.ArrayUtils;
20  import com.irurueta.algebra.Matrix;
21  import com.irurueta.algebra.RQDecomposer;
22  import com.irurueta.algebra.SingularValueDecomposer;
23  import com.irurueta.algebra.Utils;
24  import com.irurueta.algebra.WrongSizeException;
25  import com.irurueta.geometry.estimators.DLTLinePlaneCorrespondencePinholeCameraEstimator;
26  import com.irurueta.geometry.estimators.DLTPointCorrespondencePinholeCameraEstimator;
27  import com.irurueta.geometry.estimators.LinePlaneCorrespondencePinholeCameraEstimator;
28  import com.irurueta.geometry.estimators.PointCorrespondencePinholeCameraEstimator;
29  
30  import java.io.Serializable;
31  import java.util.ArrayList;
32  import java.util.List;
33  
34  /**
35   * This class implements the behavior of a pinhole camera.
36   * A pinhole camera is a linear mapping between 3D and 2D worlds.
37   * Pinhole cameras only take into account translation, rotation and camera
38   * intrinsic parameters such as focal length, aspect ratio, skewness and
39   * principal point.
40   * Pinhole cameras perform projective mappings between 3D and 2D worlds,
41   * in other words, the farther an object is, the smaller is represented or
42   * parallel lines converge into vanishing points.
43   * Pinhole cameras cannot be used for orthographic projections (where
44   * parallelism between lines is preserved and there are no vanishing points).
45   */
46  @SuppressWarnings("DuplicatedCode")
47  public class PinholeCamera extends Camera implements Serializable {
48  
49      /**
50       * Defines the number of rows of a pinhole camera.
51       */
52      public static final int PINHOLE_CAMERA_MATRIX_ROWS = 3;
53  
54      /**
55       * Defines the number of columns of a pinhole camera.
56       */
57      public static final int PINHOLE_CAMERA_MATRIX_COLS = 4;
58  
59      /**
60       * Constant defining the number of inhomogeneous coordinates.
61       */
62      public static final int INHOM_COORDS = 3;
63  
64      /**
65       * Constant defining a tiny value close to machine precision.
66       */
67      public static final double EPS = 1e-12;
68  
69      /**
70       * Indicates if camera should be decomposed into intrinsic parameters and
71       * rotation by default after creation or setting new parameters.
72       */
73      private static final boolean DEFAULT_DECOMPOSE_INTRINSICS_AND_ROTATION =
74              true;
75  
76      /**
77       * Indicates if camera should be decomposed to obtain its center after
78       * creation or setting new parameters.
79       */
80      private static final boolean DEFAULT_DECOMPOSE_CAMERA_CENTER = true;
81  
82      /**
83       * Threshold to determine whether a point is in front or behind the camera.
84       */
85      private static final double FRONT_THRESHOLD = 0.0;
86  
87      /**
88       * Threshold to determine camera sign. If camera sign is negative, its sign
89       * must be fixed (multiplying its matrix by -1) so that point cheirality
90       * to determine whether points are in front or behind the camera can be
91       * correctly determined. When sign is reversed, cheirality gets reversed
92       * too. Notice that camera matrix is expressed in homogeneous coordinates,
93       * hence multiplying it by -1.0 has no effect on point projection.
94       */
95      private static final double SIGN_THRESHOLD = 0.0;
96  
97      /**
98       * Internal matrix defining this camera.
99       */
100     private Matrix internalMatrix;
101 
102     /**
103      * Boolean indicating whether this camera has already been normalized.
104      * Normalization can help to increase numerical accuracy on camera
105      * computations.
106      */
107     private boolean normalized;
108 
109     /**
110      * Boolean indicating whether camera sign has been fixed (it is 1.0).
111      * When camera sign is negative, cheirality is reversed, hence it cannot be
112      * correctly determined whether points are located in front or behind the
113      * camera.
114      */
115     private boolean cameraSignFixed;
116 
117     /**
118      * Intrinsic parameters of the camera after decomposition.
119      */
120     private PinholeCameraIntrinsicParameters intrinsicParameters;
121 
122     /**
123      * 3D rotation of the camera after decomposition.
124      */
125     private Rotation3D cameraRotation;
126 
127     /**
128      * Camera center after decomposition.
129      */
130     private Point3D cameraCenter;
131 
132     /**
133      * Constructor.
134      * Creates a canonical camera, which is equal to the identity 3x4 matrix.
135      */
136     public PinholeCamera() {
137         super();
138         normalized = false;
139         cameraSignFixed = false;
140 
141         intrinsicParameters = null;
142         cameraRotation = null;
143         cameraCenter = null;
144 
145         try {
146             internalMatrix = Matrix.identity(PINHOLE_CAMERA_MATRIX_ROWS, PINHOLE_CAMERA_MATRIX_COLS);
147         } catch (final WrongSizeException ignore) {
148             // never happens
149         }
150     }
151 
152     /**
153      * Constructor.
154      * Creates a camera using provided matrix.
155      *
156      * @param internalMatrix matrix to create the camera from.
157      * @throws WrongSizeException If provided matrix is not 3x4.
158      */
159     public PinholeCamera(final Matrix internalMatrix) throws WrongSizeException {
160         super();
161         normalized = false;
162         cameraSignFixed = false;
163 
164         intrinsicParameters = null;
165         cameraRotation = null;
166         cameraCenter = null;
167 
168         setInternalMatrix(internalMatrix);
169     }
170 
171     /**
172      * Constructor.
173      * Creates a camera using provided intrinsic parameters, 3D rotation and
174      * 2D coordinates of the world origin.
175      *
176      * @param intrinsicParameters    Intrinsic parameters of the camera.
177      * @param rotation               3D rotation of the camera.
178      * @param originImageCoordinates 2D coordinates of the world origin.
179      */
180     public PinholeCamera(
181             final PinholeCameraIntrinsicParameters intrinsicParameters, final Rotation3D rotation,
182             final Point2D originImageCoordinates) {
183         super();
184         normalized = false;
185         cameraSignFixed = false;
186 
187         this.intrinsicParameters = null;
188         cameraRotation = null;
189         cameraCenter = null;
190         try {
191             internalMatrix = Matrix.identity(PINHOLE_CAMERA_MATRIX_ROWS, PINHOLE_CAMERA_MATRIX_COLS);
192         } catch (final WrongSizeException ignore) {
193             // this will never happen
194         }
195 
196         setIntrinsicAndExtrinsicParameters(intrinsicParameters, rotation, originImageCoordinates);
197     }
198 
199     /**
200      * Constructor.
201      * Creates a camera using provided intrinsic parameters, 3D rotation and
202      * 3D coordinates of the camera center.
203      *
204      * @param intrinsicParameters Intrinsic parameters of the camera.
205      * @param rotation            3D rotation of the camera.
206      * @param cameraCenter        3D coordinates of the camera center.
207      */
208     public PinholeCamera(final PinholeCameraIntrinsicParameters intrinsicParameters, final Rotation3D rotation,
209                          final Point3D cameraCenter) {
210         super();
211         normalized = false;
212         cameraSignFixed = false;
213 
214         this.intrinsicParameters = null;
215         cameraRotation = null;
216         this.cameraCenter = null;
217         try {
218             internalMatrix = Matrix.identity(PINHOLE_CAMERA_MATRIX_ROWS, PINHOLE_CAMERA_MATRIX_COLS);
219         } catch (final WrongSizeException ignore) {
220             // this will never happen
221         }
222 
223         setIntrinsicAndExtrinsicParameters(intrinsicParameters, rotation, cameraCenter);
224     }
225 
226     /**
227      * Creates an instance of a pinhole camera by estimating its parameters from
228      * 2D-3D point correspondences.
229      *
230      * @param point3D1 1st 3D point.
231      * @param point3D2 2nd 3D point.
232      * @param point3D3 3rd 3D point.
233      * @param point3D4 4th 3D point.
234      * @param point3D5 5th 3D point.
235      * @param point3D6 6th 3D point.
236      * @param point2D1 1st 2D point corresponding to the projection of 1st 3D
237      *                 point.
238      * @param point2D2 2nd 2D point corresponding to the projection of 2nd 3D
239      *                 point.
240      * @param point2D3 3rd 2D point corresponding to the projection of 3rd 3D
241      *                 point.
242      * @param point2D4 4th 2D point corresponding to the projection of 4th 3D
243      *                 point.
244      * @param point2D5 5th 2D point corresponding to the projection of 5th 3D
245      *                 point.
246      * @param point2D6 6th 2D point corresponding to the projection of 6th 3D
247      *                 point.
248      * @throws CameraException if camera cannot be estimated using provided
249      *                         points because of a degeneracy.
250      */
251     public PinholeCamera(
252             final Point3D point3D1, final Point3D point3D2, final Point3D point3D3, final Point3D point3D4,
253             final Point3D point3D5, final Point3D point3D6, final Point2D point2D1, final Point2D point2D2,
254             final Point2D point2D3, final Point2D point2D4, final Point2D point2D5, final Point2D point2D6)
255             throws CameraException {
256         super();
257         normalized = false;
258         cameraSignFixed = false;
259 
260         intrinsicParameters = null;
261         cameraRotation = null;
262         cameraCenter = null;
263 
264         try {
265             internalMatrix = Matrix.identity(PINHOLE_CAMERA_MATRIX_ROWS, PINHOLE_CAMERA_MATRIX_COLS);
266         } catch (final WrongSizeException ignore) {
267             // never happens
268         }
269 
270         setFromPointCorrespondences(point3D1, point3D2, point3D3, point3D4, point3D5, point3D6, point2D1, point2D2,
271                 point2D3, point2D4, point2D5, point2D6);
272     }
273 
274     /**
275      * Creates an instance of a pinhole camera by estimating its parameters from
276      * line/plane correspondences.
277      *
278      * @param plane1 1st 3D plane.
279      * @param plane2 2nd 3D plane.
280      * @param plane3 3rd 3D plane.
281      * @param plane4 4th 3D plane.
282      * @param line1  1st 2D line corresponding to 1st 3D plane.
283      * @param line2  2nd 2D line corresponding to 2nd 3D plane.
284      * @param line3  3rd 2D line corresponding to 3rd 3D plane.
285      * @param line4  4th 2D line corresponding to 4th 3D plane.
286      * @throws CameraException if camera cannot be estimated using provided
287      *                         lines and planes because of a degeneracy.
288      */
289     public PinholeCamera(
290             final Plane plane1, final Plane plane2, final Plane plane3, final Plane plane4, final Line2D line1,
291             final Line2D line2, final Line2D line3, final Line2D line4) throws CameraException {
292         super();
293         normalized = false;
294         cameraSignFixed = false;
295 
296         intrinsicParameters = null;
297         cameraRotation = null;
298         cameraCenter = null;
299 
300         try {
301             internalMatrix = Matrix.identity(PINHOLE_CAMERA_MATRIX_ROWS, PINHOLE_CAMERA_MATRIX_COLS);
302         } catch (final WrongSizeException ignore) {
303             // never happens
304         }
305 
306         setFromLineAndPlaneCorrespondences(plane1, plane2, plane3, plane4, line1, line2, line3, line4);
307     }
308 
309 
310     /**
311      * Projects a 3D point into a 2D point in a retinal plane.
312      *
313      * @param inputPoint 3D point to be projected.
314      * @param result     2D projected point.
315      */
316     @Override
317     public void project(final Point3D inputPoint, final Point2D result) {
318         // convert input point to homogeneous and normalize to increase accuracy
319         final var point3D = new HomogeneousPoint3D(inputPoint);
320         point3D.normalize();
321         // normalize this camera to increase accuracy
322         normalize();
323 
324         try {
325             // copy point3D coordinates in a matrix
326             final var m3D = new Matrix(Point3D.POINT3D_HOMOGENEOUS_COORDINATES_LENGTH, 1);
327             m3D.setElementAtIndex(0, point3D.getHomX());
328             m3D.setElementAtIndex(1, point3D.getHomY());
329             m3D.setElementAtIndex(2, point3D.getHomZ());
330             m3D.setElementAtIndex(3, point3D.getHomW());
331 
332             // make product of 3D point column matrix with pinhole camera
333             // internal matrix
334             final var m = internalMatrix.multiplyAndReturnNew(m3D);
335 
336             result.setHomogeneousCoordinates(m.getElementAtIndex(0), m.getElementAtIndex(1), m.getElementAtIndex(2));
337             // to increase accuracy
338             result.normalize();
339         } catch (final WrongSizeException ignore) {
340             // never happens
341         }
342     }
343 
344     /**
345      * Back-projects a line into a plane and stores the result into provided
346      * instance.
347      *
348      * @param line   2D line to be back-projected.
349      * @param result Instance where computed back-projected 3D plane data is stored.
350      */
351     @Override
352     public void backProject(final Line2D line, final Plane result) {
353 
354         // normalize input line and camera to increase accuracy
355         line.normalize();
356         normalize();
357 
358         try {
359             final var l = new Matrix(Line2D.LINE_NUMBER_PARAMS, 1);
360             l.setElementAtIndex(0, line.getA());
361             l.setElementAtIndex(1, line.getB());
362             l.setElementAtIndex(2, line.getC());
363 
364             // Compute transposed of pinhole camera matrix
365             final var m = internalMatrix.transposeAndReturnNew();
366             // PLANE = P^T * l
367             m.multiply(l);
368 
369             // set coordinates on plane
370             result.setParameters(m.getElementAtIndex(0), m.getElementAtIndex(1), m.getElementAtIndex(2),
371                     m.getElementAtIndex(3));
372             // to increase accuracy
373             result.normalize();
374         } catch (final WrongSizeException ignore) {
375             // never happens
376         }
377     }
378 
379     /**
380      * Back-projects provided 2D point into a 3D point and stores the result into
381      * provided instance.
382      * Notice that estimated solution is not unique, since back-projecting a 2D
383      * point results in an infinite number of 3D points located in the same
384      * ray of light.
385      * This method only computes one possible solution. Any other solution can
386      * be computed as a linear combination between the camera center and the
387      * estimated back-projected point.
388      *
389      * @param point  2D point to be back-projected.
390      * @param result Instance where back-projected 3D point data will be stored.
391      * @throws CameraException thrown if 2D point cannot be back-projected
392      *                         because camera is degenerate.
393      */
394     @Override
395     public void backProject(final Point2D point, final Point3D result) throws CameraException {
396         // convert to homogeneous and normalize to increase accuracy
397         final var p = new HomogeneousPoint2D(point);
398         p.normalize();
399         // normalize camera to increase accuracy
400         normalize();
401 
402         try {
403             final var m = new Matrix(Point2D.POINT2D_HOMOGENEOUS_COORDINATES_LENGTH, 1);
404             m.setElementAtIndex(0, p.getHomX());
405             m.setElementAtIndex(1, p.getHomY());
406             m.setElementAtIndex(2, p.getHomW());
407 
408             // Compute pseudo-inverse of internal matrix
409             final var pseudoInverseInternalMatrix = Utils.pseudoInverse(internalMatrix);
410 
411             // normalize pseudo-inverse
412             final var norm = Utils.normF(pseudoInverseInternalMatrix);
413             pseudoInverseInternalMatrix.multiplyByScalar(1.0 / norm);
414 
415             // back-projected point (ray of light) is the product of pseudo-inverse
416             // of internal matrix with image point matrix
417             pseudoInverseInternalMatrix.multiply(m);
418 
419             result.setHomogeneousCoordinates(
420                     pseudoInverseInternalMatrix.getElementAtIndex(0),
421                     pseudoInverseInternalMatrix.getElementAtIndex(1),
422                     pseudoInverseInternalMatrix.getElementAtIndex(2),
423                     pseudoInverseInternalMatrix.getElementAtIndex(3));
424         } catch (final AlgebraException e) {
425             throw new CameraException(e);
426         }
427     }
428 
429     /**
430      * Back-projects a 2D conic into a 3D quadric and stores the result into
431      * provided instance.
432      *
433      * @param conic  2D conic to be back-projected.
434      * @param result Instance where data of back-projected 3D quadric will be stored.
435      */
436     @Override
437     public void backProject(final Conic conic, final Quadric result) {
438         // We need to compute:
439         // Q = P^T * C * P
440 
441         // normalize conic and camera to increase accuracy
442         conic.normalize();
443         normalize();
444 
445         // get transposed internal matrix
446         final var m = internalMatrix.transposeAndReturnNew();
447         try {
448             // multiply by conic
449             m.multiply(conic.asMatrix());
450             // and then by internal matrix
451             m.multiply(internalMatrix);
452             // resulting matrix m is the internal matrix of a quadric
453             result.setParameters(m);
454         } catch (final WrongSizeException | NonSymmetricMatrixException ignore) {
455             // never happens
456         }
457     }
458 
459     /**
460      * Projects a 3D dual quadric into a 2D dual conic and stores the result
461      * into provided instance.
462      *
463      * @param dualQuadric 3D dual quadric to be projected.
464      * @param result      Instance where data of projected 2D dual conic will be
465      *                    stored.
466      */
467     @Override
468     public void project(final DualQuadric dualQuadric, final DualConic result) {
469         // We need to compute:
470         // C^-1 = P * Q^-1 * P^T
471         dualQuadric.normalize();
472         normalize();
473 
474         try {
475             var m = internalMatrix.multiplyAndReturnNew(dualQuadric.asMatrix());
476             m.multiply(internalMatrix.transposeAndReturnNew());
477             // resulting matrix m is the internal matrix of a dual conic
478             result.setParameters(m);
479         } catch (final WrongSizeException | NonSymmetricMatrixException ignore) {
480             //never happens
481         }
482     }
483 
484     /**
485      * Returns the type of this camera, which is always PINHOLE_CAMERA for
486      * instance of this class.
487      *
488      * @return Type of this camera.
489      */
490     @Override
491     public CameraType getType() {
492         return CameraType.PINHOLE_CAMERA;
493     }
494 
495     /**
496      * Decomposes current camera matrix to determine its intrinsic and extrinsic
497      * parameters (rotation and translation).
498      *
499      * @throws CameraException thrown if camera matrix is degenerate.
500      */
501     public void decompose() throws CameraException {
502         decompose(DEFAULT_DECOMPOSE_INTRINSICS_AND_ROTATION);
503     }
504 
505     /**
506      * Decomposes current camera matrix to determine its camera center.
507      * Intrinsic parameters and rotation will be decomposed as well depending on
508      * provided value.
509      *
510      * @param decomposeIntrinsicsAndRotation if true, intrinsic parameters and
511      *                                       rotation are computed as well.
512      * @throws CameraException thrown if camera matrix is degenerate.
513      */
514     public void decompose(final boolean decomposeIntrinsicsAndRotation) throws CameraException {
515         decompose(decomposeIntrinsicsAndRotation, DEFAULT_DECOMPOSE_CAMERA_CENTER);
516     }
517 
518     /**
519      * Decomposes current camera matrix.
520      * Intrinsic parameters, rotation and camera center will be computed
521      * depending on provided values.
522      *
523      * @param decomposeIntrinsicsAndRotation if true, intrinsic parameters and
524      *                                       rotation are computed as well.
525      * @param decomposeCameraCenter          if true, camera center is computed as well.
526      * @throws CameraException thrown if camera matrix is degenerate.
527      */
528     public void decompose(final boolean decomposeIntrinsicsAndRotation, final boolean decomposeCameraCenter)
529             throws CameraException {
530         // clean up previous intrinsics, rotation and camera center
531         intrinsicParameters = null;
532         cameraRotation = null;
533         cameraCenter = null;
534         if (decomposeIntrinsicsAndRotation) {
535             // compute new intrinsics and rotation
536             computeIntrinsicsAndRotation();
537         }
538         if (decomposeCameraCenter) {
539             // compute camera center
540             if (cameraCenter == null) {
541                 cameraCenter = Point3D.create(CoordinatesType.HOMOGENEOUS_COORDINATES);
542             }
543             computeCameraCenterSVD(cameraCenter);
544         }
545     }
546 
547     /**
548      * Normalizes camera matrix.
549      * Normalization can help to increase accuracy on camera operations.
550      * This method should only be called when an increase on accuracy is needed
551      * to save the additional computational cost.
552      * Notice that affine pinhole cameras are never normalized, since elements
553      * to be used for normalization have norm equal to zero in such case.
554      */
555     public void normalize() {
556         if (!normalized) {
557             // normalize camera matrix using norm of P31, P32 and P33, which is
558             // equal to a row of rotation, which in a canonical sense should have
559             // unitary norm
560             final var p20 = internalMatrix.getElementAt(2, 0);
561             final var p21 = internalMatrix.getElementAt(2, 1);
562             final var p22 = internalMatrix.getElementAt(2, 2);
563             var norm = Math.sqrt(p20 * p20 + p21 * p21 + p22 * p22);
564             if (Math.abs(norm) <= EPS) {
565                 // camera is affine, so we use whole matrix norm
566                 norm = Utils.normF(internalMatrix);
567             }
568             internalMatrix.multiplyByScalar(1.0 / norm);
569             normalized = true;
570         }
571     }
572 
573     /**
574      * Indicates if camera matrix has already been normalized.
575      * Notice that this value will be set to false when any camera parameter is
576      * modified
577      *
578      * @return true if camera is normalized, false otherwise
579      */
580     public boolean isNormalized() {
581         return normalized;
582     }
583 
584     /**
585      * Fixes the camera sign so that point cheirality can be correctly
586      * determined. Cheirality indicates if points are located in front or behind
587      * the camera.
588      * Sign needs to be fixed if it is negative.
589      *
590      * @throws CameraException thrown if there are numerical instabilities.
591      */
592     public void fixCameraSign() throws CameraException {
593         if (isCameraSignFixed()) {
594             return;
595         }
596 
597         // use sign of determinant to fix pinhole camera matrix sign
598         final var cameraSign = getCameraSign();
599 
600         internalMatrix.multiplyByScalar(cameraSign);
601         cameraSignFixed = true;
602     }
603 
604     /**
605      * Indicates if camera sign has been fixed.
606      * When camera sign has been fixed cheirality can be correctly determined.
607      * Cheirality indicates if points are located in front or behind the camera.
608      * Notice that when camera parameters are modified, this parameter is set to
609      * false again.
610      *
611      * @return true if camera sign has been fixed, false otherwise.
612      */
613     public boolean isCameraSignFixed() {
614         return cameraSignFixed;
615     }
616 
617     /**
618      * Returns camera rotation if camera has already been decomposed and
619      * rotation is available.
620      *
621      * @return camera rotation.
622      * @throws NotAvailableException if camera rotation is not available yet.
623      */
624     public Rotation3D getCameraRotation() throws NotAvailableException {
625         if (!isCameraRotationAvailable()) {
626             throw new NotAvailableException();
627         }
628 
629         return cameraRotation;
630     }
631 
632     /**
633      * Returns camera intrinsic parameters if camera has already been decomposed
634      * and intrinsic parameters are available.
635      * Intrinsic parameters contain information related to internal parameters
636      * of a camera, usually related to the camera lens and sensor.
637      *
638      * @return camera intrinsic parameters.
639      * @throws NotAvailableException if camera intrinsic parameters are not
640      *                               available yet.
641      */
642     public PinholeCameraIntrinsicParameters getIntrinsicParameters() throws NotAvailableException {
643         if (!areIntrinsicParametersAvailable()) {
644             throw new NotAvailableException();
645         }
646 
647         return intrinsicParameters;
648     }
649 
650     /**
651      * Returns a copy of internal matrix to avoid malicious modifications.
652      *
653      * @return a copy of the internal camera matrix.
654      */
655     public Matrix getInternalMatrix() {
656         return new Matrix(internalMatrix);
657     }
658 
659     /**
660      * Sets internal matrix of this camera. Internal matrix of a pinhole camera
661      * must have size 3x4 (i.e. 3 rows and 4 columns).
662      *
663      * @param internalMatrix internal matrix to be set
664      * @throws WrongSizeException if provided matrix doesn't have size 3x4.
665      */
666     public final void setInternalMatrix(final Matrix internalMatrix) throws WrongSizeException {
667         if (internalMatrix.getRows() != PINHOLE_CAMERA_MATRIX_ROWS
668                 || internalMatrix.getColumns() != PINHOLE_CAMERA_MATRIX_COLS) {
669             throw new WrongSizeException();
670         }
671 
672         this.internalMatrix = internalMatrix;
673         cameraSignFixed = false;
674         intrinsicParameters = null;
675         cameraRotation = null;
676         cameraCenter = null;
677         normalized = false;
678     }
679 
680     /**
681      * Indicates if camera intrinsic parameters are available for retrieval.
682      * Intrinsic parameters become available after camera decomposition (if
683      * intrinsic parameters are requested). And become unavailable when any
684      * camera parameters are modified.
685      *
686      * @return true if camera intrinsic parameters are available, false
687      * otherwise.
688      */
689     public boolean areIntrinsicParametersAvailable() {
690         return intrinsicParameters != null;
691     }
692 
693     /**
694      * Indicates if camera rotation is available for retrieval.
695      * Camera rotation become available after camera decomposition (if camera
696      * rotation is requested). And become unavailable when any camera parameters
697      * are modified.
698      *
699      * @return true if camera rotation is available, false otherwise.
700      */
701     public boolean isCameraRotationAvailable() {
702         return cameraRotation != null;
703     }
704 
705     /**
706      * Sets camera rotation of this camera.
707      * When setting rotation the camera sign becomes unknown and the camera
708      * becomes non-normalized.
709      *
710      * @param cameraRotation Camera 3D rotation to be set.
711      * @throws CameraException if there are numerical instabilities.
712      */
713     public void setCameraRotation(final Rotation3D cameraRotation) throws CameraException {
714         try {
715             if (!areIntrinsicParametersAvailable()) {
716                 // Find intrinsic parameters
717                 computeIntrinsicsAndRotation();
718             }
719 
720             final var k = intrinsicParameters.getInternalMatrix();
721             final var r = cameraRotation.asInhomogeneousMatrix();
722 
723             // compute new left 3x3 sub-matrix of pinhole camera matrix
724             final var mp = k.multiplyAndReturnNew(r);
725 
726             // set new rotation
727             this.cameraRotation = cameraRotation;
728 
729             // set new rotation on left 3x3 sub-matrix of internal pinhole camera
730             // matrix
731             internalMatrix.setSubmatrix(0, 0, PINHOLE_CAMERA_MATRIX_ROWS - 1,
732                     PINHOLE_CAMERA_MATRIX_ROWS - 1, mp);
733 
734             // reset camera sign fixed
735             cameraSignFixed = false;
736             normalized = false;
737         } catch (final WrongSizeException ignore) {
738             // never happens
739         }
740     }
741 
742     /**
743      * Combines current camera rotation with provided rotation.
744      *
745      * @param cameraRotation Camera 3D rotation to be added to current rotation.
746      * @throws CameraException if there are numerical instabilities.
747      */
748     public void rotate(final Rotation3D cameraRotation) throws CameraException {
749         if (!isCameraRotationAvailable()) {
750             // compute new intrinsics and rotation
751             computeIntrinsicsAndRotation();
752         }
753 
754         // compute new rotation matrix and set it
755         this.cameraRotation.combine(cameraRotation);
756         // set new camera rotation
757         setCameraRotation(this.cameraRotation);
758     }
759 
760     /**
761      * Modifies camera so that it points to provided point while keeping the
762      * camera center.
763      *
764      * @param point Point to look at.
765      * @throws CameraException thrown if operation cannot be done. This happens
766      *                         usually when point is located very close to the camera center. In those
767      *                         situations orientation cannot be reliably computed.
768      */
769     public void pointAt(final Point3D point) throws CameraException {
770         try {
771             if (!isCameraCenterAvailable()) {
772                 // compute camera center
773                 if (cameraCenter == null) {
774                     cameraCenter = Point3D.create(CoordinatesType.HOMOGENEOUS_COORDINATES);
775                 }
776                 computeCameraCenterSVD(cameraCenter);
777             }
778             if (!areIntrinsicParametersAvailable()) {
779                 // Find intrinsics
780                 computeIntrinsicsAndRotation();
781             }
782 
783             // difference vector between 3D point and camera center using
784             // inhomogeneous coordinates. This vector after normalization should
785             // be the new principal vector
786             final var mDiff = new Matrix(1, PINHOLE_CAMERA_MATRIX_ROWS);
787             mDiff.setElementAtIndex(0, point.getInhomX() - cameraCenter.getInhomX());
788             mDiff.setElementAtIndex(1, point.getInhomY() - cameraCenter.getInhomY());
789             mDiff.setElementAtIndex(2, point.getInhomZ() - cameraCenter.getInhomZ());
790 
791             final var norm = Utils.normF(mDiff);
792             mDiff.multiplyByScalar(1.0 / norm);
793 
794             // mDiff has to be the lower row of rotation matrix, we need to find
795             // the remaining rows as two orthonormal vectors respect to this one,
796             // hence we use SVD of mDiff
797             final var decomposer = new SingularValueDecomposer(mDiff);
798             decomposer.decompose();
799 
800             final var v = decomposer.getV();
801 
802             // set new rotation matrix by setting mDiff (which is 1st column of
803             // V) on the last row of rotation matrix
804             final var r = new Matrix(PINHOLE_CAMERA_MATRIX_ROWS, PINHOLE_CAMERA_MATRIX_ROWS);
805             r.setElementAt(2, 0, v.getElementAt(0, 0));
806             r.setElementAt(2, 1, v.getElementAt(1, 0));
807             r.setElementAt(2, 2, v.getElementAt(2, 0));
808 
809             r.setElementAt(1, 0, v.getElementAt(0, 1));
810             r.setElementAt(1, 1, v.getElementAt(1, 1));
811             r.setElementAt(1, 2, v.getElementAt(2, 1));
812 
813             r.setElementAt(0, 0, v.getElementAt(0, 2));
814             r.setElementAt(0, 1, v.getElementAt(1, 2));
815             r.setElementAt(0, 2, v.getElementAt(2, 2));
816 
817             // set new rotation
818             final var rotation = new MatrixRotation3D(r);
819             setCameraRotation(rotation);
820 
821             // because left 3x3 sub-matrix has been modified, then last column of
822             // internal matrix has to be modified to ensure that camera center
823             // does not change
824             final var mp = internalMatrix.getSubmatrix(
825                     0, 0, 2, 2);
826             final var center = new Matrix(PINHOLE_CAMERA_MATRIX_ROWS, 1);
827             center.setElementAtIndex(0, cameraCenter.getInhomX());
828             center.setElementAtIndex(1, cameraCenter.getInhomY());
829             center.setElementAtIndex(2, cameraCenter.getInhomZ());
830 
831             // MP will be p4 (last column)
832             mp.multiply(center);
833             mp.multiplyByScalar(-1.0);
834 
835             // set last column of pinhole camera matrix
836             internalMatrix.setSubmatrix(0, PINHOLE_CAMERA_MATRIX_COLS - 1,
837                     PINHOLE_CAMERA_MATRIX_ROWS - 1, PINHOLE_CAMERA_MATRIX_COLS - 1, mp);
838 
839             // because we do not check sign of new director vector, we reset
840             // camera sign so that it is checked afterward when needed
841             cameraSignFixed = false;
842         } catch (final WrongSizeException ignore) {
843             // never happens
844         } catch (final AlgebraException | InvalidRotationMatrixException e) {
845             throw new CameraException(e);
846         }
847     }
848 
849     /**
850      * Sets camera intrinsic parameters.
851      * Intrinsic parameters are related to camera lens and sensor and contain
852      * parameters such as focal length, skewness or principal point
853      *
854      * @param intrinsicParameters intrinsic parameters to be set
855      * @throws CameraException if there are numerical instabilities
856      */
857     public void setIntrinsicParameters(final PinholeCameraIntrinsicParameters intrinsicParameters)
858             throws CameraException {
859 
860         if (!isCameraRotationAvailable()) {
861             // Find rotation
862             computeIntrinsicsAndRotation();
863         }
864 
865         // get K and R matrices
866         final var k = intrinsicParameters.getInternalMatrix();
867         final var r = cameraRotation.asInhomogeneousMatrix();
868 
869         // compute now left 3x3 sub-matrix of pinhole camera matrix
870         try {
871             k.multiply(r);
872         } catch (final WrongSizeException ignore) {
873             // never happens
874         }
875         this.intrinsicParameters = intrinsicParameters;
876 
877         // set new intrinsic parameters on left 3x3 sub-matrix of internal
878         // pinhole camera matrix
879         internalMatrix.setSubmatrix(0, 0, PINHOLE_CAMERA_MATRIX_ROWS - 1,
880                 PINHOLE_CAMERA_MATRIX_ROWS - 1, k);
881         cameraSignFixed = false;
882         normalized = false;
883     }
884 
885     /**
886      * Returns a 3D point indicating camera center (i.e. location) if center
887      * has already been computed and is available for retrieval.
888      * If camera center is not available, camera must be decomposed before
889      * calling this method.
890      *
891      * @return camera center.
892      * @throws NotAvailableException if camera center is not yet available for
893      *                               retrieval.
894      */
895     public Point3D getCameraCenter() throws NotAvailableException {
896         if (!isCameraCenterAvailable()) {
897             throw new NotAvailableException();
898         }
899 
900         return cameraCenter;
901     }
902 
903     /**
904      * Indicates if camera center has been decomposed and is available for
905      * retrieval.
906      *
907      * @return true if camera center is available, false otherwise.
908      */
909     public boolean isCameraCenterAvailable() {
910         return cameraCenter != null;
911     }
912 
913     /**
914      * Sets 3D coordinates of camera center.
915      * When setting camera center camera becomes not normalized.
916      *
917      * @param cameraCenter camera center to be set.
918      */
919     public void setCameraCenter(final Point3D cameraCenter) {
920         try {
921             normalize();
922             // new last column is the product of left 3x3 pinhole camera
923             // sub-matrix by the inhomogeneous coordinates of new camera center
924             final var mp = internalMatrix.getSubmatrix(
925                     0, 0, 2, 2);
926             final var center = new Matrix(PINHOLE_CAMERA_MATRIX_ROWS, 1);
927             center.setElementAtIndex(0, cameraCenter.getInhomX());
928             center.setElementAtIndex(1, cameraCenter.getInhomY());
929             center.setElementAtIndex(2, cameraCenter.getInhomZ());
930 
931             // Mp will be p4 (4th column)
932             mp.multiply(center);
933             mp.multiplyByScalar(-1.0);
934 
935             // set camera center
936             this.cameraCenter = cameraCenter;
937 
938             // set last column of pinhole camera matrix
939             internalMatrix.setSubmatrix(0, PINHOLE_CAMERA_MATRIX_COLS - 1,
940                     PINHOLE_CAMERA_MATRIX_ROWS - 1,
941                     PINHOLE_CAMERA_MATRIX_COLS - 1, mp);
942             normalized = false;
943         } catch (final WrongSizeException ignore) {
944             // never happens
945         }
946     }
947 
948     /**
949      * Sets camera intrinsic parameters and camera 3D rotation.
950      * Intrinsic parameters indicate internal camera parameters related to
951      * camera lens and camera sensor and rotation indicates camera orientation.
952      *
953      * @param intrinsicParameters intrinsic camera parameters to be set.
954      * @param rotation            3D camera rotation to be set.
955      */
956     public void setIntrinsicParametersAndRotation(
957             final PinholeCameraIntrinsicParameters intrinsicParameters, final Rotation3D rotation) {
958         try {
959             normalize();
960 
961             // K and R are obtained
962             final var k = intrinsicParameters.getInternalMatrix();
963             final var r = rotation.asInhomogeneousMatrix();
964 
965             // compute new left 3x3 sub-matrix of pinhole camera matrix (also known as Mp)
966             k.multiply(r);
967 
968             // set new intrinsic parameters
969             this.intrinsicParameters = intrinsicParameters;
970 
971             // set new rotation
972             cameraRotation = rotation;
973 
974             // set new left 3x3 sub-matrix of internal pinhole camera matrix
975             internalMatrix.setSubmatrix(0, 0, PINHOLE_CAMERA_MATRIX_ROWS - 1,
976                     PINHOLE_CAMERA_MATRIX_ROWS - 1, k);
977             cameraSignFixed = false;
978             normalized = false;
979         } catch (final WrongSizeException ignore) {
980             // this will never happen
981         }
982     }
983 
984     /**
985      * Sets both intrinsic and extrinsic camera parameters.
986      * Intrinsic parameters indicate internal camera parameters related to
987      * camera lens and sensor, and extrinsic parameters are parameters that
988      * indicate camera location and orientation by providing the projected
989      * coordinates of world origin and the camera 3D rotation.
990      *
991      * @param intrinsicParameters    intrinsic parameters to be set.
992      * @param rotation               camera rotation to be set.
993      * @param originImageCoordinates projected coordinates of world origin to be
994      *                               set.
995      */
996     public final void setIntrinsicAndExtrinsicParameters(
997             final PinholeCameraIntrinsicParameters intrinsicParameters, final Rotation3D rotation,
998             final Point2D originImageCoordinates) {
999         setIntrinsicParametersAndRotation(intrinsicParameters, rotation);
1000         setImageOfWorldOrigin(originImageCoordinates);
1001     }
1002 
1003     /**
1004      * Sets both intrinsic and extrinsic camera parameters.
1005      * Intrinsic parameters indicate internal camera parameters related to
1006      * camera lens and sensor, and extrinsic parameters are parameters that
1007      * indicate camera location and orientation by providing camera center
1008      * and the camera 3D rotation.
1009      *
1010      * @param intrinsicParameters intrinsic parameters to be set.
1011      * @param rotation            camera rotation to be set.
1012      * @param cameraCenter        location of camera center to be set.
1013      */
1014     public final void setIntrinsicAndExtrinsicParameters(
1015             final PinholeCameraIntrinsicParameters intrinsicParameters, final Rotation3D rotation,
1016             final Point3D cameraCenter) {
1017         setIntrinsicParametersAndRotation(intrinsicParameters, rotation);
1018         setCameraCenter(cameraCenter);
1019     }
1020 
1021     /**
1022      * Returns the projected 2D coordinates of the x-axis, which corresponds to
1023      * its vanishing point.
1024      *
1025      * @return vanishing point of x-axis.
1026      */
1027     public Point2D getXAxisVanishingPoint() {
1028         final var result = Point2D.create();
1029         xAxisVanishingPoint(result);
1030         return result;
1031     }
1032 
1033     /**
1034      * Computes the projected 2D coordinates of the x-axis, which corresponds to
1035      * its vanishing point.
1036      *
1037      * @param result 2D point where vanishing point of x-axis will be stored.
1038      */
1039     public void xAxisVanishingPoint(final Point2D result) {
1040 
1041         // use first camera matrix column to set 2D point
1042         result.setHomogeneousCoordinates(internalMatrix.getElementAt(0, 0),
1043                 internalMatrix.getElementAt(1, 0), internalMatrix.getElementAt(2, 0));
1044     }
1045 
1046     /**
1047      * Returns the projected 2D coordinates of the y-axis, which corresponds to
1048      * its vanishing point.
1049      *
1050      * @return vanishing point of y-axis.
1051      */
1052     public Point2D getYAxisVanishingPoint() {
1053         final var result = Point2D.create();
1054         yAxisVanishingPoint(result);
1055         return result;
1056     }
1057 
1058     /**
1059      * Computes the projected 2D coordinates of the y-axis, which corresponds to
1060      * its vanishing point.
1061      *
1062      * @param result 2D point where vanishing point of y-axis will be stored.
1063      */
1064     public void yAxisVanishingPoint(final Point2D result) {
1065 
1066         // use second camera matrix column to set 2D point
1067         result.setHomogeneousCoordinates(internalMatrix.getElementAt(0, 1),
1068                 internalMatrix.getElementAt(1, 1), internalMatrix.getElementAt(2, 1));
1069     }
1070 
1071     /**
1072      * Returns the projected 2D coordinates of the z axis, which corresponds to
1073      * its vanishing point.
1074      *
1075      * @return vanishing point of z axis.
1076      */
1077     public Point2D getZAxisVanishingPoint() {
1078         final var result = Point2D.create();
1079         zAxisVanishingPoint(result);
1080         return result;
1081     }
1082 
1083     /**
1084      * Computes the projected 2D coordinates of the z axis, which corresponds to
1085      * its vanishing point.
1086      *
1087      * @param result 2D point where vanishing point of z axis will be stored.
1088      */
1089     public void zAxisVanishingPoint(final Point2D result) {
1090 
1091         // use third camera matrix column to set 2D point
1092         result.setHomogeneousCoordinates(internalMatrix.getElementAt(0, 2),
1093                 internalMatrix.getElementAt(1, 2), internalMatrix.getElementAt(2, 2));
1094     }
1095 
1096     /**
1097      * Returns the projected 2D coordinates of the world origin (0, 0, 0).
1098      *
1099      * @return projected point of world origin.
1100      */
1101     public Point2D getImageOfWorldOrigin() {
1102         final var result = Point2D.create();
1103         imageOfWorldOrigin(result);
1104         return result;
1105     }
1106 
1107     /**
1108      * Computes the projected 2D coordinates of the world origin (0, 0, 0).
1109      *
1110      * @param result 2D point where projected point of world origin will be
1111      *               stored.
1112      */
1113     public void imageOfWorldOrigin(final Point2D result) {
1114 
1115         // use fourth camera matrix column to set 2D point
1116         result.setHomogeneousCoordinates(internalMatrix.getElementAt(0, 3),
1117                 internalMatrix.getElementAt(1, 3), internalMatrix.getElementAt(2, 3));
1118     }
1119 
1120     /**
1121      * Sets the projected 2D coordinates of the x-axis, which corresponds to its
1122      * vanishing point.
1123      *
1124      * @param xAxisVanishingPoint vanishing point of x-axis to be set.
1125      */
1126     public void setXAxisVanishingPoint(final Point2D xAxisVanishingPoint) {
1127         // to increase accuracy
1128         xAxisVanishingPoint.normalize();
1129 
1130         final var homX = xAxisVanishingPoint.getHomX();
1131         final var homY = xAxisVanishingPoint.getHomY();
1132         final var homW = xAxisVanishingPoint.getHomW();
1133 
1134         internalMatrix.setElementAt(0, 0, homX);
1135         internalMatrix.setElementAt(1, 0, homY);
1136         internalMatrix.setElementAt(2, 0, homW);
1137 
1138         // set camera sign fixed and normalized
1139         cameraSignFixed = false;
1140         normalized = false;
1141     }
1142 
1143     /**
1144      * Sets the projected 2D coordinates of the y-axis, which corresponds to its
1145      * vanishing point.
1146      *
1147      * @param yAxisVanishingPoint vanishing point of y-axis to be set.
1148      */
1149     public void setYAxisVanishingPoint(final Point2D yAxisVanishingPoint) {
1150         // to increase accuracy
1151         yAxisVanishingPoint.normalize();
1152 
1153         final var homX = yAxisVanishingPoint.getHomX();
1154         final var homY = yAxisVanishingPoint.getHomY();
1155         final var homW = yAxisVanishingPoint.getHomW();
1156 
1157         internalMatrix.setElementAt(0, 1, homX);
1158         internalMatrix.setElementAt(1, 1, homY);
1159         internalMatrix.setElementAt(2, 1, homW);
1160 
1161         //set camera sign fixed and normalized
1162         cameraSignFixed = false;
1163         normalized = false;
1164     }
1165 
1166     /**
1167      * Sets the projected 2D coordinates of the z axis, which corresponds to its
1168      * vanishing point.
1169      *
1170      * @param zAxisVanishingPoint vanishing point of z axis to be set.
1171      */
1172     public void setZAxisVanishingPoint(final Point2D zAxisVanishingPoint) {
1173         // to increase accuracy
1174         zAxisVanishingPoint.normalize();
1175 
1176         final var homX = zAxisVanishingPoint.getHomX();
1177         final var homY = zAxisVanishingPoint.getHomY();
1178         final var homW = zAxisVanishingPoint.getHomW();
1179 
1180         internalMatrix.setElementAt(0, 2, homX);
1181         internalMatrix.setElementAt(1, 2, homY);
1182         internalMatrix.setElementAt(2, 2, homW);
1183 
1184         // set camera sign fixed and normalized
1185         cameraSignFixed = false;
1186         normalized = false;
1187     }
1188 
1189     /**
1190      * Sets the projected 2D coordinates of the world origin (0, 0, 0).
1191      *
1192      * @param imageOfWorldOrigin projected world origin to be set.
1193      */
1194     public void setImageOfWorldOrigin(final Point2D imageOfWorldOrigin) {
1195         // to increase accuracy
1196         imageOfWorldOrigin.normalize();
1197 
1198         final var homX = imageOfWorldOrigin.getHomX();
1199         final var homY = imageOfWorldOrigin.getHomY();
1200         final var homW = imageOfWorldOrigin.getHomW();
1201 
1202         internalMatrix.setElementAt(0, 3, homX);
1203         internalMatrix.setElementAt(1, 3, homY);
1204         internalMatrix.setElementAt(2, 3, homW);
1205 
1206         // set normalized (no need to reset camera sign)
1207         normalized = false;
1208     }
1209 
1210     /**
1211      * Returns plane formed by x and z retinal axes. x-axis is taken respect the
1212      * projected camera coordinates (i.e. retinal plane), and z-axis just points
1213      * in the direction that the camera is looking at.
1214      *
1215      * @return horizontal plane respect camera retinal plane.
1216      */
1217     public Plane getHorizontalAxisPlane() {
1218         final var result = new Plane();
1219         horizontalAxisPlane(result);
1220         return result;
1221     }
1222 
1223     /**
1224      * Computes the plane formed by x and z retinal axes. x-axis is taken
1225      * respect the projected camera coordinates (i.e. retinal plane), and z-axis
1226      * just points in the direction that the camera is looking at.
1227      *
1228      * @param result plane where results will be stored.
1229      */
1230     public void horizontalAxisPlane(final Plane result) {
1231 
1232         // use second row of camera matrix to set plane
1233         result.setParameters(internalMatrix.getElementAt(1, 0),
1234                 internalMatrix.getElementAt(1, 1),
1235                 internalMatrix.getElementAt(1, 2),
1236                 internalMatrix.getElementAt(1, 3));
1237     }
1238 
1239     /**
1240      * Returns plane formed by y and z retinal axes. y-axis is taken respect the
1241      * projected camera coordinates (i.e. retinal plane), and z-axis just points
1242      * in the direction that the camera is looking at.
1243      *
1244      * @return vertical plane respect camera retinal plane.
1245      */
1246     public Plane getVerticalAxisPlane() {
1247         final var result = new Plane();
1248         verticalAxisPlane(result);
1249         return result;
1250     }
1251 
1252     /**
1253      * Computes the plane formed by y and z retinal axes. y-axis is taken
1254      * respect the projected camera coordinates (i.e. retinal plane), and z-axis
1255      * just point in the direction that the camera is looking at.
1256      *
1257      * @param result plane where results will be stored.
1258      */
1259     public void verticalAxisPlane(final Plane result) {
1260         // use first row of camera matrix to set plane
1261         result.setParameters(internalMatrix.getElementAt(0, 0),
1262                 internalMatrix.getElementAt(0, 1), internalMatrix.getElementAt(0, 2),
1263                 internalMatrix.getElementAt(0, 3));
1264     }
1265 
1266     /**
1267      * Returns a plane equivalent to the retinal plane (i.e. the plane where 3D
1268      * points get projected). The principal plane director vector always points
1269      * in the direction that the camera is looking at, and the camera center is
1270      * locus of the principal plane.
1271      *
1272      * @return a plane equivalent to the retinal plane.
1273      */
1274     public Plane getPrincipalPlane() {
1275         final var result = new Plane();
1276         principalPlane(result);
1277         return result;
1278     }
1279 
1280     /**
1281      * Computes a plane equivalent to the retinal plane (i.e. the plane where 3D
1282      * points get projected). The principal plane director vector always points
1283      * in the direction that the camera is looking at, and the camera center is
1284      * locus of the principal plane.
1285      *
1286      * @param result plane where results will be stored.
1287      */
1288     public void principalPlane(final Plane result) {
1289         // use third row of camera matrix to set plane
1290         result.setParameters(internalMatrix.getElementAt(2, 0),
1291                 internalMatrix.getElementAt(2, 1), internalMatrix.getElementAt(2, 2),
1292                 internalMatrix.getElementAt(2, 3));
1293     }
1294 
1295     /**
1296      * Sets plane formed by x and z retinal plane. x-axis is taken respect the
1297      * projected camera coordinates (i.e. retinal plane), and z-axis just points
1298      * in the direction that the camera is looking at.
1299      *
1300      * @param horizontalAxisPlane horizontal plane respect camera retinal plane
1301      *                            to be set.
1302      */
1303     public void setHorizontalAxisPlane(final Plane horizontalAxisPlane) {
1304         // to increase accuracy
1305         horizontalAxisPlane.normalize();
1306 
1307         final var a = horizontalAxisPlane.getA();
1308         final var b = horizontalAxisPlane.getB();
1309         final var c = horizontalAxisPlane.getC();
1310         final var d = horizontalAxisPlane.getD();
1311 
1312         internalMatrix.setElementAt(1, 0, a);
1313         internalMatrix.setElementAt(1, 1, b);
1314         internalMatrix.setElementAt(1, 2, c);
1315         internalMatrix.setElementAt(1, 3, d);
1316 
1317         // set camera sign fixed and normalized
1318         cameraSignFixed = false;
1319         normalized = false;
1320     }
1321 
1322     /**
1323      * Sets plane formed by y and z retinal plane. y-axis is taken respect the
1324      * projected camera coordinates (i.e. retinal plane), and z-axis just points
1325      * in the direction that the camera is looking at.
1326      *
1327      * @param verticalAxisPlane vertical plane respect camera retinal plane to
1328      *                          be set.
1329      */
1330     public void setVerticalAxisPlane(final Plane verticalAxisPlane) {
1331         // to increase accuracy
1332         verticalAxisPlane.normalize();
1333 
1334         final var a = verticalAxisPlane.getA();
1335         final var b = verticalAxisPlane.getB();
1336         final var c = verticalAxisPlane.getC();
1337         final var d = verticalAxisPlane.getD();
1338 
1339         internalMatrix.setElementAt(0, 0, a);
1340         internalMatrix.setElementAt(0, 1, b);
1341         internalMatrix.setElementAt(0, 2, c);
1342         internalMatrix.setElementAt(0, 3, d);
1343 
1344         // set camera sign fixed and normalized
1345         cameraSignFixed = false;
1346         normalized = false;
1347     }
1348 
1349     /**
1350      * Sets plane equivalent to the retinal plane (i.e. the plane where 3D
1351      * points get projected). Notice that the principal plane director vector
1352      * always points in the direction that the camera is looking at, and that
1353      * the camera center is locus of the principal plane.
1354      *
1355      * @param principalPlane principal plane to be set.
1356      */
1357     public void setPrincipalPlane(final Plane principalPlane) {
1358         // to increase accuracy
1359         principalPlane.normalize();
1360 
1361         final var a = principalPlane.getA();
1362         final var b = principalPlane.getB();
1363         final var c = principalPlane.getC();
1364         final var d = principalPlane.getD();
1365 
1366         internalMatrix.setElementAt(2, 0, a);
1367         internalMatrix.setElementAt(2, 1, b);
1368         internalMatrix.setElementAt(2, 2, c);
1369         internalMatrix.setElementAt(2, 3, d);
1370 
1371         // set camera sign fixed and normalized
1372         cameraSignFixed = false;
1373         normalized = false;
1374     }
1375 
1376     /**
1377      * Returns a 2D point indicating where the camera center (or the principal
1378      * axis) is projected on the retinal plane.
1379      * Usually the principal plane is located at the center (i.e. origin of
1380      * coordinates) of the retinal plane.
1381      *
1382      * @return the principal point laying on the retinal plane
1383      */
1384     public Point2D getPrincipalPoint() {
1385         final var result = Point2D.create();
1386         principalPoint(result);
1387         return result;
1388     }
1389 
1390     /**
1391      * Computes the principal point which is a 2D point indicating where the
1392      * camera center (or the principal axis) is projected on the retinal plane.
1393      * Usually the principal plane is located at the center (i.e. origin of
1394      * coordinates) of the retinal plane.
1395      *
1396      * @param result 2D point where computed principal point will be stored
1397      */
1398     public void principalPoint(final Point2D result) {
1399 
1400         try {
1401             // the principal point is retrieved as the product of the 3x3
1402             // top-left sub-matrix of the internal camera with its second row
1403             final var mMp = internalMatrix.getSubmatrix(0, 0,
1404                     PINHOLE_CAMERA_MATRIX_ROWS - 1,
1405                     PINHOLE_CAMERA_MATRIX_ROWS - 1);
1406             final var m = new Matrix(PINHOLE_CAMERA_MATRIX_ROWS, 1);
1407             m.setElementAtIndex(0, mMp.getElementAt(2, 0));
1408             m.setElementAtIndex(1, mMp.getElementAt(2, 1));
1409             m.setElementAtIndex(2, mMp.getElementAt(2, 2));
1410 
1411             mMp.multiply(m);
1412 
1413             result.setHomogeneousCoordinates(mMp.getElementAtIndex(0), mMp.getElementAtIndex(1),
1414                     mMp.getElementAtIndex(2));
1415         } catch (final WrongSizeException ignore) {
1416             // never happens
1417         }
1418     }
1419 
1420     /**
1421      * Returns the principal axis as an array consisting of the x,y,z
1422      * coordinates of the director vector of the principal plane. Hence, the
1423      * principal axis contains the direction that the camera is looking at.
1424      *
1425      * @return principal axis as an array.
1426      * @throws CameraException if there is numerical instability in camera
1427      *                         parameters.
1428      */
1429     public double[] getPrincipalAxisArray() throws CameraException {
1430         final var result = new double[PINHOLE_CAMERA_MATRIX_ROWS];
1431         principalAxisArray(result);
1432         return result;
1433     }
1434 
1435     /**
1436      * Computes the principal axis as an array consisting of the x,y,z
1437      * coordinates of the director vector of the principal plane. Hence, the
1438      * principal axis contains the direction that the camera is looking at.
1439      *
1440      * @param result array where principal axis coordinates will be stored.
1441      * @throws IllegalArgumentException if provided array does not have length 3.
1442      * @throws CameraException          if there is numerical instability in camera
1443      *                                  parameters.
1444      */
1445     public void principalAxisArray(final double[] result) throws CameraException {
1446         if (result.length != PINHOLE_CAMERA_MATRIX_ROWS) {
1447             throw new IllegalArgumentException();
1448         }
1449 
1450         try {
1451             // get first 3 elements of last row of camera matrix, which contains
1452             // director vector of principal plane
1453             internalMatrix.getSubmatrixAsArray(PINHOLE_CAMERA_MATRIX_ROWS - 1,
1454                     0, PINHOLE_CAMERA_MATRIX_ROWS - 1,
1455                     PINHOLE_CAMERA_MATRIX_ROWS - 1, result);
1456 
1457             if (!isCameraSignFixed()) {
1458                 final double cameraSign = getCameraSign();
1459 
1460                 // fix sign of director vector
1461                 ArrayUtils.multiplyByScalar(result, cameraSign, result);
1462             }
1463 
1464             // normalize director vector
1465             final var norm = Utils.normF(result);
1466             ArrayUtils.multiplyByScalar(result, 1.0 / norm, result);
1467         } catch (final WrongSizeException ignore) {
1468             // never happens
1469         }
1470     }
1471 
1472     /**
1473      * Returns camera sign of this camera.
1474      * Pinhole camera is defined in homogeneous coordinates, hence its internal
1475      * matrix can theoretically be scaled without affecting results (in practice
1476      * it can affect accuracy). However, scaling the camera with a different
1477      * sign can have an impact on determining whether points or objects are
1478      * located in front or behind the camera.
1479      * When camera sign is positive (i.e. 1.0), then points are correctly
1480      * detected whether they are in front or behind the camera (this is called
1481      * cheirality), when sign is negative, point cheirality is reversed and
1482      * needs to be fixed.
1483      *
1484      * @return 1.0 if camera sign is correct, or -1.0 if camera sign needs to be
1485      * reversed.
1486      * @throws CameraException if there is numerical instability.
1487      */
1488     public double getCameraSign() throws CameraException {
1489         return getCameraSign(SIGN_THRESHOLD);
1490     }
1491 
1492     /**
1493      * Returns camera sign of this camera up to provided threshold.
1494      * Pinhole camera is defined in homogeneous coordinates, hence its internal
1495      * matrix can theoretically be scaled without affecting results (in practice
1496      * it can affect accuracy). However, scaling the camera with a different
1497      * sign can have an impact on determining whether points or objects are
1498      * located in front or behind the camera.
1499      * When camera sign is positive (i.e. 1.0), then points are correctly
1500      * detected whether they are in front or behind the camera (this is called
1501      * cheirality), when sign is negative, point cheirality is reversed and
1502      * needs to be fixed.
1503      *
1504      * @param threshold threshold to determine whether camera sign is positive
1505      *                  or negative. Usually threshold is a very small value close to zero
1506      * @return 1.0 if camera sign is correct, or -1.0 if camera sign needs to be
1507      * reversed.
1508      * @throws CameraException if there is numerical instability.
1509      */
1510     public double getCameraSign(final double threshold) throws CameraException {
1511         try {
1512             // pick left 3x3 top-left sub-matrix
1513             final var mMp = internalMatrix.getSubmatrix(0, 0,
1514                     PINHOLE_CAMERA_MATRIX_ROWS - 1,
1515                     PINHOLE_CAMERA_MATRIX_ROWS - 1);
1516 
1517             // compute its determinant
1518             final var det = Utils.det(mMp);
1519 
1520             // get sign of determinant to determine pinhole camera matrix sign
1521             return (det > threshold) ? 1.0 : -1.0;
1522         } catch (final AlgebraException e) {
1523             throw new CameraException(e);
1524         }
1525     }
1526 
1527     /**
1528      * Returns the depth of provided point respect to camera center.
1529      * A positive value indicates that point is in front of the camera, a
1530      * negative value indicates that point is behind the camera.
1531      *
1532      * @param point point to be checked.
1533      * @return depth of provided point respect to camera center.
1534      * @throws CameraException if there is numerical instability.
1535      */
1536     public double getDepth(final Point3D point) throws CameraException {
1537         if (!isCameraCenterAvailable()) {
1538             // compute camera center
1539             cameraCenter = computeCameraCenterSVD();
1540         }
1541 
1542         // because when computing principal axis vector it is normalized, we can
1543         // compute depth of world points respect to camera just as the dot
1544         // product between world points minus camera center and principal axis
1545         // vector using inhomogeneous coordinates
1546         final var principalAxis = getPrincipalAxisArray();
1547         final var diff = new double[INHOM_COORDS];
1548         diff[0] = point.getInhomX() - cameraCenter.getInhomX();
1549         diff[1] = point.getInhomY() - cameraCenter.getInhomY();
1550         diff[2] = point.getInhomZ() - cameraCenter.getInhomZ();
1551 
1552         return ArrayUtils.dotProduct(principalAxis, diff);
1553     }
1554 
1555     /**
1556      * Returns the depth of provided points respect to camera center.
1557      * A positive value indicates that point is in front of the camera, a
1558      * negative value indicates that point is behind the camera.
1559      *
1560      * @param points points to be checked.
1561      * @return depth of provided points respect to camera center.
1562      * @throws CameraException if there is numerical instability.
1563      */
1564     public List<Double> getDepths(final List<Point3D> points) throws CameraException {
1565         final var depths = new ArrayList<Double>(points.size());
1566         depths(points, depths);
1567         return depths;
1568     }
1569 
1570     /**
1571      * Computes the depth of provided points respect to camera center and stores
1572      * the result in provided result list.
1573      * A positive value indicates that point is in front of the camera, a
1574      * negative value indicates that point is behind the camera.
1575      *
1576      * @param points points to be checked.
1577      * @param result list where depths of provided points will be stored.
1578      * @throws CameraException if there is numerical instability.
1579      */
1580     public void depths(final List<Point3D> points, final List<Double> result) throws CameraException {
1581         result.clear();
1582         for (final var point : points) {
1583             result.add(getDepth(point));
1584         }
1585     }
1586 
1587     /**
1588      * Computes the cheirality of a point.
1589      * A positive cheirality indicates that a point is in front of the camera,
1590      * a negative value indicates that a point is behind the camera.
1591      * Cheirality is less expensive to compute than point depth, for that reason
1592      * when trying to determine if a point is in front or behind the camera is
1593      * preferable to check cheirality sign rather than depth sign.
1594      *
1595      * @param point point to be checked.
1596      * @return a positive value if point is in front of the camera, a negative
1597      * value otherwise.
1598      * @throws CameraException if there is numerical instability.
1599      */
1600     public double getCheirality(final Point3D point) throws CameraException {
1601 
1602         // normalize input point and camera to increase accuracy
1603         point.normalize();
1604         normalize();
1605 
1606         // pick last homogeneous component of 3D and 2D points
1607         final var hom3DW = point.getHomW();
1608 
1609         // W component of projected point can be computed as the dot product
1610         // between homogeneous 3D point and last row of pinhole camera matrix
1611         final var hom2DW = point.getHomX() * internalMatrix.getElementAt(2, 0)
1612                 + point.getHomY() * internalMatrix.getElementAt(2, 1)
1613                 + point.getHomZ() * internalMatrix.getElementAt(2, 2)
1614                 + point.getHomW() * internalMatrix.getElementAt(2, 3);
1615 
1616         var cheiral = hom3DW * hom2DW;
1617 
1618         if (!isCameraSignFixed()) {
1619             cheiral *= getCameraSign();
1620         }
1621 
1622         return cheiral;
1623     }
1624 
1625     /**
1626      * Return the cheirality of the list of provided points.
1627      * A positive cheirality indicates that a point is in front of the camera,
1628      * a negative value indicates that a point is behind the camera.
1629      * Cheirality is less expensive to compute than point depth, for that reason
1630      * when trying to determine if a point is in front or behind the camera it
1631      * is preferable to check cheirality sign rather than depth sign.
1632      *
1633      * @param points list of points to be checked.
1634      * @return a list of cheirality values corresponding to provided points.
1635      * @throws CameraException if there is numerical instability.
1636      */
1637     public List<Double> getCheiralities(final List<Point3D> points) throws CameraException {
1638         final var cheiralities = new ArrayList<Double>(points.size());
1639         cheiralities(points, cheiralities);
1640         return cheiralities;
1641     }
1642 
1643     /**
1644      * Computes the cheirality of the list of provided points and stores the
1645      * result in result list.
1646      * A positive cheirality indicates that a point is in front of the camera,
1647      * a negative value indicates that a point is behind the camera.
1648      * Cheirality is less expensive to compute than point depth, for that reason
1649      * when trying to determine if a point is in front or behind the camera it
1650      * is preferable to check cheirality sign rather than depth sign.
1651      *
1652      * @param points list of points to be checked.
1653      * @param result list where cheiralities will be stored.
1654      * @throws CameraException if there is numerical instability.
1655      */
1656     public void cheiralities(final List<Point3D> points, final List<Double> result) throws CameraException {
1657         result.clear();
1658         for (final var point : points) {
1659             result.add(getCheirality(point));
1660         }
1661     }
1662 
1663     /**
1664      * Determines if a given point is located in front of the camera.
1665      *
1666      * @param point point to be checked.
1667      * @return true if point is in front of the camera, false otherwise.
1668      * @throws CameraException if there is numerical instability.
1669      */
1670     public boolean isPointInFrontOfCamera(final Point3D point) throws CameraException {
1671         return isPointInFrontOfCamera(point, FRONT_THRESHOLD);
1672     }
1673 
1674     /**
1675      * Determines if a given point is located in front of the camera up to given
1676      * threshold.
1677      *
1678      * @param point     point to be checked.
1679      * @param threshold a threshold which typically is a small value close to
1680      *                  zero.
1681      * @return true if point is in front of the camera, false otherwise.
1682      * @throws CameraException if there is numerical instability.
1683      */
1684     public boolean isPointInFrontOfCamera(final Point3D point, final double threshold) throws CameraException {
1685         return getCheirality(point) > threshold;
1686     }
1687 
1688     /**
1689      * Returns list indicating if corresponding provided points are located in
1690      * front of the camera.
1691      *
1692      * @param points    points to be checked.
1693      * @param threshold a threshold which typically is a small value close to
1694      *                  zero.
1695      * @return a list of booleans indicating if the corresponding provided point
1696      * is located in front of the camera or not.
1697      * @throws CameraException if there is numerical instability.
1698      */
1699     public List<Boolean> arePointsInFrontOfCamera(final List<Point3D> points, final double threshold)
1700             throws CameraException {
1701         final var result = new ArrayList<Boolean>(points.size());
1702         arePointsInFrontOfCamera(points, result, threshold);
1703         return result;
1704     }
1705 
1706     /**
1707      * Returns list indicating if corresponding provided points are located in
1708      * front of the camera.
1709      *
1710      * @param points points to be checked.
1711      * @return a list of booleans indicating if the corresponding provided point
1712      * is located in front of the camera or not.
1713      * @throws CameraException if there is numerical instability.
1714      */
1715     public List<Boolean> arePointsInFrontOfCamera(final List<Point3D> points) throws CameraException {
1716         return arePointsInFrontOfCamera(points, FRONT_THRESHOLD);
1717     }
1718 
1719     /**
1720      * Computes list indicating if corresponding provided points are located in
1721      * front of the camera or not.
1722      *
1723      * @param points    points to be checked.
1724      * @param result    list where results will be stored.
1725      * @param threshold a threshold which typically is a small value close to
1726      *                  zero.
1727      * @throws CameraException if there is numerical instability.
1728      */
1729     public void arePointsInFrontOfCamera(final List<Point3D> points, final List<Boolean> result, final double threshold)
1730             throws CameraException {
1731         result.clear();
1732         for (final var point : points) {
1733             result.add(PinholeCamera.this.isPointInFrontOfCamera(point, threshold));
1734         }
1735     }
1736 
1737     /**
1738      * Computes list indicating if corresponding provided points are located in
1739      * front of the camera or not.
1740      *
1741      * @param points points to be checked.
1742      * @param result list where results will be stored.
1743      * @throws CameraException if there is numerical instability.
1744      */
1745     public void arePointsInFrontOfCamera(final List<Point3D> points, final List<Boolean> result)
1746             throws CameraException {
1747         arePointsInFrontOfCamera(points, result, FRONT_THRESHOLD);
1748     }
1749 
1750     /**
1751      * Creates an instance of PinholeCamera. Created instance is a canonical
1752      * camera equal to the 3x4 identity, which means that camera is located at
1753      * the origin with no translation or rotation.
1754      *
1755      * @return a canonical pinhole camera.
1756      */
1757     public static PinholeCamera createCanonicalCamera() {
1758         return new PinholeCamera();
1759     }
1760 
1761     /**
1762      * Decompose camera matrix 3x3 left minor and computes camera intrinsic
1763      * parameters and rotation.
1764      *
1765      * @throws CameraException if there is numerical instability.
1766      */
1767     private void computeIntrinsicsAndRotation() throws CameraException {
1768         try {
1769             // normalize camera to increase accuracy
1770             normalize();
1771 
1772             final var mMp = internalMatrix.getSubmatrix(0, 0,
1773                     PINHOLE_CAMERA_MATRIX_ROWS - 1, PINHOLE_CAMERA_MATRIX_ROWS - 1);
1774 
1775             // Use RQ decomposition to obtain intrinsic parameters as R ensuring
1776             // that elements on the diagonal are positive and element (3, 3) is 1,
1777             // and Q is an orthogonal matrix
1778             final var decomposer = new RQDecomposer(mMp);
1779             decomposer.decompose();
1780 
1781             // Intrinsic parameters
1782             final var r = decomposer.getR();
1783             final var q = decomposer.getQ();
1784 
1785             // norm to normalize R
1786             var norm = r.getElementAt(2, 2);
1787 
1788             // ensure that norm is not too small
1789             if (Math.abs(norm) < EPS) {
1790                 norm = (norm > 0.0 ? 1.0 : -1.0);
1791             }
1792 
1793             final var invNorm = 1.0 / norm;
1794 
1795             // build diagonal matrix to normalize R and obtain K
1796             final var vDiag = new double[PINHOLE_CAMERA_MATRIX_ROWS];
1797             final var vDiag2 = new double[PINHOLE_CAMERA_MATRIX_ROWS];
1798 
1799             if (invNorm * r.getElementAt(0, 0) > 0.0) {
1800                 vDiag[0] = invNorm;
1801                 vDiag2[0] = norm;
1802             } else {
1803                 vDiag[0] = -invNorm;
1804                 vDiag2[0] = -norm;
1805             }
1806             if (invNorm * r.getElementAt(1, 1) > 0.0) {
1807                 vDiag[1] = invNorm;
1808                 vDiag2[1] = norm;
1809             } else {
1810                 vDiag[1] = -invNorm;
1811                 vDiag2[1] = -norm;
1812             }
1813 
1814             vDiag[2] = invNorm;
1815             vDiag2[2] = norm;
1816 
1817             final var mDiag = Matrix.diagonal(vDiag);
1818             final var mDiag2 = Matrix.diagonal(vDiag2);
1819 
1820             r.multiply(mDiag);
1821 
1822             mDiag2.multiply(q);
1823 
1824             // thresholds should not be a problem, and so we disable the change of
1825             // throwing any exception by setting infinity threshold (and ignoring
1826             // GeometryException)
1827             intrinsicParameters = new PinholeCameraIntrinsicParameters(r, Double.POSITIVE_INFINITY);
1828             cameraRotation = new MatrixRotation3D(mDiag2, Double.POSITIVE_INFINITY);
1829         } catch (final AlgebraException e) {
1830             throw new CameraException(e);
1831         } catch (final GeometryException ignore) {
1832             // never happens
1833         }
1834     }
1835 
1836     /**
1837      * Computes camera center using singular value decomposition. This method
1838      * is valid even when center is located at infinity (w = 0), although it is
1839      * computationally more complex than other methods. This is the default
1840      * method used when decomposing a camera and computing its center.
1841      *
1842      * @return camera center.
1843      * @throws CameraException if there is numerical instability.
1844      */
1845     public Point3D computeCameraCenterSVD() throws CameraException {
1846         final var result = Point3D.create();
1847         computeCameraCenterSVD(result);
1848         return result;
1849     }
1850 
1851     /**
1852      * Computes camera center using singular value decomposition. This method
1853      * is valid even when center is located at infinity, although it is
1854      * computationally more complex than other methods. This is the default
1855      * method used when decomposing a camera and computing its center.
1856      *
1857      * @param result point where camera center will be stored.
1858      * @throws CameraException if there is numerical instability.
1859      */
1860     public void computeCameraCenterSVD(final Point3D result) throws CameraException {
1861         try {
1862             normalize(); //to increase accuracy
1863 
1864             // camera center is the null-space of camera matrix
1865             final var decomposer = new SingularValueDecomposer(internalMatrix);
1866 
1867             decomposer.decompose();
1868 
1869             // because camera matrix is at most rank 3, the camera center is the
1870             // last column of decomposed matrix V
1871             final var v = decomposer.getV();
1872 
1873             result.setHomogeneousCoordinates(
1874                     v.getElementAt(0, PINHOLE_CAMERA_MATRIX_COLS - 1),
1875                     v.getElementAt(1, PINHOLE_CAMERA_MATRIX_COLS - 1),
1876                     v.getElementAt(2, PINHOLE_CAMERA_MATRIX_COLS - 1),
1877                     v.getElementAt(3, PINHOLE_CAMERA_MATRIX_COLS - 1));
1878         } catch (final AlgebraException e) {
1879             throw new CameraException(e);
1880         }
1881     }
1882 
1883     /**
1884      * Computes camera center using determinants of camera matrix minors. This
1885      * method also works when center is located at infinity (w = 0) and is less
1886      * computationally expensive than SVD, however it might also be less
1887      * accurate.
1888      *
1889      * @return camera center.
1890      * @throws CameraException if there is numerical instabilities.
1891      */
1892     public Point3D computeCameraCenterDet() throws CameraException {
1893         final var result = Point3D.create();
1894         computeCameraCenterDet(result);
1895         return result;
1896     }
1897 
1898     /**
1899      * Computes camera center using determinants of camera matrix minors. This
1900      * method also works when center is located at infinity (w = 0) and is less
1901      * computationally expensive than SVD, however it might also be less
1902      * accurate.
1903      *
1904      * @param result point where camera center will be stored.
1905      * @throws CameraException if there is numerical instability.
1906      */
1907     public void computeCameraCenterDet(final Point3D result) throws CameraException {
1908 
1909         try {
1910             // to increase accuracy
1911             normalize();
1912 
1913             final var m = new Matrix(PINHOLE_CAMERA_MATRIX_ROWS, PINHOLE_CAMERA_MATRIX_ROWS);
1914 
1915             // build minor using columns 2, 3 and 4
1916             m.setElementAt(0, 0, internalMatrix.getElementAt(0, 1));
1917             m.setElementAt(1, 0, internalMatrix.getElementAt(1, 1));
1918             m.setElementAt(2, 0, internalMatrix.getElementAt(2, 1));
1919 
1920             m.setElementAt(0, 1, internalMatrix.getElementAt(0, 2));
1921             m.setElementAt(1, 1, internalMatrix.getElementAt(1, 2));
1922             m.setElementAt(2, 1, internalMatrix.getElementAt(2, 2));
1923 
1924             m.setElementAt(0, 2, internalMatrix.getElementAt(0, 3));
1925             m.setElementAt(1, 2, internalMatrix.getElementAt(1, 3));
1926             m.setElementAt(2, 2, internalMatrix.getElementAt(2, 3));
1927 
1928             final var x = Utils.det(m);
1929 
1930             // build minor using columns 1, 3 and 4
1931             m.setElementAt(0, 0, internalMatrix.getElementAt(0, 0));
1932             m.setElementAt(1, 0, internalMatrix.getElementAt(1, 0));
1933             m.setElementAt(2, 0, internalMatrix.getElementAt(2, 0));
1934 
1935             m.setElementAt(0, 1, internalMatrix.getElementAt(0, 2));
1936             m.setElementAt(1, 1, internalMatrix.getElementAt(1, 2));
1937             m.setElementAt(2, 1, internalMatrix.getElementAt(2, 2));
1938 
1939             m.setElementAt(0, 2, internalMatrix.getElementAt(0, 3));
1940             m.setElementAt(1, 2, internalMatrix.getElementAt(1, 3));
1941             m.setElementAt(2, 2, internalMatrix.getElementAt(2, 3));
1942 
1943             final var y = -Utils.det(m);
1944 
1945             // build minor using columns 1, 2 and 4
1946             m.setElementAt(0, 0, internalMatrix.getElementAt(0, 0));
1947             m.setElementAt(1, 0, internalMatrix.getElementAt(1, 0));
1948             m.setElementAt(2, 0, internalMatrix.getElementAt(2, 0));
1949 
1950             m.setElementAt(0, 1, internalMatrix.getElementAt(0, 1));
1951             m.setElementAt(1, 1, internalMatrix.getElementAt(1, 1));
1952             m.setElementAt(2, 1, internalMatrix.getElementAt(2, 1));
1953 
1954             m.setElementAt(0, 2, internalMatrix.getElementAt(0, 3));
1955             m.setElementAt(1, 2, internalMatrix.getElementAt(1, 3));
1956             m.setElementAt(2, 2, internalMatrix.getElementAt(2, 3));
1957 
1958             final var z = Utils.det(m);
1959 
1960             // build minor using columns 1, 2 and 3
1961             m.setElementAt(0, 0, internalMatrix.getElementAt(0, 0));
1962             m.setElementAt(1, 0, internalMatrix.getElementAt(1, 0));
1963             m.setElementAt(2, 0, internalMatrix.getElementAt(2, 0));
1964 
1965             m.setElementAt(0, 1, internalMatrix.getElementAt(0, 1));
1966             m.setElementAt(1, 1, internalMatrix.getElementAt(1, 1));
1967             m.setElementAt(2, 1, internalMatrix.getElementAt(2, 1));
1968 
1969             m.setElementAt(0, 2, internalMatrix.getElementAt(0, 2));
1970             m.setElementAt(1, 2, internalMatrix.getElementAt(1, 2));
1971             m.setElementAt(2, 2, internalMatrix.getElementAt(2, 2));
1972 
1973             final var w = -Utils.det(m);
1974 
1975             result.setHomogeneousCoordinates(x, y, z, w);
1976             result.normalize();
1977 
1978         } catch (final AlgebraException e) {
1979             throw new CameraException(e);
1980         }
1981     }
1982 
1983     /**
1984      * Computes camera center. This method is better suited when camera is
1985      * finite (its center is not located at the infinity or close to it).
1986      * Otherwise, because of numerical precision inaccurate results might be
1987      * obtained, or even a CameraException might be thrown. This is the less
1988      * computationally expensive method.
1989      *
1990      * @return camera center.
1991      * @throws CameraException if there is numerical instabilities or center is
1992      *                         located at the infinity or close to it.
1993      */
1994     public Point3D computeCameraCenterFiniteCamera() throws CameraException {
1995         final var result = Point3D.create();
1996         computeCameraCenterFiniteCamera(result);
1997         return result;
1998     }
1999 
2000     /**
2001      * Computes camera center. This method is better suited when camera is
2002      * finite (its center is not located at the infinity or close to it).
2003      * Otherwise, because of numerical precision inaccurate results might be
2004      * obtained, or even a CameraException might be thrown. This is the less
2005      * computationally expensive method.
2006      *
2007      * @param result point where camera center will be stored.
2008      * @throws CameraException if there is numerical instability.
2009      */
2010     public void computeCameraCenterFiniteCamera(final Point3D result) throws CameraException {
2011 
2012         try {
2013             // to increase accuracy
2014             normalize();
2015 
2016             // get top-left 3x3 sub-matrix
2017             final var mMp = internalMatrix.getSubmatrix(0, 0,
2018                     PINHOLE_CAMERA_MATRIX_ROWS - 1, PINHOLE_CAMERA_MATRIX_ROWS - 1);
2019 
2020             // make inverse
2021             final var mInvMp = Utils.inverse(mMp);
2022 
2023             // pick 4th column of camera matrix
2024             final var mP4 = internalMatrix.getSubmatrix(0, PINHOLE_CAMERA_MATRIX_COLS - 1,
2025                     PINHOLE_CAMERA_MATRIX_ROWS - 1, PINHOLE_CAMERA_MATRIX_COLS - 1);
2026 
2027             // get inhomogeneous coordinates of camera center
2028             mInvMp.multiply(mP4);
2029 
2030             result.setInhomogeneousCoordinates(-mInvMp.getElementAtIndex(0), -mInvMp.getElementAtIndex(1),
2031                     -mInvMp.getElementAtIndex(2));
2032             result.normalize();
2033         } catch (final AlgebraException e) {
2034             throw new CameraException(e);
2035         }
2036     }
2037 
2038     /**
2039      * Estimates this camera parameters from 2D-3D point correspondences.
2040      *
2041      * @param point3D1 1st 3D point.
2042      * @param point3D2 2nd 3D point.
2043      * @param point3D3 3rd 3D point.
2044      * @param point3D4 4th 3D point.
2045      * @param point3D5 5th 3D point.
2046      * @param point3D6 6th 3D point.
2047      * @param point2D1 1st 2D point corresponding to the projection of 1st 3D
2048      *                 point.
2049      * @param point2D2 2nd 2D point corresponding to the projection of 2nd 3D
2050      *                 point.
2051      * @param point2D3 3rd 2D point corresponding to the projection of 3rd 3D
2052      *                 point.
2053      * @param point2D4 4th 2D point corresponding to the projection of 4th 3D
2054      *                 point.
2055      * @param point2D5 5th 2D point corresponding to the projection of 5th 3D
2056      *                 point.
2057      * @param point2D6 6th 2D point corresponding to the projection of 6th 3D
2058      *                 point.
2059      * @throws CameraException if camera cannot be estimated using provided
2060      *                         points because of a degeneracy.
2061      */
2062     public final void setFromPointCorrespondences(
2063             final Point3D point3D1, final Point3D point3D2, final Point3D point3D3, final Point3D point3D4,
2064             final Point3D point3D5, final Point3D point3D6, final Point2D point2D1, final Point2D point2D2,
2065             final Point2D point2D3, final Point2D point2D4, final Point2D point2D5, final Point2D point2D6)
2066             throws CameraException {
2067 
2068         final var points3D = new ArrayList<Point3D>(
2069                 PointCorrespondencePinholeCameraEstimator.MIN_NUMBER_OF_POINT_CORRESPONDENCES);
2070         final var points2D = new ArrayList<Point2D>(
2071                 PointCorrespondencePinholeCameraEstimator.MIN_NUMBER_OF_POINT_CORRESPONDENCES);
2072 
2073         points3D.add(point3D1);
2074         points3D.add(point3D2);
2075         points3D.add(point3D3);
2076         points3D.add(point3D4);
2077         points3D.add(point3D5);
2078         points3D.add(point3D6);
2079 
2080         points2D.add(point2D1);
2081         points2D.add(point2D2);
2082         points2D.add(point2D3);
2083         points2D.add(point2D4);
2084         points2D.add(point2D5);
2085         points2D.add(point2D6);
2086 
2087         try {
2088             final var estimator = new DLTPointCorrespondencePinholeCameraEstimator(points3D, points2D);
2089             estimator.setLMSESolutionAllowed(false);
2090             final var camera = estimator.estimate();
2091 
2092             setInternalMatrix(camera.internalMatrix);
2093         } catch (final WrongSizeException | GeometryException e) {
2094             throw new CameraException(e);
2095         }
2096     }
2097 
2098     /**
2099      * Estimates this camera parameters from line/plane correspondences.
2100      *
2101      * @param plane1 1st 3D plane.
2102      * @param plane2 2nd 3D plane.
2103      * @param plane3 3rd 3D plane.
2104      * @param plane4 4th 3D plane.
2105      * @param line1  1st 2D line corresponding to 1st 3D plane.
2106      * @param line2  2nd 2D line corresponding to 2nd 3D plane.
2107      * @param line3  3rd 2D line corresponding to 3rd 3D plane.
2108      * @param line4  4th 2D line corresponding to 4th 3D plane.
2109      * @throws CameraException if camera cannot be estimated using provided
2110      *                         lines and planes because of a degeneracy.
2111      */
2112     public final void setFromLineAndPlaneCorrespondences(
2113             final Plane plane1, final Plane plane2, final Plane plane3, final Plane plane4, final Line2D line1,
2114             final Line2D line2, final Line2D line3, final Line2D line4) throws CameraException {
2115 
2116         final var planes = new ArrayList<Plane>(
2117                 LinePlaneCorrespondencePinholeCameraEstimator.MIN_NUMBER_OF_LINE_PLANE_CORRESPONDENCES);
2118         final var lines2D = new ArrayList<Line2D>(
2119                 LinePlaneCorrespondencePinholeCameraEstimator.MIN_NUMBER_OF_LINE_PLANE_CORRESPONDENCES);
2120 
2121         planes.add(plane1);
2122         planes.add(plane2);
2123         planes.add(plane3);
2124         planes.add(plane4);
2125 
2126         lines2D.add(line1);
2127         lines2D.add(line2);
2128         lines2D.add(line3);
2129         lines2D.add(line4);
2130 
2131         try {
2132             final var estimator = new DLTLinePlaneCorrespondencePinholeCameraEstimator(planes, lines2D);
2133             estimator.setLMSESolutionAllowed(false);
2134             final var camera = estimator.estimate();
2135 
2136             setInternalMatrix(camera.internalMatrix);
2137         } catch (final WrongSizeException | GeometryException e) {
2138             throw new CameraException(e);
2139         }
2140     }
2141 }