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 }