← all projects
Computer Vision · October 2024 · 4 min read

Face Morphing with Delaunay Triangulation

Smooth warping between two faces via point correspondences, Delaunay triangulation, and affine warps per triangle. Plus: population mean faces and caricature generation by extrapolation.

PythonNumPyscikit-imageComputer Vision

A morph between two faces needs to do two things simultaneously: warp the geometry of one face toward the other, and blend the pixel colors. Doing either alone looks wrong: color blending without warping produces a ghost double-exposure; shape warping without blending leaves the wrong texture in the wrong place. The pipeline does both, parameterized by a single scalar α ∈ [0, 1].

Correspondence and Triangulation

~50 landmark points are manually annotated on each face: eyes, nose tip, mouth corners, jawline, hairline. For the morph at parameter α, the intermediate landmark set is the linear interpolation:

pα=(1α)pA+αpB\mathbf{p}_\alpha = (1 - \alpha)\,\mathbf{p}_A + \alpha\,\mathbf{p}_B

Delaunay triangulation is computed on pα\mathbf{p}_\alpha and the same triangulation topology is applied to both source point sets. Delaunay is chosen because it maximizes the minimum angle across all triangles, preventing degenerate sliver triangles whose affine transforms produce visible seams at triangle edges.

Parham triangulation mesh Conor triangulation mesh
Elon triangulation mesh Christian triangulation mesh

Affine Warp Per Triangle

For each triangle in the intermediate mesh, we need to find the affine transform that maps it back to the corresponding triangle in each source image. An affine map T:R2R2T: \mathbb{R}^2 \to \mathbb{R}^2 has the form T(x)=Ax+tT(\mathbf{x}) = A\mathbf{x} + \mathbf{t}, or in homogeneous coordinates:

[xy1]=[abtxcdty001][xy1]\begin{bmatrix} x' \\ y' \\ 1 \end{bmatrix} = \begin{bmatrix} a & b & t_x \\ c & d & t_y \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x \\ y \\ 1 \end{bmatrix}

Given source triangle vertices {(x1,y1),(x2,y2),(x3,y3)}\{(x_1,y_1), (x_2,y_2), (x_3,y_3)\} and destination vertices {(x1,y1),(x2,y2),(x3,y3)}\{(x_1',y_1'), (x_2',y_2'), (x_3',y_3')\}, the 6 unknowns (a,b,c,d,tx,ty)(a, b, c, d, t_x, t_y) are solved from the 6 linear equations:

[x1y11000000x1y11x2y21000000x2y21x3y31000000x3y31][abtxcdty]=[x1y1x2y2x3y3]\begin{bmatrix} x_1 & y_1 & 1 & 0 & 0 & 0 \\ 0 & 0 & 0 & x_1 & y_1 & 1 \\ x_2 & y_2 & 1 & 0 & 0 & 0 \\ 0 & 0 & 0 & x_2 & y_2 & 1 \\ x_3 & y_3 & 1 & 0 & 0 & 0 \\ 0 & 0 & 0 & x_3 & y_3 & 1 \end{bmatrix} \begin{bmatrix} a \\ b \\ t_x \\ c \\ d \\ t_y \end{bmatrix} = \begin{bmatrix} x_1' \\ y_1' \\ x_2' \\ y_2' \\ x_3' \\ y_3' \end{bmatrix}

The warp is applied inverse: for every pixel in the output triangle, apply the inverse transform to find the source coordinate, then sample with bilinear interpolation. This avoids holes from forward-mapping. The final pixel at each location is the weighted blend:

Iα(x)=(1α)IA(TA1(x))+αIB(TB1(x))I_\alpha(\mathbf{x}) = (1-\alpha)\, I_A(T_A^{-1}(\mathbf{x})) + \alpha\, I_B(T_B^{-1}(\mathbf{x}))

Morph Sequence

Five frames from a 46-frame morph sequence. The eyes shift early; they’re constrained by many nearby landmarks. The jawline transitions more gradually because it spans a larger area with fewer triangle boundaries between source and target positions.

Full 46-frame morph animation at 30fps

The full animation at 30 fps. One failure mode worth noting: large brightness differences between faces cause the cross-dissolve to produce a visible “fade through gray” artifact mid-morph. A color-matched preprocessing step (histogram matching) largely eliminates this, at the cost of changing the source images.

Population Mean Face

Given a dataset of 40+ Danish faces with annotated landmarks, the mean face is computed by:

  1. Averaging all landmark coordinates to get pˉ\bar{\mathbf{p}}
  2. Warping each face to the mean shape using the same per-triangle affine procedure
  3. Averaging the warped pixel values

Iˉ(x)=1Ni=1NIi(Ti1(x))\bar{I}(\mathbf{x}) = \frac{1}{N}\sum_{i=1}^N I_i(T_i^{-1}(\mathbf{x}))

Population mean of 40+ Danish faces My face warped to mean shape Caricature at α=1.5 extrapolation

Left: the population mean. Center: my face warped to the mean’s geometry, same texture, average proportions. Right: caricature at α = 1.5. Instead of interpolating toward the mean, the landmarks are extrapolated away from it: p1.5=pme+0.5(pmepˉ)\mathbf{p}_{1.5} = \mathbf{p}_\text{me} + 0.5\,(\mathbf{p}_\text{me} - \bar{\mathbf{p}}). Whatever made my face geometrically distinctive gets amplified. The effect exaggerates the features that differ most from the population average.

Caricature quality is sensitive to correspondence quality; a poorly placed landmark gets extrapolated into a more severe artifact than the original misplacement. This is a good diagnostic: caricatures reveal which correspondences were lazily annotated.

#face-morphing#delaunay-triangulation#affine-warping#image-interpolation

Related projects