File size: 3,102 Bytes
7045a9e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
"""
HF Space: Image Preprocessor (MTCNN Face Detection)
----------------------------------------------------
Detects a face in the input image, crops it with a small margin,
and resizes it to 224x224 — the exact format expected by the
image-to-emotion model downstream.

API endpoint: POST /preprocess
  Input : image (file upload)
  Output: cropped & resized face image (PNG)
"""

import gradio as gr
from facenet_pytorch import MTCNN
from PIL import Image

# ---------------------------------------------------------------------------
# Initialise MTCNN once at module load so the Space is warm for requests.
# keep_all=False → only the highest-confidence face is returned.
# post_process=False → skip internal normalisation; we only need the crop.
# ---------------------------------------------------------------------------
mtcnn = MTCNN(
    image_size=224,
    margin=20,
    keep_all=False,
    post_process=False,
)


# ---------------------------------------------------------------------------
# Core preprocessing function
# ---------------------------------------------------------------------------
def preprocess(image: Image.Image) -> Image.Image:
    """
    Detect a single face, apply a 10 % context margin, and resize to 224x224.

    Raises gr.Error if no face is detected so the caller receives a clear
    HTTP-level error (status 400) rather than a silent None.
    """
    if image is None:
        raise gr.Error("No image provided.")

    img = image.convert("RGB")

    boxes, probs = mtcnn.detect(img)

    if boxes is None or len(boxes) == 0:
        raise gr.Error("No face detected in the provided image.")

    # Pick the face with highest detection confidence.
    best_idx = int(probs.argmax()) if probs is not None else 0
    x1, y1, x2, y2 = boxes[best_idx]

    # Add a 10 % context margin for robustness.
    w, h = x2 - x1, y2 - y1
    margin = 0.10
    x1 -= w * margin
    y1 -= h * margin
    x2 += w * margin
    y2 += h * margin

    # Clamp to image bounds.
    img_w, img_h = img.size
    x1 = max(0, int(round(x1)))
    y1 = max(0, int(round(y1)))
    x2 = min(img_w, int(round(x2)))
    y2 = min(img_h, int(round(y2)))

    if x2 <= x1 or y2 <= y1:
        raise gr.Error("Face bounding box is degenerate after clamping.")

    face_img = img.crop((x1, y1, x2, y2)).resize((224, 224), Image.BILINEAR)
    return face_img


# ---------------------------------------------------------------------------
# Gradio Interface
# ---------------------------------------------------------------------------
demo = gr.Interface(
    fn=preprocess,
    inputs=gr.Image(type="pil", label="Input Image"),
    outputs=gr.Image(type="pil", label="Preprocessed Face (224×224)"),
    title="Image Preprocessor — MTCNN Face Detection",
    description=(
        "Upload any photo. The service detects the dominant face, "
        "crops it with a small context margin, and returns a 224×224 RGB image "
        "ready for the image-to-emotion model."
    ),
    api_name="preprocess",
    allow_flagging="never",
)

if __name__ == "__main__":
    demo.launch()