Transformation Estimators

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

com.irurueta.geometry.estimators has four robust-estimator hierarchies for the stratified transformation families described in Transformations: Euclidean, metric, affine and projective, each in 2D and 3D. Every one follows the general pattern laid out in Estimators: draw a minimal sample of correspondences, build a candidate transformation from it, score the candidate against the rest of the data, and repeat — with RANSAC, LMedS, MSAC, PROSAC and PROMedS differing only in the scoring rule (estimators.adoc#ransac, estimators.adoc#lmeds, estimators.adoc#msac, estimators.adoc#prosac-promeds).

What is genuinely specific to these four hierarchies is where they split into two architecturally different groups:

Family Euclidean & Metric Affine & Projective

Non-robust algorithm

a real, standalone, reusable XxxEstimator class

none — closed form is inlined per robust subclass

How the robust wrapper gets a hypothesis

delegates: constructs the non-robust estimator internally and calls estimate()

duplicates: rebuilds the same linear solve inline, once per robust subclass

Correspondence types

point only

point, and line (2D) / plane (3D)

Minimal sample size (2D / 3D)

3 / 4 (weak minimum 2 / 3)

3 / 4 (affine), 4 / 5 (projective) — same count for the line/plane variant as the point one

Euclidean and metric: the robust wrapper reuses a real non-robust estimator

EuclideanTransformation2DEstimator is the one non-robust algorithm on this page that is not just an inlined constructor call. Its javadoc states plainly: "This estimator uses Kabsch algorithm on 2D. A minimum of 3 non-coincident matched 2D input/output points is required for estimation. For some point configurations 2 points are enough to find a valid solution. If more points are provided an LMSE (Least Mean Squared Error) solution will be found." (EuclideanTransformation2DEstimator.java:31-41, citing Wikipedia’s Kabsch algorithm article). EuclideanTransformation3DEstimator is the 3D analogue (minimum 4 points, weak minimum 3), citing the same Wikipedia article plus nghiaho.com (EuclideanTransformation3DEstimator.java:31-43). MetricTransformation2DEstimator/3DEstimator follow the identical shape (minimum 3/4 points, weak minimum 2/3, LMSE when over-determined), with the 3D class explicitly citing a StackOverflow derivation of least-squares scale/rotation/translation fitting (MetricTransformation3DEstimator.java:36-39).

The Kabsch algorithm (Kabsch, 1976) finds the rotation minimizing the sum of squared distances between one point set and a rotated/translated copy of another:

The Kabsch construction, step by step

Kabsch’s own closed-form solution — not itself in any of the three source PDFs, since it is a 1976 crystallography paper the library only cites via Wikipedia, but built entirely from the SVD machinery this library already relies on elsewhere (Numerical Recipes §2.6) — proceeds in four steps that a reader of EuclideanTransformation2D/3DEstimator.java will recognize directly:

  1. Center both point sets on their centroids: , , , . The translation is recovered at the very end as  — rotation and translation are decoupled by this centering, which is exactly why the rotation problem below never mentions .

  2. Build the cross-covariance matrix (a 2×2 matrix in 2D, 3×3 in 3D) — the only place the actual coordinates enter the computation.

  3. Take the SVD .

  4. Recover the optimal rotation , unless , in which case one singular value’s sign is flipped first (MetricTransformation2DEstimator.java:409-429: if (Utils.det(r) < 0.0) { …​ }, negating the last column of before recombining). This check is not optional bookkeeping: without it, a reflection ( ) minimizes the same sum of squared distances just as well as a true rotation whenever the point configuration is close to planar/collinear, and the sign flip is precisely what keeps the result inside rather than the larger group of all orthogonal matrices.

MetricTransformation2D/3DEstimator solve the identical rotation problem, then recover a single extra unknown — a uniform scale — directly from the same SVD, with no further decomposition (MetricTransformation2DEstimator.java:432-436):

where are 's singular values and carries the same reflection sign-fix as the rotation step (the denominator, inCov in the source, is the input point set’s own variance around its centroid). EuclideanTransformation2D/3DEstimator is the special case, consistent with Transformations's statement that Euclidean is metric with scale fixed at 1 — which is why every transformation on that page’s Euclidean/metric rows (rotation plus translation, optionally plus scale) reduces to the same handful of linear-algebra primitives regardless of dimension.

Because this non-robust estimator is a real, independently useful class, the robust wrapper does not reimplement anything: it forwards the minimal-sample-size constants from the non-robust class — 

// EuclideanTransformation2DRobustEstimator.java:41,46
public static final int MINIMUM_SIZE = EuclideanTransformation2DEstimator.MINIMUM_SIZE;         // 3
public static final int WEAK_MINIMUM_SIZE = EuclideanTransformation2DEstimator.WEAK_MINIMUM_SIZE; // 2

 — and every concrete {RANSAC|LMedS|MSAC|PROSAC|PROMedS}EuclideanTransformation2DRobustEstimator literally constructs a private EuclideanTransformation2DEstimator and delegates the minimal-sample hypothesis to it:

// RANSACEuclideanTransformation2DRobustEstimator.java:338-374 (abridged)
private final EuclideanTransformation2DEstimator nonRobustEstimator =
        new EuclideanTransformation2DEstimator(isWeakMinimumSizeAllowed());
...
public void estimatePreliminarSolutions(final int[] samplesIndices, final List<EuclideanTransformation2D> solutions) {
    nonRobustEstimator.setPoints(subsetInputPoints, subsetOutputPoints);
    solutions.add(nonRobustEstimator.estimate());
}

MetricTransformation2D/3DRobustEstimator follow the identical delegation pattern. This is the only place in com.irurueta.geometry.estimators where "compose, don’t duplicate" holds across the non-robust/robust boundary — contrast with affine and projective transformations below, and with the inlined constructions in Point, Line and Plane Estimators and Conic and Quadric Estimators. Only the point-correspondence variant exists for Euclidean/metric transformations (no line/plane correspondence flavor). 20 concrete robust subclasses total (5 algorithms × {Euclidean2D, Euclidean3D, Metric2D, Metric3D}).

Affine transformations: the closed form is duplicated, not shared

AffineTransformation2DRobustEstimator has no standalone non-robust sibling. Its javadoc: "algorithms to robustly find the best affine 2D transformation for collections of matching 2D points, or 2D lines" (AffineTransformation2DRobustEstimator.java:26-30), with MINIMUM_SIZE = 3 (AffineTransformation2DRobustEstimator.java:40) and two intermediate abstracts, PointCorrespondenceAffineTransformation2DRobustEstimator and LineCorrespondenceAffineTransformation2DRobustEstimator. AffineTransformation3DRobustEstimator is the 3D analogue (MINIMUM_SIZE = 4, AffineTransformation3DRobustEstimator.java:40), with PointCorrespondenceAffineTransformation3DRobustEstimator and PlaneCorrespondenceAffineTransformation3DRobustEstimator (a 3D affine map’s codimension-1 analogue of a 2D line is a plane).

Why 3 points in 2D, 4 in 3D

A 2D affine map has 6 unknowns (a 2×2 matrix plus a 2-vector), and each point correspondence contributes 2 equations (its and ) — hence 3 points exactly pin down the 6 unknowns, with no scale ambiguity to divide out (unlike the homogeneous projective case below, affine parameters are already inhomogeneous). The 3D case is the same argument with a 3×3 matrix plus a 3-vector (12 unknowns, 3 equations per point) — 4 points. A line correspondence in 2D contributes exactly the same 2 independent constraints as a point correspondence (a Line2D has 2 degrees of freedom, dual to a Point2D’s), which is exactly why the code needs no separate, larger `MINIMUM_SIZE for the line-correspondence variant — LineCorrespondenceAffineTransformation2DRobustEstimator inherits MINIMUM_SIZE = 3 unchanged from AffineTransformation2DRobustEstimator (verified: it does not redeclare the constant), and likewise PlaneCorrespondenceAffineTransformation3DRobustEstimator inherits MINIMUM_SIZE = 4.

The closed form: inlined once per robust subclass

Unlike Euclidean/metric, there is no shared AffineTransformation2DEstimator class to delegate to — each of the 5 concrete {RANSAC|LMedS|MSAC|PROSAC|PROMedS}PointCorrespondenceAffineTransformation2DRobustEstimator classes independently calls the same 3-point constructor:

// RANSACPointCorrespondenceAffineTransformation2DRobustEstimator.java:278-294 (abridged)
public void estimatePreliminarSolutions(final int[] samplesIndices, final List<AffineTransformation2D> solutions) {
    final var inputPoint1 = inputPoints.get(samplesIndices[0]);
    final var inputPoint2 = inputPoints.get(samplesIndices[1]);
    final var inputPoint3 = inputPoints.get(samplesIndices[2]);
    final var outputPoint1 = outputPoints.get(samplesIndices[0]);
    final var outputPoint2 = outputPoints.get(samplesIndices[1]);
    final var outputPoint3 = outputPoints.get(samplesIndices[2]);
    try {
        final var transformation = new AffineTransformation2D(
                inputPoint1, inputPoint2, inputPoint3, outputPoint1, outputPoint2, outputPoint3);
        solutions.add(transformation);
    } catch (final CoincidentPointsException e) {
        // if points are coincident, no solution is added
    }
}

@Override
public double computeResidual(final AffineTransformation2D currentEstimation, final int i) {
    currentEstimation.transform(inputPoints.get(i), testPoint);
    return outputPoints.get(i).distanceTo(testPoint); // true geometric (Euclidean) distance
}

The residual here is a genuine Euclidean distance between the transformed input point and the measured output point — not an algebraic proxy like the conic/quadric residuals in Conic and Quadric Estimators — because an affine transformation’s output is already an ordinary inhomogeneous point, with no matrix normalization or scale ambiguity to complicate the comparison. The line-correspondence variant’s residual combines the transformed line’s angular and positional agreement with the target line rather than a single point distance (see getResidual in RANSACLineCorrespondenceAffineTransformation2DRobustEstimator.java).

After a robust affine estimate is found, an optional LMSE polish over all detected inliers is available through a separate package: attemptRefine builds a com.irurueta.geometry.refiners.PointCorrespondenceAffineTransformation2DRefiner when the refineResult flag is set (PointCorrespondenceAffineTransformation2DRobustEstimator.java:568-584) — exactly the generic "LMSE re-estimation" step estimators.adoc#shared-architecture describes, concretely instantiated for this hierarchy.

20 concrete robust subclasses total (5 algorithms × {Point2D, Line2D, Point3D, Plane3D}).

Projective transformations (homographies): the same duplication pattern, grounded in the DLT algorithm

ProjectiveTransformation2DRobustEstimator likewise has no non-robust sibling: "algorithms to robustly find the best projective 2D transformation for collections of matching 2D points, or 2D lines" (ProjectiveTransformation2DRobustEstimator.java:26-30), MINIMUM_SIZE = 4 (ProjectiveTransformation2DRobustEstimator.java:40), with PointCorrespondenceProjectiveTransformation2DRobustEstimator and LineCorrespondenceProjectiveTransformation2DRobustEstimator. ProjectiveTransformation3DRobustEstimator is the 3D analogue, MINIMUM_SIZE = 5 (ProjectiveTransformation3DRobustEstimator.java:40), with point and plane correspondence variants.

Why 4 points in 2D, 5 in 3D — the classic DLT count

A 2D projective transformation (homography) has 9 homogeneous entries, hence 8 degrees of freedom once scale is divided out. Each point correspondence contributes only 2 independent equations, not 3, even though naively looks like 3 equations — Hartley & Zisserman show that only 2 of the 3 rows are linearly independent (§4.1, eq. 4.1-4.3, p.89): "each point correspondence gives two equations in the entries of H". So 8 DOF over 2 equations per point gives exactly 4 points — HZ’s own words: "it is necessary to specify four point correspondences in order to constrain H fully" (p.88), and this is precisely ProjectiveTransformation2DRobustEstimator.MINIMUM_SIZE = 4. One dimension up, a 3D projective transformation has 15 DOF and each point correspondence still contributes only 3 independent equations (of the naive 4), giving points — matching ProjectiveTransformation3DRobustEstimator.MINIMUM_SIZE = 5. As with the affine case, a line correspondence in 2D (or a plane correspondence in 3D) contributes the same equation count as a point, so the minimal sample size is unchanged across correspondence types.

The closed form: the DLT algorithm, one minimal sample at a time

HZ’s Algorithm 4.1 (p.109) is the textbook version of exactly what `RANSACPointCorrespondenceProjectiveTransformation2DRobustEstimator’s minimal-sample hook computes: stack the constraint row from each of the 4 correspondences into an matrix , and take as the right singular vector of with the smallest singular value (the null-space of when the 4 points are in general position):

// RANSACPointCorrespondenceProjectiveTransformation2DRobustEstimator.estimatePreliminarSolutions (abridged)
final var transformation = new ProjectiveTransformation2D(
        inputPoint1, inputPoint2, inputPoint3, inputPoint4,
        outputPoint1, outputPoint2, outputPoint3, outputPoint4); // solves A·h=0 via SVD internally
solutions.add(transformation);

Why the smallest singular vector solves it

HZ’s own justification (Appendix A5.3-A5.4, pp.592-593) for "take the last column of " is a short, self-contained proof worth restating, since every homogeneous minimal-sample solve on this site (Point, Line and Plane Estimators, Conic and Quadric Estimators, and this page) ultimately reduces to it. The problem is: find minimizing subject to . Writing and substituting (an orthogonal change of variables, so ):

Because is diagonal with entries in descending order, is minimized by putting all of 's unit "budget" on the coordinate multiplied by the smallest diagonal entry — i.e.  — so is simply the last column of (HZ Algorithm A5.4, p.593). Equivalently, that last column is the eigenvector of with the smallest eigenvalue. This is the exact justification for every new ProjectiveTransformation2D(p1..p4, q1..q4)-style constructor’s use of SingularValueDecomposer across this hierarchy, and for the identical pattern in Conic/Quadric/DualConic/DualQuadric (Conic and Quadric Estimators).

Normalizing the data before solving

HZ’s Algorithm 4.2 (p.109, motivated in §4.4 "Transformation invariance and normalization", pp.104-108) does not hand the raw pixel coordinates to the SVD solve above — it first applies a similarity transformation to the points so that their centroid sits at the origin and their average distance from it is (so the "average" normalized point is ):


solves for from the normalized points, then denormalizes to recover the homography in the original coordinates ( for the input points, for the output points). HZ’s own motivation (p.107-108) for this step: without normalization, raw image coordinates like make the design matrix’s entries span several orders of magnitude (products like land around while stays at ), which inflates the equation system’s condition number and, in the presence of noise, measurably worsens the SVD solution — HZ states this in so many words: "Data normalization is an essential step in the DLT algorithm. It must not be considered optional."

This is worth stating precisely rather than glossing over, because the library’s own affine/projective robust estimators do not actually implement HZ’s centroid-plus-average-distance scheme above. Two normalization mechanisms exist in this codebase, and neither is that one:

  • Point2DNormalizer/Point3DNormalizer (com.irurueta.geometry.estimators) do compute a per-axis, bounding-box-based scale (scaleX = 1.0 / width, scaleY = 1.0 / height, Point2DNormalizer.java:299-300) plus a centroid translation — closer in spirit to HZ’s goal than to its exact prescription (anisotropic per-axis scale rather than one isotropic scale to a target average distance of ) — but a repository-wide search shows no estimator class in this package actually constructs one; they exist as unused, self-contained utilities.

  • The normalize() calls this page’s Kabsch-based estimators and ProjectiveTransformation2D itself expose (and the ones Pinhole Camera Estimators describes on DLTPointCorrespondencePinholeCameraEstimator) are a different, much narrower operation: Point2D.normalize()/Point3D.normalize() (HomogeneousPoint2D.java:512-521) simply rescale one homogeneous coordinate vector to unit norm, independently per point — a numerically sensible step before stacking a point into a design matrix row, but not HZ’s dataset-wide, translate-then-isotropically-scale preconditioning transform above. Neither mechanism denormalizes the result the way Algorithm 4.2 does, because neither one changes the coordinate frame the answer is expressed in — only the numerical scale of the intermediate computation.

3D projective (plane/point correspondence) and the line-correspondence 2D variant follow the identical SVD-null-space pattern one dimension up or with lines/planes substituted for points. 20 concrete robust subclasses total (5 algorithms × {Point2D, Line2D, Point3D, Plane3D}).

From a linear estimate to the Gold Standard: two-stage refinement

Every closed-form solve on this page — Kabsch, the affine linear system, the projective DLT — minimizes an algebraic error ( or an analogous quadratic form), not the true geometric error a user actually cares about (the pixel distance between a transformed input point and its matched output point). HZ calls the procedure that does minimize geometric error directly the Gold Standard algorithm (§4.3, Algorithm 4.3, p.114): use the cheap linear DLT solution purely as a starting point, then run an iterative non-linear minimizer — Levenberg-Marquardt — on the true reprojection-error cost function:

This is exactly the two-stage pattern this library exposes across every hierarchy on this site (estimators.adoc#shared-architecture): a robust estimator’s estimatePreliminarSolutions plays the role of HZ’s cheap linear DLT step (run many times, on many random minimal samples, since it must be fast); once the best consensus set is found, attemptRefine — backed by a com.irurueta.geometry.refiners.Refiner class, e.g. PointCorrespondenceAffineTransformation2DRefiner — plays the role of HZ’s Levenberg-Marquardt geometric polish, run only *once, on only the inliers. The computeResidual methods quoted throughout this page already use true geometric distance for scoring candidates (so RANSAC picks the consensus set a geometric criterion would also pick), even though the candidates themselves come from an algebraic-error minimizer — the optional refinement step is what additionally moves the final transformation itself towards the geometric-error optimum, not just towards the inlier set a geometric criterion would select.

References

Full citations are in the bibliography. In detail:

  • Kabsch, 1976 — the optimal-rotation algorithm EuclideanTransformation2D/3DEstimator implement, cited directly in the library’s own javadoc via Wikipedia.

  • Hartley & Zisserman, §4.1 "The Direct Linear Transformation (DLT) algorithm" (pp. 88-92, Algorithm 4.1 p.109, Algorithm 4.2 p.109 with normalization) — the exact constraint-counting argument and SVD null-space solve `ProjectiveTransformation2D/3D’s minimal-sample constructors implement; §4.4 "Transformation invariance and normalization" (pp.104-108) — the isotropic centroid/average-distance normalization Algorithm 4.2 prescribes (contrasted above with what this library actually implements); §4.3 "Statistical cost functions and Maximum Likelihood estimation" and Algorithm 4.3 "The Gold Standard algorithm" (p.114) — the two-stage linear-then-Levenberg-Marquardt refinement pattern; Appendix A5.3-A5.4 "Least-squares solution of homogeneous equations" (pp.592-593) — the proof that the smallest right singular vector minimizes subject to .

  • Numerical Recipes, §2.6 "Singular Value Decomposition" (pp. 65-67) — reused citation for the SVD null-space technique behind the affine/projective closed forms.

  • MetricTransformation3DEstimator cites a StackOverflow answer directly in its javadoc for the least-squares scale/rotation/translation derivation; not independently verified against any of the three source PDFs, reported here only because the library’s own source cites it.

  • No book, paper or web source is cited directly in AffineTransformation2D/3D, ProjectiveTransformation2D/3D or any of their robust-estimator subclasses for the minimal-sample construction itself; the HZ citation above is supplied as external context that matches the verified algorithm, not a citation present in the source.

Key classes

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

Class Links

EuclideanTransformation2DEstimator

Source
Javadoc

EuclideanTransformation2DRobustEstimator

Source
Javadoc

RANSACEuclideanTransformation2DRobustEstimator

Source
Javadoc

AffineTransformation2D

Source
Javadoc

RANSACPointCorrespondenceAffineTransformation2DRobustEstimator

Source
Javadoc

ProjectiveTransformation2D

Source
Javadoc

RANSACPointCorrespondenceProjectiveTransformation2DRobustEstimator

Source
Javadoc

  • Estimators — the general robust-estimation theory (RANSAC/LMedS/MSAC/PROSAC/PROMedS, non-robust-vs-robust, the shared com.irurueta.numerical.robust architecture) this page specializes.

  • Transformations — the Euclidean/metric/affine/projective math itself: internal representations, transform rules, invariants and the plane-at-infinity/absolute-conic proofs behind the stratification.

  • Point, Line and Plane Estimators, Conic and Quadric Estimators — the simpler estimator hierarchies these transformations act on.

  • Pinhole Camera Estimators — robust camera estimation, which reuses the same DLT/SVD null-space pattern used here for 2D/3D homographies.