Pose#

kornia.metrics.angle_error_mat(R1, R2)[source]#

Geodesic angle (in degrees) between two rotation matrices.

The relative rotation \(R = R_1^\top R_2\) has trace \(1 + 2\cos\theta\) and skew part \((R - R^\top) / 2 = \sin\theta\,[\mathbf{n}]_\times\) for its unit axis \(\mathbf{n}\), so the geodesic angle is \(\theta = \operatorname{atan2}(\sin\theta, \cos\theta)\). Reading \(\sin\theta\) from the skew part keeps the digits of small angles that \(\arccos\!\big((\mathrm{tr}\,R - 1) / 2\big)\) loses next to \(1\): in float32 the result stays within about \(2 \cdot 10^{-5}\) degrees of the exact angle over the whole range.

Convention:

See the Convention block of pose_errors(), whose "R_err" this is: one angle in degrees per pair, symmetric in R1 and R2. An unbatched pair gives a 0-d tensor. The inputs are not checked to be rotations.

Parameters:
  • R1 (Tensor) – a rotation matrix of shape \((*, 3, 3)\).

  • R2 (Tensor) – a rotation matrix of shape \((*, 3, 3)\).

Return type:

Tensor

Returns:

the per-matrix angle in degrees, with shape \((*,)\).

Note

The angle has a kink at exactly \(0^\circ\) and \(180^\circ\) (identical or opposite rotations). The gradient there is the subgradient \(0\) that norm returns at a zero vector, so backpropagating through a perfect or exactly-opposite match stays finite.

Example

>>> angle_error_mat(torch.eye(3), torch.eye(3))
tensor(0.)
kornia.metrics.angle_error_vec(v1, v2)[source]#

Angle (in degrees) between two vectors.

With the unit vectors \(\hat v_1\) and \(\hat v_2\), the angle is \(\theta = \operatorname{atan2}(\lVert \hat v_1 \times \hat v_2 \rVert, \hat v_1 \cdot \hat v_2)\). Reading \(\sin\theta\) from the cross product keeps the digits of small angles that \(\arccos(\hat v_1 \cdot \hat v_2)\) loses next to \(1\): in float32 the result stays within about \(2 \cdot 10^{-5}\) degrees of the exact angle over the whole range.

Convention:

See the Convention block of pose_errors(), whose "t_err" is this angle before folding: one angle in degrees per pair, symmetric in v1 and v2 up to roundoff. An unbatched pair gives a 0-d tensor. Only the directions are compared, so the vectors need not have unit length. The angle is not folded: opposite vectors give 180.

Parameters:
  • v1 (Tensor) – a vector of shape \((*, 3)\).

  • v2 (Tensor) – a vector of shape \((*, 3)\).

Return type:

Tensor

Returns:

the per-vector angle in degrees, with shape \((*,)\).

Note

The angle has a kink at exactly \(0^\circ\) and \(180^\circ\) (identical or opposite vectors). The gradient there is the subgradient \(0\) that norm returns at a zero vector, so backpropagating through a perfect or exactly-opposite match stays finite.

Note

A zero-length vector gives NaN rather than raising, since the angle is undefined there. Mask those entries before reducing.

Example

>>> v = torch.tensor([1.0, 0.0, 0.0])
>>> angle_error_vec(v, v)
tensor(0.)
kornia.metrics.translation_ate(t, t_gt)[source]#

Absolute translation error (ATE) between two translations.

Computes the raw Euclidean distance \(\lVert t - t_{gt} \rVert_2\). Unlike angle_error_vec(), this keeps the magnitude and is therefore only meaningful when both translations share a common metric scale (it is not scale-invariant, so it is not suitable for raw essential-matrix translations).

Convention:

See the Convention block of pose_errors(), whose "t_err" compares translation directions only; this function keeps the magnitude. The result is one distance per sample, in the units of the translations, with no alignment: a trajectory shifted by a constant keeps that offset at every pose.

