zforkash commited on
Commit
06ba967
·
verified ·
1 Parent(s): eb2b27a
Files changed (1) hide show
  1. app.py +238 -726
app.py CHANGED
@@ -2,747 +2,259 @@ import streamlit as st
2
  import cv2
3
  import mediapipe as mp
4
  import numpy as np
5
- import time
6
- from sklearn.ensemble import IsolationForest
 
 
 
7
 
8
- # Encapsulated workout functions
 
 
9
 
10
- def bicep_curl():
11
- import cv2
12
- import mediapipe as mp
13
- import numpy as np
14
- import time
15
- from sklearn.ensemble import IsolationForest
16
-
17
- # Mediapipe utilities
18
- mp_drawing = mp.solutions.drawing_utils
19
- mp_pose = mp.solutions.pose
20
-
21
-
22
- # Function to calculate angles between three points
23
- def calculate_angle(a, b, c):
24
- a = np.array(a)
25
- b = np.array(b)
26
- c = np.array(c)
27
-
28
- radians = np.arctan2(c[1] - b[1], c[0] - b[0]) - np.arctan2(a[1] - b[1], a[0] - b[0])
29
- angle = np.abs(np.degrees(radians))
30
- if angle > 180.0:
31
- angle = 360 - angle
32
- return angle
33
-
34
-
35
- # Function to draw text with a background
36
- def draw_text_with_background(image, text, position, font, font_scale, color, thickness, bg_color, padding=10):
37
- text_size = cv2.getTextSize(text, font, font_scale, thickness)[0]
38
- text_x, text_y = position
39
- box_coords = (
40
- (text_x - padding, text_y - padding),
41
- (text_x + text_size[0] + padding, text_y + text_size[1] + padding),
42
- )
43
- cv2.rectangle(image, box_coords[0], box_coords[1], bg_color, cv2.FILLED)
44
- cv2.putText(image, text, (text_x, text_y + text_size[1]), font, font_scale, color, thickness)
45
-
46
-
47
- # Real-time feedback for single rep
48
- def analyze_single_rep(rep, rep_data):
49
- """Provide actionable feedback for a single rep."""
50
- feedback = []
51
- avg_rom = np.mean([r["ROM"] for r in rep_data])
52
- avg_tempo = np.mean([r["Tempo"] for r in rep_data])
53
- avg_smoothness = np.mean([r["Smoothness"] for r in rep_data])
54
-
55
- if rep["ROM"] < avg_rom * 0.8:
56
- feedback.append("Extend arm more")
57
- if rep["Tempo"] < avg_tempo * 0.8:
58
- feedback.append("Slow down")
59
- if rep["Smoothness"] > avg_smoothness * 1.2:
60
- feedback.append("Move smoothly")
61
-
62
- return " | ".join(feedback) if feedback else "Good rep!"
63
-
64
-
65
- # Post-workout feedback function with Isolation Forest
66
- def analyze_workout_with_isolation_forest(rep_data):
67
- if not rep_data:
68
- print("No reps completed.")
69
- return
70
-
71
- print("\n--- Post-Workout Summary ---")
72
-
73
- # Convert rep_data to a feature matrix
74
- features = np.array([[rep["ROM"], rep["Tempo"], rep["Smoothness"]] for rep in rep_data])
75
-
76
- # Train Isolation Forest
77
- model = IsolationForest(contamination=0.2, random_state=42)
78
- predictions = model.fit_predict(features)
79
-
80
- # Analyze reps
81
- for i, (rep, prediction) in enumerate(zip(rep_data, predictions), 1):
82
- status = "Good" if prediction == 1 else "Anomalous"
83
- reason = []
84
- if prediction == -1: # If anomalous
85
- if rep["ROM"] < np.mean(features[:, 0]) - np.std(features[:, 0]):
86
- reason.append("Low ROM")
87
- if rep["Tempo"] < np.mean(features[:, 1]) - np.std(features[:, 1]):
88
- reason.append("Too Fast")
89
- if rep["Smoothness"] > np.mean(features[:, 2]) + np.std(features[:, 2]):
90
- reason.append("Jerky Movement")
91
- reason_str = ", ".join(reason) if reason else "None"
92
- print(f"Rep {i}: {status} | ROM: {rep['ROM']:.2f}, Tempo: {rep['Tempo']:.2f}s, Smoothness: {rep['Smoothness']:.2f} | Reason: {reason_str}")
93
-
94
-
95
- # Main workout tracking function
96
- def main():
97
- cap = cv2.VideoCapture(0)
98
- counter = 0 # Rep counter
99
- stage = None # Movement stage
100
- max_reps = 10
101
- rep_data = [] # Store metrics for each rep
102
- feedback = "" # Real-time feedback for the video feed
103
- workout_start_time = None # Timer start
104
-
105
- with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose:
106
- while cap.isOpened():
107
- ret, frame = cap.read()
108
- if not ret:
109
- print("Failed to grab frame.")
110
- break
111
-
112
- # Initialize workout start time
113
- if workout_start_time is None:
114
- workout_start_time = time.time()
115
-
116
- # Timer
117
- elapsed_time = time.time() - workout_start_time
118
- timer_text = f"Timer: {int(elapsed_time)}s"
119
-
120
- # Convert frame to RGB
121
- image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
122
- image.flags.writeable = False
123
- results = pose.process(image)
124
-
125
- # Convert back to BGR
126
- image.flags.writeable = True
127
- image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
128
-
129
- # Check if pose landmarks are detected
130
- if results.pose_landmarks:
131
- landmarks = results.pose_landmarks.landmark
132
-
133
- # Extract key joints
134
- shoulder = [
135
- landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x,
136
- landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y
137
- ]
138
- elbow = [
139
- landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].x,
140
- landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].y
141
- ]
142
- wrist = [
143
- landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x,
144
- landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y
145
- ]
146
-
147
- # Check visibility of key joints
148
- visibility_threshold = 0.5
149
- if (landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].visibility < visibility_threshold or
150
- landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].visibility < visibility_threshold or
151
- landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].visibility < visibility_threshold):
152
- draw_text_with_background(image, "Ensure all key joints are visible!", (50, 150),
153
- cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 255, 255), 5, (0, 0, 255))
154
- cv2.imshow('Workout Feedback', image)
155
- continue # Skip processing if joints are not visible
156
-
157
- # Calculate the angle
158
- angle = calculate_angle(shoulder, elbow, wrist)
159
-
160
- # Stage logic for counting reps
161
- if angle > 160 and stage != "down":
162
- stage = "down"
163
- start_time = time.time() # Start timing for the rep
164
- start_angle = angle # Record the starting angle
165
-
166
- # Stop the program if it's the 10th rep down stage
167
- if counter == max_reps:
168
- print("Workout complete at rep 10 (down stage)!")
169
- break
170
- elif angle < 40 and stage == "down":
171
- stage = "up"
172
- counter += 1
173
- end_time = time.time() # End timing for the rep
174
- end_angle = angle # Record the ending angle
175
-
176
- # Calculate rep metrics
177
- rom = start_angle - end_angle # Range of Motion
178
- tempo = end_time - start_time # Duration of the rep
179
- smoothness = np.std([start_angle, end_angle]) # Dummy smoothness metric
180
- rep_data.append({"ROM": rom, "Tempo": tempo, "Smoothness": smoothness})
181
-
182
- # Analyze the rep using Isolation Forest
183
- feedback = analyze_single_rep(rep_data[-1], rep_data)
184
-
185
- # Wireframe color based on form
186
- wireframe_color = (0, 255, 0) if stage == "up" or stage == "down" else (0, 0, 255)
187
-
188
- # Draw wireframe
189
- mp_drawing.draw_landmarks(
190
- image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS,
191
- mp_drawing.DrawingSpec(color=wireframe_color, thickness=5, circle_radius=4),
192
- mp_drawing.DrawingSpec(color=wireframe_color, thickness=5, circle_radius=4)
193
- )
194
-
195
- # Display reps, stage, timer, and feedback
196
- draw_text_with_background(image, f"Reps: {counter}", (50, 150),
197
- cv2.FONT_HERSHEY_SIMPLEX, 3, (255, 255, 255), 5, (0, 0, 0))
198
- draw_text_with_background(image, f"Stage: {stage if stage else 'N/A'}", (50, 300),
199
- cv2.FONT_HERSHEY_SIMPLEX, 3, (255, 255, 255), 5, (0, 0, 0))
200
- draw_text_with_background(image, timer_text, (1000, 50), # Timer in the top-right corner
201
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 3, (0, 0, 0))
202
- draw_text_with_background(image, feedback, (50, 450),
203
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 3, (0, 0, 0))
204
-
205
- # Show video feed
206
- cv2.imshow('Workout Feedback', image)
207
-
208
- # Break if 'q' is pressed
209
- if cv2.waitKey(10) & 0xFF == ord('q'):
210
- break
211
-
212
- cap.release()
213
- cv2.destroyAllWindows()
214
-
215
- # Post-workout analysis
216
- analyze_workout_with_isolation_forest(rep_data)
217
-
218
-
219
- if __name__ == "__main__":
220
- main()
221
 
 
 
 
 
 
222
 
