Use InsightFace for landmark-based face crop alignment

Immich's /api/faces endpoint only returns bounding boxes, not facial
landmarks. This meant align_face() never fired and all crops fell back
to a plain bbox rectangle — producing partial crops (forehead-only,
off-angle faces) when Immich's detection was slightly off.

Now, when ENABLE_FACE_ALIGNMENT is true and InsightFace is loaded,
execute_jobs() passes the app to process_face_mode(). For each face,
it expands the Immich bbox by 50%, crops that search region, runs
InsightFace detection within it, and aligns the nearest face to the
standard ArcFace 112×112 format using norm_crop(). Falls back to bbox
crop if InsightFace finds no face in the search region.

The InsightFace model is already in GPU memory from the diversity/
embedding phase, so the singleton lookup adds no load cost.
This commit is contained in:
2026-06-13 23:13:50 +00:00
parent 6fb3d2f61d
commit 341c6b0e85
2 changed files with 54 additions and 7 deletions
+13 -1
View File
@@ -155,6 +155,18 @@ def execute_jobs(jobs: list[dict]) -> None:
use_full_res = Config.USE_FULL_RESOLUTION
# Load InsightFace app for landmark-based crop alignment (face mode only).
# The model is already resident from the diversity/embedding phase, so this
# is just a singleton lookup — no load cost.
insightface_app = None
if any(j["config"].get("mode", "face") == "face" for j in jobs) and Config.ENABLE_FACE_ALIGNMENT:
try:
from .embeddings import get_insightface_app
insightface_app = get_insightface_app()
except Exception as e:
logger.debug(f"InsightFace unavailable for crop alignment: {e}")
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
@@ -216,7 +228,7 @@ def execute_jobs(jobs: list[dict]) -> None:
progress.console.print(f"[red]Failed download {asset['id']}[/red]")
else:
saved = (
process_face_mode(img, asset, person, person_dir, count)
process_face_mode(img, asset, person, person_dir, count, insightface_app=insightface_app)
if mode == "face"
else process_object_mode(img, config, person_dir, count)
if mode == "object"