Parameters:
  • t (Tensor) – an estimated translation of shape \((*, 3)\).

  • t_gt (Tensor) – a ground-truth translation of the same shape as t.

Return type:

Tensor

Returns:

the per-sample translation error, with shape \((*,)\). An unbatched \((3,)\) input is treated as a single sample and returns shape \((1,)\).

Note

The gradient stays finite even at zero distance, where norm returns the subgradient 0.

Example

>>> t = torch.tensor([0.0, 0.0, 0.0])
>>> t_gt = torch.tensor([3.0, 4.0, 0.0])
>>> translation_ate(t, t_gt)
tensor([5.])
kornia.metrics.pose_errors(P, P_gt, fold_translation=True)[source]#

Rotation and translation angular error (in degrees) between two relative poses.

Convention:
  • Every error is an angle in degrees, one per pose and never averaged over the batch; an unbatched pose gives shape \((1,)\), where angle_error_mat() gives a 0-d tensor.

  • "R_err" is angle_error_mat() of the two rotation blocks and is never folded. "t_err" is angle_error_vec() of the two translations, so it compares their directions only, folded as fold_translation says. "max_err" is the larger of the two per pose.

  • Only the top three rows are read: the bottom row of a \((4, 4)\) pose is ignored.

Parameters:
  • P (Tensor) – an estimated relative pose [R | t] of shape \((3, 4)\), \((4, 4)\), or batched \((B, 3, 4)\) / \((B, 4, 4)\).

  • P_gt (Tensor) – a ground-truth relative pose of the same shape.

  • fold_translation (bool, optional) – if True (default), fold the translation error into \([0, 90]\) via \(\min(e, 180 - e)\) to absorb the sign ambiguity of an essential-matrix translation. Default: True

Returns:

"R_err" (rotation), "t_err" (translation) and "max_err" (element-wise max of the two).

Return type:

a dict of per-pose errors of shape \((B,)\)

Note

A pose with zero translation gives NaN for "t_err" and "max_err", and auc_from_errors() propagates that into the AUC. Mask those entries first.

Example

>>> P = torch.eye(4)
>>> P[0, 3] = 1.0
>>> errs = pose_errors(P, P)
>>> errs["R_err"], errs["t_err"]
(tensor([0.]), tensor([0.]))
kornia.metrics.auc_from_errors(errors, thresholds=(1, 3, 5, 10))[source]#

Area under the cumulative error curve at one or more thresholds.

The metric is generic: any non-negative error array works. Pose-error metrics (e.g. the "max_err" of pose_errors()) are one common source, but the thresholds simply need to be in the same units as errors.

Convention:
  • See the Convention block of pose_errors(), whose "max_err" is the usual input.

  • All errors are pooled into one curve, whatever their shape. The recall curve rises by \(1/n\) at each of the \(n\) sorted errors, joined to \((0, 0)\) and to each other by straight lines (the trapezoid rule), and is held flat from the last error below the threshold out to the threshold. The AUC is its area divided by the threshold, times 100: a percentage, keyed by the threshold as a Python float. An inf error never enters the area, so it counts as a failure. Losses and metrics maps the AUC and pose_errors() onto glue-factory and SuperGlue.

Parameters:
  • errors (Tensor) – per-sample error values of shape \((B,)\). Must be non-negative. Integer and half-precision inputs are promoted to the default floating dtype before accumulating.

  • thresholds (float | Sequence[float], optional) – a single threshold or a sequence of thresholds, in the same units as errors. Must be strictly positive. Defaults to (1, 3, 5, 10). Default: (1, 3, 5, 10)

Return type:

dict[float, float]

Returns:

a dict mapping each threshold to its AUC in \([0, 100]\), or NaN at every threshold if any error is NaN.

Note

An error exactly equal to a threshold contributes no area there, so errors all equal to thr score 0 at thr. This follows the reference implementations.

Example

>>> auc_from_errors(torch.zeros(1), thresholds=5.0)
{5.0: 100.0}