Update exercises/squat.py
Browse files- exercises/squat.py +127 -47
exercises/squat.py
CHANGED
|
@@ -1,62 +1,142 @@
|
|
| 1 |
-
import
|
| 2 |
from pose_estimation.angle_calculation import calculate_angle
|
| 3 |
|
| 4 |
class Squat:
|
| 5 |
def __init__(self):
|
| 6 |
self.counter = 0
|
| 7 |
-
self.stage =
|
|
|
|
| 8 |
|
| 9 |
-
def calculate_angle(self,
|
| 10 |
-
return calculate_angle(
|
| 11 |
|
| 12 |
-
def track_squat(self,
|
| 13 |
-
#
|
| 14 |
-
hip = [int(landmarks[23].x * frame.shape[1]), int(landmarks[23].y * frame.shape[0])]
|
| 15 |
-
knee = [int(landmarks[25].x * frame.shape[1]), int(landmarks[25].y * frame.shape[0])]
|
| 16 |
-
shoulder = [int(landmarks[11].x * frame.shape[1]), int(landmarks[11].y * frame.shape[0])]
|
| 17 |
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
# Calculate angles
|
| 23 |
-
|
| 24 |
angle_right = self.calculate_angle(shoulder_right, hip_right, knee_right)
|
| 25 |
|
| 26 |
-
#
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
self.draw_circle(frame, knee, (178, 102, 255), 8)
|
| 35 |
-
self.draw_circle(frame, shoulder_right, (51, 153, 255), 8)
|
| 36 |
-
self.draw_circle(frame, hip_right, (51, 153, 255), 8)
|
| 37 |
-
self.draw_circle(frame, knee_right, (51, 153, 255), 8)
|
| 38 |
-
|
| 39 |
-
# Display angles on screen
|
| 40 |
-
angle_text_position = (knee[0] + 10, knee[1] - 10)
|
| 41 |
-
cv2.putText(frame, f'Angle Left: {int(angle)}', angle_text_position, cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)
|
| 42 |
-
|
| 43 |
-
angle_text_position_right = (knee_right[0] + 10, knee_right[1] - 10)
|
| 44 |
-
cv2.putText(frame, f'Angle Right: {int(angle_right)}', angle_text_position_right, cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)
|
| 45 |
-
|
| 46 |
-
# Update exercise stage and counter
|
| 47 |
-
if angle > 170:
|
| 48 |
-
self.stage = "Starting Position"
|
| 49 |
-
elif 90 < angle < 170 and self.stage == "Starting Position":
|
| 50 |
-
self.stage = "Descent"
|
| 51 |
-
elif angle < 90 and self.stage == "Descent":
|
| 52 |
-
self.stage = "Ascent"
|
| 53 |
self.counter += 1
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
|
| 56 |
-
def draw_line_with_style(self, frame, start_point, end_point, color, thickness):
|
| 57 |
-
"""Draw a line with specified style."""
|
| 58 |
-
cv2.line(frame, start_point, end_point, color, thickness, lineType=cv2.LINE_AA)
|
| 59 |
|
| 60 |
-
|
| 61 |
-
"""Draw a circle with specified style."""
|
| 62 |
-
cv2.circle(frame, center, radius, color, -1) # -1 to fill the circle
|
|
|
|
| 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
|
|
|
|
|
|