223
- def lateral_raise():
224
- import cv2
225
- import mediapipe as mp
226
- import numpy as np
227
- import time
228
- from sklearn.ensemble import IsolationForest
229
-
230
- # Mediapipe utilities
231
- mp_drawing = mp.solutions.drawing_utils
232
- mp_pose = mp.solutions.pose
233
-
234
-
235
- # Function to calculate lateral raise angle
236
- def calculate_angle_for_lateral_raise(shoulder, wrist):
237
- """
238
- Calculate the angle of the arm relative to the horizontal plane
239
- passing through the shoulder.
240
- """
241
- horizontal_reference = np.array([1, 0]) # Horizontal vector
242
- arm_vector = np.array([wrist[0] - shoulder[0], wrist[1] - shoulder[1]])
243
- dot_product = np.dot(horizontal_reference, arm_vector)
244
- magnitude_reference = np.linalg.norm(horizontal_reference)
245
- magnitude_arm = np.linalg.norm(arm_vector)
246
- if magnitude_arm == 0 or magnitude_reference == 0:
247
- return 0
248
- cos_angle = dot_product / (magnitude_reference * magnitude_arm)
249
- angle = np.arccos(np.clip(cos_angle, -1.0, 1.0))
250
- return np.degrees(angle)
251
-
252
-
253
- # Function to draw text with a background
254
- def draw_text_with_background(image, text, position, font, font_scale, color, thickness, bg_color, padding=10):
255
- text_size = cv2.getTextSize(text, font, font_scale, thickness)[0]
256
- text_x, text_y = position
257
- box_coords = (
258
- (text_x - padding, text_y - padding),
259
- (text_x + text_size[0] + padding, text_y + text_size[1] + padding),
260
- )
261
- cv2.rectangle(image, box_coords[0], box_coords[1], bg_color, cv2.FILLED)
262
- cv2.putText(image, text, (text_x, text_y + text_size[1]), font, font_scale, color, thickness)
263
-
264
-
265
- # Function to check if all required joints are visible
266
- def are_key_joints_visible(landmarks, visibility_threshold=0.5):
267
- """
268
- Ensure that all required joints are visible based on their visibility scores.
269
- """
270
- required_joints = [
271
- mp_pose.PoseLandmark.LEFT_SHOULDER.value,
272
- mp_pose.PoseLandmark.RIGHT_SHOULDER.value,
273
- mp_pose.PoseLandmark.LEFT_WRIST.value,
274
- mp_pose.PoseLandmark.RIGHT_WRIST.value,
275
- ]
276
- for joint in required_joints:
277
- if landmarks[joint].visibility < visibility_threshold:
278
- return False
279
- return True
280
-
281
-
282
- # Real-time feedback for single rep
283
- def analyze_single_rep(rep, rep_data):
284
- """Provide actionable feedback for a single rep."""
285
- feedback = []
286
-
287
- # Calculate averages from previous reps
288
- avg_rom = np.mean([r["ROM"] for r in rep_data]) if rep_data else 0
289
- avg_tempo = np.mean([r["Tempo"] for r in rep_data]) if rep_data else 0
290
-
291
- # Dynamic tempo thresholds
292
- lower_tempo_threshold = 2.0 # Minimum grace threshold for faster tempo
293
- upper_tempo_threshold = 9.0 # Maximum grace threshold for slower tempo
294
-
295
- # Adjust thresholds after a few reps
296
- if len(rep_data) > 3:
297
- lower_tempo_threshold = max(2.0, avg_tempo * 0.7)
298
- upper_tempo_threshold = min(9.0, avg_tempo * 1.3)
299
-
300
- # Feedback for ROM
301
- if rep["ROM"] < 30: # Minimum ROM threshold
302
- feedback.append("Lift arm higher")
303
- elif rep_data and rep["ROM"] < avg_rom * 0.8:
304
- feedback.append("Increase ROM")
305
-
306
- # Feedback for Tempo
307
- if rep["Tempo"] < lower_tempo_threshold: # Tempo too fast
308
- feedback.append("Slow down")
309
- elif rep["Tempo"] > upper_tempo_threshold: # Tempo too slow
310
- feedback.append("Speed up")
311
-
312
- return feedback
313
-
314
-
315
- # Post-workout feedback function
316
- def analyze_workout_with_isolation_forest(rep_data):
317
- if not rep_data:
318
- print("No reps completed.")
319
- return
320
-
321
- print("\n--- Post-Workout Summary ---")
322
-
323
- # Filter valid reps for recalculating thresholds
324
- valid_reps = [rep for rep in rep_data if rep["ROM"] > 20] # Ignore very low ROM reps
325
-
326
- if not valid_reps:
327
- print("No valid reps to analyze.")
328
- return
329
-
330
- features = np.array([[rep["ROM"], rep["Tempo"]] for rep in valid_reps])
331
-
332
- avg_rom = np.mean(features[:, 0])
333
- avg_tempo = np.mean(features[:, 1])
334
- std_rom = np.std(features[:, 0])
335
- std_tempo = np.std(features[:, 1])
336
-
337
- # Adjusted bounds for anomalies
338
- rom_lower_bound = max(20, avg_rom - std_rom * 2)
339
- tempo_lower_bound = max(1.0, avg_tempo - std_tempo * 2)
340
- tempo_upper_bound = min(10.0, avg_tempo + std_tempo * 2)
341
-
342
- print(f"ROM Lower Bound: {rom_lower_bound}")
343
- print(f"Tempo Bounds: {tempo_lower_bound}-{tempo_upper_bound}")
344
-
345
- # Anomaly detection
346
- for i, rep in enumerate(valid_reps, 1):
347
- feedback = []
348
- if rep["ROM"] < rom_lower_bound:
349
- feedback.append("Low ROM")
350
- if rep["Tempo"] < tempo_lower_bound:
351
- feedback.append("Too Fast")
352
- elif rep["Tempo"] > tempo_upper_bound:
353
- feedback.append("Too Slow")
354
-
355
- if feedback:
356
- print(f"Rep {i}: Anomalous | Feedback: {', '.join(feedback[:1])}")
357
-
358
- # Use Isolation Forest for secondary anomaly detection
359
- model = IsolationForest(contamination=0.1, random_state=42) # Reduced contamination
360
- predictions = model.fit_predict(features)
361
-
362
- for i, prediction in enumerate(predictions, 1):
363
- if prediction == -1: # Outlier
364
- print(f"Rep {i}: Isolation Forest flagged this rep as anomalous.")
365
-
366
-
367
- # Main workout tracking function
368
- def main():
369
- cap = cv2.VideoCapture(0)
370
- counter = 0 # Rep counter
371
- stage = None # Movement stage
372
- feedback = [] # Real-time feedback for the video feed
373
- rep_data = [] # Store metrics for each rep
374
- angles_during_rep = [] # Track angles during a single rep
375
- workout_start_time = None # Timer start
376
-
377
- with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose:
378
- while cap.isOpened():
379
- ret, frame = cap.read()
380
- if not ret:
381
- print("Failed to grab frame.")
382
- break
383
-
384
- # Initialize workout start time
385
- if workout_start_time is None:
386
- workout_start_time = time.time()
387
-
388
- # Timer
389
- elapsed_time = time.time() - workout_start_time
390
- timer_text = f"Timer: {int(elapsed_time)}s"
391
-
392
- # Convert the image to RGB
393
- image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
394
- image.flags.writeable = False
395
- results = pose.process(image)
396
-
397
- # Convert back to BGR
398
- image.flags.writeable = True
399
- image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
400
-
401
- # Check if pose landmarks are detected
402
- if results.pose_landmarks:
403
- landmarks = results.pose_landmarks.landmark
404
-
405
- # Check if key joints are visible
406
- if not are_key_joints_visible(landmarks):
407
- draw_text_with_background(
408
- image, "Ensure all joints are visible", (50, 50),
409
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 255)
410
- )
411
- cv2.imshow("Lateral Raise Tracker", image)
412
- continue
413
-
414
- # Extract key joints
415
- left_shoulder = [
416
- landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x,
417
- landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y,
418
- ]
419
- left_wrist = [
420
- landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x,
421
- landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y,
422
- ]
423
-
424
- # Calculate angle for lateral raise
425
- angle = calculate_angle_for_lateral_raise(left_shoulder, left_wrist)
426
-
427
- # Track angles during a rep
428
- if stage == "up" or stage == "down":
429
- angles_during_rep.append(angle)
430
-
431
- # Stage logic for counting reps
432
- if angle < 20 and stage != "down":
433
- stage = "down"
434
- if counter == 10: # Stop on the down stage of the 10th rep
435
- print("Workout complete! 10 reps reached.")
436
- break
437
-
438
- # Calculate ROM for the completed rep
439
- if len(angles_during_rep) > 1:
440
- rom = max(angles_during_rep) - min(angles_during_rep)
441
- else:
442
- rom = 0.0
443
-
444
- tempo = elapsed_time
445
- print(f"Rep {counter + 1}: ROM={rom:.2f}, Tempo={tempo:.2f}s")
446
-
447
- # Record metrics for the rep
448
- rep_data.append({
449
- "ROM": rom,
450
- "Tempo": tempo,
451
- })
452
-
453
- # Reset angles and timer for the next rep
454
- angles_during_rep = []
455
- workout_start_time = time.time() # Reset timer
456
-
457
- if 70 <= angle <= 110 and stage == "down":
458
- stage = "up"
459
- counter += 1
460
-
461
- # Analyze feedback
462
- feedback = analyze_single_rep(rep_data[-1], rep_data)
463
-
464
- # Determine wireframe color
465
- wireframe_color = (0, 255, 0) if not feedback else (0, 0, 255)
466
-
467
- # Display feedback
468
- draw_text_with_background(image, f"Reps: {counter}", (50, 50),
469
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0))
470
- draw_text_with_background(image, " | ".join(feedback), (50, 120),
471
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0))
472
- draw_text_with_background(image, timer_text, (50, 190),
473
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0))
474
-
475
- # Render detections with wireframe color
476
- mp_drawing.draw_landmarks(
477
- image,
478
- results.pose_landmarks,
479
- mp_pose.POSE_CONNECTIONS,
480
- mp_drawing.DrawingSpec(color=wireframe_color, thickness=2, circle_radius=2),
481
- mp_drawing.DrawingSpec(color=wireframe_color, thickness=2, circle_radius=2),
482
- )
483
-
484
- # Display the image
485
- cv2.imshow("Lateral Raise Tracker", image)
486
-
487
- if cv2.waitKey(10) & 0xFF == ord("q"):
488
- break
489
-
490
- cap.release()
491
- cv2.destroyAllWindows()
492
-
493
- # Post-workout analysis
494
- analyze_workout_with_isolation_forest(rep_data)
495
-
496
-
497
- if __name__ == "__main__":
498
- main()
499
 
 
 
 
 
 
 
 
 
 
 
 
 
