Pinhole Camera

This documentation was generated with the assistance of AI. Please report any inaccuracies.

PinholeCamera is where irurueta-geometry’s 3D and 2D worlds meet: it maps 3D points, lines, planes, conics and quadrics onto their 2D image counterparts (points, lines, conics) using the standard linear (projective) pinhole model. This page follows the treatment in chapter 2, "Camera models", of Irurueta’s PhD thesis — the repository owner’s own dissertation — cross-checked against chapters 6 and 8 of Hartley & Zisserman, and grounded line-by-line in the actual `PinholeCamera, Camera and PinholeCameraIntrinsicParameters source.

Pinhole camera model: 3D point X projects through camera center C onto the image plane

From pinhole to matrix: why a camera is a P3 → P2 map

A pinhole camera has no lens: light passes through a single small aperture and forms an inverted image on a photographic plate behind it (PHD §2.1, fig. 2.1). Equivalently, and more conveniently, one can place a virtual retinal plane in front of the projection center and reason with the (non-inverted) image formed there. Ignoring lens effects — most notably radial distortion, which bends straight lines near the image border and is not modeled by PinholeCamera at all (see the aside below) — this projection is exactly linear once points are expressed in homogeneous coordinates, which is the whole reason projective geometry is the natural language for it.

Put the camera center at the origin, pointing down the Z axis, with the retinal plane at . A 3D point projects to (PHD eq. 2.1). In homogeneous coordinates this inhomogeneous division disappears and the mapping becomes linear:



which is exactly the canonical camera ] (PHD eq. 2.2-2.3; HZ §6.1, "the basic pinhole model"). Every other camera in this library — however it was constructed or estimated — is this same linear idea generalized with a focal length, a principal-point offset, a pixel/skew correction, and an arbitrary position and orientation in the world. That generalization is what the rest of this page unpacks.

Real cameras also exhibit radial distortion (barrel or pincushion, following Brown’s model — see PHD §2.2.4). PinholeCamera intentionally does not model it: it is a purely linear projective camera, so any distortion correction has to happen upstream, on the 2D image coordinates, before they are handed to this class.

Representation: a single 3x4 matrix

A PinholeCamera is stored as one 3×4 projective matrix P (not separately as K, R, C). Starting from the canonical camera above, adding a focal length , a principal point offset , a pixel/skew correction (all folded into a single upper-triangular calibration matrix ), and a world-to-camera rigid motion (rotation , camera center ) gives the classic factorization

(PHD eq. 2.22-2.24; HZ eq. 6.7-6.8). Writing ] with the top-left 3×3 block, a camera is called finite when  — this is the case the rest of this page mostly addresses — and a camera at infinity otherwise, whose center lies on the plane at infinity (PHD §2.3; HZ §6.2.1/6.3).

The factorization above is recovered lazily, on demand, via decompose():

public void decompose() throws CameraException {
    decompose(DEFAULT_DECOMPOSE_INTRINSICS_AND_ROTATION);
}

public void decompose(final boolean decomposeIntrinsicsAndRotation) throws CameraException {
    decompose(decomposeIntrinsicsAndRotation, DEFAULT_DECOMPOSE_CAMERA_CENTER);
}

public void decompose(final boolean decomposeIntrinsicsAndRotation, final boolean decomposeCameraCenter)
        throws CameraException {
    // clean up previous intrinsics, rotation and camera center
    intrinsicParameters = null;
    cameraRotation = null;
    cameraCenter = null;
    if (decomposeIntrinsicsAndRotation) {
        computeIntrinsicsAndRotation();
    }
    if (decomposeCameraCenter) {
        if (cameraCenter == null) {
            cameraCenter = Point3D.create(CoordinatesType.HOMOGENEOUS_COORDINATES);
        }
        computeCameraCenterSVD(cameraCenter);
    }
}
  1. The top-left 3×3 block Mp is RQ-decomposed (com.irurueta.algebra.RQDecomposer) into an upper-triangular K (intrinsics) and an orthogonal Q (rotation), fixing signs so `K’s diagonal is positive — see "Decomposing `Mp`" below.

  2. The camera center defaults to the SVD null-space method (computeCameraCenterSVD), but two cheaper, interchangeable alternatives exist — see "The camera center" below.

