David Driscoll commited on
Commit
2553966
·
1 Parent(s): 5f27df7

Lag reduction

Browse files
Files changed (1) hide show
  1. app.py +103 -127
app.py CHANGED
@@ -7,11 +7,37 @@ from torchvision.models.detection import FasterRCNN_ResNet50_FPN_Weights
7
  from PIL import Image
8
  import mediapipe as mp
9
  from fer import FER # Facial emotion recognition
 
10
 
11
  # -----------------------------
12
- # Constants
13
  # -----------------------------
14
- SKIP_RATE = 5 # Run heavy detection every 5 frames
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
  # -----------------------------
17
  # Initialize Models and Helpers
@@ -37,141 +63,91 @@ obj_transform = transforms.Compose([transforms.ToTensor()])
37
  emotion_detector = FER(mtcnn=True)
38
 
39
  # -----------------------------
40
- # Define Analysis Functions with Frame Skipping
41
  # -----------------------------
42
 
43
- def analyze_posture(image):
44
- """
45
- Processes an image from the webcam with MediaPipe Pose.
46
- Runs heavy detection every SKIP_RATE frames; otherwise, returns last result.
47
- """
48
- if not hasattr(analyze_posture, "counter"):
49
- analyze_posture.counter = 0
50
- analyze_posture.last_output = None
51
- analyze_posture.counter += 1
52
-
53
- # If first frame or time to run detection:
54
- if analyze_posture.counter % SKIP_RATE == 0 or analyze_posture.last_output is None:
55
- # Convert from PIL (RGB) to OpenCV BGR format
56
- frame = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
57
- output_frame = frame.copy()
58
- frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
59
-
60
- posture_result = "No posture detected"
61
- pose_results = pose.process(frame_rgb)
62
- if pose_results.pose_landmarks:
63
- posture_result = "Posture detected"
64
- mp_drawing.draw_landmarks(
65
- output_frame, pose_results.pose_landmarks, mp_pose.POSE_CONNECTIONS,
66
- mp_drawing.DrawingSpec(color=(0, 255, 0), thickness=2, circle_radius=2),
67
- mp_drawing.DrawingSpec(color=(0, 0, 255), thickness=2)
68
- )
69
-
70
- annotated_image = cv2.cvtColor(output_frame, cv2.COLOR_BGR2RGB)
71
- result = (annotated_image, f"Posture Analysis: {posture_result}")
72
- analyze_posture.last_output = result
73
- return result
74
  else:
75
- # For frames in between, return last result
76
- return analyze_posture.last_output
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
- def analyze_emotion(image):
79
- """
80
- Uses FER to detect facial emotions from the webcam image.
81
- Runs heavy detection every SKIP_RATE frames.
82
- """
83
- if not hasattr(analyze_emotion, "counter"):
84
- analyze_emotion.counter = 0
85
- analyze_emotion.last_output = None
86
- analyze_emotion.counter += 1
87
 
88
- if analyze_emotion.counter % SKIP_RATE == 0 or analyze_emotion.last_output is None:
89
- frame = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
90
- frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
91
- emotions = emotion_detector.detect_emotions(frame_rgb)
92
- if emotions:
93
- top_emotion, score = max(emotions[0]["emotions"].items(), key=lambda x: x[1])
94
- emotion_text = f"{top_emotion} ({score:.2f})"
95
- else:
96
- emotion_text = "No face detected for emotion analysis"
97
- annotated_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
98
- result = (annotated_image, f"Emotion Analysis: {emotion_text}")
99
- analyze_emotion.last_output = result
100
- return result
101
- else:
102
- return analyze_emotion.last_output
103
 
104
  def analyze_objects(image):
