Faces, People and Pose

Face alignment and landmarks

Alignment rotates and scales every face so the eyes land in the same two places, which removes a variation the recognition model would otherwise have to learn from scratch.

On this page 7
  1. The short answer
  2. Why it exists
  3. How it works
  4. The part people get wrong
  5. Where you have already seen this
  6. Remember this
  7. What to learn next

One lesson, three depths. Pick the one that fits you today — you can switch any time.

Beginner — No maths. Plain English.

The short answer

Alignment turns a tilted, off-centre face into a straight, standard-sized one before anything else looks at it.

Think of pasting photos into a passport application form. The form has a printed box, and the rule is that the eyes must sit on a printed line. You rotate the photo, trim it and slide it until the eyes land on that line.

Every photo you paste ends up looking comparable, because they all obey the same box. That is alignment.

A face model wants the same favour. Give it faces where the eyes always sit in the same two spots. It can then spend its effort on what differs between people.

Why it exists

Picture a system trying to tell two people apart. It gets one photo of a head tilted left and one of the same head tilted right.

Without alignment, the model has to learn that a tilt does not change who you are. It has to learn that at every angle, every scale and every position. That is an enormous amount of work spent on something that is not identity.

Alignment removes that work. It handles rotation, size and position with a fixed rule, before the model sees anything. The model then only has to explain what is left over, which is the person.

The gain is real and measured. Feeding badly aligned crops to a face model is a reliable way to make a good model look bad.

How it works

The face detector already handed over five points: two eyes, the nose tip, and the two mouth corners.

There is a standard target, a small square picture with five fixed spots printed on it. The task is to find one rotation, one zoom and one slide. Together they must land the detected points on the printed spots.

   face found in the photo            the standard target
   (tilted, large, off to one side)   (small, upright, fixed)

        o                                    o     o
           o                                    o
      o  o                                  o     o
        o
              ─────────  rotate, resize, slide  ─────────►

   five detected points  ─────────►  five printed spots

Notice what is not allowed. No stretching one direction more than another. No bending. Rotate, resize, slide — nothing else. Squashing a face sideways would change its shape, and shape is identity.

The part people get wrong

Alignment cannot be perfect, and it is not meant to be. Five points cannot capture a smile, a turned head or a raised eyebrow.

What it removes is the boring variation: which way up, how big, and where in the frame. The interesting variation is left in place on purpose, because the recognition model needs it.

There is also a real limit. A face turned far to the side has landmarks that no rotation can bring into the target. Beyond roughly a three-quarter view, alignment stops helping and starts distorting. Systems handle this by rejecting extreme angles rather than pretending.

Where you have already seen this

  • Passport and visa photo checkers that reject a tilted head.
  • The steady, centred face in a video-call background blur.
  • Photo apps that line up a decade of selfies into a smooth time-lapse.
  • Face filters that keep sunglasses on your eyes as you move.

Remember this

  • Alignment maps the detected face points onto a fixed standard layout.
  • Only rotation, resizing and sliding are allowed. Stretching would change the face.
  • It removes variation that is not identity, so the model can spend its effort on identity.

What to learn next

  • How images are stored — pixels, channels and the layout every warp operates on.
  • OpenCV — warpAffine, interpolation modes and the rest of the geometry toolkit.
  • Linear algebra — the SVD that makes the closed-form solution possible.

Developer — Code and libraries.

Setup

bash
pip install numpy opencv-python

Written and run against numpy 1.26 and opencv-python 4.10. Nothing here downloads a model.

The transform, and the template every face model expects

align.py
import numpy as np
import cv2

# The 112x112 five-point template every ArcFace-style model expects.
# Order: left eye, right eye, nose tip, left mouth corner, right mouth corner.
TEMPLATE = np.array([[38.2946, 51.6963],
                     [73.5318, 51.5014],
                     [56.0252, 71.7366],
                     [41.5493, 92.3655],
                     [70.7299, 92.2041]])