The decomposed PinholeCameraIntrinsicParameters, Rotation3D and Point3D (center) are cached and are only recomputed the next time they are requested after any setter (setInternalMatrix, setIntrinsicParameters, setCameraRotation, …​) invalidates the cache; areIntrinsicParametersAvailable(), isCameraRotationAvailable() and isCameraCenterAvailable() report whether a cached value already exists without forcing a decomposition.

Intrinsic parameters

PinholeCameraIntrinsicParameters wraps a 3×3 upper-triangular calibration matrix K:

fx s px

0

fy

py

0

0

1

This matrix is built up in exactly three steps, each adding one real-world effect (PHD §2.2.2):

  1. Focal length (§2.2.2.1) — for a retinal plane at distance from the center instead of , the canonical mapping becomes , i.e. K = diag(f, f, 1).

  2. Principal point offset (§2.2.2.2) — the retinal plane’s own coordinate origin rarely coincides exactly with where the optical axis pierces it, so an offset is added: , giving K its off-diagonal px/py column.

  3. CCD pixels and skew (§2.2.2.3) — because image coordinates are measured in pixels, not the physical units of the focal length, world-to-pixel conversion multiplies by the number of pixels per unit length in each direction, , giving separate horizontal/vertical focal lengths , (fx/fy), and a possible skew term when the pixel grid is not perfectly rectangular (HZ eq. 6.9-6.10 arrives at the identical matrix).

getAspectRatio() returns fy/fx; setAspectRatioKeepingHorizontalFocalLength(double) / …​KeepingVerticalFocalLength(double) adjust it while pinning one focal length. getSkewnessAngle() / setSkewnessAngle(double) expose the skew as the angle from the formula above, rather than the raw coefficient s.

Three factories cover the common cases. createCanonicalIntrinsicParameters() returns the identity K (the bare canonical camera derived above). createTypicalIntrinsicParameters(width, height) centers the principal point on the image and picks a focal length equal to (width + height) / 2, roughly a 45° field of view — a reasonable guess when nothing else is known. create(focalLength, sensorWidth, sensorHeight, imageWidth, imageHeight) performs exactly the CCD-pixel conversion of step 3 above, in physical units (millimeters):

public static PinholeCameraIntrinsicParameters create(
        final double focalLength, final double sensorWidth, final double sensorHeight, final int imageWidth,
        final int imageHeight) {

    // compute the size of a pixel taking into account sensor and image sizes
    final var pixelWidth = sensorWidth / imageWidth; // mm/px
    final var pixelHeight = sensorHeight / imageHeight; // mm/px

    // compute focal lengths expressed in pixels
    final var horizontalFocalLength = focalLength / pixelWidth;
    final var verticalFocalLength = focalLength / pixelHeight;

    return new PinholeCameraIntrinsicParameters(horizontalFocalLength, verticalFocalLength, 0.0,
            0.0, 0.0);
}

1 / pixelWidth and 1 / pixelHeight are precisely and above, so focalLength / pixelWidth  — this is PHD eq. 2.16 read directly off a datasheet’s focal length and sensor size. Skew and principal-point offset are assumed zero here, which is the common case for real CCD/CMOS sensors. getInverseInternalMatrix() computes with a hand-derived closed form from fx, fy, s and the principal point, rather than a generic matrix inversion, purely for efficiency: K is triangular and small enough that this is both faster and better conditioned than Utils.inverse.

Decomposing Mp: RQ decomposition into K and R

