notrey commited on
Commit
e33102a
·
1 Parent(s): 89b169e

updating app

Browse files
Files changed (2) hide show
  1. app.py +452 -40
  2. requirements.txt +2 -1
app.py CHANGED
@@ -1,65 +1,477 @@
1
  import streamlit as st
2
- from transformers import pipeline
3
- from PIL import Image
4
  import cv2
5
  import numpy as np
 
 
 
 
6
  import torch
 
 
 
 
 
 
 
 
7
 
8
  # --- App Title and Description ---
9
- st.title("Real-Time Emotion Detection App")
10
  st.write("""
11
- This app uses a lightweight, pre-trained emotion detection model from Hugging Face to predict emotions
12
- from faces in an image. You can either upload an image or use your webcam to capture an image.
13
  """)
14
 
15
- # --- Load the Emotion Detection Model ---
16
- # Cache the model loading so it isn’t reloaded on every app interaction.
17
  @st.cache_resource(show_spinner=False)
18
- def load_emotion_detector():
19
- # Loads the Hugging Face image-classification pipeline with the specified model.
20
- classifier = pipeline("image-classification", model="dima806/facial_emotions_image_detection")
 
21
  return classifier
22
 
23
- classifier = load_emotion_detector()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
- # --- Sidebar: Select Input Method ---
26
- st.sidebar.header("Select Input Method")
27
- input_method = st.sidebar.radio("Choose one:", ["Upload an Image", "Capture with Webcam"])
28
 
29
- # --- Process Image and Perform Inference ---
30
- def predict_emotion(image: Image.Image):
31
- # Optionally, you can perform additional preprocessing (e.g., face detection or cropping) here.
32
- results = classifier(image)
33
- # The pipeline returns a list of dictionaries sorted by score.
34
- top_prediction = results[0]
35
- return top_prediction
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
- # --- Main Section: Handling Input Methods ---
 
 
 
 
 
 
 
 
 
 
 
 
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  if input_method == "Upload an Image":
40
  uploaded_file = st.file_uploader("Choose an image file", type=["jpg", "jpeg", "png"])
 
41
  if uploaded_file is not None:
42
- # Open the image file with PIL.
43
  image = Image.open(uploaded_file).convert("RGB")
44
- st.image(image, caption="Uploaded Image", use_column_width=True)
45
- prediction = predict_emotion(image)
46
- st.subheader("Prediction:")
47
- st.write(f"**Emotion:** {prediction['label']}")
48
- st.write(f"**Confidence:** {prediction['score']:.2f}")
49
 
50
- elif input_method == "Capture with Webcam":
51
- # st.camera_input returns an image file-like object when a picture is taken.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  picture = st.camera_input("Capture an Image")
 
53
  if picture is not None:
54
- # Load image from the captured file.
55
  image = Image.open(picture).convert("RGB")