500
 
501
- def shoulder_press():
502
- import cv2
503
- import mediapipe as mp
504
- import numpy as np
505
- import time
506
-
507
- # Mediapipe utilities
508
- mp_drawing = mp.solutions.drawing_utils
509
- mp_pose = mp.solutions.pose
510
-
511
- # Function to calculate angles
512
- def calculate_angle(point_a, point_b, point_c):
513
- vector_ab = np.array([point_a[0] - point_b[0], point_a[1] - point_b[1]])
514
- vector_cb = np.array([point_c[0] - point_b[0], point_c[1] - point_b[1]])
515
- dot_product = np.dot(vector_ab, vector_cb)
516
- magnitude_ab = np.linalg.norm(vector_ab)
517
- magnitude_cb = np.linalg.norm(vector_cb)
518
- if magnitude_ab == 0 or magnitude_cb == 0:
519
- return 0
520
- cos_angle = dot_product / (magnitude_ab * magnitude_cb)
521
- angle = np.arccos(np.clip(cos_angle, -1.0, 1.0))
522
- return np.degrees(angle)
523
 
 
 
 
524
 
525
- # Function to check if all required joints are visible
526
- def are_key_joints_visible(landmarks, visibility_threshold=0.5):
527
- required_joints = [
528
- mp_pose.PoseLandmark.LEFT_SHOULDER.value,
529
- mp_pose.PoseLandmark.RIGHT_SHOULDER.value,
530
- mp_pose.PoseLandmark.LEFT_ELBOW.value,
531
- mp_pose.PoseLandmark.RIGHT_ELBOW.value,
532
- mp_pose.PoseLandmark.LEFT_WRIST.value,
533
- mp_pose.PoseLandmark.RIGHT_WRIST.value,
534
- ]
535
- for joint in required_joints:
536
- if landmarks[joint].visibility < visibility_threshold:
537
- return False
538
- return True
539
 
