File size: 5,942 Bytes
1d76068 50d1309 857d200 4e96ac3 8998e15 4e96ac3 8998e15 4e96ac3 8998e15 4e96ac3 0e892e8 8998e15 4e96ac3 50d1309 8998e15 4e96ac3 53f483f cbd5fd8 8998e15 4e96ac3 50d1309 8998e15 50d1309 3ab193f 8998e15 4e96ac3 8998e15 4e96ac3 8998e15 50d1309 8998e15 4e96ac3 8998e15 4e96ac3 8998e15 4e96ac3 50d1309 4e96ac3 50d1309 8998e15 4e96ac3 6dcd49f 4e96ac3 6dcd49f 8998e15 4e96ac3 6dcd49f 4e96ac3 6dcd49f 8998e15 4e96ac3 8998e15 4e96ac3 857d200 4e96ac3 3a9d8e7 8998e15 4e96ac3 8998e15 4e96ac3 50d1309 8998e15 50d1309 4e96ac3 8998e15 4e96ac3 50d1309 4e96ac3 50d1309 8998e15 57c5f83 50d1309 4e96ac3 50d1309 8998e15 4e96ac3 8998e15 4e96ac3 3ab193f 0e892e8 4e96ac3 | 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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | import cv2
import numpy as np
import gradio as gr
import tempfile
import tensorflow as tf
from tensorflow import keras
# ββ Fix TensorFlow graph execution for fast inference βββββββββββββ
# Build the model once and compile the predict function into a
# concrete TF graph so every call is instant with no re-tracing
physical_devices = tf.config.list_physical_devices("CPU")
tf.config.set_visible_devices(physical_devices, "CPU")
model = keras.models.load_model("emotion_detection_model.h5")
# Warm-up: run one dummy prediction so TF compiles the graph now,
# not on the first real frame (which caused the 3-min freeze)
_dummy = np.zeros((1, 48, 48, 1), dtype=np.float32)
model.predict(_dummy, verbose=0)
# ββ Constants βββββββββββββββββββββββββββββββββββββββββββββββββββββ
EMOTION_LABELS = {
0: "Angry",
1: "Happy",
2: "Neutral",
3: "Sad",
4: "Surprised"
}
# BGR colors for each emotion
COLORS = {
"Angry": (0, 0, 255),
"Happy": (0, 200, 0),
"Neutral": (200, 200, 0),
"Sad": (255, 100, 0),
"Surprised": (0, 140, 255)
}
# Load face detector
face_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
)
# Holds last saved snapshot path for download
_snapshot_path = {"path": None}
# ββ Core detection function ββββββββββββββββββββββββββββββββββββββββ
def detect(frame):
"""
Receives one webcam frame (RGB numpy array) from Gradio.
Returns annotated RGB frame immediately β no threads, no queues.
TF graph is pre-compiled so predict() takes ~20-50ms on CPU.
"""
if frame is None:
return None
# RGB β BGR for OpenCV
bgr = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
# Resize to 480p max for faster processing without losing accuracy
h, w = bgr.shape[:2]
if w > 640:
scale = 640 / w
bgr = cv2.resize(bgr, (640, int(h * scale)))
gray = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY)
gray = cv2.equalizeHist(gray)
# Detect faces
faces = face_cascade.detectMultiScale(
gray,
scaleFactor=1.3,
minNeighbors=5,
minSize=(40, 40),
flags=cv2.CASCADE_SCALE_IMAGE
)
if len(faces) == 0:
cv2.putText(bgr, "No face detected", (20, 40),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2, cv2.LINE_AA)
else:
# Batch all faces into one predict call for speed
batch = []
regions = []
for (x, y, w, h) in faces:
roi = gray[y:y+h, x:x+w]
roi = cv2.resize(roi, (48, 48)).astype(np.float32) / 255.0
batch.append(roi.reshape(48, 48, 1))
regions.append((x, y, w, h))
# Single batched prediction β much faster than one-by-one
batch = np.array(batch, dtype=np.float32)
predictions = model.predict(batch, verbose=0)
for i, (x, y, w, h) in enumerate(regions):
emotion = EMOTION_LABELS[np.argmax(predictions[i])]
confidence = float(np.max(predictions[i])) * 100
color = COLORS[emotion]
# Bounding box
cv2.rectangle(bgr, (x, y), (x+w, y+h), color, 2)
# Label background + text
label = f"{emotion} {confidence:.1f}%"
font = cv2.FONT_HERSHEY_SIMPLEX
(tw, th), bl = cv2.getTextSize(label, font, 0.7, 2)
ly = max(y - 8, th + 8)
cv2.rectangle(bgr,
(x, ly - th - bl - 4),
(x + tw + 6, ly + bl - 2),
color, -1)
cv2.putText(bgr, label, (x + 3, ly - bl),
font, 0.7, (0, 0, 0), 2, cv2.LINE_AA)
# BGR β RGB for Gradio output
result = cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)
# Cache snapshot for download
_snapshot_path["frame"] = result.copy()
return result
def save_snapshot():
"""Saves the latest annotated frame as a PNG and returns path for download."""
frame = _snapshot_path.get("frame")
if frame is None:
return None
tmp = tempfile.NamedTemporaryFile(suffix=".png", delete=False)
tmp.close()
cv2.imwrite(tmp.name, cv2.cvtColor(frame, cv2.COLOR_RGB2BGR))
return tmp.name
# ββ Gradio UI βββββββββββββββββββββββββββββββββββββββββββββββββββββ
with gr.Blocks(title="Real-Time Emotion Detection") as demo:
gr.Markdown("""
# π Real-Time Emotion Detection
Click **βΆ Start** on the webcam feed to begin.
Press **π₯ Download Snapshot** anytime to save the current frame.
π Angry | π Happy | π Neutral | π’ Sad | π² Surprised
""")
with gr.Row():
webcam = gr.Image(sources=["webcam"], streaming=True,
label="π· Live Webcam")
output_img = gr.Image(label="π― Detected Emotion")
with gr.Row():
download_btn = gr.DownloadButton(
label="π₯ Download Snapshot", variant="primary"
)
# stream_every=0.1 β 10fps β safe for CPU, smooth, no backlog
webcam.stream(
fn=detect,
inputs=webcam,
outputs=output_img,
stream_every=0.1,
time_limit=600
)
download_btn.click(fn=save_snapshot, inputs=None, outputs=download_btn)
gr.Markdown("""
---
**Model:** CNN β Conv2D(45) β Conv2D(64) β Conv2D(128) β Dense(256) β Softmax(5) |
**Input:** 48Γ48 grayscale | **Detector:** Haar Cascade
""")
if __name__ == "__main__":
demo.launch() |