56
- st.image(image, caption="Captured Image", use_column_width=True)
57
- prediction = predict_emotion(image)
58
- st.subheader("Prediction:")
59
- st.write(f"**Emotion:** {prediction['label']}")
60
- st.write(f"**Confidence:** {prediction['score']:.2f}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
- # --- Optional: Additional Instructions ---
63
- st.write("""
64
- *Note: For best results in real-time detection, consider focusing the camera on your face or uploading a clear face image.*
65
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
 
 
2
  import cv2
3
  import numpy as np
4
+ import time
5
+ import plotly.graph_objects as go
6
+ from transformers import pipeline
7
+ from PIL import Image
8
  import torch
9
+ from collections import deque
10
+
11
+ # Set page config
12
+ st.set_page_config(
13
+ page_title="Real-Time Emotion Detection",
14
+ page_icon="😀",
15
+ layout="wide"
16
+ )
17
 
18
  # --- App Title and Description ---
19
+ st.title("Advanced Real-Time Emotion Detection")
20
  st.write("""
21
+ This app detects emotions in real-time using your webcam. It tracks facial expressions continuously
22
+ and provides visual feedback on detected emotions.
23
  """)
24
 
25
+ # --- Load Models ---
 
26
  @st.cache_resource(show_spinner=False)
27
+ def load_emotion_detector(model_name="dima806/facial_emotions_image_detection"):
28
+ """Load the emotion detection model."""
29
+ with st.spinner(f"Loading emotion detection model ({model_name})..."):
30
+ classifier = pipeline("image-classification", model=model_name)
31
  return classifier
32
 
33
+ @st.cache_resource(show_spinner=False)
34
+ def load_face_detector():
35
+ """Load the face detector model."""
36
+ with st.spinner("Loading face detection model..."):
37
+ # Load OpenCV's face detector
38
+ face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
39
+ return face_cascade
40
+
41
+ # --- Sidebar: Model and Settings ---
42
+ st.sidebar.header("Settings")
43
+
44
+ # Model selection
45
+ model_options = {
46
+ "Facial Emotions (Default)": "dima806/facial_emotions_image_detection",
47
+ "Facial Expressions": "juliensimon/distilbert-emotion"
48
+ }
49
+ selected_model = st.sidebar.selectbox(
50
+ "Choose Emotion Model",
51
+ list(model_options.keys())
52
+ )
53
+
54
+ # Input method selection
55
+ input_method = st.sidebar.radio(
56
+ "Choose Input Method",
57
+ ["Real-time Webcam", "Upload an Image", "Capture Image"]
58
+ )
59
+
60
+ # Confidence threshold
61
+ confidence_threshold = st.sidebar.slider(
62
+ "Confidence Threshold",
63
+ min_value=0.0,
64
+ max_value=1.0,
65
+ value=0.5,
66
+ step=0.05
67
+ )
68
+
69
+ # Face detection toggle
70
+ use_face_detection = st.sidebar.checkbox("Enable Face Detection", value=True)
71
+
72
+ # History length for real-time tracking
73
+ if input_method == "Real-time Webcam":
74
+ history_length = st.sidebar.slider(
75
+ "Emotion History Length (seconds)",
76
+ min_value=5,
77
+ max_value=60,
78
+ value=10,
79
+ step=5
80
+ )
81
 
82
+ # Load the selected model
83
+ classifier = load_emotion_detector(model_options[selected_model])
84
+ face_detector = load_face_detector()
85
 
86
+ # --- Utility Functions ---
87
+ def detect_faces(image):
88
+ """Detect faces in an image using OpenCV."""
89
+ # Convert PIL Image to OpenCV format
90
+ if isinstance(image, Image.Image):
91
+ opencv_image = np.array(image)
92
+ opencv_image = opencv_image[:, :, ::-1].copy() # Convert RGB to BGR
93
+ else:
94
+ opencv_image = image
95
+
96
+ # Convert to grayscale for face detection
97
+ gray = cv2.cvtColor(opencv_image, cv2.COLOR_BGR2GRAY)
98
+
99
+ # Detect faces
100
+ faces = face_detector.detectMultiScale(
101
+ gray,
102
+ scaleFactor=1.1,
103
+ minNeighbors=5,
104
+ minSize=(30, 30)
105
+ )
106
+
107
+ return faces, opencv_image
108
 
109
+ def process_image_for_emotion(image, face=None):
110
+ """Process image for emotion detection."""
111
+ if isinstance(image, np.ndarray):
112
+ # Convert OpenCV image to PIL
113
+ image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
114
+ image = Image.fromarray(image)
115
+
116
+ if face is not None:
117
+ # Crop to face region
118
+ x, y, w, h = face
119
+ image = image.crop((x, y, x+w, y+h))
120
+
121
+ return image
122
 
123
+ def predict_emotion(image):
124
+ """Predict emotion from an image."""
125
+ try:
126
+ results = classifier(image)
127
+ return results[0] # Return top prediction
128
+ except Exception as e:
129
+ st.error(f"Error during emotion prediction: {str(e)}")
130
+ return {"label": "Error", "score": 0.0}
131
+
132
+ def draw_faces_with_emotions(image, faces, emotions):
133
+ """Draw rectangles around faces and label with emotions."""
134
+ img = image.copy()
135
+
136
+ # Define colors for different emotions (BGR format)
137
+ emotion_colors = {
138
+ "happy": (0, 255, 0), # Green
139
+ "sad": (255, 0, 0), # Blue
140
+ "neutral": (255, 255, 0), # Cyan
141
+ "angry": (0, 0, 255), # Red
142
+ "surprise": (255, 165, 0), # Orange
143
+ "fear": (128, 0, 128), # Purple
144
+ "disgust": (0, 128, 128) # Brown
145
+ }
146
+
147
+ # Default color for unknown emotions
148
+ default_color = (255, 255, 255) # White
149
+
150
+ for (x, y, w, h), emotion in zip(faces, emotions):
151
+ # Get color based on emotion (lowercase and remove any prefix)
152
+ emotion_key = emotion["label"].lower().split("_")[-1]
153
+ color = emotion_colors.get(emotion_key, default_color)
154
+
155
+ # Draw rectangle around face
156
+ cv2.rectangle(img, (x, y), (x+w, y+h), color, 2)
157
+
158
+ # Add emotion label and confidence
159
+ label = f"{emotion['label']} ({emotion['score']:.2f})"
160
+ cv2.putText(img, label, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
161
+
162
+ return img
163
+
164
+ # --- Main App Logic ---
165
  if input_method == "Upload an Image":
166
  uploaded_file = st.file_uploader("Choose an image file", type=["jpg", "jpeg", "png"])
167
+
168
  if uploaded_file is not None:
169
+ # Load and display image
170
  image = Image.open(uploaded_file).convert("RGB")
 
 
 
 
 
171
 
172
+ col1, col2 = st.columns(2)
173
+ with col1:
174
+ st.image(image, caption="Uploaded Image", use_column_width=True)
175
+
176
+ # Process image
177
+ if use_face_detection:
178
+ faces, opencv_image = detect_faces(image)
179
+
180
+ if len(faces) > 0:
181
+ emotions = []
182
+ for face in faces:
183
+ face_img = process_image_for_emotion(image, face)
184
+ emotions.append(predict_emotion(face_img))
185
+
186
+ # Draw faces with emotions
187
+ result_image = draw_faces_with_emotions(opencv_image, faces, emotions)
188
+
189
+ with col2:
190
+ st.image(result_image, caption="Detected Emotions", channels="BGR", use_column_width=True)
191
+
192
+ # Display predictions
193
+ st.subheader("Detected Emotions:")
194
+ for i, (emotion, face) in enumerate(zip(emotions, faces)):
195
+ if emotion["score"] >= confidence_threshold:
196
+ st.write(f"Face {i+1}: **{emotion['label']}** (Confidence: {emotion['score']:.2f})")
197
+
198
+ # Show confidence bars
199
+ top_emotions = classifier(process_image_for_emotion(image, face))
200
+ labels = [item["label"] for item in top_emotions]
201
+ scores = [item["score"] for item in top_emotions]
202
+
203
+ fig = go.Figure(go.Bar(
204
+ x=scores,
205
+ y=labels,
206
+ orientation='h'
207
+ ))
208
+ fig.update_layout(
209
+ title=f"Emotion Confidence - Face {i+1}",
210
+ xaxis_title="Confidence",
211
+ yaxis_title="Emotion",
212
+ height=300
213
+ )
214
+ st.plotly_chart(fig, use_container_width=True)
215
+ else:
216
+ st.warning("No faces detected in the image. Try another image or disable face detection.")
217
+ else:
218
+ # Process the whole image
219
+ prediction = predict_emotion(image)
220
+ st.subheader("Prediction:")
221
+ st.write(f"**Emotion:** {prediction['label']}")
222
+ st.write(f"**Confidence:** {prediction['score']:.2f}")
223
+
224
+ elif input_method == "Capture Image":
225
  picture = st.camera_input("Capture an Image")
226
+
227
  if picture is not None:
 
228
  image = Image.open(picture).convert("RGB")
229
+
230
+ col1, col2 = st.columns(2)
231
+ with col1:
232
+ st.image(image, caption="Captured Image", use_column_width=True)
233
+
234
+ # Process image
235
+ if use_face_detection:
236
+ faces, opencv_image = detect_faces(image)
237
+
238
+ if len(faces) > 0:
239
+ emotions = []
240
+ for face in faces:
241
+ face_img = process_image_for_emotion(image, face)
242
+ emotions.append(predict_emotion(face_img))
243
+
244
+ # Draw faces with emotions
245
+ result_image = draw_faces_with_emotions(opencv_image, faces, emotions)
246
+
247
+ with col2:
248
+ st.image(result_image, caption="Detected Emotions", channels="BGR", use_column_width=True)
249
+
250
+ # Display predictions
251
+ st.subheader("Detected Emotions:")
252
+ for i, (emotion, face) in enumerate(zip(emotions, faces)):
253
+ if emotion["score"] >= confidence_threshold:
254
+ st.write(f"Face {i+1}: **{emotion['label']}** (Confidence: {emotion['score']:.2f})")
255
+ else:
256
+ st.warning("No faces detected in the image. Try another image or disable face detection.")
257
+ else:
258
+ # Process the whole image
259
+ prediction = predict_emotion(image)
260
+ st.subheader("Prediction:")
261
+ st.write(f"**Emotion:** {prediction['label']}")
262
+ st.write(f"**Confidence:** {prediction['score']:.2f}")
263
 
264
+ elif input_method == "Real-time Webcam":
265
+ st.subheader("Real-time Emotion Detection")
266
+ st.write("Click 'Start' to begin real-time emotion detection using your webcam.")
267
+
268
+ # Create a placeholder for the webcam feed
269
+ video_placeholder = st.empty()
270
+
271
+ # Create a placeholder for metrics
272
+ metrics_placeholder = st.empty()
273
+
274
+ # Create a placeholder for emotion history chart
275
+ chart_placeholder = st.empty()
276
+
277
+ # Initialize session state for tracking emotions over time
278
+ if 'emotion_history' not in st.session_state:
279
+ st.session_state.emotion_history = {}
280
+ st.session_state.last_update_time = time.time()
281
+ st.session_state.frame_count = 0
282
+
283
+ # Start/Stop button
284
+ start_button = st.button("Start" if 'running' not in st.session_state or not st.session_state.running else "Stop")
285
+
286
+ if start_button:
287
+ st.session_state.running = not st.session_state.get('running', False)
288
+
289
+ # If running, capture and process webcam feed
290
+ if st.session_state.get('running', False):
291
+ try:
292
+ # Open the webcam
293
+ cap = cv2.VideoCapture(0)
294
+
295
+ # Check if webcam opened successfully
296
+ if not cap.isOpened():
297
+ st.error("Could not open webcam. Please check your camera settings.")
298
+ st.session_state.running = False
299
+ else:
300
+ # Create deques for tracking emotions
301
+ emotion_deques = {}
302
+ timestamp_deque = deque(maxlen=30*history_length) # Store timestamps for X seconds at 30fps
303
+
304
+ while st.session_state.get('running', False):
305
+ # Read frame
306
+ ret, frame = cap.read()
307
+
308
+ if not ret:
309
+ st.error("Failed to capture frame from webcam")
310
+ break
311
+
312
+ # Flip the frame horizontally for a more natural view
313
+ frame = cv2.flip(frame, 1)
314
+
315
+ # Increment frame count for FPS calculation
316
+ st.session_state.frame_count += 1
317
+
318
+ # Detect faces
319
+ if use_face_detection:
320
+ faces, _ = detect_faces(frame)
321
+
322
+ if len(faces) > 0:
323
+ # Process each face
324
+ emotions = []
325
+ for face in faces:
326
+ face_img = process_image_for_emotion(frame, face)
327
+ emotions.append(predict_emotion(face_img))
328
+
329
+ # Draw faces with emotions
330
+ frame = draw_faces_with_emotions(frame, faces, emotions)
331
+
332
+ # Update emotion history
333
+ current_time = time.time()
334
+ timestamp_deque.append(current_time)
335
+
336
+ for i, emotion in enumerate(emotions):
337
+ if emotion["score"] >= confidence_threshold:
338
+ face_id = f"Face {i+1}"
339
+ if face_id not in emotion_deques:
340
+ emotion_deques[face_id] = deque(maxlen=30*history_length)
341
+
342
+ emotion_deques[face_id].append({
343
+ "emotion": emotion["label"],
344
+ "confidence": emotion["score"],
345
+ "time": current_time
346
+ })
347
+ else:
348
+ # No faces detected
349
+ pass
350
+ else:
351
+ # Process the whole frame
352
+ pil_image = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
353
+ emotion = predict_emotion(pil_image)
354
+
355
+ # Display emotion on frame
356
+ cv2.putText(
357
+ frame,
358
+ f"{emotion['label']} ({emotion['score']:.2f})",
359
+ (10, 30),
360
+ cv2.FONT_HERSHEY_SIMPLEX,
361
+ 1,
362
+ (0, 255, 0),
363
+ 2
364
+ )
365
+
366
+ # Update emotion history
367
+ current_time = time.time()
368
+ timestamp_deque.append(current_time)
369
+
370
+ if "Frame" not in emotion_deques:
371
+ emotion_deques["Frame"] = deque(maxlen=30*history_length)
372
+
373
+ emotion_deques["Frame"].append({
374
+ "emotion": emotion["label"],
375
+ "confidence": emotion["score"],
376
+ "time": current_time
377
+ })
378
+
379
+ # Calculate FPS
380
+ current_time = time.time()
381
+ time_diff = current_time - st.session_state.last_update_time
382
+ if time_diff >= 1.0: # Update every second
383
+ fps = st.session_state.frame_count / time_diff
384
+ st.session_state.last_update_time = current_time
385
+ st.session_state.frame_count = 0
386
+
387
+ # Update metrics
388
+ with metrics_placeholder.container():
389
+ cols = st.columns(3)
390
+ cols[0].metric("FPS", f"{fps:.1f}")
391
+ cols[1].metric("Faces Detected", len(faces) if use_face_detection else "N/A")
392
+
393
+ # Display the frame
394
+ video_placeholder.image(frame, channels="BGR", use_column_width=True)
395
+
396
+ # Update emotion history chart periodically
397
+ if len(timestamp_deque) > 0 and time_diff >= 0.5: # Update chart every 0.5 seconds
398
+ with chart_placeholder.container():
399
+ # Create tabs for each face
400
+ if len(emotion_deques) > 0:
401
+ tabs = st.tabs(list(emotion_deques.keys()))
402
+
403
+ for i, (face_id, emotion_data) in enumerate(emotion_deques.items()):
404
+ with tabs[i]:
405
+ if len(emotion_data) > 0:
406
+ # Count occurrences of each emotion
407
+ emotion_counts = {}
408
+ for entry in emotion_data:
409
+ emotion = entry["emotion"]
410
+ if emotion not in emotion_counts:
411
+ emotion_counts[emotion] = 0
412
+ emotion_counts[emotion] += 1
413
+
414
+ # Create pie chart for emotion distribution
415
+ fig = go.Figure(data=[go.Pie(
416
+ labels=list(emotion_counts.keys()),
417
+ values=list(emotion_counts.values()),
418
+ hole=.3
419
+ )])
420
+ fig.update_layout(title=f"Emotion Distribution - {face_id}")
421
+ st.plotly_chart(fig, use_container_width=True)
422
+
423
+ # Create line chart for emotion confidence over time
424
+ emotions = list(emotion_data)[-20:] # Get the last 20 entries
425
+ times = [(e["time"] - emotions[0]["time"]) for e in emotions]
426
+ confidences = [e["confidence"] for e in emotions]
427
+ emotion_labels = [e["emotion"] for e in emotions]
428
+
429
+ fig = go.Figure()
430
+ fig.add_trace(go.Scatter(
431
+ x=times,
432
+ y=confidences,
433
+ mode='lines+markers',
434
+ text=emotion_labels,
435
+ hoverinfo='text+y'
436
+ ))
437
+ fig.update_layout(
438
+ title=f"Emotion Confidence Over Time - {face_id}",
439
+ xaxis_title="Time (seconds)",
440
+ yaxis_title="Confidence",
441
+ yaxis=dict(range=[0, 1])
442
+ )
443
+ st.plotly_chart(fig, use_container_width=True)
444
+ else:
445
+ st.info(f"No emotion data available for {face_id} yet.")
446
+ else:
447
+ st.info("No emotion data available yet.")
448
+
449
+ # Release the webcam when done
450
+ cap.release()
451
+
452
+ except Exception as e:
453
+ st.error(f"Error during webcam processing: {str(e)}")
454
+ st.session_state.running = False
455
+ else:
456
+ # Display a placeholder image when not running
457
+ placeholder_img = np.zeros((300, 500, 3), dtype=np.uint8)
458
+ cv2.putText(
459
+ placeholder_img,
460
+ "Click 'Start' to begin",
461
+ (80, 150),
462
+ cv2.FONT_HERSHEY_SIMPLEX,
463
+ 1,
464
+ (255, 255, 255),
465
+ 2
466
+ )
467
+ video_placeholder.image(placeholder_img, channels="BGR", use_column_width=True)
468
+
469
+ # --- Footer ---
470
+ st.markdown("---")
471
+ st.markdown("""
472
+ **Tips for Best Results:**
473
+ - Ensure good lighting for accurate face detection
474
+ - Position your face clearly in the frame
475
+ - Try different emotion models for comparison
476
+ - Adjust the confidence threshold if emotions aren't being detected
477
+ """)
requirements.txt CHANGED
@@ -2,4 +2,5 @@ streamlit
2
  transformers
3
  pillow
4
  opencv-python
5
- torch
 
 
2
  transformers
3
  pillow
4
  opencv-python
5
+ torch
6
+ plotly