540
-
541
- # Function to draw text with a background
542
- def draw_text_with_background(image, text, position, font, font_scale, color, thickness, bg_color, padding=10):
543
- text_size = cv2.getTextSize(text, font, font_scale, thickness)[0]
544
- text_x, text_y = position
545
- box_coords = (
546
- (text_x - padding, text_y - padding),
547
- (text_x + text_size[0] + padding, text_y + text_size[1] + padding),
548
- )
549
- cv2.rectangle(image, box_coords[0], box_coords[1], bg_color, cv2.FILLED)
550
- cv2.putText(image, text, (text_x, text_y + text_size[1]), font, font_scale, color, thickness)
551
-
552
-
553
- # Main workout tracking function
554
- def main():
555
- cap = cv2.VideoCapture(0)
556
- counter = 0
557
- stage = None
558
- feedback = ""
559
- workout_start_time = None
560
- rep_start_time = None
561
-
562
- with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose:
563
- while cap.isOpened():
564
- ret, frame = cap.read()
565
- if not ret:
566
- print("Failed to grab frame.")
567
- break
568
-
569
- # Initialize workout start time
570
- if workout_start_time is None:
571
- workout_start_time = time.time()
572
-
573
- # Timer
574
- elapsed_time = time.time() - workout_start_time
575
- timer_text = f"Timer: {int(elapsed_time)}s"
576
-
577
- # Convert the image to RGB
578
- image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
579
- image.flags.writeable = False
580
- results = pose.process(image)
581
-
582
- # Convert back to BGR
583
- image.flags.writeable = True
584
- image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
585
-
586
- # Check if pose landmarks are detected
587
- if results.pose_landmarks:
588
- landmarks = results.pose_landmarks.landmark
589
-
590
- # Check if key joints are visible
591
- if not are_key_joints_visible(landmarks):
592
- feedback = "Ensure all joints are visible"
593
- draw_text_with_background(
594
- image, feedback, (50, 50),
595
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 255)
596
- )
597
- cv2.imshow("Shoulder Press Tracker", image)
598
- continue
599
-
600
- # Extract key joints for both arms
601
- left_shoulder = [
602
- landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x,
603
- landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y,
604
- ]
605
- left_elbow = [
606
- landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].x,
607
- landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].y,
608
- ]
609
- left_wrist = [
610
- landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x,
611
- landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y,
612
- ]
613
-
614
- right_shoulder = [
615
- landmarks[mp_pose.PoseLandmark.RIGHT_SHOULDER.value].x,
616
- landmarks[mp_pose.PoseLandmark.RIGHT_SHOULDER.value].y,
617
- ]
618
- right_elbow = [
619
- landmarks[mp_pose.PoseLandmark.RIGHT_ELBOW.value].x,
620
- landmarks[mp_pose.PoseLandmark.RIGHT_ELBOW.value].y,
621
- ]
622
- right_wrist = [
623
- landmarks[mp_pose.PoseLandmark.RIGHT_WRIST.value].x,
624
- landmarks[mp_pose.PoseLandmark.RIGHT_WRIST.value].y,
625
- ]
626
-
627
- # Calculate angles
628
- left_elbow_angle = calculate_angle(left_shoulder, left_elbow, left_wrist)
629
- right_elbow_angle = calculate_angle(right_shoulder, right_elbow, right_wrist)
630
-
631
- # Check starting and ending positions
632
- if 80 <= left_elbow_angle <= 100 and 80 <= right_elbow_angle <= 100 and stage != "down":
633
- stage = "down"
634
- if counter == 10:
635
- feedback = "Workout complete! 10 reps done."
636
- draw_text_with_background(image, feedback, (50, 120),
637
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 255))
638
- cv2.imshow("Shoulder Press Tracker", image)
639
- break
640
- if rep_start_time is not None:
641
- tempo = time.time() - rep_start_time
642
- feedback = f"Rep {counter} completed! Tempo: {tempo:.2f}s"
643
- rep_start_time = None
644
- elif left_elbow_angle > 160 and right_elbow_angle > 160 and stage == "down":
645
- stage = "up"
646
- counter += 1
647
- rep_start_time = time.time()
648
-
649
- # Wireframe color
650
- wireframe_color = (0, 255, 0) if "completed" in feedback or "Good" in feedback else (0, 0, 255)
651
-
652
- # Display feedback
653
- draw_text_with_background(image, f"Reps: {counter}", (50, 50),
654
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0))
655
- draw_text_with_background(image, feedback, (50, 120),
656
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0))
657
- draw_text_with_background(image, timer_text, (50, 190),
658
- cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0))
659
-
660
- # Render detections with wireframe color
661
- mp_drawing.draw_landmarks(
662
- image,
663
- results.pose_landmarks,
664
- mp_pose.POSE_CONNECTIONS,
665
- mp_drawing.DrawingSpec(color=wireframe_color, thickness=2, circle_radius=2),
666
- mp_drawing.DrawingSpec(color=wireframe_color, thickness=2, circle_radius=2),
667
- )
668
-
669
- # Display the image
670
- cv2.imshow("Shoulder Press Tracker", image)
671
-
672
- if cv2.waitKey(10) & 0xFF == ord("q"):
673
- break
674
-
675
- cap.release()
676
- cv2.destroyAllWindows()
677
-
678
-
679
- if __name__ == "__main__":
680
- main()
681
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
682
 
