feat: AI face enhancement with GFPGAN and CodeFormer (#61)

* feat(shared): add enhance-faces tool definition and i18n strings

* feat(ai): add face enhancement script with GFPGAN and CodeFormer support

Detects faces via MediaPipe dual-model approach, then enhances using
GFPGAN (proven) or CodeFormer (via codeformer-pip) with auto fallback.
Supports strength-based alpha blending with original image.

* feat(ai): add TypeScript bridge for face enhancement

* feat(api): add enhance-faces route with GFPGAN/CodeFormer support

* feat(web): add enhance-faces settings component and register in tool registry

* feat(docker): add CodeFormer dependency and model download

- Add codeformer-pip to both CPU and GPU requirements
- Download CodeFormer model (~375MB) at Docker build time
- Add CodeFormer to smoke test verification

* fix(enhance-faces): address code review findings

- Skip alpha blend for CodeFormer (strength already applied via fidelity weight)
- Hide "only enhance main face" checkbox when Best (CodeFormer) is selected
- Fix sensitivity slider labels (swap More/Fewer faces to match actual behavior)
- Register EnhanceFacesControls in pipeline step settings
- Remove model names from user-facing descriptions

* fix(enhance-faces): fix CodeFormer integration and Docker setup

- Add codeformer-pip install to Dockerfile with --no-deps to avoid numpy 2.x conflict
- Re-pin numpy==1.26.4 after codeformer-pip install
- Pin codeformer-pip==0.0.4 in requirements files
- Broaden auto-mode fallback to catch any Exception from CodeFormer

---------

Co-authored-by: stirling-image <stirling-image@users.noreply.github.com>
This commit is contained in:
stirling-image
2026-04-13 21:56:59 +08:00
committed by GitHub
co-authored by stirling-image
parent 9ddeac92b6
commit 8071fe61c5
14 changed files with 780 additions and 0 deletions
+47
View File
@@ -0,0 +1,47 @@
import { readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { type ProgressCallback, runPythonWithProgress } from "./bridge.js";
export interface EnhanceFacesOptions {
model?: "auto" | "gfpgan" | "codeformer";
strength?: number;
onlyCenterFace?: boolean;
sensitivity?: number;
}
export interface EnhanceFacesResult {
buffer: Buffer;
facesDetected: number;
faces: Array<{ x: number; y: number; w: number; h: number }>;
model: string;
}
export async function enhanceFaces(
inputBuffer: Buffer,
outputDir: string,
options: EnhanceFacesOptions = {},
onProgress?: ProgressCallback,
): Promise<EnhanceFacesResult> {
const inputPath = join(outputDir, "input_enhance_faces.png");
const outputPath = join(outputDir, "output_enhance_faces.png");
await writeFile(inputPath, inputBuffer);
const { stdout } = await runPythonWithProgress(
"enhance_faces.py",
[inputPath, outputPath, JSON.stringify(options)],
{ onProgress },
);
const result = JSON.parse(stdout);
if (!result.success) {
throw new Error(result.error || "Face enhancement failed");
}
const buffer = await readFile(outputPath);
return {
buffer,
facesDetected: result.facesDetected,
faces: result.faces ?? [],
model: result.model ?? "unknown",
};
}
+1
View File
@@ -3,6 +3,7 @@ export { isGpuAvailable, shutdownDispatcher } from "./bridge.js";
export { colorize } from "./colorization.js";
export type { DetectFacesResult, FaceRegion } from "./face-detection.js";
export { blurFaces, detectFaces } from "./face-detection.js";
export { enhanceFaces } from "./face-enhancement.js";
export { inpaint } from "./inpainting.js";
export { noiseRemoval } from "./noise-removal.js";
export { extractText } from "./ocr.js";