def similarity_transform(src, dst):
    """Umeyama (1991): the rotation + uniform scale + shift mapping src onto dst."""
    src_mean, dst_mean = src.mean(0), dst.mean(0)
    src_c, dst_c = src - src_mean, dst - dst_mean
    U, S, Vt = np.linalg.svd(dst_c.T @ src_c / len(src))
    D = np.eye(2)
    if np.linalg.det(U) * np.linalg.det(Vt) < 0:
        D[1, 1] = -1                # forbids a mirror flip, which would swap left and right
    R = U @ D @ Vt
    scale = (S * np.diag(D)).sum() / src_c.var(axis=0).sum()
    M = np.zeros((2, 3))
    M[:, :2] = scale * R
    M[:, 2] = dst_mean - scale * R @ src_mean
    return M

def apply(M, pts):
    return pts @ M[:, :2].T + M[:, 2]

# A face found in a photo: rotated 20 degrees, three times larger, off-centre.
angle = np.deg2rad(20.0)
Rot = np.array([[np.cos(angle), -np.sin(angle)], [np.sin(angle), np.cos(angle)]])
detected = TEMPLATE @ (3.0 * Rot).T + np.array([300.0, 180.0])

M = similarity_transform(detected, TEMPLATE)
print("landmarks as detected in the photo:")
print(np.round(detected, 2))
print("\nrecovered scale :", round(float(np.hypot(M[0, 0], M[1, 0])), 6), " (we applied 3.0, so 1/3)")
print("recovered angle :", round(float(np.degrees(np.arctan2(M[1, 0], M[0, 0]))), 4), "degrees")
print("max residual    :", float(np.abs(apply(M, detected) - TEMPLATE).max()))

# The realistic case: a landmark model is never exact.
rng = np.random.default_rng(0)
noisy = detected + rng.normal(0, 2.0, detected.shape)      # 2 pixels of jitter
M2 = similarity_transform(noisy, TEMPLATE)
err = np.linalg.norm(apply(M2, noisy) - TEMPLATE, axis=1)
print("\nwith 2-pixel landmark jitter, fitted on all 5 points:")
print("  per-point error inside the 112x112 crop:", np.round(err, 3))
print("  mean:", round(float(err.mean()), 3), "pixels")

err3 = np.linalg.norm(apply(similarity_transform(noisy[:3], TEMPLATE[:3]), noisy) - TEMPLATE, axis=1)
print("  same jitter, fitted on 3 points only  :", round(float(err3.mean()), 3), "pixels")

M_cv, _ = cv2.estimateAffinePartial2D(noisy.astype(np.float32),
                                      TEMPLATE.astype(np.float32), method=cv2.LMEDS)
print("\nours   :", np.round(M2, 4).tolist())
print("OpenCV :", np.round(M_cv, 4).tolist())
print("max difference:", round(float(np.abs(M_cv - M2).max()), 6))

# Finally, the crop itself. warpAffine takes exactly this 2x3 matrix.
photo = np.zeros((480, 640, 3), np.uint8)
chip = cv2.warpAffine(photo, M2, (112, 112), flags=cv2.INTER_LINEAR)
print("\naligned chip shape:", chip.shape)
Output
landmarks as detected in the photo:
[[354.91 365.03]
 [454.45 400.63]
 [384.33 439.72]
 [322.36 483.02]
 [404.79 512.5 ]]

recovered scale : 0.333333  (we applied 3.0, so 1/3)
recovered angle : -20.0 degrees
max residual    : 1.4210854715202004e-14

with 2-pixel landmark jitter, fitted on all 5 points:
  per-point error inside the 112x112 crop: [0.312 0.542 0.528 0.869 0.898]
  mean: 0.63 pixels
  same jitter, fitted on 3 points only  : 0.699 pixels

ours   : [[0.316, 0.1137, -115.5106], [-0.1137, 0.316, -23.4615]]
OpenCV : [[0.316, 0.1137, -115.5106], [-0.1137, 0.316, -23.4615]]
max difference: 6e-06

aligned chip shape: (112, 112, 3)

Reading the output

The noiseless case recovers the transform exactly. Scale 0.333333 is the inverse of the 3.0 we applied. Angle -20.0 undoes the 20-degree tilt. The residual is 1.4e-14, which is floating-point dust. The method is not an approximation when the data is clean.

