Spaces:
Sleeping
Sleeping
| import cv2 | |
| import numpy as np | |
| import gradio as gr | |
| from fer import FER | |
| # FER expects RGB images; OpenCV frames are BGR. We'll convert. | |
| detector = FER(mtcnn=True) # robust face detector; set mtcnn=False if you want a bit more speed | |
| EMOJI = { | |
| "angry": "π ", | |
| "disgust": "π€’", | |
| "fear": "π¨", | |
| "happy": "π", | |
| "sad": "π’", | |
| "surprise": "π²", | |
| "neutral": "π", | |
| } | |
| def annotate_emotions(frame: np.ndarray): | |
| """ | |
| frame: HxWxC (uint8) in RGB from gradio webcam input | |
| returns: (annotated_frame_RGB, top3_emotions_label_dict) | |
| """ | |
| if frame is None: | |
| return None, {} | |
| # Ensure uint8 RGB | |
| img_rgb = frame.astype(np.uint8) | |
| # Run FER | |
| results = detector.detect_emotions(img_rgb) # list of {box: (x,y,w,h), emotions: {...}} | |
| # Prepare overlay | |
| annotated = img_rgb.copy() | |
| # Collect aggregate scores for Label output | |
| top_scores = {} | |
| for r in results: | |
| (x, y, w, h) = r["box"] | |
| emotions = r["emotions"] # dict of emotion->score | |
| # Top emotion for this face | |
| if emotions: | |
| top_em, top_score = sorted(emotions.items(), key=lambda kv: kv[1], reverse=True)[0] | |
| # Draw rectangle | |
| cv2.rectangle(annotated, (x, y), (x + w, y + h), (0, 255, 0), 2) | |
| # Compose label with emoji | |
| label = f"{EMOJI.get(top_em, '')} {top_em} {top_score:.2f}" | |
| # Background for text | |
| (tw, th), baseline = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.6, 2) | |
| cv2.rectangle(annotated, (x, y - th - 10), (x + tw + 6, y), (0, 0, 0), -1) | |
| cv2.putText(annotated, label, (x + 3, y - 6), | |
| cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2, cv2.LINE_AA) | |
| # Keep the best score per emotion for the Label component | |
| for k, v in emotions.items(): | |
| top_scores[k] = max(top_scores.get(k, 0.0), float(v)) | |
| # Sort and keep top-3 to keep the UI tidy | |
| top3 = dict(sorted(top_scores.items(), key=lambda kv: kv[1], reverse=True)[:3]) | |
| # Gradio expects RGB; 'annotated' is already RGB | |
| return annotated, top3 | |
| with gr.Blocks(fill_height=True) as demo: | |
| gr.Markdown( | |
| """ | |
| # π Live Emotion Detection | |
| - Allow camera access and look at the preview. | |
| - The app detects faces and overlays the **top emotion** per face (plus confidence). | |
| """ | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=3): | |
| cam = gr.Image(sources=["webcam"], streaming=True, label="Webcam", height=420) | |
| with gr.Column(scale=2): | |
| out_img = gr.Image(label="Annotated", height=420) | |
| out_label = gr.Label(label="Top emotions (global top-3)") | |
| cam.stream(fn=annotate_emotions, inputs=cam, outputs=[out_img, out_label]) | |
| if __name__ == "__main__": | |
| # Share=True is handy for quick testing on local networks | |
| demo.launch() | |