Spaces:
Runtime error
Runtime error
| """ | |
| 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() | |