Spaces:
Running
Running
commiting
Browse files
app.py
CHANGED
@@ -53,13 +53,11 @@ selected_model = st.sidebar.selectbox(
|
|
53 |
list(model_options.keys())
|
54 |
)
|
55 |
|
56 |
-
# Input method selection with addition of video upload and simulation
|
57 |
input_method = st.sidebar.radio(
|
58 |
"Choose Input Method",
|
59 |
["Real-time Webcam", "Upload an Image", "Capture Image"]
|
60 |
)
|
61 |
|
62 |
-
# Confidence threshold
|
63 |
confidence_threshold = st.sidebar.slider(
|
64 |
"Confidence Threshold",
|
65 |
min_value=0.0,
|
@@ -68,11 +66,8 @@ confidence_threshold = st.sidebar.slider(
|
|
68 |
step=0.05
|
69 |
)
|
70 |
|
71 |
-
# Face detection toggle
|
72 |
use_face_detection = st.sidebar.checkbox("Enable Face Detection", value=True)
|
73 |
|
74 |
-
|
75 |
-
# History length for real-time tracking
|
76 |
if input_method in ["Real-time Webcam", "Upload Video", "Simulation Mode"]:
|
77 |
history_length = st.sidebar.slider(
|
78 |
"Emotion History Length (seconds)",
|
@@ -89,10 +84,9 @@ face_detector = load_face_detector()
|
|
89 |
# --- Utility Functions ---
|
90 |
def detect_faces(image):
|
91 |
"""Detect faces in an image using OpenCV."""
|
92 |
-
# Convert PIL Image to OpenCV format
|
93 |
if isinstance(image, Image.Image):
|
94 |
opencv_image = np.array(image)
|
95 |
-
opencv_image = opencv_image[:, :, ::-1].copy()
|
96 |
else:
|
97 |
opencv_image = image
|
98 |
|
@@ -136,7 +130,6 @@ def draw_faces_with_emotions(image, faces, emotions):
|
|
136 |
"""Draw rectangles around faces and label with emotions."""
|
137 |
img = image.copy()
|
138 |
|
139 |
-
# Define colors for different emotions (BGR format)
|
140 |
emotion_colors = {
|
141 |
"happy": (0, 255, 0), # Green
|
142 |
"sad": (255, 0, 0), # Blue
|
@@ -147,15 +140,14 @@ def draw_faces_with_emotions(image, faces, emotions):
|
|
147 |
"disgust": (0, 128, 128) # Brown
|
148 |
}
|
149 |
|
150 |
-
|
151 |
-
default_color = (255, 255, 255) # White
|
152 |
|
153 |
for (x, y, w, h), emotion in zip(faces, emotions):
|
154 |
-
|
155 |
emotion_key = emotion["label"].lower().split("_")[-1]
|
156 |
color = emotion_colors.get(emotion_key, default_color)
|
157 |
|
158 |
-
|
159 |
cv2.rectangle(img, (x, y), (x+w, y+h), color, 2)
|
160 |
|
161 |
# Add emotion label and confidence
|
@@ -190,7 +182,7 @@ def generate_simulated_face(frame_num, canvas_size=(640, 480)):
|
|
190 |
eye_size = max(5, face_radius // 8)
|
191 |
|
192 |
# Blink occasionally
|
193 |
-
if frame_num % 50 > 45:
|
194 |
cv2.ellipse(canvas, (left_eye_x, eye_y), (eye_size, 1), 0, 0, 360, (30, 30, 30), -1)
|
195 |
cv2.ellipse(canvas, (right_eye_x, eye_y), (eye_size, 1), 0, 0, 360, (30, 30, 30), -1)
|
196 |
else:
|
@@ -199,12 +191,12 @@ def generate_simulated_face(frame_num, canvas_size=(640, 480)):
|
|
199 |
cv2.circle(canvas, (left_eye_x, eye_y), eye_size-2, (70, 70, 70), -1)
|
200 |
cv2.circle(canvas, (right_eye_x, eye_y), eye_size-2, (70, 70, 70), -1)
|
201 |
|
202 |
-
|
203 |
mouth_y = face_y + int(face_radius * 0.3)
|
204 |
mouth_width = int(face_radius * 0.6)
|
205 |
mouth_height = int(face_radius * 0.2)
|
206 |
|
207 |
-
|
208 |
emotion_cycle = (frame_num // 100) % 4
|
209 |
|
210 |
if emotion_cycle == 0: # Happy
|
@@ -223,7 +215,7 @@ def generate_simulated_face(frame_num, canvas_size=(640, 480)):
|
|
223 |
cv2.line(canvas, (face_x - mouth_width//2, mouth_y),
|
224 |
(face_x + mouth_width//2, mouth_y), (50, 50, 50), 2)
|
225 |
|
226 |
-
|
227 |
emotions = ["Happy", "Sad", "Surprised", "Neutral"]
|
228 |
cv2.putText(canvas, f"Simulating: {emotions[emotion_cycle]}",
|
229 |
(20, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (50, 50, 50), 2)
|
@@ -234,12 +226,12 @@ def generate_simulated_face(frame_num, canvas_size=(640, 480)):
|
|
234 |
|
235 |
def process_video_feed(feed_source, is_simulation=False):
|
236 |
"""Process video feed (webcam, video file, or simulation)."""
|
237 |
-
|
238 |
video_placeholder = st.empty()
|
239 |
metrics_placeholder = st.empty()
|
240 |
chart_placeholder = st.empty()
|
241 |
|
242 |
-
|
243 |
if 'emotion_history' not in st.session_state:
|
244 |
st.session_state.emotion_history = {}
|
245 |
st.session_state.last_update_time = time.time()
|
@@ -270,12 +262,12 @@ def process_video_feed(feed_source, is_simulation=False):
|
|
270 |
|
271 |
# Create deques for tracking emotions
|
272 |
emotion_deques = {}
|
273 |
-
timestamp_deque = deque(maxlen=30*history_length)
|
274 |
|
275 |
while st.session_state.get('running', False):
|
276 |
# Get frame
|
277 |
if is_simulation:
|
278 |
-
|
279 |
frame = generate_simulated_face(st.session_state.simulation_frame)
|
280 |
st.session_state.simulation_frame += 1
|
281 |
ret = True
|
@@ -287,18 +279,18 @@ def process_video_feed(feed_source, is_simulation=False):
|
|
287 |
if is_simulation:
|
288 |
st.error("Simulation error")
|
289 |
elif input_method == "Upload Video":
|
290 |
-
|
291 |
cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
|
292 |
continue
|
293 |
else:
|
294 |
st.error("Failed to capture frame from video source")
|
295 |
break
|
296 |
|
297 |
-
|
298 |
if input_method == "Real-time Webcam" and not is_simulation:
|
299 |
frame = cv2.flip(frame, 1)
|
300 |
|
301 |
-
|
302 |
st.session_state.frame_count += 1
|
303 |
|
304 |
# Detect faces
|
@@ -349,7 +341,7 @@ def process_video_feed(feed_source, is_simulation=False):
|
|
349 |
2
|
350 |
)
|
351 |
|
352 |
-
|
353 |
current_time = time.time()
|
354 |
timestamp_deque.append(current_time)
|
355 |
|
@@ -362,34 +354,33 @@ def process_video_feed(feed_source, is_simulation=False):
|
|
362 |
"time": current_time
|
363 |
})
|
364 |
|
365 |
-
|
366 |
current_time = time.time()
|
367 |
time_diff = current_time - st.session_state.last_update_time
|
368 |
-
if time_diff >= 1.0:
|
369 |
fps = st.session_state.frame_count / time_diff
|
370 |
st.session_state.last_update_time = current_time
|
371 |
st.session_state.frame_count = 0
|
372 |
|
373 |
-
|
374 |
with metrics_placeholder.container():
|
375 |
cols = st.columns(3)
|
376 |
cols[0].metric("FPS", f"{fps:.1f}")
|
377 |
cols[1].metric("Faces Detected", len(faces) if use_face_detection else "N/A")
|
378 |
|
379 |
-
# Display the frame
|
380 |
video_placeholder.image(frame, channels="BGR", use_column_width=True)
|
381 |
|
382 |
-
|
383 |
-
if len(timestamp_deque) > 0 and time_diff >= 0.5:
|
384 |
with chart_placeholder.container():
|
385 |
-
|
386 |
if len(emotion_deques) > 0:
|
387 |
tabs = st.tabs(list(emotion_deques.keys()))
|
388 |
|
389 |
for i, (face_id, emotion_data) in enumerate(emotion_deques.items()):
|
390 |
with tabs[i]:
|
391 |
if len(emotion_data) > 0:
|
392 |
-
|
393 |
emotion_counts = {}
|
394 |
for entry in emotion_data:
|
395 |
emotion = entry["emotion"]
|
@@ -397,7 +388,7 @@ def process_video_feed(feed_source, is_simulation=False):
|
|
397 |
emotion_counts[emotion] = 0
|
398 |
emotion_counts[emotion] += 1
|
399 |
|
400 |
-
|
401 |
fig = go.Figure(data=[go.Pie(
|
402 |
labels=list(emotion_counts.keys()),
|
403 |
values=list(emotion_counts.values()),
|
@@ -406,8 +397,8 @@ def process_video_feed(feed_source, is_simulation=False):
|
|
406 |
fig.update_layout(title=f"Emotion Distribution - {face_id}")
|
407 |
st.plotly_chart(fig, use_container_width=True)
|
408 |
|
409 |
-
|
410 |
-
emotions = list(emotion_data)[-20:]
|
411 |
times = [(e["time"] - emotions[0]["time"]) for e in emotions]
|
412 |
confidences = [e["confidence"] for e in emotions]
|
413 |
emotion_labels = [e["emotion"] for e in emotions]
|
@@ -432,11 +423,11 @@ def process_video_feed(feed_source, is_simulation=False):
|
|
432 |
else:
|
433 |
st.info("No emotion data available yet.")
|
434 |
|
435 |
-
|
436 |
if input_method in ["Upload Video", "Simulation Mode"]:
|
437 |
-
time.sleep(0.03 / processing_speed)
|
|
|
438 |
|
439 |
-
# Release resources when done
|
440 |
if not is_simulation and cap.isOpened():
|
441 |
cap.release()
|
442 |
|
@@ -444,7 +435,6 @@ def process_video_feed(feed_source, is_simulation=False):
|
|
444 |
st.error(f"Error during processing: {str(e)}")
|
445 |
st.session_state.running = False
|
446 |
else:
|
447 |
-
# Display a placeholder image when not running
|
448 |
placeholder_img = np.zeros((300, 500, 3), dtype=np.uint8)
|
449 |
cv2.putText(
|
450 |
placeholder_img,
|
@@ -479,7 +469,6 @@ def process_static_image(image):
|
|
479 |
with col2:
|
480 |
st.image(result_image, caption="Detected Emotions", channels="BGR", use_column_width=True)
|
481 |
|
482 |
-
# Display predictions
|
483 |
st.subheader("Detected Emotions:")
|
484 |
for i, (emotion, face) in enumerate(zip(emotions, faces)):
|
485 |
if emotion["score"] >= confidence_threshold:
|
@@ -505,7 +494,6 @@ def process_static_image(image):
|
|
505 |
else:
|
506 |
st.warning("No faces detected in the image. Try another image or disable face detection.")
|
507 |
else:
|
508 |
-
# Process the whole image
|
509 |
prediction = predict_emotion(image)
|
510 |
st.subheader("Prediction:")
|
511 |
st.write(f"**Emotion:** {prediction['label']}")
|
@@ -516,7 +504,6 @@ if input_method == "Upload an Image":
|
|
516 |
uploaded_file = st.file_uploader("Choose an image file", type=["jpg", "jpeg", "png"])
|
517 |
|
518 |
if uploaded_file is not None:
|
519 |
-
# Load and display image
|
520 |
image = Image.open(uploaded_file).convert("RGB")
|
521 |
process_static_image(image)
|
522 |
|
@@ -531,17 +518,13 @@ elif input_method == "Upload Video":
|
|
531 |
uploaded_video = st.file_uploader("Upload a video file", type=["mp4", "avi", "mov", "mkv"])
|
532 |
|
533 |
if uploaded_video is not None:
|
534 |
-
# Save the uploaded video to a temporary file
|
535 |
tfile = tempfile.NamedTemporaryFile(delete=False)
|
536 |
tfile.write(uploaded_video.read())
|
537 |
|
538 |
-
# Open the video file
|
539 |
cap = cv2.VideoCapture(tfile.name)
|
540 |
|
541 |
-
# Process the video
|
542 |
process_video_feed(cap)
|
543 |
|
544 |
-
# Clean up the temporary file
|
545 |
os.unlink(tfile.name)
|
546 |
|
547 |
elif input_method == "Simulation Mode":
|
@@ -556,15 +539,6 @@ elif input_method == "Real-time Webcam":
|
|
556 |
st.error("Could not open webcam. Please try the Simulation Mode instead.")
|
557 |
st.info("If you're using Streamlit in a browser, make sure you've granted camera permissions.")
|
558 |
|
559 |
-
# Show troubleshooting tips
|
560 |
-
with st.expander("Webcam Troubleshooting Tips"):
|
561 |
-
st.markdown("""
|
562 |
-
1. **Check Browser Permissions**: Make sure your browser has permission to access your camera.
|
563 |
-
2. **Close Other Applications**: Other applications might be using your webcam.
|
564 |
-
3. **Refresh the Page**: Sometimes simply refreshing can resolve the issue.
|
565 |
-
4. **Try a Different Browser**: Some browsers handle webcam access better than others.
|
566 |
-
5. **Use Simulation Mode**: If you cannot get the webcam working, use the Simulation Mode.
|
567 |
-
""")
|
568 |
else:
|
569 |
# Webcam available, process it
|
570 |
process_video_feed(cap)
|
|
|
53 |
list(model_options.keys())
|
54 |
)
|
55 |
|
|
|
56 |
input_method = st.sidebar.radio(
|
57 |
"Choose Input Method",
|
58 |
["Real-time Webcam", "Upload an Image", "Capture Image"]
|
59 |
)
|
60 |
|
|
|
61 |
confidence_threshold = st.sidebar.slider(
|
62 |
"Confidence Threshold",
|
63 |
min_value=0.0,
|
|
|
66 |
step=0.05
|
67 |
)
|
68 |
|
|
|
69 |
use_face_detection = st.sidebar.checkbox("Enable Face Detection", value=True)
|
70 |
|
|
|
|
|
71 |
if input_method in ["Real-time Webcam", "Upload Video", "Simulation Mode"]:
|
72 |
history_length = st.sidebar.slider(
|
73 |
"Emotion History Length (seconds)",
|
|
|
84 |
# --- Utility Functions ---
|
85 |
def detect_faces(image):
|
86 |
"""Detect faces in an image using OpenCV."""
|
|
|
87 |
if isinstance(image, Image.Image):
|
88 |
opencv_image = np.array(image)
|
89 |
+
opencv_image = opencv_image[:, :, ::-1].copy()
|
90 |
else:
|
91 |
opencv_image = image
|
92 |
|
|
|
130 |
"""Draw rectangles around faces and label with emotions."""
|
131 |
img = image.copy()
|
132 |
|
|
|
133 |
emotion_colors = {
|
134 |
"happy": (0, 255, 0), # Green
|
135 |
"sad": (255, 0, 0), # Blue
|
|
|
140 |
"disgust": (0, 128, 128) # Brown
|
141 |
}
|
142 |
|
143 |
+
default_color = (255, 255, 255)
|
|
|
144 |
|
145 |
for (x, y, w, h), emotion in zip(faces, emotions):
|
146 |
+
|
147 |
emotion_key = emotion["label"].lower().split("_")[-1]
|
148 |
color = emotion_colors.get(emotion_key, default_color)
|
149 |
|
150 |
+
|
151 |
cv2.rectangle(img, (x, y), (x+w, y+h), color, 2)
|
152 |
|
153 |
# Add emotion label and confidence
|
|
|
182 |
eye_size = max(5, face_radius // 8)
|
183 |
|
184 |
# Blink occasionally
|
185 |
+
if frame_num % 50 > 45:
|
186 |
cv2.ellipse(canvas, (left_eye_x, eye_y), (eye_size, 1), 0, 0, 360, (30, 30, 30), -1)
|
187 |
cv2.ellipse(canvas, (right_eye_x, eye_y), (eye_size, 1), 0, 0, 360, (30, 30, 30), -1)
|
188 |
else:
|
|
|
191 |
cv2.circle(canvas, (left_eye_x, eye_y), eye_size-2, (70, 70, 70), -1)
|
192 |
cv2.circle(canvas, (right_eye_x, eye_y), eye_size-2, (70, 70, 70), -1)
|
193 |
|
194 |
+
|
195 |
mouth_y = face_y + int(face_radius * 0.3)
|
196 |
mouth_width = int(face_radius * 0.6)
|
197 |
mouth_height = int(face_radius * 0.2)
|
198 |
|
199 |
+
|
200 |
emotion_cycle = (frame_num // 100) % 4
|
201 |
|
202 |
if emotion_cycle == 0: # Happy
|
|
|
215 |
cv2.line(canvas, (face_x - mouth_width//2, mouth_y),
|
216 |
(face_x + mouth_width//2, mouth_y), (50, 50, 50), 2)
|
217 |
|
218 |
+
|
219 |
emotions = ["Happy", "Sad", "Surprised", "Neutral"]
|
220 |
cv2.putText(canvas, f"Simulating: {emotions[emotion_cycle]}",
|
221 |
(20, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (50, 50, 50), 2)
|
|
|
226 |
|
227 |
def process_video_feed(feed_source, is_simulation=False):
|
228 |
"""Process video feed (webcam, video file, or simulation)."""
|
229 |
+
|
230 |
video_placeholder = st.empty()
|
231 |
metrics_placeholder = st.empty()
|
232 |
chart_placeholder = st.empty()
|
233 |
|
234 |
+
|
235 |
if 'emotion_history' not in st.session_state:
|
236 |
st.session_state.emotion_history = {}
|
237 |
st.session_state.last_update_time = time.time()
|
|
|
262 |
|
263 |
# Create deques for tracking emotions
|
264 |
emotion_deques = {}
|
265 |
+
timestamp_deque = deque(maxlen=30*history_length)
|
266 |
|
267 |
while st.session_state.get('running', False):
|
268 |
# Get frame
|
269 |
if is_simulation:
|
270 |
+
|
271 |
frame = generate_simulated_face(st.session_state.simulation_frame)
|
272 |
st.session_state.simulation_frame += 1
|
273 |
ret = True
|
|
|
279 |
if is_simulation:
|
280 |
st.error("Simulation error")
|
281 |
elif input_method == "Upload Video":
|
282 |
+
|
283 |
cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
|
284 |
continue
|
285 |
else:
|
286 |
st.error("Failed to capture frame from video source")
|
287 |
break
|
288 |
|
289 |
+
|
290 |
if input_method == "Real-time Webcam" and not is_simulation:
|
291 |
frame = cv2.flip(frame, 1)
|
292 |
|
293 |
+
|
294 |
st.session_state.frame_count += 1
|
295 |
|
296 |
# Detect faces
|
|
|
341 |
2
|
342 |
)
|
343 |
|
344 |
+
|
345 |
current_time = time.time()
|
346 |
timestamp_deque.append(current_time)
|
347 |
|
|
|
354 |
"time": current_time
|
355 |
})
|
356 |
|
357 |
+
|
358 |
current_time = time.time()
|
359 |
time_diff = current_time - st.session_state.last_update_time
|
360 |
+
if time_diff >= 1.0:
|
361 |
fps = st.session_state.frame_count / time_diff
|
362 |
st.session_state.last_update_time = current_time
|
363 |
st.session_state.frame_count = 0
|
364 |
|
365 |
+
|
366 |
with metrics_placeholder.container():
|
367 |
cols = st.columns(3)
|
368 |
cols[0].metric("FPS", f"{fps:.1f}")
|
369 |
cols[1].metric("Faces Detected", len(faces) if use_face_detection else "N/A")
|
370 |
|
|
|
371 |
video_placeholder.image(frame, channels="BGR", use_column_width=True)
|
372 |
|
373 |
+
|
374 |
+
if len(timestamp_deque) > 0 and time_diff >= 0.5:
|
375 |
with chart_placeholder.container():
|
376 |
+
|
377 |
if len(emotion_deques) > 0:
|
378 |
tabs = st.tabs(list(emotion_deques.keys()))
|
379 |
|
380 |
for i, (face_id, emotion_data) in enumerate(emotion_deques.items()):
|
381 |
with tabs[i]:
|
382 |
if len(emotion_data) > 0:
|
383 |
+
|
384 |
emotion_counts = {}
|
385 |
for entry in emotion_data:
|
386 |
emotion = entry["emotion"]
|
|
|
388 |
emotion_counts[emotion] = 0
|
389 |
emotion_counts[emotion] += 1
|
390 |
|
391 |
+
|
392 |
fig = go.Figure(data=[go.Pie(
|
393 |
labels=list(emotion_counts.keys()),
|
394 |
values=list(emotion_counts.values()),
|
|
|
397 |
fig.update_layout(title=f"Emotion Distribution - {face_id}")
|
398 |
st.plotly_chart(fig, use_container_width=True)
|
399 |
|
400 |
+
|
401 |
+
emotions = list(emotion_data)[-20:]
|
402 |
times = [(e["time"] - emotions[0]["time"]) for e in emotions]
|
403 |
confidences = [e["confidence"] for e in emotions]
|
404 |
emotion_labels = [e["emotion"] for e in emotions]
|
|
|
423 |
else:
|
424 |
st.info("No emotion data available yet.")
|
425 |
|
426 |
+
|
427 |
if input_method in ["Upload Video", "Simulation Mode"]:
|
428 |
+
time.sleep(0.03 / processing_speed)
|
429 |
+
|
430 |
|
|
|
431 |
if not is_simulation and cap.isOpened():
|
432 |
cap.release()
|
433 |
|
|
|
435 |
st.error(f"Error during processing: {str(e)}")
|
436 |
st.session_state.running = False
|
437 |
else:
|
|
|
438 |
placeholder_img = np.zeros((300, 500, 3), dtype=np.uint8)
|
439 |
cv2.putText(
|
440 |
placeholder_img,
|
|
|
469 |
with col2:
|
470 |
st.image(result_image, caption="Detected Emotions", channels="BGR", use_column_width=True)
|
471 |
|
|
|
472 |
st.subheader("Detected Emotions:")
|
473 |
for i, (emotion, face) in enumerate(zip(emotions, faces)):
|
474 |
if emotion["score"] >= confidence_threshold:
|
|
|
494 |
else:
|
495 |
st.warning("No faces detected in the image. Try another image or disable face detection.")
|
496 |
else:
|
|
|
497 |
prediction = predict_emotion(image)
|
498 |
st.subheader("Prediction:")
|
499 |
st.write(f"**Emotion:** {prediction['label']}")
|
|
|
504 |
uploaded_file = st.file_uploader("Choose an image file", type=["jpg", "jpeg", "png"])
|
505 |
|
506 |
if uploaded_file is not None:
|
|
|
507 |
image = Image.open(uploaded_file).convert("RGB")
|
508 |
process_static_image(image)
|
509 |
|
|
|
518 |
uploaded_video = st.file_uploader("Upload a video file", type=["mp4", "avi", "mov", "mkv"])
|
519 |
|
520 |
if uploaded_video is not None:
|
|
|
521 |
tfile = tempfile.NamedTemporaryFile(delete=False)
|
522 |
tfile.write(uploaded_video.read())
|
523 |
|
|
|
524 |
cap = cv2.VideoCapture(tfile.name)
|
525 |
|
|
|
526 |
process_video_feed(cap)
|
527 |
|
|
|
528 |
os.unlink(tfile.name)
|
529 |
|
530 |
elif input_method == "Simulation Mode":
|
|
|
539 |
st.error("Could not open webcam. Please try the Simulation Mode instead.")
|
540 |
st.info("If you're using Streamlit in a browser, make sure you've granted camera permissions.")
|
541 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
542 |
else:
|
543 |
# Webcam available, process it
|
544 |
process_video_feed(cap)
|