Update app.py
Browse files
app.py
CHANGED
@@ -4,7 +4,7 @@ import numpy as np
|
|
4 |
import torch
|
5 |
import torch.nn as nn
|
6 |
import torch.optim as optim
|
7 |
-
import
|
8 |
from facenet_pytorch import InceptionResnetV1, MTCNN
|
9 |
import mediapipe as mp
|
10 |
from fer import FER
|
@@ -12,6 +12,7 @@ from sklearn.cluster import KMeans
|
|
12 |
from sklearn.preprocessing import StandardScaler, MinMaxScaler
|
13 |
import umap
|
14 |
import pandas as pd
|
|
|
15 |
import matplotlib.pyplot as plt
|
16 |
from matplotlib.ticker import MaxNLocator
|
17 |
from moviepy.editor import VideoFileClip
|
@@ -19,8 +20,7 @@ from PIL import Image
|
|
19 |
import gradio as gr
|
20 |
import tempfile
|
21 |
import shutil
|
22 |
-
|
23 |
-
import fractions
|
24 |
|
25 |
|
26 |
# Suppress TensorFlow warnings
|
@@ -29,6 +29,9 @@ import tensorflow as tf
|
|
29 |
|
30 |
tf.get_logger().setLevel('ERROR')
|
31 |
|
|
|
|
|
|
|
32 |
# Initialize models and other global variables
|
33 |
device = 'cuda'
|
34 |
|
@@ -40,8 +43,8 @@ face_mesh = mp_face_mesh.FaceMesh(static_image_mode=False, max_num_faces=1, min_
|
|
40 |
emotion_detector = FER(mtcnn=False)
|
41 |
|
42 |
|
43 |
-
def frame_to_timecode(frame_num,
|
44 |
-
total_seconds = frame_num /
|
45 |
hours = int(total_seconds // 3600)
|
46 |
minutes = int((total_seconds % 3600) // 60)
|
47 |
seconds = int(total_seconds % 60)
|
@@ -116,8 +119,8 @@ def extract_frames(video_path, output_folder, desired_fps, progress_callback=Non
|
|
116 |
|
117 |
# Report progress
|
118 |
if progress_callback:
|
119 |
-
progress = frame_count / total_frames_to_extract
|
120 |
-
progress_callback(progress, f"Extracting frame
|
121 |
|
122 |
if frame_count >= total_frames_to_extract:
|
123 |
break
|
@@ -190,7 +193,7 @@ def organize_faces_by_person(embeddings_by_frame, clusters, aligned_faces_folder
|
|
190 |
|
191 |
|
192 |
def save_person_data_to_csv(embeddings_by_frame, emotions_by_frame, clusters, desired_fps, original_fps, output_folder,
|
193 |
-
num_components):
|
194 |
emotions = ['angry', 'disgust', 'fear', 'happy', 'sad', 'surprise', 'neutral']
|
195 |
person_data = {}
|
196 |
|
@@ -215,8 +218,9 @@ def save_person_data_to_csv(embeddings_by_frame, emotions_by_frame, clusters, de
|
|
215 |
scaler = MinMaxScaler(feature_range=(0, 1))
|
216 |
embeddings_reduced_normalized = scaler.fit_transform(embeddings_reduced)
|
217 |
|
218 |
-
|
219 |
-
|
|
|
220 |
|
221 |
df_data = {
|
222 |
'Frame': frames,
|
@@ -253,7 +257,7 @@ class LSTMAutoencoder(nn.Module):
|
|
253 |
|
254 |
|
255 |
def lstm_anomaly_detection(X, feature_columns, num_anomalies=10, epochs=100, batch_size=64):
|
256 |
-
device = 'cuda'
|
257 |
|
258 |
X = torch.FloatTensor(X).to(device)
|
259 |
|
@@ -316,42 +320,80 @@ def lstm_anomaly_detection(X, feature_columns, num_anomalies=10, epochs=100, bat
|
|
316 |
model)
|
317 |
|
318 |
|
319 |
-
def
|
|
|
320 |
fig, ax = plt.subplots(figsize=(16, 8))
|
321 |
-
bars = ax.bar(range(len(df)), anomaly_scores, width=0.8, color='skyblue')
|
322 |
-
for i in top_indices:
|
323 |
-
bars[i].set_color('red')
|
324 |
-
ax.set_xlabel('Timecode')
|
325 |
-
ax.set_ylabel('Anomaly Score')
|
326 |
-
ax.set_title(f'Anomaly Scores Over Time ({title})')
|
327 |
-
ax.xaxis.set_major_locator(MaxNLocator(nbins=100))
|
328 |
-
ticks = ax.get_xticks()
|
329 |
-
ax.set_xticklabels([df['Timecode'].iloc[int(tick)] if tick >= 0 and tick < len(df) else '' for tick in ticks],
|
330 |
-
rotation=90, ha='right')
|
331 |
-
plt.tight_layout()
|
332 |
-
return fig
|
333 |
|
|
|
|
|
|
|
334 |
|
335 |
-
|
336 |
-
|
337 |
-
|
338 |
-
|
339 |
-
|
340 |
-
|
341 |
-
|
342 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
343 |
ax.set_ylabel(f'{emotion.capitalize()} Score')
|
344 |
-
ax.set_title(f'{emotion.capitalize()}
|
345 |
-
|
346 |
-
|
347 |
-
ax.
|
348 |
-
|
349 |
plt.tight_layout()
|
350 |
return fig
|
351 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
352 |
|
353 |
-
|
|
|
354 |
|
|
|
|
|
355 |
|
356 |
def get_random_face_sample(organized_faces_folder, largest_cluster, output_folder):
|
357 |
person_folder = os.path.join(organized_faces_folder, f"person_{largest_cluster}")
|
@@ -363,7 +405,7 @@ def get_random_face_sample(organized_faces_folder, largest_cluster, output_folde
|
|
363 |
|
364 |
# Read the image and resize it to be smaller
|
365 |
face_img = cv2.imread(face_path)
|
366 |
-
small_face = cv2.resize(face_img, (
|
367 |
cv2.imwrite(output_path, small_face)
|
368 |
|
369 |
return output_path
|
@@ -380,16 +422,20 @@ def process_video(video_path, num_anomalies, num_components, desired_fps, batch_
|
|
380 |
os.makedirs(aligned_faces_folder, exist_ok=True)
|
381 |
os.makedirs(organized_faces_folder, exist_ok=True)
|
382 |
|
383 |
-
|
|
|
|
|
|
|
|
|
|
|
384 |
frames_folder = os.path.join(temp_dir, 'extracted_frames')
|
385 |
|
386 |
def extraction_progress(percent, message):
|
387 |
-
|
388 |
-
overall_progress = 0.05 + (percent * 0.25)
|
389 |
-
progress(overall_progress, message)
|
390 |
|
391 |
frame_count, original_fps = extract_frames(video_path, frames_folder, desired_fps, extraction_progress)
|
392 |
|
|
|
393 |
|
394 |
progress(0.3, "Processing frames")
|
395 |
embeddings_by_frame, emotions_by_frame = process_frames(frames_folder, aligned_faces_folder, frame_count,
|
@@ -407,7 +453,7 @@ def process_video(video_path, num_anomalies, num_components, desired_fps, batch_
|
|
407 |
|
408 |
progress(0.8, "Saving person data")
|
409 |
df, largest_cluster = save_person_data_to_csv(embeddings_by_frame, emotions_by_frame, clusters, desired_fps,
|
410 |
-
original_fps, temp_dir, num_components)
|
411 |
|
412 |
progress(0.9, "Performing anomaly detection")
|
413 |
feature_columns = [col for col in df.columns if
|
@@ -472,12 +518,11 @@ iface = gr.Interface(
|
|
472 |
gr.Video(),
|
473 |
gr.Slider(minimum=1, maximum=20, step=1, value=5, label="Number of Anomalies"),
|
474 |
gr.Slider(minimum=1, maximum=20, step=1, value=10, label="Number of Components"),
|
475 |
-
gr.Slider(minimum=1, maximum=
|
476 |
-
gr.Slider(minimum=1, maximum=64, step=1, value=
|
477 |
],
|
478 |
outputs=[
|
479 |
gr.Textbox(label="Anomaly Detection Results"),
|
480 |
-
gr.Image(type="filepath", label="Random Face Sample of Most Frequent Person"),
|
481 |
gr.Plot(label="Anomaly Scores (All Features)"),
|
482 |
gr.Plot(label="Anomaly Scores (Components Only)"),
|
483 |
gr.Plot(label="Fear Anomalies"),
|
@@ -485,7 +530,8 @@ iface = gr.Interface(
|
|
485 |
gr.Plot(label="Angry Anomalies"),
|
486 |
gr.Plot(label="Happy Anomalies"),
|
487 |
gr.Plot(label="Surprise Anomalies"),
|
488 |
-
gr.Plot(label="Neutral Anomalies")
|
|
|
489 |
],
|
490 |
title="Facial Expressions Anomaly Detection",
|
491 |
description="""
|
|
|
4 |
import torch
|
5 |
import torch.nn as nn
|
6 |
import torch.optim as optim
|
7 |
+
import seaborn as sns
|
8 |
from facenet_pytorch import InceptionResnetV1, MTCNN
|
9 |
import mediapipe as mp
|
10 |
from fer import FER
|
|
|
12 |
from sklearn.preprocessing import StandardScaler, MinMaxScaler
|
13 |
import umap
|
14 |
import pandas as pd
|
15 |
+
import matplotlib
|
16 |
import matplotlib.pyplot as plt
|
17 |
from matplotlib.ticker import MaxNLocator
|
18 |
from moviepy.editor import VideoFileClip
|
|
|
20 |
import gradio as gr
|
21 |
import tempfile
|
22 |
import shutil
|
23 |
+
|
|
|
24 |
|
25 |
|
26 |
# Suppress TensorFlow warnings
|
|
|
29 |
|
30 |
tf.get_logger().setLevel('ERROR')
|
31 |
|
32 |
+
matplotlib.rcParams['figure.dpi'] = 400
|
33 |
+
matplotlib.rcParams['savefig.dpi'] = 400
|
34 |
+
|
35 |
# Initialize models and other global variables
|
36 |
device = 'cuda'
|
37 |
|
|
|
43 |
emotion_detector = FER(mtcnn=False)
|
44 |
|
45 |
|
46 |
+
def frame_to_timecode(frame_num, total_frames, duration):
|
47 |
+
total_seconds = (frame_num / total_frames) * duration
|
48 |
hours = int(total_seconds // 3600)
|
49 |
minutes = int((total_seconds % 3600) // 60)
|
50 |
seconds = int(total_seconds % 60)
|
|
|
119 |
|
120 |
# Report progress
|
121 |
if progress_callback:
|
122 |
+
progress = min(100, (frame_count / total_frames_to_extract) * 100) # Ensure it doesn't exceed 100%
|
123 |
+
progress_callback(progress, f"Extracting frame")
|
124 |
|
125 |
if frame_count >= total_frames_to_extract:
|
126 |
break
|
|
|
193 |
|
194 |
|
195 |
def save_person_data_to_csv(embeddings_by_frame, emotions_by_frame, clusters, desired_fps, original_fps, output_folder,
|
196 |
+
num_components, video_duration):
|
197 |
emotions = ['angry', 'disgust', 'fear', 'happy', 'sad', 'surprise', 'neutral']
|
198 |
person_data = {}
|
199 |
|
|
|
218 |
scaler = MinMaxScaler(feature_range=(0, 1))
|
219 |
embeddings_reduced_normalized = scaler.fit_transform(embeddings_reduced)
|
220 |
|
221 |
+
total_frames = max(frames)
|
222 |
+
timecodes = [frame_to_timecode(frame, total_frames, video_duration) for frame in frames]
|
223 |
+
times_in_minutes = [frame / total_frames * video_duration / 60 for frame in frames]
|
224 |
|
225 |
df_data = {
|
226 |
'Frame': frames,
|
|
|
257 |
|
258 |
|
259 |
def lstm_anomaly_detection(X, feature_columns, num_anomalies=10, epochs=100, batch_size=64):
|
260 |
+
device = 'cuda'
|
261 |
|
262 |
X = torch.FloatTensor(X).to(device)
|
263 |
|
|
|
320 |
model)
|
321 |
|
322 |
|
323 |
+
def plot_emotion(df, emotion, num_anomalies, color):
|
324 |
+
plt.figure(figsize=(16, 8), dpi=400) # Increase DPI for higher quality
|
325 |
fig, ax = plt.subplots(figsize=(16, 8))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
326 |
|
327 |
+
# Convert timecodes to seconds for proper plotting
|
328 |
+
df['Seconds'] = df['Timecode'].apply(
|
329 |
+
lambda x: sum(float(t) * 60 ** i for i, t in enumerate(reversed(x.split(':')))))
|
330 |
|
331 |
+
# Create a DataFrame for seaborn
|
332 |
+
plot_df = pd.DataFrame({
|
333 |
+
'Seconds': df['Seconds'],
|
334 |
+
'Emotion Score': df[emotion]
|
335 |
+
})
|
336 |
+
|
337 |
+
# Plot using seaborn
|
338 |
+
sns.lineplot(x='Seconds', y='Emotion Score', data=plot_df, ax=ax, color=color)
|
339 |
+
|
340 |
+
# Highlight top anomalies
|
341 |
+
top_indices = np.argsort(df[emotion].values)[-num_anomalies:][::-1]
|
342 |
+
ax.scatter(df['Seconds'].iloc[top_indices], df[emotion].iloc[top_indices], color='red', s=50, zorder=5)
|
343 |
+
|
344 |
+
# Set x-axis
|
345 |
+
max_seconds = df['Seconds'].max()
|
346 |
+
ax.set_xlim(0, max_seconds)
|
347 |
+
num_ticks = 80 # Reduce number of ticks for emotion graphs
|
348 |
+
ax.set_xticks(np.linspace(0, max_seconds, num_ticks))
|
349 |
+
ax.set_xticklabels([f"{int(x // 60):02d}:{int(x % 60):02d}" for x in ax.get_xticks()], rotation=90, ha='right')
|
350 |
+
|
351 |
+
ax.set_xlabel('Time')
|
352 |
ax.set_ylabel(f'{emotion.capitalize()} Score')
|
353 |
+
ax.set_title(f'{emotion.capitalize()} Scores Over Time (Top {num_anomalies} in Red)')
|
354 |
+
|
355 |
+
# Add grid
|
356 |
+
ax.grid(True, linestyle='--', alpha=0.7)
|
357 |
+
|
358 |
plt.tight_layout()
|
359 |
return fig
|
360 |
|
361 |
+
def plot_anomaly_scores(df, anomaly_scores, top_indices, title):
|
362 |
+
plt.figure(figsize=(16, 8), dpi=400) # Increase DPI for higher quality
|
363 |
+
fig, ax = plt.subplots(figsize=(16, 8))
|
364 |
+
|
365 |
+
# Convert timecodes to seconds for proper plotting
|
366 |
+
df['Seconds'] = df['Timecode'].apply(
|
367 |
+
lambda x: sum(float(t) * 60 ** i for i, t in enumerate(reversed(x.split(':')))))
|
368 |
+
|
369 |
+
# Create a DataFrame for seaborn
|
370 |
+
plot_df = pd.DataFrame({
|
371 |
+
'Seconds': df['Seconds'],
|
372 |
+
'Anomaly Score': anomaly_scores
|
373 |
+
})
|
374 |
+
|
375 |
+
# Plot using seaborn
|
376 |
+
sns.lineplot(x='Seconds', y='Anomaly Score', data=plot_df, ax=ax)
|
377 |
+
|
378 |
+
# Highlight top anomalies
|
379 |
+
ax.scatter(df['Seconds'].iloc[top_indices], anomaly_scores[top_indices], color='red', s=50, zorder=5)
|
380 |
+
|
381 |
+
# Set x-axis
|
382 |
+
max_seconds = df['Seconds'].max()
|
383 |
+
ax.set_xlim(0, max_seconds)
|
384 |
+
num_ticks = 80 # Increase number of ticks for anomaly score graphs
|
385 |
+
ax.set_xticks(np.linspace(0, max_seconds, num_ticks))
|
386 |
+
ax.set_xticklabels([f"{int(x // 60):02d}:{int(x % 60):02d}" for x in ax.get_xticks()], rotation=90, ha='right')
|
387 |
+
|
388 |
+
ax.set_xlabel('Time')
|
389 |
+
ax.set_ylabel('Anomaly Score')
|
390 |
+
ax.set_title(f'Anomaly Scores Over Time ({title})')
|
391 |
|
392 |
+
# Add grid
|
393 |
+
ax.grid(True, linestyle='--', alpha=0.7)
|
394 |
|
395 |
+
plt.tight_layout()
|
396 |
+
return fig
|
397 |
|
398 |
def get_random_face_sample(organized_faces_folder, largest_cluster, output_folder):
|
399 |
person_folder = os.path.join(organized_faces_folder, f"person_{largest_cluster}")
|
|
|
405 |
|
406 |
# Read the image and resize it to be smaller
|
407 |
face_img = cv2.imread(face_path)
|
408 |
+
small_face = cv2.resize(face_img, (160, 160)) # Resize to NxN pixels
|
409 |
cv2.imwrite(output_path, small_face)
|
410 |
|
411 |
return output_path
|
|
|
422 |
os.makedirs(aligned_faces_folder, exist_ok=True)
|
423 |
os.makedirs(organized_faces_folder, exist_ok=True)
|
424 |
|
425 |
+
|
426 |
+
clip = VideoFileClip(video_path)
|
427 |
+
video_duration = clip.duration
|
428 |
+
clip.close()
|
429 |
+
|
430 |
+
progress(0, "Starting frame extraction")
|
431 |
frames_folder = os.path.join(temp_dir, 'extracted_frames')
|
432 |
|
433 |
def extraction_progress(percent, message):
|
434 |
+
progress(percent / 100, f"Extracting frames")
|
|
|
|
|
435 |
|
436 |
frame_count, original_fps = extract_frames(video_path, frames_folder, desired_fps, extraction_progress)
|
437 |
|
438 |
+
progress(1, "Frame extraction complete")
|
439 |
|
440 |
progress(0.3, "Processing frames")
|
441 |
embeddings_by_frame, emotions_by_frame = process_frames(frames_folder, aligned_faces_folder, frame_count,
|
|
|
453 |
|
454 |
progress(0.8, "Saving person data")
|
455 |
df, largest_cluster = save_person_data_to_csv(embeddings_by_frame, emotions_by_frame, clusters, desired_fps,
|
456 |
+
original_fps, temp_dir, num_components, video_duration)
|
457 |
|
458 |
progress(0.9, "Performing anomaly detection")
|
459 |
feature_columns = [col for col in df.columns if
|
|
|
518 |
gr.Video(),
|
519 |
gr.Slider(minimum=1, maximum=20, step=1, value=5, label="Number of Anomalies"),
|
520 |
gr.Slider(minimum=1, maximum=20, step=1, value=10, label="Number of Components"),
|
521 |
+
gr.Slider(minimum=1, maximum=20, step=1, value=10, label="Desired FPS"),
|
522 |
+
gr.Slider(minimum=1, maximum=64, step=1, value=8, label="Batch Size")
|
523 |
],
|
524 |
outputs=[
|
525 |
gr.Textbox(label="Anomaly Detection Results"),
|
|
|
526 |
gr.Plot(label="Anomaly Scores (All Features)"),
|
527 |
gr.Plot(label="Anomaly Scores (Components Only)"),
|
528 |
gr.Plot(label="Fear Anomalies"),
|
|
|
530 |
gr.Plot(label="Angry Anomalies"),
|
531 |
gr.Plot(label="Happy Anomalies"),
|
532 |
gr.Plot(label="Surprise Anomalies"),
|
533 |
+
gr.Plot(label="Neutral Anomalies"),
|
534 |
+
gr.Image(type="filepath", label="Random Face Sample of Most Frequent Person"),
|
535 |
],
|
536 |
title="Facial Expressions Anomaly Detection",
|
537 |
description="""
|