105
- """
106
- Uses Faster R-CNN to detect objects in the webcam image.
107
- Heavy detection is run every SKIP_RATE frames.
108
- """
109
- if not hasattr(analyze_objects, "counter"):
110
- analyze_objects.counter = 0
111
- analyze_objects.last_output = None
112
- analyze_objects.counter += 1
113
-
114
- if analyze_objects.counter % SKIP_RATE == 0 or analyze_objects.last_output is None:
115
- frame = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
116
- output_frame = frame.copy()
117
- frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
118
- image_pil = Image.fromarray(frame_rgb)
119
- img_tensor = obj_transform(image_pil)
120
-
121
- with torch.no_grad():
122
- detections = object_detection_model([img_tensor])[0]
123
-
124
- threshold = 0.8
125
- detected_boxes = detections["boxes"][detections["scores"] > threshold]
126
- for box in detected_boxes:
127
- box = box.int().cpu().numpy()
128
- cv2.rectangle(output_frame, (box[0], box[1]), (box[2], box[3]), (255, 255, 0), 2)
129
-
130
- object_result = f"Detected {len(detected_boxes)} object(s)" if len(detected_boxes) else "No objects detected"
131
- annotated_image = cv2.cvtColor(output_frame, cv2.COLOR_BGR2RGB)
132
- result = (annotated_image, f"Object Detection: {object_result}")
133
- analyze_objects.last_output = result
134
- return result
135
- else:
136
- return analyze_objects.last_output
137
 
138
  def analyze_faces(image):
139
- """
140
- Uses MediaPipe to detect faces in the webcam image.
141
- Runs heavy detection every SKIP_RATE frames.
142
- """
143
- if not hasattr(analyze_faces, "counter"):
144
- analyze_faces.counter = 0
145
- analyze_faces.last_output = None
146
- analyze_faces.counter += 1
147
-
148
- if analyze_faces.counter % SKIP_RATE == 0 or analyze_faces.last_output is None:
149
- frame = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
150
- output_frame = frame.copy()
151
- frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
152
- face_results = face_detection.process(frame_rgb)
153
-
154
- face_result = "No faces detected"
155
- if face_results.detections:
156
- face_result = f"Detected {len(face_results.detections)} face(s)"
157
- h, w, _ = output_frame.shape
158
- for detection in face_results.detections:
159
- bbox = detection.location_data.relative_bounding_box
160
- x = int(bbox.xmin * w)
161
- y = int(bbox.ymin * h)
162
- box_w = int(bbox.width * w)
163
- box_h = int(bbox.height * h)
164
- cv2.rectangle(output_frame, (x, y), (x + box_w, y + box_h), (0, 0, 255), 2)
165
-
166
- annotated_image = cv2.cvtColor(output_frame, cv2.COLOR_BGR2RGB)
167
- result = (annotated_image, f"Face Detection: {face_result}")
168
- analyze_faces.last_output = result
169
- return result
170
- else:
171
- return analyze_faces.last_output
172
 
173
  # -----------------------------
174
- # Custom CSS for a High-Tech Look (with white fonts)
175
  # -----------------------------
176
  custom_css = """
177
  @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap');
@@ -204,7 +180,7 @@ body {
204
  """
205
 
206
  # -----------------------------
207
- # Create Individual Interfaces for Each Analysis (using real-time webcam input)
208
  # -----------------------------
