deepfakes/faceswap · error · FaceswapError

Spatial smoothing only supports 68 point facial landmarks

Error message

Spatial smoothing only supports 68 point facial landmarks

What it means

The Alignments tool's 'spatial' smoothing method builds a (68, 2, N) landmark tensor, so it hard-requires 68-point landmarks. It samples the first aligned face in the file; if that landmark array's first dimension is not 68 (e.g. 4-point 2D landmarks from some detectors/masks), it aborts.

Source

Thrown at tools/alignments/jobs.py:610

        # move back to the correct scale
        shapes_centered = shapes_normalized * np.tile(scale_factors, [num_pts, num_dims, 1])
        # move back to the correct location
        shapes_im_coords = shapes_centered + np.tile(mean_coords, [num_pts, 1, 1])

        logger.debug("Normalized to original: %s", shapes_im_coords)
        return shapes_im_coords

    def _normalize(self) -> None:
        """Compile all original and normalized alignments"""
        logger.debug("Normalize")
        count = sum(1 for val in self._alignments.data.values() if val.faces)

        sample_lm = next((val.faces[0].landmarks_xy
                          for val in self._alignments.data.values() if val.faces), 68)
        assert isinstance(sample_lm, np.ndarray)
        lm_count = sample_lm.shape[0]
        if lm_count != 68:
            raise FaceswapError("Spatial smoothing only supports 68 point facial landmarks")

        landmarks_all = np.zeros((lm_count, 2, int(count)))

        end = 0
        for key in tqdm(sorted(self._alignments.data.keys()), desc="Compiling", leave=False):
            val = self._alignments.data[key].faces
            if not val:
                continue
            # We should only be normalizing a single face, so just take
            # the first landmarks found
            landmarks = np.array(val[0].landmarks_xy).reshape((lm_count, 2, 1))
            start = end
            end = start + landmarks.shape[2]
            # Store in one big array
            landmarks_all[:, :, start:end] = landmarks
            # Make sure we keep track of the mapping to the original frame
            self._mappings[start] = key

View on GitHub (pinned to f530cb7508)

Solutions

  1. Re-extract the faces with a 68-point landmark configuration (standard FAN/dlib S3FD pipeline) so alignments contain 68-point landmarks, then retry spatial smoothing.
  2. Or use a smoothing method that does not depend on landmark count (e.g. temporal smoothing) if it fits your use case.
  3. Inspect the alignments file to confirm landmark shape before choosing the tool job.

Example fix

# before
python tools.py alignments -j spatial -a alignments.fsa   # faces have 4-point landmarks -> FaceswapError

# after
# re-extract with 68-point landmarks, then:
python tools.py alignments -j spatial -a alignments.fsa
Defensive patterns

Strategy: validation

Validate before calling

import numpy as np

def landmarks_are_68(alignments_file: str) -> bool:
    from lib.align import Alignments
    al = Alignments(alignments_file)
    face = next(v.faces[0] for v in al.data.values() if v.faces)
    return np.asarray(face.landmarks_xy).shape[0] == 68

Prevention

When it happens

Trigger: Running `python tools.py alignments -j spatial` on an alignments file whose faces were extracted with a landmark set other than 68-point (e.g. LM_2D_4 produced by certain mask/detector configurations), or where the first face's stored landmarks array has a different row count.

Common situations: Alignments produced with newer extraction defaults that store 4-point landmarks for some faces; mixing alignments files from different detector versions; legacy files converted from 81-point formats.

Related errors


AI-assisted analysis of deepfakes/faceswap@f530cb7508 (2026-08-15). Data as JSON: /api/errors/6ff1858b3daea088. Report an issue: GitHub.