683
- # Streamlit configuration
684
- st.set_page_config(page_title="Workout Tracker", page_icon="💪", layout="centered")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
685
 
686
- # Custom CSS for styling
687
- st.markdown(
688
- '''
689
- <style>
690
- body {
691
- background-color: #001f3f;
692
- color: #7FDBFF;
693
- font-family: Arial, sans-serif;
694
- }
695
- .stButton > button {
696
- background-color: #0074D9;
697
- color: white;
698
- border-radius: 5px;
699
- padding: 10px 20px;
700
- font-size: 18px;
701
- }
702
- .stButton > button:hover {
703
- background-color: #7FDBFF;
704
- color: #001f3f;
705
- }
706
- </style>
707
- ''',
708
- unsafe_allow_html=True
709
- )
710
 
711
- # Title and Introduction
712
- st.title("Workout Tracker")
713
- st.markdown("Welcome to the **Workout Tracker App**! Select your desired workout below and receive real-time feedback as you exercise.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
714
 
715
- # Check webcam availability
716
- def check_webcam():
717
- try:
718
- cap = cv2.VideoCapture(0)
719
- if not cap.isOpened():
720
- st.error("Webcam not detected! Please ensure a webcam is connected.")
721
- return False
722
- cap.release()
723
- return True
724
- except Exception as e:
725
- st.error(f"Error accessing webcam: {e}")
726
- return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
727
 
728
- # Workout Selection
729
- workout_option = st.radio("Select Your Workout:", ["Bicep Curl", "Lateral Raise", "Shoulder Press"])
 
 
 
 
 
 
 
 
 
 
 
730
 
731
- # Start Button
732
- if st.button("Start Workout"):
733
- if not check_webcam():
734
- st.stop()
735
- if workout_option == "Bicep Curl":
736
- st.write("Launching Bicep Curl Tracker...")
737
- bicep_curl()
738
- elif workout_option == "Lateral Raise":
739
- st.write("Launching Lateral Raise Tracker...")
740
- lateral_raise()
741
- elif workout_option == "Shoulder Press":
742
- st.write("Launching Shoulder Press Tracker...")
743
- shoulder_press()
 
 
 
 
 
 
 
744
 
745
- # Footer
746
- st.markdown("---")
747
- st.markdown("**Note**: Close the workout window or press 'q' in the camera feed to stop the workout.")
748
 
 
2
  import cv2
3
  import mediapipe as mp
4
  import numpy as np
5
+ from streamlit_webrtc import webrtc_streamer, VideoTransformerBase
6
+ import av
7
+ import threading
8
+ from dataclasses import dataclass
9
+ from typing import List
10
 
11
+ # Mediapipe setup
12
+ mp_drawing = mp.solutions.drawing_utils
13
+ mp_pose = mp.solutions.pose
14
 
15
+ # Custom CSS
16
+ st.markdown("""
17
+ <style>
18
+ .main {
19
+ background: linear-gradient(135deg, #001f3f 0%, #00b4d8 100%);
20
+ }
21
+ .stButton > button {
22
+ background-color: #00b4d8;
23
+ color: white;
24
+ border: none;
25
+ padding: 0.5rem 2rem;
26
+ border-radius: 5px;
27
+ margin: 0.5rem;
28
+ transition: all 0.3s;
29
+ }
30
+ .stButton > button:hover {
31
+ background-color: #0077b6;
32
+ }
33
+ h1, h2, h3 {
34
+ color: #001f3f;
35
+ }
36
+ .workout-container {
37
+ background: rgba(0, 180, 216, 0.1);
38
+ padding: 2rem;
39
+ border-radius: 10px;
40
+ margin: 1rem 0;
41
+ }
42
+ .feedback-text {
43
+ background: rgba(0, 31, 63, 0.1);
44
+ padding: 1rem;
45
+ border-radius: 5px;
46
+ margin: 1rem 0;
47
+ }
48
+ </style>
49
+ """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
+ @dataclass
52
+ class ExerciseState:
53
+ counter: int = 0
54
+ stage: str = None
55
+ feedback: str = ""
56
 
57
+ # Global state
58
+ state = ExerciseState()
59
+ lock = threading.Lock()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
+ def calculate_angle(a, b, c):
62
+ """Calculate angle between three points."""
63
+ a = np.array(a)
64
+ b = np.array(b)
65
+ c = np.array(c)
66
+
67
+ radians = np.arctan2(c[1]-b[1], c[0]-b[0]) - np.arctan2(a[1]-b[1], a[0]-b[0])
68
+ angle = np.abs(np.degrees(radians))
69
+
70
+ if angle > 180.0:
71
+ angle = 360 - angle
72
+ return angle
73
 
74
+ def calculate_lateral_raise_angle(shoulder, wrist):
75
+ """Calculate angle for lateral raise."""
76
+ horizontal_reference = np.array([1, 0])
77
+ arm_vector = np.array([wrist[0] - shoulder[0], wrist[1] - shoulder[1]])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
+ dot_product = np.dot(horizontal_reference, arm_vector)
80
+ magnitude_reference = np.linalg.norm(horizontal_reference)
81
+ magnitude_arm = np.linalg.norm(arm_vector)
82
 
83
+ if magnitude_arm == 0 or magnitude_reference == 0:
84
+ return 0
 
 
 
 
 
 
 
 
 
 
 
 
85
 
86
+ cos_angle = dot_product / (magnitude_reference * magnitude_arm)
87
+ angle = np.arccos(np.clip(cos_angle, -1.0, 1.0))
88
+ return np.degrees(angle)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
 
90
+ class VideoTransformer(VideoTransformerBase):
91
+ def __init__(self):
92
+ self.pose = mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5)
93
+ self.workout_type = "bicep_curl" # Default workout
94
+
95
+ def process_bicep_curl(self, landmarks):
96
+ """Process frame for bicep curl exercise."""
97
+ shoulder = [landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x,
98
+ landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y]
99
+ elbow = [landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].x,
100
+ landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].y]
101
+ wrist = [landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x,
102
+ landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y]
103
+
104
+ angle = calculate_angle(shoulder, elbow, wrist)
105
+
106
+ with lock:
107
+ if angle > 160 and state.stage != "down":
108
+ state.stage = "down"
109
+ state.feedback = "Lower the weight"
110
+ elif angle < 40 and state.stage == "down":
111
+ state.stage = "up"
112
+ state.counter += 1
113
+ state.feedback = f"Good rep! Count: {state.counter}"
114
+
115
+ return angle
116
 