Given only P, recovering K and R from the top-left 3×3 block Mp = K·R is exactly the problem an RQ decomposition solves: factor an arbitrary square matrix into an upper-triangular matrix times an orthogonal one, (note the unfortunate name clash — the "R" in "RQ" is unrelated to the camera’s rotation matrix, which happens to be the "Q" here). RQ decomposition is not unique up to sign/scale, so the extra constraints that K’s diagonal be positive and `R be a proper rotation ( , not just orthogonal) pin down a unique answer, up to the camera matrix’s own overall scale ambiguity (PHD §2.3.6, eq. 2.74-2.78).

com.irurueta.algebra.RQDecomposer (in the sibling module irurueta-algebra) implements this with Givens rotations, following HZ's Algorithm A4.1: three successive rotations about the coordinate axes, each chosen to zero one below-diagonal entry of the matrix without disturbing the entries already cleared, leaving an upper-triangular R times a rotation Q (HZ Appendix §A4.1.1, p. 579). This is the same family of factorizations as the more familiar QR decomposition via Householder reflections (Numerical Recipes §2.10, pp. 102-106) — RQDecomposer internally wraps a QRDecomposer rather than reimplementing Givens rotations directly.

PinholeCamera.computeIntrinsicsAndRotation() uses it exactly like this:

final var mMp = internalMatrix.getSubmatrix(0, 0,
        PINHOLE_CAMERA_MATRIX_ROWS - 1, PINHOLE_CAMERA_MATRIX_ROWS - 1);

// Use RQ decomposition to obtain intrinsic parameters as R ensuring
// that elements on the diagonal are positive and element (3, 3) is 1,
// and Q is an orthogonal matrix
final var decomposer = new RQDecomposer(mMp);
decomposer.decompose();

final var r = decomposer.getR();
final var q = decomposer.getQ();

The rest of the method (not reproduced in full) multiplies r and q by a suitable so that r’s diagonal ends up strictly positive and `q ends up a proper rotation — exactly the sign-fixing recipe of PHD eq. 2.76-2.78 — before wrapping the results as PinholeCameraIntrinsicParameters and MatrixRotation3D.

The camera center: three methods, one geometric fact

The camera center C is the unique 4-vector (up to scale) with  — the right null-space of P. Geometrically, any 3D line through C and another point M is a ray of light: every point on it projects to the same image point (the projection of M), so the "extra" degree of freedom along the ray must vanish when multiplied by P, which is exactly (PHD §2.3.1, Proof 2.1; HZ §6.2.1, "camera centre"). Because P is rank 3 with 4 columns, this null-space is always exactly one-dimensional — the camera always has a well-defined center, even when it lies at infinity (an "affine" or degenerate camera, ).

That single geometric fact can be turned into a concrete number in three different ways, and PinholeCamera ships all three because they trade off cost against generality:

Camera decomposition: RQ-decomposing P’s top-left block into K and R

computeCameraCenterSVD() (the default used by decompose()). Any null-space is most robustly found via singular value decomposition: writing P’s SVD as , the column of `V corresponding to P’s zero singular value is exactly the null-space vector (PHD §2.3.1.1; HZ §6.2.4, "Finding the camera centre", citing its own Appendix §A4.4 for SVD; general SVD algorithm in Numerical Recipes §2.6, pp. 65-67). It remains correct even when the true center is at infinity (rank-deficient `Mp) since the SVD makes no finiteness assumption — at the cost of being the most expensive of the three:

public void computeCameraCenterSVD(final Point3D result) throws CameraException {
    normalize(); // to increase accuracy

    // camera center is the null-space of camera matrix
    final var decomposer = new SingularValueDecomposer(internalMatrix);
    decomposer.decompose();

    // because camera matrix is at most rank 3, the camera center is the
    // last column of decomposed matrix V
    final var v = decomposer.getV();

    result.setHomogeneousCoordinates(
            v.getElementAt(0, PINHOLE_CAMERA_MATRIX_COLS - 1),
            v.getElementAt(1, PINHOLE_CAMERA_MATRIX_COLS - 1),
            v.getElementAt(2, PINHOLE_CAMERA_MATRIX_COLS - 1),
            v.getElementAt(3, PINHOLE_CAMERA_MATRIX_COLS - 1));
}

