Spaces:
Sleeping
Sleeping
Upload 4 files
Browse files- exercises/__init__.py +0 -0
- exercises/hammer_curl.py +122 -0
- exercises/push_up.py +137 -0
- exercises/squat.py +148 -0
exercises/__init__.py
ADDED
File without changes
|
exercises/hammer_curl.py
ADDED
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import cv2
|
2 |
+
import numpy as np
|
3 |
+
from pose_estimation.angle_calculation import calculate_angle
|
4 |
+
from voice_feedback.feedback import provide_hammer_curl_feedback , speak
|
5 |
+
|
6 |
+
|
7 |
+
class HammerCurl:
|
8 |
+
def __init__(self):
|
9 |
+
self.counter_right = 0
|
10 |
+
self.counter_left = 0
|
11 |
+
self.stage_right = None # 'up' or 'down' for right arm
|
12 |
+
self.stage_left = None # 'up' or 'down' for left arm
|
13 |
+
|
14 |
+
self.angle_threshold = 40 # Angle threshold for misalignment
|
15 |
+
self.flexion_angle_up = 155 # Flexion angle for 'up' stage
|
16 |
+
self.flexion_angle_down = 35 # Flexion angle for 'down' stage
|
17 |
+
|
18 |
+
self.angle_threshold_up = 155 # Upper threshold for 'up' stage
|
19 |
+
self.angle_threshold_down = 47 # Lower threshold for 'down' stage
|
20 |
+
|
21 |
+
def calculate_shoulder_elbow_hip_angle(self, shoulder, elbow, hip):
|
22 |
+
"""Calculate the angle between shoulder, elbow, and hip."""
|
23 |
+
return calculate_angle(elbow, shoulder, hip)
|
24 |
+
|
25 |
+
def calculate_shoulder_elbow_wrist(self, shoulder, elbow, wrist):
|
26 |
+
"""Calculate the angle between shoulder, elbow, and wrist."""
|
27 |
+
return calculate_angle(shoulder, elbow, wrist)
|
28 |
+
|
29 |
+
def track_hammer_curl(self, landmarks, frame):
|
30 |
+
# Right arm landmarks (shoulder, elbow, hip, wrist)
|
31 |
+
shoulder_right = [int(landmarks[11].x * frame.shape[1]), int(landmarks[11].y * frame.shape[0])]
|
32 |
+
elbow_right = [int(landmarks[13].x * frame.shape[1]), int(landmarks[13].y * frame.shape[0])]
|
33 |
+
hip_right = [int(landmarks[23].x * frame.shape[1]), int(landmarks[23].y * frame.shape[0])]
|
34 |
+
wrist_right = [int(landmarks[15].x * frame.shape[1]), int(landmarks[15].y * frame.shape[0])]
|
35 |
+
|
36 |
+
# Left arm landmarks (shoulder, elbow, hip, wrist)
|
37 |
+
shoulder_left = [int(landmarks[12].x * frame.shape[1]), int(landmarks[12].y * frame.shape[0])]
|
38 |
+
elbow_left = [int(landmarks[14].x * frame.shape[1]), int(landmarks[14].y * frame.shape[0])]
|
39 |
+
hip_left = [int(landmarks[24].x * frame.shape[1]), int(landmarks[24].y * frame.shape[0])]
|
40 |
+
wrist_left = [int(landmarks[16].x * frame.shape[1]), int(landmarks[16].y * frame.shape[0])]
|
41 |
+
|
42 |
+
# Calculate the angle for counting (elbow flexion angle)
|
43 |
+
angle_right_counter = self.calculate_shoulder_elbow_wrist(shoulder_right, elbow_right, wrist_right)
|
44 |
+
angle_left_counter = self.calculate_shoulder_elbow_wrist(shoulder_left, elbow_left, wrist_left)
|
45 |
+
|
46 |
+
# Calculate the angle for the right arm (shoulder, elbow, hip)
|
47 |
+
angle_right = self.calculate_shoulder_elbow_hip_angle(shoulder_right, elbow_right, hip_right)
|
48 |
+
|
49 |
+
# Calculate the angle for the left arm (shoulder, elbow, hip)
|
50 |
+
angle_left = self.calculate_shoulder_elbow_hip_angle(shoulder_left, elbow_left, hip_left)
|
51 |
+
|
52 |
+
# Draw lines with improved style
|
53 |
+
self.draw_line_with_style(frame, shoulder_left, elbow_left, (0, 0, 255), 4)
|
54 |
+
self.draw_line_with_style(frame, elbow_left, wrist_left, (0, 0, 255), 4)
|
55 |
+
|
56 |
+
self.draw_line_with_style(frame, shoulder_right, elbow_right, (0, 0, 255), 4)
|
57 |
+
self.draw_line_with_style(frame, elbow_right, wrist_right, (0, 0, 255), 4)
|
58 |
+
|
59 |
+
# Add circles to highlight key points
|
60 |
+
self.draw_circle(frame, shoulder_left, (0, 0, 255), 8)
|
61 |
+
self.draw_circle(frame, elbow_left, (0, 0, 255), 8)
|
62 |
+
self.draw_circle(frame, wrist_left, (0, 0, 255), 8)
|
63 |
+
|
64 |
+
self.draw_circle(frame, shoulder_right, (0, 0, 255), 8)
|
65 |
+
self.draw_circle(frame, elbow_right, (0, 0, 255), 8)
|
66 |
+
self.draw_circle(frame, wrist_right, (0, 0, 255), 8)
|
67 |
+
|
68 |
+
# Convert the angles to integers and update the text positions
|
69 |
+
angle_text_position_left = (elbow_left[0] + 10, elbow_left[1] - 10)
|
70 |
+
cv2.putText(frame, f'Angle: {int(angle_left_counter)}', angle_text_position_left, cv2.FONT_HERSHEY_SIMPLEX, 0.5,
|
71 |
+
(255, 255, 255), 2)
|
72 |
+
|
73 |
+
angle_text_position_right = (elbow_right[0] + 10, elbow_right[1] - 10)
|
74 |
+
cv2.putText(frame, f'Angle: {int(angle_right_counter)}', angle_text_position_right, cv2.FONT_HERSHEY_SIMPLEX,
|
75 |
+
0.5,
|
76 |
+
(255, 255, 255), 2)
|
77 |
+
|
78 |
+
warning_message_right = None
|
79 |
+
warning_message_left = None
|
80 |
+
|
81 |
+
# Check for misalignment based on shoulder-elbow-hip angle
|
82 |
+
if abs(angle_right) > self.angle_threshold:
|
83 |
+
warning_message_right = f"Right Shoulder-Elbow-Hip Misalignment! Angle: {angle_right:.2f}Β°"
|
84 |
+
if abs(angle_left) > self.angle_threshold:
|
85 |
+
warning_message_left = f"Left Shoulder-Elbow-Hip Misalignment! Angle: {angle_left:.2f}Β°"
|
86 |
+
|
87 |
+
if angle_right_counter > self.angle_threshold_up:
|
88 |
+
self.stage_right = "Flex"
|
89 |
+
elif self.angle_threshold_down < angle_right_counter < self.angle_threshold_up and self.stage_right == "Flex":
|
90 |
+
self.stage_right = "Up"
|
91 |
+
elif angle_right_counter < self.angle_threshold_down and self.stage_right=="Up":
|
92 |
+
self.stage_right = "Down"
|
93 |
+
self.counter_right +=1
|
94 |
+
|
95 |
+
if angle_left_counter > self.angle_threshold_up:
|
96 |
+
self.stage_left = "Flex"
|
97 |
+
elif self.angle_threshold_down < angle_left_counter < self.angle_threshold_up and self.stage_left == "Flex":
|
98 |
+
self.stage_left = "Up"
|
99 |
+
elif angle_left_counter < self.angle_threshold_down and self.stage_left == "Up":
|
100 |
+
self.stage_left = "Down"
|
101 |
+
self.counter_left +=1
|
102 |
+
|
103 |
+
# Progress percentages: 1 for "up", 0 for "down"
|
104 |
+
progress_right = 1 if self.stage_right == "up" else 0 # Note: your stage names are 'Flex', 'Up', 'Down'
|
105 |
+
progress_left = 1 if self.stage_left == "up" else 0 # This logic might need adjusting based on stage names
|
106 |
+
|
107 |
+
return self.counter_right, angle_right_counter, self.counter_left, angle_left_counter, warning_message_right, warning_message_left, progress_right, progress_left, self.stage_right, self.stage_left
|
108 |
+
|
109 |
+
def draw_line_with_style(self, frame, start_point, end_point, color, thickness):
|
110 |
+
cv2.line(frame, start_point, end_point, color, thickness, lineType=cv2.LINE_AA)
|
111 |
+
|
112 |
+
def draw_circle(self, frame, center, color, radius):
|
113 |
+
"""Draw a circle with specified style."""
|
114 |
+
cv2.circle(frame, center, radius, color, -1) # -1 to fill the circle
|
115 |
+
|
116 |
+
# Inside the HammerCurl class in hammer_curl.py
|
117 |
+
def reset_reps(self):
|
118 |
+
self.counter_right = 0
|
119 |
+
self.counter_left = 0
|
120 |
+
self.stage_right = None # Or your desired initial stage, e.g., "Down" or "Flex"
|
121 |
+
self.stage_left = None # Or your desired initial stage
|
122 |
+
print("HammerCurl reps and stages reset for new set.")
|
exercises/push_up.py
ADDED
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import mediapipe as mp
|
2 |
+
import time
|
3 |
+
from pose_estimation.angle_calculation import calculate_angle
|
4 |
+
|
5 |
+
class PushUp:
|
6 |
+
def __init__(self):
|
7 |
+
self.counter = 0
|
8 |
+
self.stage = "up" # Changed from "Initial"
|
9 |
+
self.angle_threshold_up = 150
|
10 |
+
self.angle_threshold_down = 70
|
11 |
+
self.last_counter_update = time.time()
|
12 |
+
self.mp_pose = mp.solutions.pose # Added
|
13 |
+
|
14 |
+
def calculate_shoulder_elbow_wrist_angle(self, shoulder, elbow, wrist):
|
15 |
+
"""Calculate the angle between shoulder, elbow, and wrist."""
|
16 |
+
return calculate_angle(shoulder, elbow, wrist)
|
17 |
+
|
18 |
+
def track_push_up(self, landmarks_mp, frame_width, frame_height):
|
19 |
+
lm = landmarks_mp # shortcut
|
20 |
+
|
21 |
+
# Left side landmarks
|
22 |
+
shoulder_left = [int(lm[self.mp_pose.PoseLandmark.LEFT_SHOULDER.value].x * frame_width),
|
23 |
+
int(lm[self.mp_pose.PoseLandmark.LEFT_SHOULDER.value].y * frame_height)]
|
24 |
+
elbow_left = [int(lm[self.mp_pose.PoseLandmark.LEFT_ELBOW.value].x * frame_width),
|
25 |
+
int(lm[self.mp_pose.PoseLandmark.LEFT_ELBOW.value].y * frame_height)]
|
26 |
+
wrist_left = [int(lm[self.mp_pose.PoseLandmark.LEFT_WRIST.value].x * frame_width),
|
27 |
+
int(lm[self.mp_pose.PoseLandmark.LEFT_WRIST.value].y * frame_height)]
|
28 |
+
|
29 |
+
# Right side landmarks
|
30 |
+
shoulder_right = [int(lm[self.mp_pose.PoseLandmark.RIGHT_SHOULDER.value].x * frame_width),
|
31 |
+
int(lm[self.mp_pose.PoseLandmark.RIGHT_SHOULDER.value].y * frame_height)]
|
32 |
+
elbow_right = [int(lm[self.mp_pose.PoseLandmark.RIGHT_ELBOW.value].x * frame_width),
|
33 |
+
int(lm[self.mp_pose.PoseLandmark.RIGHT_ELBOW.value].y * frame_height)]
|
34 |
+
wrist_right = [int(lm[self.mp_pose.PoseLandmark.RIGHT_WRIST.value].x * frame_width),
|
35 |
+
int(lm[self.mp_pose.PoseLandmark.RIGHT_WRIST.value].y * frame_height)]
|
36 |
+
|
37 |
+
# Calculate angles
|
38 |
+
angle_left = self.calculate_shoulder_elbow_wrist_angle(shoulder_left, elbow_left, wrist_left)
|
39 |
+
angle_right = self.calculate_shoulder_elbow_wrist_angle(shoulder_right, elbow_right, wrist_right)
|
40 |
+
|
41 |
+
# Stage and Counter Logic
|
42 |
+
current_angle_for_logic = angle_left
|
43 |
+
current_time = time.time()
|
44 |
+
|
45 |
+
if current_angle_for_logic > self.angle_threshold_up:
|
46 |
+
self.stage = "up"
|
47 |
+
elif self.angle_threshold_down < current_angle_for_logic < self.angle_threshold_up and self.stage == "up":
|
48 |
+
self.stage = "down"
|
49 |
+
elif current_angle_for_logic < self.angle_threshold_down and self.stage == "down":
|
50 |
+
if current_time - self.last_counter_update > 1: # 1 second debounce
|
51 |
+
self.counter += 1
|
52 |
+
self.last_counter_update = current_time
|
53 |
+
self.stage = "up" # Transition back to up
|
54 |
+
|
55 |
+
feedback = self._get_push_up_feedback(angle_left, angle_right, self.stage)
|
56 |
+
|
57 |
+
return {
|
58 |
+
"counter": self.counter,
|
59 |
+
"stage": self.stage,
|
60 |
+
"angle_left": angle_left,
|
61 |
+
"angle_right": angle_right,
|
62 |
+
"feedback": feedback
|
63 |
+
}
|
64 |
+
|
65 |
+
def _get_push_up_feedback(self, angle_left, angle_right, stage):
|
66 |
+
feedback = "Keep going!" # Default
|
67 |
+
|
68 |
+
if stage == "down":
|
69 |
+
if min(angle_left, angle_right) < self.angle_threshold_down - 5:
|
70 |
+
feedback = "Good depth!"
|
71 |
+
elif min(angle_left, angle_right) > self.angle_threshold_down + 10:
|
72 |
+
feedback = "Go lower."
|
73 |
+
elif stage == "up":
|
74 |
+
feedback = "Push up!" # Or "Ready"
|
75 |
+
|
76 |
+
if abs(angle_left - angle_right) > 25:
|
77 |
+
feedback += " Try to keep your push-up even." if feedback != "Keep going!" else "Try to keep your push-up even."
|
78 |
+
|
79 |
+
return feedback.strip()
|
80 |
+
|
81 |
+
def get_drawing_annotations(self, landmarks_mp, frame_width, frame_height, exercise_data_dict):
|
82 |
+
annotations = []
|
83 |
+
lm = landmarks_mp # shortcut
|
84 |
+
|
85 |
+
# Pixel coordinates
|
86 |
+
shoulder_left = [int(lm[self.mp_pose.PoseLandmark.LEFT_SHOULDER.value].x * frame_width),
|
87 |
+
int(lm[self.mp_pose.PoseLandmark.LEFT_SHOULDER.value].y * frame_height)]
|
88 |
+
elbow_left = [int(lm[self.mp_pose.PoseLandmark.LEFT_ELBOW.value].x * frame_width),
|
89 |
+
int(lm[self.mp_pose.PoseLandmark.LEFT_ELBOW.value].y * frame_height)]
|
90 |
+
wrist_left = [int(lm[self.mp_pose.PoseLandmark.LEFT_WRIST.value].x * frame_width),
|
91 |
+
int(lm[self.mp_pose.PoseLandmark.LEFT_WRIST.value].y * frame_height)]
|
92 |
+
|
93 |
+
shoulder_right = [int(lm[self.mp_pose.PoseLandmark.RIGHT_SHOULDER.value].x * frame_width),
|
94 |
+
int(lm[self.mp_pose.PoseLandmark.RIGHT_SHOULDER.value].y * frame_height)]
|
95 |
+
elbow_right = [int(lm[self.mp_pose.PoseLandmark.RIGHT_ELBOW.value].x * frame_width),
|
96 |
+
int(lm[self.mp_pose.PoseLandmark.RIGHT_ELBOW.value].y * frame_height)]
|
97 |
+
wrist_right = [int(lm[self.mp_pose.PoseLandmark.RIGHT_WRIST.value].x * frame_width),
|
98 |
+
int(lm[self.mp_pose.PoseLandmark.RIGHT_WRIST.value].y * frame_height)]
|
99 |
+
|
100 |
+
# Lines (original colors: left (0,0,255) -> BGR [255,0,0], right (102,0,0) -> BGR [0,0,102])
|
101 |
+
annotations.append({"type": "line", "start_point": shoulder_left, "end_point": elbow_left, "color_bgr": [255, 0, 0], "thickness": 2})
|
102 |
+
annotations.append({"type": "line", "start_point": elbow_left, "end_point": wrist_left, "color_bgr": [255, 0, 0], "thickness": 2})
|
103 |
+
annotations.append({"type": "line", "start_point": shoulder_right, "end_point": elbow_right, "color_bgr": [0, 0, 102], "thickness": 2})
|
104 |
+
annotations.append({"type": "line", "start_point": elbow_right, "end_point": wrist_right, "color_bgr": [0, 0, 102], "thickness": 2})
|
105 |
+
|
106 |
+
# Circles
|
107 |
+
annotations.append({"type": "circle", "center_point": shoulder_left, "radius": 8, "color_bgr": [255, 0, 0], "filled": True})
|
108 |
+
annotations.append({"type": "circle", "center_point": elbow_left, "radius": 8, "color_bgr": [255, 0, 0], "filled": True})
|
109 |
+
annotations.append({"type": "circle", "center_point": wrist_left, "radius": 8, "color_bgr": [255, 0, 0], "filled": True})
|
110 |
+
annotations.append({"type": "circle", "center_point": shoulder_right, "radius": 8, "color_bgr": [0, 0, 102], "filled": True})
|
111 |
+
annotations.append({"type": "circle", "center_point": elbow_right, "radius": 8, "color_bgr": [0, 0, 102], "filled": True})
|
112 |
+
annotations.append({"type": "circle", "center_point": wrist_right, "radius": 8, "color_bgr": [0, 0, 102], "filled": True})
|
113 |
+
|
114 |
+
# Text for angles
|
115 |
+
if 'angle_left' in exercise_data_dict:
|
116 |
+
annotations.append({"type": "text", "text_content": f"Angle L: {int(exercise_data_dict['angle_left'])}",
|
117 |
+
"position": [elbow_left[0] + 10, elbow_left[1] - 10],
|
118 |
+
"font_scale": 0.5, "color_bgr": [255, 255, 255], "thickness": 2})
|
119 |
+
if 'angle_right' in exercise_data_dict:
|
120 |
+
annotations.append({"type": "text", "text_content": f"Angle R: {int(exercise_data_dict['angle_right'])}",
|
121 |
+
"position": [elbow_right[0] + 10, elbow_right[1] - 10],
|
122 |
+
"font_scale": 0.5, "color_bgr": [255, 255, 255], "thickness": 2})
|
123 |
+
|
124 |
+
# Display main feedback from exercise_data_dict
|
125 |
+
if 'feedback' in exercise_data_dict:
|
126 |
+
annotations.append({"type": "text", "text_content": exercise_data_dict['feedback'],
|
127 |
+
"position": [frame_width // 2 - 150, frame_height - 40], # Adjusted for longer text
|
128 |
+
"font_scale": 0.7, "color_bgr": [0, 255, 0], "thickness": 2}) # Green for feedback
|
129 |
+
|
130 |
+
return annotations
|
131 |
+
|
132 |
+
# Inside the PushUp class in push_up.py
|
133 |
+
def reset_reps(self):
|
134 |
+
self.counter = 0
|
135 |
+
self.stage = "up" # Reset to the initial stage
|
136 |
+
self.last_counter_update = time.time() # Important to reset debounce timer
|
137 |
+
print("PushUp reps and stage reset for new set.")
|
exercises/squat.py
ADDED
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import mediapipe as mp
|
2 |
+
from pose_estimation.angle_calculation import calculate_angle
|
3 |
+
|
4 |
+
class Squat:
|
5 |
+
def __init__(self):
|
6 |
+
self.counter = 0
|
7 |
+
self.stage = "up" # Initial stage
|
8 |
+
self.mp_pose = mp.solutions.pose # Added for convenience
|
9 |
+
|
10 |
+
def calculate_angle(self, point1, point2, point3): # Assuming these are pixel coordinates
|
11 |
+
return calculate_angle(point1, point2, point3)
|
12 |
+
|
13 |
+
def track_squat(self, landmarks_mp, frame_width, frame_height):
|
14 |
+
lm = landmarks_mp # shortcut
|
15 |
+
|
16 |
+
# Left side landmarks
|
17 |
+
shoulder_left = [int(lm[self.mp_pose.PoseLandmark.LEFT_SHOULDER.value].x * frame_width),
|
18 |
+
int(lm[self.mp_pose.PoseLandmark.LEFT_SHOULDER.value].y * frame_height)]
|
19 |
+
hip_left = [int(lm[self.mp_pose.PoseLandmark.LEFT_HIP.value].x * frame_width),
|
20 |
+
int(lm[self.mp_pose.PoseLandmark.LEFT_HIP.value].y * frame_height)]
|
21 |
+
knee_left = [int(lm[self.mp_pose.PoseLandmark.LEFT_KNEE.value].x * frame_width),
|
22 |
+
int(lm[self.mp_pose.PoseLandmark.LEFT_KNEE.value].y * frame_height)]
|
23 |
+
# ankle_left = [int(lm[self.mp_pose.PoseLandmark.LEFT_ANKLE.value].x * frame_width),
|
24 |
+
# int(lm[self.mp_pose.PoseLandmark.LEFT_ANKLE.value].y * frame_height)]
|
25 |
+
|
26 |
+
|
27 |
+
# Right side landmarks
|
28 |
+
shoulder_right = [int(lm[self.mp_pose.PoseLandmark.RIGHT_SHOULDER.value].x * frame_width),
|
29 |
+
int(lm[self.mp_pose.PoseLandmark.RIGHT_SHOULDER.value].y * frame_height)]
|
30 |
+
hip_right = [int(lm[self.mp_pose.PoseLandmark.RIGHT_HIP.value].x * frame_width),
|
31 |
+
int(lm[self.mp_pose.PoseLandmark.RIGHT_HIP.value].y * frame_height)]
|
32 |
+
knee_right = [int(lm[self.mp_pose.PoseLandmark.RIGHT_KNEE.value].x * frame_width),
|
33 |
+
int(lm[self.mp_pose.PoseLandmark.RIGHT_KNEE.value].y * frame_height)]
|
34 |
+
# ankle_right = [int(lm[self.mp_pose.PoseLandmark.RIGHT_ANKLE.value].x * frame_width),
|
35 |
+
# int(lm[self.mp_pose.PoseLandmark.RIGHT_ANKLE.value].y * frame_height)]
|
36 |
+
|
37 |
+
# Calculate angles
|
38 |
+
angle_left = self.calculate_angle(shoulder_left, hip_left, knee_left)
|
39 |
+
angle_right = self.calculate_angle(shoulder_right, hip_right, knee_right)
|
40 |
+
|
41 |
+
# Stage and counter logic (using angle_left for primary logic)
|
42 |
+
current_angle_for_logic = angle_left
|
43 |
+
if current_angle_for_logic > 170:
|
44 |
+
self.stage = "up"
|
45 |
+
elif 90 < current_angle_for_logic < 170 and self.stage == "up":
|
46 |
+
self.stage = "down"
|
47 |
+
elif current_angle_for_logic < 90 and self.stage == "down":
|
48 |
+
self.stage = "up"
|
49 |
+
self.counter += 1
|
50 |
+
|
51 |
+
feedback_message = self._get_squat_feedback(angle_left, angle_right, self.stage,
|
52 |
+
knee_left, hip_left, shoulder_left,
|
53 |
+
knee_right, hip_right, shoulder_right)
|
54 |
+
|
55 |
+
return {
|
56 |
+
"counter": self.counter,
|
57 |
+
"stage": self.stage,
|
58 |
+
"angle_left": angle_left,
|
59 |
+
"angle_right": angle_right,
|
60 |
+
"feedback": feedback_message
|
61 |
+
}
|
62 |
+
|
63 |
+
def _get_squat_feedback(self, angle_left, angle_right, stage,
|
64 |
+
knee_left, hip_left, shoulder_left,
|
65 |
+
knee_right, hip_right, shoulder_right): # Added points for future use
|
66 |
+
feedback = "Keep going." # Default feedback
|
67 |
+
|
68 |
+
if stage == "down":
|
69 |
+
if min(angle_left, angle_right) < 80:
|
70 |
+
feedback = "Good depth!"
|
71 |
+
elif min(angle_left, angle_right) > 100: # Knees should be more bent
|
72 |
+
feedback = "Go lower."
|
73 |
+
|
74 |
+
if abs(angle_left - angle_right) > 20: # Check for uneven squat
|
75 |
+
# Adding a check to see if there's significant movement, e.g., not in "up" stage fully extended
|
76 |
+
if not (stage == "up" and min(angle_left, angle_right) > 160): # Avoid this message if standing straight
|
77 |
+
feedback += " Try to keep your squat even." if feedback != "Keep going." else "Try to keep your squat even."
|
78 |
+
|
79 |
+
|
80 |
+
# Placeholder for more advanced feedback using the passed points:
|
81 |
+
# E.g., Knee valgus: check if knee_left.x < hip_left.x and knee_left.x > shoulder_left.x (simplified)
|
82 |
+
# E.g., Back posture: calculate angle shoulder-hip-ankle (requires ankle points)
|
83 |
+
|
84 |
+
return feedback.strip()
|
85 |
+
|
86 |
+
|
87 |
+
def get_drawing_annotations(self, landmarks_mp, frame_width, frame_height, exercise_data_dict):
|
88 |
+
annotations = []
|
89 |
+
lm = landmarks_mp # shortcut
|
90 |
+
|
91 |
+
# Re-calculate or retrieve necessary points (pixel coordinates)
|
92 |
+
# For simplicity, re-calculating here. Could be optimized by passing from track_squat.
|
93 |
+
shoulder_left = [int(lm[self.mp_pose.PoseLandmark.LEFT_SHOULDER.value].x * frame_width),
|
94 |
+
int(lm[self.mp_pose.PoseLandmark.LEFT_SHOULDER.value].y * frame_height)]
|
95 |
+
hip_left = [int(lm[self.mp_pose.PoseLandmark.LEFT_HIP.value].x * frame_width),
|
96 |
+
int(lm[self.mp_pose.PoseLandmark.LEFT_HIP.value].y * frame_height)]
|
97 |
+
knee_left = [int(lm[self.mp_pose.PoseLandmark.LEFT_KNEE.value].x * frame_width),
|
98 |
+
int(lm[self.mp_pose.PoseLandmark.LEFT_KNEE.value].y * frame_height)]
|
99 |
+
|
100 |
+
shoulder_right = [int(lm[self.mp_pose.PoseLandmark.RIGHT_SHOULDER.value].x * frame_width),
|
101 |
+
int(lm[self.mp_pose.PoseLandmark.RIGHT_SHOULDER.value].y * frame_height)]
|
102 |
+
hip_right = [int(lm[self.mp_pose.PoseLandmark.RIGHT_HIP.value].x * frame_width),
|
103 |
+
int(lm[self.mp_pose.PoseLandmark.RIGHT_HIP.value].y * frame_height)]
|
104 |
+
knee_right = [int(lm[self.mp_pose.PoseLandmark.RIGHT_KNEE.value].x * frame_width),
|
105 |
+
int(lm[self.mp_pose.PoseLandmark.RIGHT_KNEE.value].y * frame_height)]
|
106 |
+
|
107 |
+
# Lines for left side (original color: (178, 102, 255) -> BGR: [255, 102, 178])
|
108 |
+
annotations.append({"type": "line", "start_point": shoulder_left, "end_point": hip_left, "color_bgr": [255, 102, 178], "thickness": 2})
|
109 |
+
annotations.append({"type": "line", "start_point": hip_left, "end_point": knee_left, "color_bgr": [255, 102, 178], "thickness": 2})
|
110 |
+
|
111 |
+
# Lines for right side (original color: (51, 153, 255) -> BGR: [255, 153, 51])
|
112 |
+
annotations.append({"type": "line", "start_point": shoulder_right, "end_point": hip_right, "color_bgr": [255, 153, 51], "thickness": 2})
|
113 |
+
annotations.append({"type": "line", "start_point": hip_right, "end_point": knee_right, "color_bgr": [255, 153, 51], "thickness": 2})
|
114 |
+
|
115 |
+
# Circles for left side
|
116 |
+
annotations.append({"type": "circle", "center_point": shoulder_left, "radius": 8, "color_bgr": [255, 102, 178], "filled": True})
|
117 |
+
annotations.append({"type": "circle", "center_point": hip_left, "radius": 8, "color_bgr": [255, 102, 178], "filled": True})
|
118 |
+
annotations.append({"type": "circle", "center_point": knee_left, "radius": 8, "color_bgr": [255, 102, 178], "filled": True})
|
119 |
+
|
120 |
+
# Circles for right side
|
121 |
+
annotations.append({"type": "circle", "center_point": shoulder_right, "radius": 8, "color_bgr": [255, 153, 51], "filled": True})
|
122 |
+
annotations.append({"type": "circle", "center_point": hip_right, "radius": 8, "color_bgr": [255, 153, 51], "filled": True})
|
123 |
+
annotations.append({"type": "circle", "center_point": knee_right, "radius": 8, "color_bgr": [255, 153, 51], "filled": True})
|
124 |
+
|
125 |
+
# Text for angles
|
126 |
+
if 'angle_left' in exercise_data_dict:
|
127 |
+
annotations.append({"type": "text", "text_content": f"Angle L: {int(exercise_data_dict['angle_left'])}",
|
128 |
+
"position": [knee_left[0] + 10, knee_left[1] - 10],
|
129 |
+
"font_scale": 0.5, "color_bgr": [255, 255, 255], "thickness": 2})
|
130 |
+
if 'angle_right' in exercise_data_dict:
|
131 |
+
annotations.append({"type": "text", "text_content": f"Angle R: {int(exercise_data_dict['angle_right'])}",
|
132 |
+
"position": [knee_right[0] + 10, knee_right[1] - 10],
|
133 |
+
"font_scale": 0.5, "color_bgr": [255, 255, 255], "thickness": 2})
|
134 |
+
|
135 |
+
# Display main feedback from exercise_data_dict
|
136 |
+
if 'feedback' in exercise_data_dict:
|
137 |
+
annotations.append({"type": "text", "text_content": exercise_data_dict['feedback'],
|
138 |
+
"position": [frame_width // 2 - 100, frame_height - 40], # Centered at bottom
|
139 |
+
"font_scale": 0.7, "color_bgr": [0, 0, 255], "thickness": 2})
|
140 |
+
|
141 |
+
|
142 |
+
return annotations
|
143 |
+
|
144 |
+
# Inside the Squat class in squat.py
|
145 |
+
def reset_reps(self):
|
146 |
+
self.counter = 0
|
147 |
+
self.stage = "up" # Reset to the initial stage
|
148 |
+
print("Squat reps and stage reset for new set.")
|