117
+ def process_lateral_raise(self, landmarks):
118
+ """Process frame for lateral raise exercise."""
119
+ shoulder = [landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x,
120
+ landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y]
121
+ wrist = [landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x,
122
+ landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y]
123
+
124
+ angle = calculate_lateral_raise_angle(shoulder, wrist)
125
+
126
+ with lock:
127
+ if angle < 20 and state.stage != "down":
128
+ state.stage = "down"
129
+ state.feedback = "Raise your arms"
130
+ elif 70 <= angle <= 110 and state.stage == "down":
131
+ state.stage = "up"
132
+ state.counter += 1
133
+ state.feedback = f"Good rep! Count: {state.counter}"
134
+
135
+ return angle
136
 
137
+ def process_shoulder_press(self, landmarks):
138
+ """Process frame for shoulder press exercise."""
139
+ shoulder = [landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x,
140
+ landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y]
141
+ elbow = [landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].x,
142
+ landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].y]
143
+ wrist = [landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x,
144
+ landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y]
145
+
146
+ angle = calculate_angle(shoulder, elbow, wrist)
147
+
148
+ with lock:
149
+ if 80 <= angle <= 100 and state.stage != "down":
150
+ state.stage = "down"
151
+ state.feedback = "Press up!"
152
+ elif angle > 160 and state.stage == "down":
153
+ state.stage = "up"
154
+ state.counter += 1
155
+ state.feedback = f"Good rep! Count: {state.counter}"
156
+
157
+ return angle
 
 
 
