Sohan2004 commited on
Commit
51a1e75
·
verified ·
1 Parent(s): 2b73233

Delete main.ipynb

Browse files
Files changed (1) hide show
  1. main.ipynb +0 -975
main.ipynb DELETED
@@ -1,975 +0,0 @@
1
- # Complete Stress Detection System - 10 Action Units
2
- # Real-time Multi-AU Detection with Comprehensive Analysis
3
- # Research Assistant: [Your Name]
4
- # Guide: Prof. Anup Nandy
5
- # Based on Facial Action Coding System (FACS) - Ekman & Friesen
6
-
7
- import cv2
8
- import mediapipe as mp
9
- import numpy as np
10
- import pandas as pd
11
- import matplotlib.pyplot as plt
12
- from matplotlib.gridspec import GridSpec
13
- from collections import deque
14
- import time
15
- from datetime import datetime
16
- import warnings
17
- warnings.filterwarnings('ignore')
18
-
19
- # ==================== CONFIGURATION ====================
20
- WINDOW_SIZE = 30
21
- RECORDING_DURATION = 15
22
- FPS = 30
23
-
24
- # ==================== MediaPipe Setup ====================
25
- mp_face_mesh = mp.solutions.face_mesh
26
- face_mesh = mp_face_mesh.FaceMesh(
27
- min_detection_confidence=0.5,
28
- min_tracking_confidence=0.5,
29
- refine_landmarks=True
30
- )
31
-
32
- # ==================== LANDMARK INDICES (468 landmarks) ====================
33
-
34
- # AU01 - Inner Brow Raiser (Surprise, Fear, Sadness)
35
- AU01_LANDMARKS = {
36
- 'left_inner_brow': 336,
37
- 'right_inner_brow': 107,
38
- 'nose_bridge': 6,
39
- 'left_outer_brow': 285,
40
- 'right_outer_brow': 55
41
- }
42
-
43
- # AU04 - Brow Lowerer (Anger, Sadness, Concentration)
44
- AU04_LANDMARKS = {
45
- 'left_inner_brow': 336,
46
- 'right_inner_brow': 107,
47
- 'left_mid_brow': 285,
48
- 'right_mid_brow': 55,
49
- 'left_eyelid': 159,
50
- 'right_eyelid': 386,
51
- 'nose_bridge': 6
52
- }
53
-
54
- # AU06 - Cheek Raiser (Genuine Smile - Duchenne)
55
- AU06_LANDMARKS = {
56
- 'left_cheek': 205,
57
- 'right_cheek': 425,
58
- 'left_lower_eyelid': 145,
59
- 'right_lower_eyelid': 374,
60
- 'left_eye_outer': 33,
61
- 'right_eye_outer': 263
62
- }
63
-
64
- # AU07 - Lid Tightener (Concentration, Anger, Disgust)
65
- AU07_LANDMARKS = {
66
- 'left_upper_lid': 159,
67
- 'right_upper_lid': 386,
68
- 'left_lower_lid': 145,
69
- 'right_lower_lid': 374,
70
- 'left_eye_top': 159,
71
- 'right_eye_top': 386
72
- }
73
-
74
- # AU12 - Lip Corner Puller (Happiness)
75
- AU12_LANDMARKS = {
76
- 'left_corner': 61,
77
- 'right_corner': 291,
78
- 'upper_center': 13,
79
- 'lower_center': 14
80
- }
81
-
82
- # AU14 - Dimpler (Smile Intensity)
83
- AU14_LANDMARKS = {
84
- 'left_dimple': 206,
85
- 'right_dimple': 426,
86
- 'left_corner': 61,
87
- 'right_corner': 291
88
- }
89
-
90
- # AU17 - Chin Raiser (Doubt, Sadness, Pouting)
91
- AU17_LANDMARKS = {
92
- 'chin_center': 152,
93
- 'lower_lip': 17,
94
- 'chin_left': 176,
95
- 'chin_right': 400
96
- }
97
-
98
- # AU23 - Lip Tightener (Anger, Tension)
99
- AU23_LANDMARKS = {
100
- 'left_corner': 61,
101
- 'right_corner': 291,
102
- 'left_outer': 57,
103
- 'right_outer': 287
104
- }
105
-
106
- # AU24 - Lip Pressor (Stress, Tension, Anger)
107
- AU24_LANDMARKS = {
108
- 'upper_lip_top': 0,
109
- 'upper_lip_bottom': 13,
110
- 'lower_lip_top': 14,
111
- 'lower_lip_bottom': 17
112
- }
113
-
114
- # AU26 - Jaw Drop (Surprise, Shock, Mouth Opening)
115
- AU26_LANDMARKS = {
116
- 'upper_lip': 13,
117
- 'lower_lip': 14,
118
- 'chin': 152,
119
- 'nose': 1
120
- }
121
-
122
- # ==================== UTILITY FUNCTIONS ====================
123
- def calculate_distance(point1, point2):
124
- return np.sqrt((point1[0] - point2[0])**2 + (point1[1] - point2[1])**2)
125
-
126
- def get_landmark_coords(landmarks, idx, frame_width, frame_height):
127
- lm = landmarks[idx]
128
- return np.array([lm.x * frame_width, lm.y * frame_height])
129
-
130
- # ==================== AU DETECTOR CLASSES ====================
131
-
132
- class AU01Detector:
133
- """AU01 - Inner Brow Raiser (Surprise, Fear, Worry)"""
134
- def __init__(self, window_size=30):
135
- self.name = "AU01_InnerBrowRaise"
136
- self.activation_history = deque(maxlen=window_size)
137
- self.intensity_history = deque(maxlen=window_size)
138
-
139
- def detect(self, landmarks, frame_width, frame_height):
140
- left_inner = get_landmark_coords(landmarks, AU01_LANDMARKS['left_inner_brow'], frame_width, frame_height)
141
- right_inner = get_landmark_coords(landmarks, AU01_LANDMARKS['right_inner_brow'], frame_width, frame_height)
142
- left_outer = get_landmark_coords(landmarks, AU01_LANDMARKS['left_outer_brow'], frame_width, frame_height)
143
- right_outer = get_landmark_coords(landmarks, AU01_LANDMARKS['right_outer_brow'], frame_width, frame_height)
144
- nose = get_landmark_coords(landmarks, AU01_LANDMARKS['nose_bridge'], frame_width, frame_height)
145
-
146
- # Calculate inner vs outer brow height
147
- inner_height = ((nose[1] - left_inner[1]) + (nose[1] - right_inner[1])) / 2
148
- outer_height = ((nose[1] - left_outer[1]) + (nose[1] - right_outer[1])) / 2
149
-
150
- # AU01 active when inner brows raised MORE than outer (creates worried look)
151
- raise_ratio = inner_height / (outer_height + 1e-6)
152
-
153
- is_active = raise_ratio > 1.15 # Inner brows 15% higher than outer
154
- intensity = min(100, max(0, (raise_ratio - 1.0) * 500))
155
-
156
- self.activation_history.append(int(is_active))
157
- self.intensity_history.append(intensity)
158
-
159
- return is_active, intensity
160
-
161
-
162
- class AU04Detector:
163
- """AU04 - Brow Lowerer (Anger, Concentration, Stress)"""
164
- def __init__(self, window_size=30):
165
- self.name = "AU04_BrowLower"
166
- self.activation_history = deque(maxlen=window_size)
167
- self.intensity_history = deque(maxlen=window_size)
168
-
169
- def detect(self, landmarks, frame_width, frame_height):
170
- left_inner_brow = get_landmark_coords(landmarks, AU04_LANDMARKS['left_inner_brow'], frame_width, frame_height)
171
- right_inner_brow = get_landmark_coords(landmarks, AU04_LANDMARKS['right_inner_brow'], frame_width, frame_height)
172
- left_eyelid = get_landmark_coords(landmarks, AU04_LANDMARKS['left_eyelid'], frame_width, frame_height)
173
- right_eyelid = get_landmark_coords(landmarks, AU04_LANDMARKS['right_eyelid'], frame_width, frame_height)
174
- nose_bridge = get_landmark_coords(landmarks, AU04_LANDMARKS['nose_bridge'], frame_width, frame_height)
175
-
176
- left_brow_eyelid_dist = left_inner_brow[1] - left_eyelid[1]
177
- right_brow_eyelid_dist = right_inner_brow[1] - right_eyelid[1]
178
- avg_brow_eyelid_dist = (left_brow_eyelid_dist + right_brow_eyelid_dist) / 2
179
-
180
- face_height = calculate_distance(left_inner_brow, nose_bridge)
181
- normalized_distance = avg_brow_eyelid_dist / (face_height + 1e-6)
182
-
183
- inner_brow_distance = calculate_distance(left_inner_brow, right_inner_brow)
184
- outer_eye_distance = calculate_distance(left_eyelid, right_eyelid)
185
- brow_compression_ratio = inner_brow_distance / (outer_eye_distance + 1e-6)
186
-
187
- is_active = (normalized_distance > -0.30 or brow_compression_ratio < 0.95)
188
- intensity = min(100, max(0, (normalized_distance + 0.40) / 0.40 * 100))
189
-
190
- self.activation_history.append(int(is_active))
191
- self.intensity_history.append(intensity)
192
-
193
- return is_active, intensity
194
-
195
-
196
- class AU06Detector:
197
- """AU06 - Cheek Raiser (Genuine Smile)"""
198
- def __init__(self, window_size=30):
199
- self.name = "AU06_CheekRaise"
200
- self.activation_history = deque(maxlen=window_size)
201
- self.intensity_history = deque(maxlen=window_size)
202
-
203
- def detect(self, landmarks, frame_width, frame_height):
204
- left_cheek = get_landmark_coords(landmarks, AU06_LANDMARKS['left_cheek'], frame_width, frame_height)
205
- right_cheek = get_landmark_coords(landmarks, AU06_LANDMARKS['right_cheek'], frame_width, frame_height)
206
- left_lower_lid = get_landmark_coords(landmarks, AU06_LANDMARKS['left_lower_eyelid'], frame_width, frame_height)
207
- right_lower_lid = get_landmark_coords(landmarks, AU06_LANDMARKS['right_lower_eyelid'], frame_width, frame_height)
208
-
209
- # When cheeks raise, distance between cheek and lower eyelid decreases
210
- left_distance = calculate_distance(left_cheek, left_lower_lid)
211
- right_distance = calculate_distance(right_cheek, right_lower_lid)
212
- avg_distance = (left_distance + right_distance) / 2
213
-
214
- # Also check if lower eyelid moves up
215
- left_eye_outer = get_landmark_coords(landmarks, AU06_LANDMARKS['left_eye_outer'], frame_width, frame_height)
216
- eye_height = abs(left_eye_outer[1] - left_lower_lid[1])
217
-
218
- cheek_raise_score = eye_height / (avg_distance + 1e-6)
219
-
220
- is_active = cheek_raise_score > 0.8
221
- intensity = min(100, max(0, (cheek_raise_score - 0.5) * 200))
222
-
223
- self.activation_history.append(int(is_active))
224
- self.intensity_history.append(intensity)
225
-
226
- return is_active, intensity
227
-
228
-
229
- class AU07Detector:
230
- """AU07 - Lid Tightener (Tension, Squinting)"""
231
- def __init__(self, window_size=30):
232
- self.name = "AU07_LidTighten"
233
- self.activation_history = deque(maxlen=window_size)
234
- self.intensity_history = deque(maxlen=window_size)
235
-
236
- def detect(self, landmarks, frame_width, frame_height):
237
- left_upper = get_landmark_coords(landmarks, AU07_LANDMARKS['left_upper_lid'], frame_width, frame_height)
238
- right_upper = get_landmark_coords(landmarks, AU07_LANDMARKS['right_upper_lid'], frame_width, frame_height)
239
- left_lower = get_landmark_coords(landmarks, AU07_LANDMARKS['left_lower_lid'], frame_width, frame_height)
240
- right_lower = get_landmark_coords(landmarks, AU07_LANDMARKS['right_lower_lid'], frame_width, frame_height)
241
-
242
- # Eye opening (smaller = more tightened)
243
- left_eye_opening = abs(left_upper[1] - left_lower[1])
244
- right_eye_opening = abs(right_upper[1] - right_lower[1])
245
- avg_eye_opening = (left_eye_opening + right_eye_opening) / 2
246
-
247
- # Normalize by face height
248
- face_ref = calculate_distance(left_upper,
249
- get_landmark_coords(landmarks, 152, frame_width, frame_height))
250
- normalized_opening = avg_eye_opening / (face_ref + 1e-6)
251
-
252
- is_active = normalized_opening < 0.025 # Eyes tightened/squinted
253
- intensity = min(100, max(0, (0.035 - normalized_opening) / 0.035 * 100))
254
-
255
- self.activation_history.append(int(is_active))
256
- self.intensity_history.append(intensity)
257
-
258
- return is_active, intensity
259
-
260
-
261
- class AU12Detector:
262
- """AU12 - Lip Corner Puller (Smile)"""
263
- def __init__(self, window_size=30):
264
- self.name = "AU12_LipCornerPull"
265
- self.activation_history = deque(maxlen=window_size)
266
- self.intensity_history = deque(maxlen=window_size)
267
-
268
- def detect(self, landmarks, frame_width, frame_height):
269
- left_corner = get_landmark_coords(landmarks, AU12_LANDMARKS['left_corner'], frame_width, frame_height)
270
- right_corner = get_landmark_coords(landmarks, AU12_LANDMARKS['right_corner'], frame_width, frame_height)
271
- upper_center = get_landmark_coords(landmarks, AU12_LANDMARKS['upper_center'], frame_width, frame_height)
272
- lower_center = get_landmark_coords(landmarks, AU12_LANDMARKS['lower_center'], frame_width, frame_height)
273
-
274
- mouth_width = calculate_distance(left_corner, right_corner)
275
- mouth_height = calculate_distance(upper_center, lower_center)
276
- mouth_center_y = (upper_center[1] + lower_center[1]) / 2
277
-
278
- left_corner_lift = mouth_center_y - left_corner[1]
279
- right_corner_lift = mouth_center_y - right_corner[1]
280
- avg_corner_lift = (left_corner_lift + right_corner_lift) / 2
281
-
282
- mouth_ratio = mouth_width / (mouth_height + 1e-6)
283
- normalized_lift = avg_corner_lift / mouth_height if mouth_height > 0 else 0
284
-
285
- lift_difference = abs(left_corner_lift - right_corner_lift)
286
- symmetry_score = 1.0 - min(1.0, lift_difference / (mouth_height + 1e-6))
287
-
288
- is_active = (normalized_lift > 0.25 and mouth_ratio > 2.8 and symmetry_score > 0.6)
289
- intensity = min(100, max(0, normalized_lift * 250))
290
-
291
- self.activation_history.append(int(is_active))
292
- self.intensity_history.append(intensity)
293
-
294
- return is_active, intensity
295
-
296
-
297
- class AU14Detector:
298
- """AU14 - Dimpler (Smile Depth Indicator)"""
299
- def __init__(self, window_size=30):
300
- self.name = "AU14_Dimpler"
301
- self.activation_history = deque(maxlen=window_size)
302
- self.intensity_history = deque(maxlen=window_size)
303
-
304
- def detect(self, landmarks, frame_width, frame_height):
305
- left_dimple = get_landmark_coords(landmarks, AU14_LANDMARKS['left_dimple'], frame_width, frame_height)
306
- right_dimple = get_landmark_coords(landmarks, AU14_LANDMARKS['right_dimple'], frame_width, frame_height)
307
- left_corner = get_landmark_coords(landmarks, AU14_LANDMARKS['left_corner'], frame_width, frame_height)
308
- right_corner = get_landmark_coords(landmarks, AU14_LANDMARKS['right_corner'], frame_width, frame_height)
309
-
310
- # Dimples appear when corners pull back and create indentation
311
- left_depth = calculate_distance(left_dimple, left_corner)
312
- right_depth = calculate_distance(right_dimple, right_corner)
313
- avg_depth = (left_depth + right_depth) / 2
314
-
315
- # Check corner retraction
316
- corner_distance = calculate_distance(left_corner, right_corner)
317
- dimple_score = avg_depth / (corner_distance + 1e-6)
318
-
319
- is_active = dimple_score > 0.15
320
- intensity = min(100, max(0, (dimple_score - 0.10) * 500))
321
-
322
- self.activation_history.append(int(is_active))
323
- self.intensity_history.append(intensity)
324
-
325
- return is_active, intensity
326
-
327
-
328
- class AU17Detector:
329
- """AU17 - Chin Raiser (Doubt, Pouting, Sadness)"""
330
- def __init__(self, window_size=30):
331
- self.name = "AU17_ChinRaise"
332
- self.activation_history = deque(maxlen=window_size)
333
- self.intensity_history = deque(maxlen=window_size)
334
-
335
- def detect(self, landmarks, frame_width, frame_height):
336
- chin = get_landmark_coords(landmarks, AU17_LANDMARKS['chin_center'], frame_width, frame_height)
337
- lower_lip = get_landmark_coords(landmarks, AU17_LANDMARKS['lower_lip'], frame_width, frame_height)
338
-
339
- # When chin raises, distance between chin and lower lip decreases
340
- chin_lip_distance = calculate_distance(chin, lower_lip)
341
-
342
- # Normalize by face height
343
- nose = get_landmark_coords(landmarks, 1, frame_width, frame_height)
344
- face_height = calculate_distance(nose, chin)
345
- normalized_distance = chin_lip_distance / (face_height + 1e-6)
346
-
347
- is_active = normalized_distance < 0.08 # Chin pushed up
348
- intensity = min(100, max(0, (0.12 - normalized_distance) / 0.12 * 100))
349
-
350
- self.activation_history.append(int(is_active))
351
- self.intensity_history.append(intensity)
352
-
353
- return is_active, intensity
354
-
355
-
356
- class AU23Detector:
357
- """AU23 - Lip Tightener (Anger, Tension)"""
358
- def __init__(self, window_size=30):
359
- self.name = "AU23_LipTighten"
360
- self.activation_history = deque(maxlen=window_size)
361
- self.intensity_history = deque(maxlen=window_size)
362
-
363
- def detect(self, landmarks, frame_width, frame_height):
364
- left_corner = get_landmark_coords(landmarks, AU23_LANDMARKS['left_corner'], frame_width, frame_height)
365
- right_corner = get_landmark_coords(landmarks, AU23_LANDMARKS['right_corner'], frame_width, frame_height)
366
- left_outer = get_landmark_coords(landmarks, AU23_LANDMARKS['left_outer'], frame_width, frame_height)
367
- right_outer = get_landmark_coords(landmarks, AU23_LANDMARKS['right_outer'], frame_width, frame_height)
368
-
369
- corner_width = calculate_distance(left_corner, right_corner)
370
- outer_width = calculate_distance(left_outer, right_outer)
371
- tightness_ratio = corner_width / (outer_width + 1e-6)
372
-
373
- is_active = (tightness_ratio < 0.85)
374
- intensity = min(100, max(0, (0.95 - tightness_ratio) / 0.20 * 100))
375
-
376
- self.activation_history.append(int(is_active))
377
- self.intensity_history.append(intensity)
378
-
379
- return is_active, intensity
380
-
381
-
382
- class AU24Detector:
383
- """AU24 - Lip Pressor (Stress, Tension)"""
384
- def __init__(self, window_size=30):
385
- self.name = "AU24_LipPress"
386
- self.activation_history = deque(maxlen=window_size)
387
- self.intensity_history = deque(maxlen=window_size)
388
-
389
- def detect(self, landmarks, frame_width, frame_height):
390
- upper_lip_top = get_landmark_coords(landmarks, AU24_LANDMARKS['upper_lip_top'], frame_width, frame_height)
391
- upper_lip_bottom = get_landmark_coords(landmarks, AU24_LANDMARKS['upper_lip_bottom'], frame_width, frame_height)
392
- lower_lip_top = get_landmark_coords(landmarks, AU24_LANDMARKS['lower_lip_top'], frame_width, frame_height)
393
- lower_lip_bottom = get_landmark_coords(landmarks, AU24_LANDMARKS['lower_lip_bottom'], frame_width, frame_height)
394
-
395
- upper_lip_thickness = calculate_distance(upper_lip_top, upper_lip_bottom)
396
- lower_lip_thickness = calculate_distance(lower_lip_top, lower_lip_bottom)
397
- total_lip_thickness = upper_lip_thickness + lower_lip_thickness
398
-
399
- mouth_opening = calculate_distance(upper_lip_bottom, lower_lip_top)
400
- lip_press_score = mouth_opening / (total_lip_thickness + 1e-6)
401
-
402
- is_active = (lip_press_score < 0.4 and total_lip_thickness < 15)
403
- intensity = min(100, max(0, (0.6 - lip_press_score) / 0.6 * 100))
404
-
405
- self.activation_history.append(int(is_active))
406
- self.intensity_history.append(intensity)
407
-
408
- return is_active, intensity
409
-
410
-
411
- class AU26Detector:
412
- """AU26 - Jaw Drop (Surprise, Shock)"""
413
- def __init__(self, window_size=30):
414
- self.name = "AU26_JawDrop"
415
- self.activation_history = deque(maxlen=window_size)
416
- self.intensity_history = deque(maxlen=window_size)
417
-
418
- def detect(self, landmarks, frame_width, frame_height):
419
- upper_lip = get_landmark_coords(landmarks, AU26_LANDMARKS['upper_lip'], frame_width, frame_height)
420
- lower_lip = get_landmark_coords(landmarks, AU26_LANDMARKS['lower_lip'], frame_width, frame_height)
421
- chin = get_landmark_coords(landmarks, AU26_LANDMARKS['chin'], frame_width, frame_height)
422
- nose = get_landmark_coords(landmarks, AU26_LANDMARKS['nose'], frame_width, frame_height)
423
-
424
- # Mouth opening
425
- mouth_opening = calculate_distance(upper_lip, lower_lip)
426
-
427
- # Jaw drop (distance from nose to chin increases)
428
- jaw_length = calculate_distance(nose, chin)
429
-
430
- # Normalize
431
- mouth_opening_ratio = mouth_opening / (jaw_length + 1e-6)
432
-
433
- is_active = mouth_opening_ratio > 0.15 # Mouth significantly open
434
- intensity = min(100, max(0, (mouth_opening_ratio - 0.10) / 0.20 * 100))
435
-
436
- self.activation_history.append(int(is_active))
437
- self.intensity_history.append(intensity)
438
-
439
- return is_active, intensity
440
-
441
-
442
- # ==================== FEATURE EXTRACTOR ====================
443
-
444
- class MultiAUFeatureExtractor:
445
- def __init__(self, detectors):
446
- self.detectors = detectors
447
- self.feature_log = []
448
-
449
- def extract_features(self, timestamp):
450
- features = {'timestamp': timestamp}
451
-
452
- for detector in self.detectors:
453
- is_active = detector.activation_history[-1] if detector.activation_history else 0
454
- intensity = detector.intensity_history[-1] if detector.intensity_history else 0
455
-
456
- activation_rate = sum(detector.activation_history) / len(detector.activation_history) if detector.activation_history else 0
457
- avg_intensity = np.mean(detector.intensity_history) if detector.intensity_history else 0
458
- max_intensity = np.max(detector.intensity_history) if detector.intensity_history else 0
459
- intensity_std = np.std(detector.intensity_history) if detector.intensity_history else 0
460
-
461
- features[f'{detector.name}_active'] = is_active
462
- features[f'{detector.name}_intensity'] = intensity
463
- features[f'{detector.name}_activation_rate'] = activation_rate
464
- features[f'{detector.name}_avg_intensity'] = avg_intensity
465
- features[f'{detector.name}_max_intensity'] = max_intensity
466
- features[f'{detector.name}_intensity_std'] = intensity_std
467
-
468
- self.feature_log.append(features)
469
- return features
470
-
471
- def get_dataframe(self):
472
- return pd.DataFrame(self.feature_log)
473
-
474
- def save_features(self, filename):
475
- df = self.get_dataframe()
476
- df.to_csv(filename, index=False)
477
- print(f"✓ Features saved to {filename}")
478
-
479
-
480
- # ==================== DETECTION SESSION ====================
481
-
482
- def run_detection_session(duration_seconds=15, save_data=True):
483
- # Initialize all 10 AU detectors
484
- au01 = AU01Detector()
485
- au04 = AU04Detector()
486
- au06 = AU06Detector()
487
- au07 = AU07Detector()
488
- au12 = AU12Detector()
489
- au14 = AU14Detector()
490
- au17 = AU17Detector()
491
- au23 = AU23Detector()
492
- au24 = AU24Detector()
493
- au26 = AU26Detector()
494
-
495
- detectors = [au01, au04, au06, au07, au12, au14, au17, au23, au24, au26]
496
- feature_extractor = MultiAUFeatureExtractor(detectors)
497
-
498
- cap = cv2.VideoCapture(0)
499
-
500
- print(f"\n{'='*70}")
501
- print(f" COMPLETE 10-AU STRESS DETECTION SYSTEM")
502
- print(f" Recording for {duration_seconds} seconds...")
503
- print(f"{'='*70}\n")
504
-
505
- start_time = time.time()
506
- frame_count = 0
507
-
508
- while True:
509
- ret, frame = cap.read()
510
- if not ret:
511
- break
512
-
513
- current_time = time.time()
514
- elapsed = current_time - start_time
515
-
516
- if elapsed >= duration_seconds:
517
- break
518
-
519
- frame = cv2.flip(frame, 1)
520
- frame_height, frame_width = frame.shape[:2]
521
- rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
522
-
523
- results = face_mesh.process(rgb_frame)
524
-
525
- if results.multi_face_landmarks:
526
- landmarks = results.multi_face_landmarks[0].landmark
527
-
528
- # Detect all 10 AUs
529
- au01_active, au01_intensity = au01.detect(landmarks, frame_width, frame_height)
530
- au04_active, au04_intensity = au04.detect(landmarks, frame_width, frame_height)
531
- au06_active, au06_intensity = au06.detect(landmarks, frame_width, frame_height)
532
- au07_active, au07_intensity = au07.detect(landmarks, frame_width, frame_height)
533
- au12_active, au12_intensity = au12.detect(landmarks, frame_width, frame_height)
534
- au14_active, au14_intensity = au14.detect(landmarks, frame_width, frame_height)
535
- au17_active, au17_intensity = au17.detect(landmarks, frame_width, frame_height)
536
- au23_active, au23_intensity = au23.detect(landmarks, frame_width, frame_height)
537
- au24_active, au24_intensity = au24.detect(landmarks, frame_width, frame_height)
538
- au26_active, au26_intensity = au26.detect(landmarks, frame_width, frame_height)
539
-
540
- features = feature_extractor.extract_features(elapsed)
541
-
542
- # Display (2 columns)
543
- y_offset = 25
544
- col1_x = 10
545
- col2_x = frame_width // 2 + 10
546
-
547
- # Header
548
- cv2.putText(frame, f"Time: {elapsed:.1f}s / {duration_seconds}s",
549
- (10, y_offset), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
550
-
551
- y_offset += 35
552
-
553
- # Column 1: Stress Indicators
554
- cv2.putText(frame, "STRESS INDICATORS:", (col1_x, y_offset),
555
- cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
556
- y_offset += 25
557
-
558
- stress_aus = [
559
- (au01_active, au01_intensity, "AU01-BrowRaise"),
560
- (au04_active, au04_intensity, "AU04-BrowLower"),
561
- (au07_active, au07_intensity, "AU07-LidTight"),
562
- (au17_active, au17_intensity, "AU17-ChinRaise"),
563
- (au23_active, au23_intensity, "AU23-LipTight"),
564
- (au24_active, au24_intensity, "AU24-LipPress")
565
- ]
566
-
567
- for active, intensity, name in stress_aus:
568
- color = (0, 0, 255) if active else (100, 100, 100)
569
- cv2.putText(frame, f"{name}: {intensity:.0f}%",
570
- (col1_x, y_offset), cv2.FONT_HERSHEY_SIMPLEX, 0.4, color, 1)
571
- y_offset += 20
572
-
573
- # Column 2: Positive Indicators
574
- y_offset = 60
575
- cv2.putText(frame, "POSITIVE INDICATORS:", (col2_x, y_offset),
576
- cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
577
- y_offset += 25
578
-
579
- positive_aus = [
580
- (au06_active, au06_intensity, "AU06-CheekRaise"),
581
- (au12_active, au12_intensity, "AU12-SmilePull"),
582
- (au14_active, au14_intensity, "AU14-Dimpler"),
583
- (au26_active, au26_intensity, "AU26-JawDrop")
584
- ]
585
-
586
- for active, intensity, name in positive_aus:
587
- color = (0, 255, 0) if active else (100, 100, 100)
588
- cv2.putText(frame, f"{name}: {intensity:.0f}%",
589
- (col2_x, y_offset), cv2.FONT_HERSHEY_SIMPLEX, 0.4, color, 1)
590
- y_offset += 20
591
-
592
- # Bottom summary
593
- stress_count = sum([au01_active, au04_active, au07_active, au17_active, au23_active, au24_active])
594
- positive_count = sum([au06_active, au12_active, au14_active])
595
-
596
- cv2.rectangle(frame, (10, frame_height - 60), (frame_width - 10, frame_height - 10), (50, 50, 50), -1)
597
- cv2.putText(frame, f"Stress AUs: {stress_count}/6 | Positive AUs: {positive_count}/4",
598
- (20, frame_height - 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
599
-
600
- cv2.imshow('Complete 10-AU Stress Detection', frame)
601
- frame_count += 1
602
-
603
- if cv2.waitKey(1) & 0xFF == ord('q'):
604
- break
605
-
606
- cap.release()
607
- cv2.destroyAllWindows()
608
-
609
- if save_data:
610
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
611
- filename = f"complete_au_features_{timestamp}.csv"
612
- feature_extractor.save_features(filename)
613
-
614
- print(f"\n✓ Session complete! Processed {frame_count} frames")
615
- print(f"✓ Average FPS: {frame_count/duration_seconds:.1f}")
616
-
617
- return feature_extractor.get_dataframe()
618
-
619
-
620
- # ==================== ADVANCED ANALYSIS ====================
621
-
622
- def calculate_comprehensive_stress_score(df):
623
- """
624
- Research-based stress scoring using all 10 AUs
625
- Weights based on affective computing literature
626
- """
627
-
628
- # STRESS INDICATORS (weighted by research evidence)
629
- # AU04 (Brow Lower) - Primary anger/stress indicator
630
- au04_score = (df['AU04_BrowLower_intensity'].mean() / 100) * (df['AU04_BrowLower_activation_rate'].mean()) * 25
631
-
632
- # AU01 (Inner Brow Raise) - Worry/sadness indicator
633
- au01_score = (df['AU01_InnerBrowRaise_intensity'].mean() / 100) * (df['AU01_InnerBrowRaise_activation_rate'].mean()) * 15
634
-
635
- # AU07 (Lid Tighten) - Tension indicator
636
- au07_score = (df['AU07_LidTighten_intensity'].mean() / 100) * (df['AU07_LidTighten_activation_rate'].mean()) * 12
637
-
638
- # AU24 (Lip Press) - Stress/tension
639
- au24_score = (df['AU24_LipPress_intensity'].mean() / 100) * (df['AU24_LipPress_activation_rate'].mean()) * 15
640
-
641
- # AU23 (Lip Tighten) - Anger/tension
642
- au23_score = (df['AU23_LipTighten_intensity'].mean() / 100) * (df['AU23_LipTighten_activation_rate'].mean()) * 12
643
-
644
- # AU17 (Chin Raise) - Doubt/sadness
645
- au17_score = (df['AU17_ChinRaise_intensity'].mean() / 100) * (df['AU17_ChinRaise_activation_rate'].mean()) * 8
646
-
647
- # POSITIVE INDICATORS (reduce stress score)
648
- # AU06 + AU12 (Duchenne Smile - genuine happiness)
649
- duchenne_smile = ((df['AU06_CheekRaise_active'] == 1) & (df['AU12_LipCornerPull_active'] == 1)).sum() / len(df)
650
- positive_reduction = duchenne_smile * 15
651
-
652
- # AU12 alone (social smile - may mask stress)
653
- social_smile = ((df['AU12_LipCornerPull_active'] == 1) & (df['AU06_CheekRaise_active'] == 0)).sum() / len(df)
654
- masking_indicator = social_smile * 5 # Adds to stress if smiling without cheek raise
655
-
656
- # TEMPORAL PATTERNS
657
- # Sustained stress (continuous activation 3+ seconds)
658
- sustained_stress = 0
659
- for au_name in ['AU04_BrowLower', 'AU24_LipPress', 'AU23_LipTighten']:
660
- streak = 0
661
- for val in df[f'{au_name}_active']:
662
- if val == 1:
663
- streak += 1
664
- if streak >= 90: # 3 seconds at 30fps
665
- sustained_stress += 1
666
- break
667
- else:
668
- streak = 0
669
- sustained_score = min(10, sustained_stress * 3)
670
-
671
- # Co-occurrence of multiple stress AUs
672
- stress_cols = ['AU01_InnerBrowRaise_active', 'AU04_BrowLower_active',
673
- 'AU07_LidTighten_active', 'AU23_LipTighten_active',
674
- 'AU24_LipPress_active', 'AU17_ChinRaise_active']
675
- co_occurrence = (df[stress_cols].sum(axis=1) >= 3).sum() / len(df)
676
- co_occurrence_score = co_occurrence * 8
677
-
678
- # COMBINED STRESS SCORE
679
- raw_stress = (au04_score + au01_score + au07_score + au24_score +
680
- au23_score + au17_score + sustained_score +
681
- co_occurrence_score + masking_indicator - positive_reduction)
682
-
683
- stress_score = min(100, max(0, raw_stress))
684
-
685
- # Classification
686
- if stress_score < 25:
687
- classification = "NOT STRESSED"
688
- color = "🟢"
689
- elif stress_score < 55:
690
- classification = "POSSIBLY STRESSED"
691
- color = "🟡"
692
- else:
693
- classification = "STRESSED"
694
- color = "🔴"
695
-
696
- return {
697
- 'classification': classification,
698
- 'color': color,
699
- 'stress_score': stress_score,
700
- 'components': {
701
- 'au04': au04_score,
702
- 'au01': au01_score,
703
- 'au07': au07_score,
704
- 'au24': au24_score,
705
- 'au23': au23_score,
706
- 'au17': au17_score,
707
- 'sustained': sustained_score,
708
- 'co_occurrence': co_occurrence_score,
709
- 'duchenne_smile': duchenne_smile,
710
- 'social_smile_masking': masking_indicator
711
- },
712
- 'activation_percentages': {
713
- 'AU01': (df['AU01_InnerBrowRaise_active'].sum() / len(df)) * 100,
714
- 'AU04': (df['AU04_BrowLower_active'].sum() / len(df)) * 100,
715
- 'AU06': (df['AU06_CheekRaise_active'].sum() / len(df)) * 100,
716
- 'AU07': (df['AU07_LidTighten_active'].sum() / len(df)) * 100,
717
- 'AU12': (df['AU12_LipCornerPull_active'].sum() / len(df)) * 100,
718
- 'AU14': (df['AU14_Dimpler_active'].sum() / len(df)) * 100,
719
- 'AU17': (df['AU17_ChinRaise_active'].sum() / len(df)) * 100,
720
- 'AU23': (df['AU23_LipTighten_active'].sum() / len(df)) * 100,
721
- 'AU24': (df['AU24_LipPress_active'].sum() / len(df)) * 100,
722
- 'AU26': (df['AU26_JawDrop_active'].sum() / len(df)) * 100
723
- }
724
- }
725
-
726
-
727
- # ==================== COMPREHENSIVE VISUALIZATION ====================
728
-
729
- def plot_comprehensive_analysis(df):
730
- """Create 10 publication-quality plots"""
731
-
732
- fig = plt.figure(figsize=(20, 16))
733
- gs = GridSpec(5, 3, figure=fig, hspace=0.35, wspace=0.3)
734
-
735
- fig.suptitle('Comprehensive 10-AU Facial Expression Analysis\nStress Detection System',
736
- fontsize=18, fontweight='bold', y=0.995)
737
-
738
- # Plot 1: All AU Activations Over Time
739
- ax1 = fig.add_subplot(gs[0, :2])
740
- stress_aus = ['AU01_InnerBrowRaise', 'AU04_BrowLower', 'AU07_LidTighten',
741
- 'AU17_ChinRaise', 'AU23_LipTighten', 'AU24_LipPress']
742
- colors_stress = ['orange', 'red', 'darkred', 'brown', 'purple', 'magenta']
743
-
744
- for au, color in zip(stress_aus, colors_stress):
745
- ax1.plot(df['timestamp'], df[f'{au}_active'], label=au.split('_')[1],
746
- color=color, linewidth=1.5, alpha=0.7)
747
-
748
- ax1.set_xlabel('Time (seconds)', fontweight='bold')
749
- ax1.set_ylabel('Activation (Binary)', fontweight='bold')
750
- ax1.set_title('Stress-Related AU Temporal Patterns')
751
- ax1.legend(loc='upper right', ncol=3, fontsize=8)
752
- ax1.grid(True, alpha=0.3)
753
- ax1.set_ylim(-0.1, 1.1)
754
-
755
- # Plot 2: Positive AUs Over Time
756
- ax2 = fig.add_subplot(gs[0, 2])
757
- positive_aus = ['AU06_CheekRaise', 'AU12_LipCornerPull', 'AU14_Dimpler', 'AU26_JawDrop']
758
- colors_pos = ['lightgreen', 'green', 'darkgreen', 'blue']
759
-
760
- for au, color in zip(positive_aus, colors_pos):
761
- ax2.plot(df['timestamp'], df[f'{au}_active'], label=au.split('_')[1],
762
- color=color, linewidth=1.5, alpha=0.7)
763
-
764
- ax2.set_xlabel('Time (s)', fontweight='bold')
765
- ax2.set_ylabel('Activation', fontweight='bold')
766
- ax2.set_title('Positive AU Patterns')
767
- ax2.legend(fontsize=7)
768
- ax2.grid(True, alpha=0.3)
769
- ax2.set_ylim(-0.1, 1.1)
770
-
771
- # Plot 3: Intensity Heatmap (All 10 AUs)
772
- ax3 = fig.add_subplot(gs[1, :])
773
- all_aus = ['AU01_InnerBrowRaise', 'AU04_BrowLower', 'AU06_CheekRaise',
774
- 'AU07_LidTighten', 'AU12_LipCornerPull', 'AU14_Dimpler',
775
- 'AU17_ChinRaise', 'AU23_LipTighten', 'AU24_LipPress', 'AU26_JawDrop']
776
-
777
- intensity_data = df[[f'{au}_intensity' for au in all_aus]].T
778
- im = ax3.imshow(intensity_data, aspect='auto', cmap='RdYlGn_r', interpolation='nearest')
779
- ax3.set_yticks(range(10))
780
- ax3.set_yticklabels([au.split('_')[0] for au in all_aus])
781
- ax3.set_xlabel('Frame Number', fontweight='bold')
782
- ax3.set_title('Complete AU Intensity Heatmap (All 10 Action Units)')
783
- plt.colorbar(im, ax=ax3, label='Intensity (%)')
784
-
785
- # Plot 4: AU Activation Frequency Bar Chart
786
- ax4 = fig.add_subplot(gs[2, 0])
787
- result = calculate_comprehensive_stress_score(df)
788
- au_names = list(result['activation_percentages'].keys())
789
- au_values = list(result['activation_percentages'].values())
790
- colors = ['red' if 'AU04' in au or 'AU24' in au or 'AU23' in au
791
- else 'orange' if 'AU01' in au or 'AU07' in au or 'AU17' in au
792
- else 'green' for au in au_names]
793
-
794
- bars = ax4.barh(au_names, au_values, color=colors, alpha=0.7)
795
- ax4.set_xlabel('Activation Percentage (%)', fontweight='bold')
796
- ax4.set_title('AU Activation Frequencies')
797
- ax4.grid(True, alpha=0.3, axis='x')
798
-
799
- # Plot 5: Duchenne vs Non-Duchenne Smile Detection
800
- ax5 = fig.add_subplot(gs[2, 1])
801
- duchenne = ((df['AU06_CheekRaise_active'] == 1) & (df['AU12_LipCornerPull_active'] == 1)).sum()
802
- non_duchenne = ((df['AU12_LipCornerPull_active'] == 1) & (df['AU06_CheekRaise_active'] == 0)).sum()
803
- no_smile = len(df) - duchenne - non_duchenne
804
-
805
- labels = ['Genuine\n(Duchenne)', 'Social\n(Masking)', 'No Smile']
806
- sizes = [duchenne, non_duchenne, no_smile]
807
- colors_pie = ['green', 'yellow', 'lightgray']
808
-
809
- ax5.pie(sizes, labels=labels, colors=colors_pie, autopct='%1.1f%%', startangle=90)
810
- ax5.set_title('Smile Type Distribution')
811
-
812
- # Plot 6: Stress vs Positive Balance
813
- ax6 = fig.add_subplot(gs[2, 2])
814
- stress_intensity_avg = df[['AU01_InnerBrowRaise_intensity', 'AU04_BrowLower_intensity',
815
- 'AU07_LidTighten_intensity', 'AU23_LipTighten_intensity',
816
- 'AU24_LipPress_intensity', 'AU17_ChinRaise_intensity']].mean(axis=1)
817
- positive_intensity_avg = df[['AU06_CheekRaise_intensity', 'AU12_LipCornerPull_intensity',
818
- 'AU14_Dimpler_intensity']].mean(axis=1)
819
-
820
- ax6.plot(df['timestamp'], stress_intensity_avg, color='red', linewidth=2, label='Stress AUs')
821
- ax6.plot(df['timestamp'], positive_intensity_avg, color='green', linewidth=2, label='Positive AUs')
822
- ax6.fill_between(df['timestamp'], stress_intensity_avg, alpha=0.3, color='red')
823
- ax6.fill_between(df['timestamp'], positive_intensity_avg, alpha=0.3, color='green')
824
- ax6.set_xlabel('Time (seconds)', fontweight='bold')
825
- ax6.set_ylabel('Average Intensity (%)', fontweight='bold')
826
- ax6.set_title('Stress vs Positive Affect Balance')
827
- ax6.legend()
828
- ax6.grid(True, alpha=0.3)
829
-
830
- # Plot 7: Correlation Matrix (All AUs)
831
- ax7 = fig.add_subplot(gs[3, :2])
832
- correlation_cols = [f'{au}_intensity' for au in all_aus]
833
- corr_matrix = df[correlation_cols].corr()
834
-
835
- im = ax7.imshow(corr_matrix, cmap='coolwarm', vmin=-1, vmax=1, aspect='auto')
836
- ax7.set_xticks(range(10))
837
- ax7.set_yticks(range(10))
838
- ax7.set_xticklabels([au.split('_')[0] for au in all_aus], rotation=45, ha='right')
839
- ax7.set_yticklabels([au.split('_')[0] for au in all_aus])
840
- ax7.set_title('Complete AU Correlation Matrix')
841
-
842
- # Add correlation values
843
- for i in range(10):
844
- for j in range(10):
845
- if abs(corr_matrix.iloc[i, j]) > 0.3: # Only show strong correlations
846
- ax7.text(j, i, f'{corr_matrix.iloc[i, j]:.2f}',
847
- ha="center", va="center", color="black", fontsize=7)
848
-
849
- plt.colorbar(im, ax=ax7)
850
-
851
- # Plot 8: Time-Windowed Stress Evolution
852
- ax8 = fig.add_subplot(gs[3, 2])
853
- window_size = 90 # 3 seconds
854
- windowed_stress = []
855
- window_times = []
856
-
857
- for i in range(0, len(df) - window_size, window_size // 2):
858
- window_df = df.iloc[i:i+window_size]
859
- if len(window_df) > 0:
860
- window_result = calculate_comprehensive_stress_score(window_df)
861
- windowed_stress.append(window_result['stress_score'])
862
- window_times.append(window_df['timestamp'].mean())
863
-
864
- ax8.plot(window_times, windowed_stress, color='red', linewidth=2, marker='o')
865
- ax8.fill_between(window_times, windowed_stress, alpha=0.3, color='red')
866
- ax8.axhline(y=25, color='green', linestyle='--', label='Low threshold', alpha=0.5)
867
- ax8.axhline(y=55, color='orange', linestyle='--', label='High threshold', alpha=0.5)
868
- ax8.set_xlabel('Time (seconds)', fontweight='bold')
869
- ax8.set_ylabel('Stress Score', fontweight='bold')
870
- ax8.set_title('Stress Score Evolution (3s windows)')
871
- ax8.legend()
872
- ax8.grid(True, alpha=0.3)
873
-
874
- # Plot 9: AU Co-occurrence Matrix
875
- ax9 = fig.add_subplot(gs[4, 0])
876
- stress_au_cols = [f'{au}_active' for au in stress_aus]
877
- co_occur_matrix = np.zeros((6, 6))
878
-
879
- for i in range(6):
880
- for j in range(6):
881
- co_occur = ((df[stress_au_cols[i]] == 1) & (df[stress_au_cols[j]] == 1)).sum()
882
- co_occur_matrix[i, j] = co_occur / len(df) * 100
883
-
884
- im = ax9.imshow(co_occur_matrix, cmap='Reds', aspect='auto')
885
- ax9.set_xticks(range(6))
886
- ax9.set_yticks(range(6))
887
- ax9.set_xticklabels([au.split('_')[0] for au in stress_aus], rotation=45, ha='right')
888
- ax9.set_yticklabels([au.split('_')[0] for au in stress_aus])
889
- ax9.set_title('Stress AU Co-occurrence (%)')
890
- plt.colorbar(im, ax=ax9)
891
-
892
- # Plot 10: Comprehensive Summary Report
893
- ax10 = fig.add_subplot(gs[4, 1:])
894
- ax10.axis('off')
895
-
896
- result = calculate_comprehensive_stress_score(df)
897
-
898
- summary_text = f"""
899
- ╔═══════════════════════════════════════════════════════════════════════════════════════════════╗
900
- ║ COMPREHENSIVE STRESS ASSESSMENT REPORT ║
901
- ╠═══════════════════════════════════════════════════════════════════════════════════════════════╣
902
- ║ ║
903
- ║ CLASSIFICATION: {result['color']} {result['classification']:<25} | STRESS SCORE: {result['stress_score']:.1f}/100 ║
904
- ║ ║
905
- ╠═════════════════���═════════════════════════════════════════════════════════════════════════════╣
906
- ║ COMPONENT CONTRIBUTIONS: ║
907
- ║ • AU04 (Brow Lower): {result['components']['au04']:.2f} / 25.0 [{result['activation_percentages']['AU04']:5.1f}% active] ║
908
- ║ • AU01 (Inner Brow): {result['components']['au01']:.2f} / 15.0 [{result['activation_percentages']['AU01']:5.1f}% active] ║
909
- ║ • AU07 (Lid Tighten): {result['components']['au07']:.2f} / 12.0 [{result['activation_percentages']['AU07']:5.1f}% active] ║
910
- ║ • AU24 (Lip Press): {result['components']['au24']:.2f} / 15.0 [{result['activation_percentages']['AU24']:5.1f}% active] ║
911
- ║ • AU23 (Lip Tighten): {result['components']['au23']:.2f} / 12.0 [{result['activation_percentages']['AU23']:5.1f}% active] ║
912
- ║ • AU17 (Chin Raise): {result['components']['au17']:.2f} / 8.0 [{result['activation_percentages']['AU17']:5.1f}% active] ║
913
- ║ • Sustained Activation: {result['components']['sustained']:.2f} / 10.0 ║
914
- ║ • Co-occurrence Pattern: {result['components']['co_occurrence']:.2f} / 8.0 ║
915
- ║ • Smile Masking Effect: {result['components']['social_smile_masking']:.2f} (adds stress if present) ║
916
- ║ • Duchenne Smile Bonus: -{result['components']['duchenne_smile']*15:.2f} (reduces stress) ║
917
- ║ ║
918
- ╠═══════════════════════════════════════════════════════════════════════════════════════════════╣
919
- ║ POSITIVE AFFECT INDICATORS: ║
920
- ║ • AU06 (Cheek Raise): {result['activation_percentages']['AU06']:5.1f}% active ║
921
- ║ • AU12 (Lip Pull): {result['activation_percentages']['AU12']:5.1f}% active ║
922
- ║ • AU14 (Dimpler): {result['activation_percentages']['AU14']:5.1f}% active ║
923
- ║ • Duchenne Smile Rate: {result['components']['duchenne_smile']*100:.1f}% ║
924
- ║ ║
925
- ╠═══════════════════════════════════════════════════════════════════════════════════════════════╣
926
- ║ RESEARCH BASIS: ║
927
- ║ Weights based on Facial Action Coding System (FACS) research: ║
928
- ║ • AU04 highest weight (Ekman & Friesen, 1978) - primary anger/stress indicator ║
929
- ║ • AU06+AU12 combination identifies genuine happiness (Duchenne marker) ║
930
- ║ • Sustained activation and co-occurrence patterns enhance stress detection accuracy ║
931
- ║ • Temporal windowing allows detection of acute stress episodes vs chronic patterns ║
932
- ╚═══════════════════════════════════════════════════════════════════════════════════════════════╝
933
- """
934
-
935
- ax10.text(0.05, 0.5, summary_text, fontsize=8, family='monospace',
936
- verticalalignment='center',
937
- bbox=dict(boxstyle='round', facecolor='lightgray', alpha=0.2))
938
-
939
- plt.tight_layout()
940
- return fig, result
941
-
942
-
943
- # ==================== MAIN EXECUTION ====================
944
-
945
- if __name__ == "__main__":
946
- print("\n" + "="*70)
947
- print(" COMPLETE 10-AU STRESS DETECTION SYSTEM")
948
- print(" Based on Facial Action Coding System (FACS)")
949
- print(" Research Guide: Prof. Anup Nandy")
950
- print("="*70)
951
-
952
- print("\n Action Units Detected:")
953
- print(" STRESS: AU01, AU04, AU07, AU17, AU23, AU24")
954
- print(" POSITIVE: AU06, AU12, AU14, AU26")
955
- print("\n Press Enter to start 15-second recording...")
956
-
957
- input()
958
-
959
- df = run_detection_session(duration_seconds=15, save_data=True)
960
-
961
- print("\n" + "="*70)
962
- print(" Generating comprehensive analysis...")
963
- print("="*70 + "\n")
964
-
965
- fig, result = plot_comprehensive_analysis(df)
966
-
967
- print(f"\n{result['color']} FINAL ASSESSMENT: {result['classification']}")
968
- print(f" Stress Score: {result['stress_score']:.1f}/100")
969
- print(f"\n Data saved with {len(df)} frames")
970
- print(f" Total features per frame: {len(df.columns) - 1}")
971
- print("\n" + "="*70)
972
-
973
- plt.show()
974
-
975
- print("\n✓ Analysis complete!")