Two pixels of landmark jitter becomes 0.63 pixels of alignment error. The transform shrinks the photo by roughly three, so the error shrinks with it, and the least-squares fit averages the five independent errors. Errors do not survive alignment unchanged; they get diluted.

Three points do worse than five: 0.699 against 0.630. Both fit the same two-degrees-of-freedom rotation plus one scale plus two shifts. More points means more averaging. This is why detectors return five and not three.

OpenCV agrees to six decimal places. cv2.estimateAffinePartial2D solves the same problem. Writing it out is worth doing once, so the 2x3 matrix stops being a black box, and then using the library version.

Line by line, for the parts that trip people up

D[1, 1] = -1 is the reflection guard from Umeyama's paper. Without it, a degenerate landmark set can produce a mirrored transform. The face would come out flipped, left eye where the right one should be, and the embedding would be silently wrong.

src_c.var(axis=0).sum() is the total variance of the source points, and it is the denominator of the scale. If all five landmarks collapse to one point this is zero, and you get a division warning. Guard it in production code.

cv2.warpAffine(img, M, (112, 112)) takes the matrix directly. Do not build the crop by slicing the box and then resizing — that throws away the rotation you worked out.

Common mistakes

Swapping the eye order. The template's first row is the subject's left eye, which appears on the right side of the image. Reverse it and every crop comes out mirrored, at which point recognition accuracy drops without a single error message.

Using a template built for a different crop size. The numbers above are for 112x112. A 160x160 model needs the template scaled, otherwise faces sit too small inside the frame and the model sees mostly background.

Cropping the box, then resizing. This ignores roll entirely. It is the most common shortcut and it costs measurable accuracy on tilted faces.

Aligning a profile face and trusting the result. Past roughly a three-quarter view, the five points no longer describe a plane, and the transform stretches the face into a smear. Filter by yaw angle and reject rather than align.

Forgetting that alignment is part of the model contract. Train with one template, deploy with another, and the embeddings from the two do not compare. Store the template alongside the weights.

Try it yourself

Take the noisy landmarks and add ten pixels of error to the nose point alone, leaving the other four clean. Measure the alignment error. Then remove the nose from the fit entirely and measure again. You will find out whether least squares protects you from one bad point, and the answer is instructive.

What to learn next

  • How images are stored — pixels, channels and the layout every warp operates on.
  • OpenCV — warpAffine, interpolation modes and the rest of the geometry toolkit.
  • Linear algebra — the SVD that makes the closed-form solution possible.

Researcher — Mathematics and papers.

The problem, stated

Given detected landmarks ${p_i}_{i=1}^{n} \subset \mathbb{R}^2$ and a canonical template ${q_i}_{i=1}^{n}$, find the similarity transform minimising

$$ E(s, R, t) = \frac{1}{n}\sum_{i=1}^{n} \lVert q_i - (sRp_i + t) \rVert^2 $$

$s > 0$ is a scalar, $R \in SO(2)$ a rotation, $t \in \mathbb{R}^2$ a translation. The restriction to $SO(2)$ rather than $O(2)$ excludes reflections, and to a single scalar $s$ rather than a diagonal matrix excludes anisotropic scaling.

Umeyama (1991) gives the closed-form solution. With $\mu_p, \mu_q$ the centroids and $\Sigma = \frac{1}{n}\sum (q_i - \mu_q)(p_i - \mu_p)^\top$ the cross-covariance, take the SVD $\Sigma = UDV^\top$ and set

$$ R = USV^\top, \qquad s = \frac{\operatorname{tr}(DS)}{\sigma_p^2}, \qquad t = \mu_q - sR\mu_p $$

where $\sigma_p^2 = \frac{1}{n}\sum \lVert p_i - \mu_p \rVert^2$ and

$$ S = \begin{cases} I & \text{if } \det(U)\det(V) \geq 0 \ \operatorname{diag}(1, -1) & \text{otherwise} \end{cases} $$