158
 
159
+ def recv(self, frame):
160
+ img = frame.to_ndarray(format="bgr24")
161
+
162
+ # Process the image
163
+ image = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
164
+ results = self.pose.process(image)
165
+
166
+ if results.pose_landmarks:
167
+ # Draw pose landmarks
168
+ mp_drawing.draw_landmarks(
169
+ img, results.pose_landmarks, mp_pose.POSE_CONNECTIONS,
170
+ mp_drawing.DrawingSpec(color=(0, 255, 0), thickness=2, circle_radius=2),
171
+ mp_drawing.DrawingSpec(color=(0, 255, 0), thickness=2)
172
+ )
173
+
174
+ # Process based on workout type
175
+ if self.workout_type == "bicep_curl":
176
+ angle = self.process_bicep_curl(results.pose_landmarks.landmark)
177
+ elif self.workout_type == "lateral_raise":
178
+ angle = self.process_lateral_raise(results.pose_landmarks.landmark)
179
+ else: # shoulder_press
180
+ angle = self.process_shoulder_press(results.pose_landmarks.landmark)
181
+
182
+ # Draw angle and counter
183
+ cv2.putText(img, f"Angle: {angle:.2f}", (10, 30),
184
+ cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
185
+ cv2.putText(img, f"Counter: {state.counter}", (10, 70),
186
+ cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
187
+ cv2.putText(img, f"Feedback: {state.feedback}", (10, 110),
188
+ cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
189
+
190
+ return av.VideoFrame.from_ndarray(img, format="bgr24")
191
 
192
+ def main():
193
+ st.title("🏋️‍♂️ AI Workout Trainer")
194
+
195
+ st.markdown("""
196
+ <div class='workout-container'>
197
+ Welcome to your AI Workout Trainer! This app will help you perfect your form
198
+ and track your exercises in real-time. Choose a workout and follow the feedback
199
+ to improve your technique.
200
+ </div>
201
+ """, unsafe_allow_html=True)
202
+
203
+ # Workout selection
204
+ workout_options = {
205
+ "Bicep Curl": "bicep_curl",
206
+ "Lateral Raise": "lateral_raise",
207
+ "Shoulder Press": "shoulder_press"
208
+ }
209
+
210
+ selected_workout = st.selectbox(
211
+ "Choose your workout:",
212
+ list(workout_options.keys())
213
+ )
214
+
215
+ # Reset state when workout changes
216
+ if 'last_workout' not in st.session_state or st.session_state.last_workout != selected_workout:
217
+ with lock:
218
+ state.counter = 0
219
+ state.stage = None
220
+ state.feedback = ""
221
+ st.session_state.last_workout = selected_workout
222
 
223
+ # Exercise descriptions
224
+ descriptions = {
225
+ "Bicep Curl": "Focus on keeping your upper arm still and curl the weight up smoothly.",
226
+ "Lateral Raise": "Raise your arms to shoulder height, keeping them slightly bent.",
227
+ "Shoulder Press": "Press the weight overhead, fully extending your arms."
228
+ }
229
+
230
+ st.markdown(f"""
231
+ <div class='workout-container'>
232
+ <h3>{selected_workout}</h3>
233
+ <p>{descriptions[selected_workout]}</p>
234
+ </div>
235
+ """, unsafe_allow_html=True)
236
 
237
+ # Initialize WebRTC streamer
238
+ webrtc_ctx = webrtc_streamer(
239
+ key="workout",
240
+ video_transformer_factory=VideoTransformer,
241
+ rtc_configuration={"iceServers": [{"urls": ["stun:stun.l.google.com:19302"]}]}
242
+ )
243
+
244
+ if webrtc_ctx.video_transformer:
245
+ webrtc_ctx.video_transformer.workout_type = workout_options[selected_workout]
246
+
247
+ # Display feedback
248
+ feedback_placeholder = st.empty()
249
+ if webrtc_ctx.state.playing:
250
+ feedback_placeholder.markdown(f"""
251
+ <div class='feedback-text'>
252
+ <h4>Current Exercise: {selected_workout}</h4>
253
+ <p>Reps Completed: {state.counter}</p>
254
+ <p>Feedback: {state.feedback}</p>
255
+ </div>
256
+ """, unsafe_allow_html=True)
257
 
258
+ if __name__ == "__main__":
259
+ main()
 
260