209
  posture_interface = gr.Interface(
210
  fn=analyze_posture,
 
7
  from PIL import Image
8
  import mediapipe as mp
9
  from fer import FER # Facial emotion recognition
10
+ from concurrent.futures import ThreadPoolExecutor
11
 
12
  # -----------------------------
13
+ # Global Asynchronous Executor & Caches
14
  # -----------------------------
15
+ executor = ThreadPoolExecutor(max_workers=4)
16
+ latest_results = {
17
+ "posture": None,
18
+ "emotion": None,
19
+ "objects": None,
20
+ "faces": None
21
+ }
22
+ futures = {
23
+ "posture": None,
24
+ "emotion": None,
25
+ "objects": None,
26
+ "faces": None
27
+ }
28
+
29
+ def async_analyze(key, func, image):
30
+ """
31
+ Runs the heavy detection function 'func' in a background thread.
32
+ Returns the last computed result (if available) so that the output
33
+ FPS remains high even if the detection lags.
34
+ """
35
+ if futures[key] is None or futures[key].done():
36
+ futures[key] = executor.submit(func, image)
37
+ if futures[key].done():
38
+ latest_results[key] = futures[key].result()
39
+ # Return latest result if available; otherwise, compute synchronously
40
+ return latest_results.get(key, func(image))
41
 
42
  # -----------------------------
43
  # Initialize Models and Helpers
 
63
  emotion_detector = FER(mtcnn=True)
64
 
65
  # -----------------------------
66
+ # Heavy (Synchronous) Analysis Functions
67
  # -----------------------------
68
 
69
+ def _analyze_posture(image):
70
+ # Convert from PIL (RGB) to OpenCV BGR format
71
+ frame = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
72
+ output_frame = frame.copy()
73
+ frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
74
+ posture_result = "No posture detected"
75
+ pose_results = pose.process(frame_rgb)
76
+ if pose_results.pose_landmarks:
77
+ posture_result = "Posture detected"
78
+ mp_drawing.draw_landmarks(
79
+ output_frame, pose_results.pose_landmarks, mp_pose.POSE_CONNECTIONS,
80
+ mp_drawing.DrawingSpec(color=(0, 255, 0), thickness=2, circle_radius=2),
81
+ mp_drawing.DrawingSpec(color=(0, 0, 255), thickness=2)
82
+ )
83
+ annotated_image = cv2.cvtColor(output_frame, cv2.COLOR_BGR2RGB)
84
+ return annotated_image, f"Posture Analysis: {posture_result}"
85
+
86
+ def _analyze_emotion(image):
87
+ frame = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
88
+ frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
89
+ emotions = emotion_detector.detect_emotions(frame_rgb)
90
+ if emotions:
91
+ top_emotion, score = max(emotions[0]["emotions"].items(), key=lambda x: x[1])
92
+ emotion_text = f"{top_emotion} ({score:.2f})"
 
 
 
 
 
 
 
93
  else:
94
+ emotion_text = "No face detected for emotion analysis"
95
+ annotated_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
96
+ return annotated_image, f"Emotion Analysis: {emotion_text}"
97
+
98
+ def _analyze_objects(image):
99
+ frame = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
100
+ output_frame = frame.copy()
101
+ frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
102
+ image_pil = Image.fromarray(frame_rgb)
103
+ img_tensor = obj_transform(image_pil)
104
+ with torch.no_grad():
105
+ detections = object_detection_model([img_tensor])[0]
106
+ threshold = 0.8
107
+ detected_boxes = detections["boxes"][detections["scores"] > threshold]
108
+ for box in detected_boxes:
109
+ box = box.int().cpu().numpy()
110
+ cv2.rectangle(output_frame, (box[0], box[1]), (box[2], box[3]), (255, 255, 0), 2)
111
+ object_result = f"Detected {len(detected_boxes)} object(s)" if len(detected_boxes) else "No objects detected"
112
+ annotated_image = cv2.cvtColor(output_frame, cv2.COLOR_BGR2RGB)
113
+ return annotated_image, f"Object Detection: {object_result}"
114
+
115
+ def _analyze_faces(image):
116
+ frame = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
117
+ output_frame = frame.copy()
118
+ frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
119
+ face_results = face_detection.process(frame_rgb)
120
+ face_result = "No faces detected"
121
+ if face_results.detections:
122
+ face_result = f"Detected {len(face_results.detections)} face(s)"
123
+ h, w, _ = output_frame.shape
124
+ for detection in face_results.detections:
125
+ bbox = detection.location_data.relative_bounding_box
126
+ x = int(bbox.xmin * w)
127
+ y = int(bbox.ymin * h)
128
+ box_w = int(bbox.width * w)
129
+ box_h = int(bbox.height * h)
130
+ cv2.rectangle(output_frame, (x, y), (x + box_w, y + box_h), (0, 0, 255), 2)
131
+ annotated_image = cv2.cvtColor(output_frame, cv2.COLOR_BGR2RGB)
132
+ return annotated_image, f"Face Detection: {face_result}"
133
 
134
+ # -----------------------------
135
+ # Asynchronous (Fast) Analysis Functions
136
+ # -----------------------------
137
+ def analyze_posture(image):
138
+ return async_analyze("posture", _analyze_posture, image)
 
 
 
 
139
 
140
+ def analyze_emotion(image):
141
+ return async_analyze("emotion", _analyze_emotion, image)
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
  def analyze_objects(image):
144
+ return async_analyze("objects", _analyze_objects, image)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
 
146
  def analyze_faces(image):
147
+ return async_analyze("faces", _analyze_faces, image)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
 
149
  # -----------------------------
150
+ # Custom CSS for a High-Tech Look (White Font)
151
  # -----------------------------
152
  custom_css = """
153
  @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap');
 
180
  """
181
 
182
  # -----------------------------
183
+ # Create Individual Interfaces for Each Analysis
184
  # -----------------------------
185
  posture_interface = gr.Interface(
186
  fn=analyze_posture,