computeCameraCenterDet(). The same null-space can be obtained algebraically, without a full SVD, from the determinants of `P’s four 3×3 minors (each formed by dropping one column):

This follows from the fact that the camera center lies on all three of the camera’s row-planes (see below), so appending any other plane through it to a 3×4 stack yields a rank-deficient, zero-determinant matrix; expanding that determinant by Laplace’s formula reproduces exactly this cofactor expression (PHD §2.3.1.2, Proof 2.2). It is cheaper than SVD (four 3×3 determinants via Utils.det, instead of a full decomposition of the whole 3×4 matrix) and, like the SVD method, remains valid even at infinity. The real method repeats the same four lines (build a 3×3 minor by copying three of the four columns, take its determinant) once per coordinate, with alternating sign (abbreviated here for brevity):

public void computeCameraCenterDet(final Point3D result) throws CameraException {
    normalize();

    final var m = new Matrix(PINHOLE_CAMERA_MATRIX_ROWS, PINHOLE_CAMERA_MATRIX_ROWS);

    // build minor using columns 2, 3 and 4 (drop column 1) -> x =  det(m)
    // build minor using columns 1, 3 and 4 (drop column 2) -> y = -det(m)
    // build minor using columns 1, 2 and 4 (drop column 3) -> z =  det(m)
    // build minor using columns 1, 2 and 3 (drop column 4) -> w = -det(m)
    final var x = Utils.det(m);
    // ... (y, z, w computed the same way, from the other three minors)

    result.setHomogeneousCoordinates(x, y, z, w);
    result.normalize();
}

computeCameraCenterFiniteCamera(). If the camera is known to be finite ( invertible), the center follows directly from (PHD §2.3.1.3, eq. 2.45-2.46; HZ §6.2.4, closed form ). This is the cheapest of the three (one 3×3 matrix inverse instead of a determinant per column or a full SVD) but the only one of the three that requires a non-degenerate, finite camera — it will fail or be numerically unstable if Mp is singular or ill-conditioned:

public void computeCameraCenterFiniteCamera(final Point3D result) throws CameraException {
    normalize();

    final var mMp = internalMatrix.getSubmatrix(0, 0,
            PINHOLE_CAMERA_MATRIX_ROWS - 1, PINHOLE_CAMERA_MATRIX_ROWS - 1);
    final var mInvMp = Utils.inverse(mMp);

    final var mP4 = internalMatrix.getSubmatrix(0, PINHOLE_CAMERA_MATRIX_COLS - 1,
            PINHOLE_CAMERA_MATRIX_ROWS - 1, PINHOLE_CAMERA_MATRIX_COLS - 1);

    mInvMp.multiply(mP4);

    result.setInhomogeneousCoordinates(-mInvMp.getElementAtIndex(0), -mInvMp.getElementAtIndex(1),
            -mInvMp.getElementAtIndex(2));
    result.normalize();
}

In short: use the closed form when the camera is known to be finite and speed matters; fall back to the cofactor method for a cheaper alternative that still tolerates a center at infinity; use SVD (the default) when robustness matters more than speed, or when nothing is known about the camera’s degeneracy in advance.

Vanishing points, axis planes and the principal point

Several classic projective-camera facts fall directly out of P’s own columns and rows, without needing a full `decompose():

  • Vanishing points — getXAxisVanishingPoint()/Y/Z (plus xAxisVanishingPoint(Point2D) etc.) return columns 1-3 of P. Since the world X/Y/Z axis directions are the points at infinity , , , their images and so on are exactly those columns (PHD §2.3.2, eq. 2.47-2.48).

  • Image of the world origin — getImageOfWorldOrigin()/imageOfWorldOrigin(Point2D) return column 4 of P, the image of (PHD eq. 2.49).

  • Axis and principal planes — getVerticalAxisPlane()/getHorizontalAxisPlane()/getPrincipalPlane() read off rows 1/2/3 of P as 3D plane coefficients. Each row, read as a plane equation, is satisfied by every point that projects onto one particular image line (the vertical or horizontal image axis) or onto the line at infinity (the principal plane, parallel to the retinal plane); the camera center lies on all three, since holds row by row (PHD §2.3.3, eq. 2.51-2.59; HZ Table 6.1, p. 158). The principal plane’s director vector always points where the camera looks, and the camera center is its locus.

  • Principal point — getPrincipalPoint()/principalPoint(Point2D) compute , the projection of the point at infinity perpendicular to the principal plane, where is the principal plane’s own first three coefficients (PHD §2.3.4, eq. 2.60-2.63; HZ §6.2.1, ).

  • Principal axis vector — getPrincipalAxisArray()/principalAxisArray(double[]) return the first three elements of `P’s last row (the principal plane’s director vector), scaled by and re-normalized so that it always points towards the front of the camera regardless of the matrix’s own homogeneous scale — see camera sign below (PHD §2.3.5, eq. 2.66-2.72).

Cheirality, camera sign and depth

Because P is only defined up to an arbitrary non-zero scale, and describe the exact same camera geometry — yet only one of the two sign choices consistently places 3D points "in front of" rather than "behind" the lens. Resolving that ambiguity is what camera sign and cheirality are for.

getCameraSign() / getCameraSign(double threshold) return the sign of . This works because scaling P by any scales by and the third row of Mp by k, so the vector is invariant to regardless of the sign of k — it always points toward the front of the camera (PHD §2.3.5, eq. 2.65-2.72; HZ §6.2.1, principal axis vector). fixCameraSign() multiplies the whole matrix by that sign once, and isCameraSignFixed() reports whether that has already happened (any setter resets the flag).

getDepth(Point3D point) computes the signed distance of a 3D point from the principal plane, along the principal axis:

(PHD §2.4.1, eq. 2.79-2.82; HZ Result 6.1, eq. 6.15). In code this is exactly the dot product of (point - cameraCenter) with the already sign-fixed, unit-length principalAxisArray():

public double getDepth(final Point3D point) throws CameraException {
    if (!isCameraCenterAvailable()) {
        cameraCenter = computeCameraCenterSVD();
    }
    final var principalAxis = getPrincipalAxisArray();
    final var diff = new double[INHOM_COORDS];
    diff[0] = point.getInhomX() - cameraCenter.getInhomX();
    diff[1] = point.getInhomY() - cameraCenter.getInhomY();
    diff[2] = point.getInhomZ() - cameraCenter.getInhomZ();

    return ArrayUtils.dotProduct(principalAxis, diff);
}

getCheirality(Point3D point) answers the cheaper yes/no question — is the point in front of, or behind, the camera — without computing an actual distance. It reduces to the sign of the last homogeneous coordinate of the projected point times the last homogeneous coordinate of the 3D point, (PHD §2.4.2, eq. 2.83-2.96), corrected by the camera sign whenever it has not already been fixed:

public double getCheirality(final Point3D point) throws CameraException {
    point.normalize();
    normalize();

    final var hom3DW = point.getHomW();
    final var hom2DW = point.getHomX() * internalMatrix.getElementAt(2, 0)
            + point.getHomY() * internalMatrix.getElementAt(2, 1)
            + point.getHomZ() * internalMatrix.getElementAt(2, 2)
            + point.getHomW() * internalMatrix.getElementAt(2, 3);

    var cheiral = hom3DW * hom2DW;
    if (!isCameraSignFixed()) {
        cheiral *= getCameraSign();
    }
    return cheiral;
}

Projection and back-projection

Because lines/planes and points transform contragradiently (see Transformations), the camera’s project/back-project operations use different formulas depending on what is being mapped:

From To Formula

Point3D

Point2D

(direct forward map, PHD eq. 2.3)

Line2D

Plane

(transpose, not inverse — back-projecting an image line gives the 3D plane through the camera center and that line; PHD §2.7, eq. 2.152)

Point2D

Point3D (ray)

using the Moore-Penrose pseudo-inverse ; explicitly non-unique — any point on the same ray of light is an equally valid answer, and any solution is a linear combination of this one and the camera center (PHD §2.7, eq. 2.146-2.150; HZ §6.2.2, eq. 6.13)

Conic

Quadric

(back-projection, sandwiched transpose — see derivation below)

DualQuadric

DualConic

(forward projection of the dual; PHD §2.8, eq. 2.157; HZ Result 8.9, eq. 8.5)

Deriving Q = Pᵗ·C·P, and how duality composes

The pseudo-inverse ray formula above (backProject(Point2D, Point3D)) computes via Utils.pseudoInverse(internalMatrix), normalized by its Frobenius norm, exactly as the general pseudo-inverse built from an SVD (Numerical Recipes §2.6, pp. 65-67 for the SVD itself; Appendix §A5.2 of HZ, p. 590, for the pseudo-inverse built from it):

final var pseudoInverseInternalMatrix = Utils.pseudoInverse(internalMatrix);
final var norm = Utils.normF(pseudoInverseInternalMatrix);
pseudoInverseInternalMatrix.multiplyByScalar(1.0 / norm);
pseudoInverseInternalMatrix.multiply(m); // m = homogeneous image point

The conic back-projection is a different, simpler idea: it is a direct algebraic pullback of the conic’s quadratic form. If a 3D point projects to , and satisfies the conic equation , then

so is exactly the quadric locus of every 3D point whose projection lies on C — the cone of rays traced back through the conic. The source comment in PinholeCamera.backProject(Conic, Quadric) states this identity directly ("We need to compute: Q = P^T * C * P"), and one can check that the camera center itself always lies on this cone: \$\mathbf{C}_{\text{cam}}^{\mathsf T}\mathbf{Q}\mathbf{C}_{\text{cam}}=(\mathbf{P}\,\mathbf{C}_{\text{cam}})^{\mathsf T}\mathbf{C}(\mathbf{P}\,\mathbf{C}_{\text{cam}})=\mathbf 0^{\mathsf T}\mathbf{C}\,\mathbf 0=0\$, as expected of a cone whose vertex is the camera center (HZ §8.3, fig. 8.4, "the cone of rays"). The dual analogue — forward-projecting a dual quadric into a dual conic  — is HZ’s Result 8.9 and PHD’s eq. 2.155-2.157, obtained the mirror way: a plane tangent to back-projects from a line tangent to , via the same used for plain lines/planes above.

Because the direct (Conic/Quadric) and dual (DualConic/DualQuadric) representations of the same geometric object are related by matrix inversion, duality composes: Camera.project(Quadric) does not reimplement anything new, it converts the quadric to its dual, calls project(DualQuadric, DualConic) above, then converts the resulting dual conic back:

public void project(final Quadric quadric, final Conic result) throws CameraException {
    quadric.normalize();
    final var dualQuadric = quadric.getDualQuadric();
    final var dualConic = new DualConic();
    project(dualQuadric, dualConic);
    dualConic.conic(result);
}

and Camera.backProject(DualConic) is the exact mirror route (dual conic → conic → backProject(Conic, Quadric) → dual quadric).

Camera matrices, the geometry strata and the absolute conic

A camera’s images are unaffected by which projective/affine/metric stratum the world points and the camera are expressed in, as long as both are transformed consistently: if , then , i.e. any 3D transformation induces on the camera (PHD §2.8, eq. 2.153-2.154; see Transformations for how Transformation3D acts on a PinholeCamera). Composing this with the dual-quadric projection formula above and specializing to the dual absolute quadric gives the dual image of the absolute conic (DIAC), , which in the metric stratum reduces to  — i.e. K is recoverable from by Cholesky factorization (PHD §2.8, eq. 2.158-2.160; HZ §8.5, Result 8.17, ). This is the theoretical basis for camera self-calibration and for recovering the metric stratum from a purely projective reconstruction.

This library does not, however, ship a ready-made self-calibration/DIAC-estimator class — there is no Quadric.createCanonicalAbsoluteQuadric() or Kruppa-equation solver in com.irurueta.geometry.estimators or com.irurueta.geometry.refiners; only DualQuadric.createCanonicalDualAbsoluteQuadric() exists as a building block. The EPnPPointCorrespondencePinholeCameraEstimator (Lepetit, Moreno-Noguer and Fua) remains the only estimator this page cites directly. See Conics and Quadrics for the absolute conic/quadric themselves, and Transformations for the stratification of 3D transformations.

Structural shortcuts baked into the matrix, at a glance

  • Camera center at infinity — explicitly supported: computeCameraCenterSVD/computeCameraCenterDet are documented as valid "even when center is located at infinity (w = 0)", modeling the classical affine-camera degeneracy (HZ §6.3); only the cheapest computeCameraCenterFiniteCamera method requires a finite center.

  • Cheirality/sign — getCameraSign()/fixCameraSign() use the sign of the top-left 3×3 block’s determinant to resolve the front/behind-camera ambiguity inherent to a homogeneous, scale-free matrix.

  • Everything else (vanishing points, axis/principal planes, principal point/axis) is read directly off `P’s own rows and columns without any decomposition at all — see "Vanishing points, axis planes and the principal point" above.

