feat: GFPGAN face-identity restoration post-pass

Add an optional, commercial-safe face-restoration post-pass that recovers
face identity the diffusion removal pass drifts (canny holds structure, not
likeness) while still scrubbing the pixel watermark in the face regions.

- face_restore.py: GFPGANer singleton (CPU unless CUDA), the basicsr
  torchvision.transforms.functional_tensor shim, and the pure feather
  _composite_faces helper (unit-tested without the model). GFPGAN
  re-synthesizes each face from a StyleGAN2 prior, so composited face pixels
  are GAN-generated (no watermark, no pixel-copy) -- oracle-clean at weight 0.5
  with identity preserved.
- InvisibleEngine.remove_watermark: restore_faces / restore_faces_weight,
  best-effort, auto-skips when the extra is absent or no face is detected.
- CLI --restore-faces/--no-restore-faces + --restore-faces-weight on
  invisible/all/batch (on by default).
- restore extra (gfpgan/facexlib/basicsr), numpy<2-pinned (scipy<1.18,
  numba<0.60) and kept out of `all`; basicsr needs Python <3.13 + setuptools<69
  to build, so pin .python-version 3.12.

Commercial-safe: GFPGAN Apache-2.0, RetinaFace MIT. The CodeFormer alternative
is non-commercial and is not shipped. The earlier IP-Adapter FaceID layer was
removed (footgun: needs high strength, corrupts faces at the low removal
strength).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Victor Kuznetsov
2026-06-03 16:59:28 -07:00
co-authored by Claude Opus 4.8
parent d90d5d886a
commit 411ef16ec3
11 changed files with 1624 additions and 14 deletions
+24
View File
@@ -76,6 +76,22 @@ lama = [
"onnxruntime>=1.16.0",
"huggingface-hub>=0.20.0",
]
# Optional GFPGAN face-restoration post-pass (commercial-safe Apache-2.0 GFPGAN +
# MIT RetinaFace). Re-synthesizes each face from a StyleGAN2 prior after the
# diffusion removal pass, so it restores identity while still scrubbing the pixel
# watermark. The GFPGANv1.4 weights + RetinaFace detector download on first use;
# they are never bundled. gfpgan/basicsr/facexlib are an OLD ecosystem and must
# stay on numpy < 2.0 to match the pinned gpu diffusion stack -- scipy is capped
# < 1.18 (>= 1.18 uses np.long, gone in numpy 1.24-1.26) and numba < 0.60 to keep
# the whole env on one numpy 1.26 resolution (same trap class as the removed
# faceid/insightface extra). Kept OUT of `all` (heavy + model download).
restore = [
"gfpgan>=1.3.8",
"facexlib>=0.3.0",
"basicsr>=1.4.2",
"scipy<1.18",
"numba<0.60",
]
dev = [
"pytest>=8.0.0",
"pytest-cov>=4.1.0",
@@ -92,6 +108,14 @@ all = ["remove-ai-watermarks[gpu,detect,trustmark,lama,dev]"]
[tool.uv]
prerelease = "allow"
# basicsr 1.4.2 (pulled by the `restore` GFPGAN extra) ships sdist-only and its
# setup.py get_version() reads basicsr/version.py in a way that newer setuptools
# (>= 69) breaks with ``KeyError: '__version__'`` under isolated PEP 517 builds.
# Pin an old setuptools as its build dependency so the sdist builds; this is
# scoped to basicsr and does not affect the rest of the resolution.
[tool.uv.extra-build-dependencies]
basicsr = ["setuptools<69"]
# PyTorch Intel-GPU (XPU) wheel index. ``explicit = true`` keeps it inert for
# the default CPU/CUDA install: uv consults it only when a torch install
# explicitly targets it (see the ``gpu`` extra comment), so it does not alter