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()