$S$ is the reflection guard. Its omission is a recurring bug in reimplementations, and it fails silently on well-conditioned data.

Reference: Umeyama, Least-Squares Estimation of Transformation Parameters Between Two Point Patterns, IEEE TPAMI 13(4), 1991.

The canonical template

The five-point template used above is the arcface_src array shipped with InsightFace, for a $112 \times 112$ crop. It is not derived from first principles; it is the mean landmark configuration of a large frontal training set, and it is a convention that the weights depend on.

This makes the template part of the model interface. Two systems using different templates produce embeddings that are not comparable, even with identical architectures and losses.

Alignment sensitivity

The relationship between landmark error and recognition accuracy is steep and well documented. Deng et al. (2020) report that landmark supervision inside RetinaFace improves downstream verification accuracy on IJB-C, attributing the gain to alignment quality rather than detection recall.

The mechanism is a train/test distribution shift. A recognition model trained on crops aligned with a particular detector inherits that detector's landmark bias. Swapping detectors at deployment shifts the crop statistics, and accuracy falls without any component being individually broken.

ARoFace (Ebrahimi et al., ECCV 2024, arxiv.org/abs/2407.14972) attacks this by treating alignment perturbation as an augmentation during recognition training, improving robustness on low-quality benchmarks where landmark estimates are unreliable.

Dense landmarks

Five points suffice for a similarity transform. Denser sets serve other purposes:

  • 68 points (iBUG 300-W, Sagonas et al., 2013) — the long-standing academic convention, giving jaw, brow, eye, nose and lip contours. Estimated by cascaded regression (Kazemi and Sullivan, CVPR 2014, the ensemble-of-regression-trees method behind dlib) or by heatmap CNNs.
  • 468 or 478 points (MediaPipe Face Mesh) — a topologically fixed mesh, used for effects, expression and gaze rather than alignment.
  • 3D morphable model fits (3DMM; Blanz and Vetter, 1999) — parametric shape and expression coefficients, enabling frontalisation of non-frontal faces.

Note the trade-off. Dense landmarks make expression and gaze available, and they are noisier per point than the sparse five. For a similarity transform, five well-estimated points beat sixty-eight poorly estimated ones.

Heatmap versus direct regression

Landmark models split the same way pose models do. Direct coordinate regression is fast and has no quantisation error but generalises less well. Heatmap regression is more accurate and pays a resolution cost, because a coordinate read back from a low-resolution heatmap is snapped to that grid. The sub-pixel decoding problem this creates is worked through later in this section.

Alignment-free alternatives

Two lines of work question whether alignment is needed at all.

Spatial transformer networks (Jaderberg et al., NeurIPS 2015) learn the warp end to end inside the recognition network. Elegant, and in practice unstable to train for faces, because the warp and the identity objective interact badly early in training.

Larger, augmentation-heavy training sets reduce the alignment gain considerably. Modern high-capacity models trained with aggressive geometric augmentation lose less from imperfect alignment than 2018-era models did. The gain has narrowed; it has not vanished, and it remains largest exactly where accuracy matters most — low-resolution, non-frontal, poorly lit input.

Papers

  • Umeyama, Least-Squares Estimation of Transformation Parameters, TPAMI 1991
  • Blanz and Vetter, A Morphable Model for the Synthesis of 3D Faces, SIGGRAPH 1999
  • Sagonas et al., 300 Faces in-the-Wild Challenge, ICCV Workshops 2013
  • Kazemi and Sullivan, One Millisecond Face Alignment with an Ensemble of Regression Trees, CVPR 2014
  • Jaderberg et al., Spatial Transformer Networks, NeurIPS 2015 — arxiv.org/abs/1506.02025
  • Ebrahimi et al., ARoFace: Alignment Robustness to Improve Low-Quality Face Recognition, ECCV 2024 — arxiv.org/abs/2407.14972

What to learn next

  • How images are stored — pixels, channels and the layout every warp operates on.
  • OpenCV — warpAffine, interpolation modes and the rest of the geometry toolkit.
  • Linear algebra — the SVD that makes the closed-form solution possible.