References

  • Alberto Irurueta, Fixed Scene 3D Reconstruction for Mobile Applications, PhD thesis, chapter 2, "Camera models" — the pinhole and canonical camera (§2.1-2.2, pp. 44-50), intrinsic/extrinsic parameters and CCD pixel conversion (§2.2.2-2.2.3, pp. 47-52), radial distortion aside (§2.2.4, pp. 52-54), camera decomposition and the three camera-center methods (§2.3.1.1-2.3.1.3, pp. 55-58), vanishing points/axis planes/principal point/principal axis and camera sign (§2.3.2-2.3.5, pp. 59-63), RQ decomposition for K and R (§2.3.6, p. 64), depth and cheirality (§2.4, pp. 64-68), back-projection of points and lines (§2.7, pp. 82-83), and camera matrices, the geometry strata and their relation to conics/quadrics and the DIAC (§2.8, pp. 83-85). See the bibliography entry for the full citation.

  • Hartley & Zisserman, Multiple View Geometry in Computer Vision, 2nd edition — chapter 6, "Camera Models": the projective/finite camera hierarchy, camera anatomy, centre, column/row vectors, principal point/axis (§6.1-6.2, pp. 153-165, Table 6.1 p. 158), depth of points (Result 6.1, pp. 162-163), decomposition via RQ and SVD/cofactors (§6.2.4, p. 164), and cameras at infinity (§6.3, pp. 166-174); chapter 8, "More Single View Geometry": action on quadrics and the cone of rays (§8.3, Result 8.9-8.10, pp. 201-202), the importance of the camera centre (§8.4, pp. 202-208), camera calibration and the image of the absolute conic (§8.5, Result 8.17, pp. 208-211), vanishing points and lines (§8.6, pp. 212-220), affine 3D measurements (§8.7, pp. 220-222), and calibration from a single view (§8.8, pp. 222-225); Appendix 4, "Matrix Properties and Decompositions": RQ decomposition via Givens rotations (Algorithm A4.1, §A4.1.1, p. 579) and QR via Householder matrices (§A4.1.2, p. 580); Appendix 5, "Least-squares Minimization": the pseudo-inverse (§A5.2, p. 590) and its SVD-based construction (Algorithm A5.4, p. 593).

  • Press, Teukolsky, Vetterling and Flannery, Numerical Recipes: The Art of Scientific Computing, 3rd edition — §2.6, "Singular Value Decomposition" (pp. 65-67), the general-purpose numerical machinery behind Camera.backProject(Point2D, Point3D)’s pseudo-inverse and `computeCameraCenterSVD()’s null-space computation; §2.10, "QR Decomposition" (pp. 102-106), the broader algorithm family that HZ’s Appendix 4 specializes into the RQ decomposition `com.irurueta.algebra.RQDecomposer implements.

  • Lepetit, Moreno-Noguer and Fua, "EPnP: An Accurate O(n) Solution to the PnP Problem" — cited directly in EPnPPointCorrespondencePinholeCameraEstimator javadoc (the estimators package), used internally by some pinhole camera estimators.

Key classes

The classes exercised by the code examples above, with links to their source and Javadoc:

Class Links

PinholeCamera

Source
Javadoc

PinholeCameraIntrinsicParameters

Source
Javadoc

CameraException

Source
Javadoc

Point3D

Source
Javadoc

Conic

Source
Javadoc

Quadric

Source
Javadoc

DualConic

Source
Javadoc

DualQuadric

Source
Javadoc

The examples also use com.irurueta.algebra.RQDecomposer, SingularValueDecomposer, Matrix and Utils, from the sibling irurueta-algebra library, not this repository.
  • Transformations — how a Transformation3D acts on a PinholeCamera ( ), and the projective/affine/metric stratification referenced in "Camera matrices, the geometry strata…​" above.

  • Conics and Quadrics — conics, quadrics, their duals, and the absolute conic/quadric that underlie the DIAC and back-projection formulas on this page.

  • Rotations — the camera’s orientation is stored as a Rotation3D.

  • Points, Lines and Planes — the points, lines and planes being projected/back-projected.