Spaces:
Runtime error
Runtime error
Upload 9 files
Browse files- athletic_performance.py +31 -116
athletic_performance.py
CHANGED
|
@@ -182,124 +182,42 @@ def draw_pose_landmarks(frame, landmarks, knee_analysis=None):
|
|
| 182 |
return frame
|
| 183 |
|
| 184 |
|
| 185 |
-
def
|
| 186 |
-
"""
|
| 187 |
-
|
| 188 |
-
Only shows when the person is actively jumping.
|
| 189 |
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
is_jumping (bool): Whether the person is currently jumping
|
| 195 |
-
user_height_cm (float): User's height for scaling
|
| 196 |
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
"""
|
| 200 |
-
if not is_jumping or not person_landmarks:
|
| 201 |
-
return frame
|
| 202 |
|
| 203 |
-
|
|
|
|
|
|
|
| 204 |
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
right_hip = person_landmarks.landmark[mp.solutions.pose.PoseLandmark.RIGHT_HIP]
|
| 208 |
-
|
| 209 |
-
# Calculate person center
|
| 210 |
-
person_x = int((left_hip.x + right_hip.x) / 2 * w)
|
| 211 |
-
person_y = int((left_hip.y + right_hip.y) / 2 * h)
|
| 212 |
-
|
| 213 |
-
# Calculate scaling factor (pixels per cm based on person height in frame)
|
| 214 |
-
head = person_landmarks.landmark[mp.solutions.pose.PoseLandmark.NOSE]
|
| 215 |
-
left_ankle = person_landmarks.landmark[mp.solutions.pose.PoseLandmark.LEFT_ANKLE]
|
| 216 |
-
right_ankle = person_landmarks.landmark[mp.solutions.pose.PoseLandmark.RIGHT_ANKLE]
|
| 217 |
-
|
| 218 |
-
# Person height in frame (head to ankle)
|
| 219 |
-
ankle_y = (left_ankle.y + right_ankle.y) / 2
|
| 220 |
-
person_height_pixels = abs(ankle_y - head.y) * h
|
| 221 |
-
pixels_per_cm = person_height_pixels / user_height_cm if user_height_cm > 0 else 1
|
| 222 |
-
|
| 223 |
-
# Ground reference (ankle level)
|
| 224 |
-
ground_y = int(ankle_y * h)
|
| 225 |
-
|
| 226 |
-
# Calculate reference positions
|
| 227 |
-
avg_height = references.get('average', 0)
|
| 228 |
-
pro_height = references.get('professional', 0)
|
| 229 |
-
|
| 230 |
-
# Position arrows to the right side of the person
|
| 231 |
-
arrow_x_start = person_x + 60
|
| 232 |
-
arrow_x_end = person_x + 120
|
| 233 |
-
|
| 234 |
-
# Draw average performance arrow
|
| 235 |
-
if avg_height > 0:
|
| 236 |
-
avg_y = int(ground_y - (avg_height * pixels_per_cm))
|
| 237 |
-
if 20 < avg_y < h - 20: # Only draw if within frame
|
| 238 |
-
# Draw arrow pointing to average height
|
| 239 |
-
cv2.arrowedLine(frame, (arrow_x_start, avg_y), (arrow_x_end, avg_y),
|
| 240 |
-
(255, 255, 0), 4, tipLength=0.3) # Yellow arrow
|
| 241 |
-
cv2.putText(frame, f"Avg: {avg_height:.0f}cm", (arrow_x_end + 10, avg_y + 5),
|
| 242 |
-
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 0), 2)
|
| 243 |
-
|
| 244 |
-
# Draw professional performance arrow
|
| 245 |
-
if pro_height > 0:
|
| 246 |
-
pro_y = int(ground_y - (pro_height * pixels_per_cm))
|
| 247 |
-
if 20 < pro_y < h - 20: # Only draw if within frame
|
| 248 |
-
# Draw arrow pointing to professional height
|
| 249 |
-
cv2.arrowedLine(frame, (arrow_x_start, pro_y), (arrow_x_end, pro_y),
|
| 250 |
-
(0, 255, 0), 4, tipLength=0.3) # Green arrow
|
| 251 |
-
cv2.putText(frame, f"Pro: {pro_height:.0f}cm", (arrow_x_end + 10, pro_y + 5),
|
| 252 |
-
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
|
| 253 |
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
def detect_jumping_phase(landmarks, frame_height):
|
| 258 |
-
"""
|
| 259 |
-
Detect if the person is currently in a jumping phase based on pose.
|
| 260 |
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
|
|
|
|
|
|
| 264 |
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
|
| 271 |
-
|
| 272 |
-
# Get key landmarks
|
| 273 |
-
left_ankle = landmarks.landmark[mp.solutions.pose.PoseLandmark.LEFT_ANKLE]
|
| 274 |
-
right_ankle = landmarks.landmark[mp.solutions.pose.PoseLandmark.RIGHT_ANKLE]
|
| 275 |
-
left_knee = landmarks.landmark[mp.solutions.pose.PoseLandmark.LEFT_KNEE]
|
| 276 |
-
right_knee = landmarks.landmark[mp.solutions.pose.PoseLandmark.RIGHT_KNEE]
|
| 277 |
-
left_hip = landmarks.landmark[mp.solutions.pose.PoseLandmark.LEFT_HIP]
|
| 278 |
-
right_hip = landmarks.landmark[mp.solutions.pose.PoseLandmark.RIGHT_HIP]
|
| 279 |
-
|
| 280 |
-
# Calculate average positions
|
| 281 |
-
ankle_y = (left_ankle.y + right_ankle.y) / 2
|
| 282 |
-
knee_y = (left_knee.y + right_knee.y) / 2
|
| 283 |
-
hip_y = (left_hip.y + right_hip.y) / 2
|
| 284 |
-
|
| 285 |
-
# Check if person is in jumping posture
|
| 286 |
-
# Jumping indicators:
|
| 287 |
-
# 1. Knees are significantly bent (knee-hip distance is small)
|
| 288 |
-
# 2. Person is in takeoff/landing phase (dynamic posture)
|
| 289 |
-
|
| 290 |
-
knee_hip_distance = abs(knee_y - hip_y) * frame_height
|
| 291 |
-
ankle_knee_distance = abs(ankle_y - knee_y) * frame_height
|
| 292 |
-
|
| 293 |
-
# If knees are bent (shorter distances) or in dynamic position, likely jumping
|
| 294 |
-
is_crouched = knee_hip_distance < 40 or ankle_knee_distance < 40
|
| 295 |
-
|
| 296 |
-
# Additional check: if ankles are not at the bottom of frame, person might be airborne
|
| 297 |
-
is_airborne = ankle_y < 0.85 # If ankles are not in bottom 15% of frame
|
| 298 |
-
|
| 299 |
-
return is_crouched or is_airborne
|
| 300 |
-
|
| 301 |
-
except (AttributeError, IndexError):
|
| 302 |
-
return False
|
| 303 |
|
| 304 |
|
| 305 |
def calculate_peak_power_output(jump_height_m, body_mass_kg, flight_time_s):
|
|
@@ -555,12 +473,9 @@ def generate_annotated_video(video_path, user_height_cm, user_weight_kg, gender,
|
|
| 555 |
# Draw pose landmarks with strain indicators
|
| 556 |
annotated_frame = draw_pose_landmarks(frame, results.pose_landmarks, knee_analysis)
|
| 557 |
|
| 558 |
-
#
|
| 559 |
-
|
| 560 |
-
|
| 561 |
-
# Draw jump reference arrows (only when jumping)
|
| 562 |
-
annotated_frame = draw_jump_reference_arrows(
|
| 563 |
-
annotated_frame, jump_references, results.pose_landmarks, is_jumping, user_height_cm
|
| 564 |
)
|
| 565 |
|
| 566 |
# Add performance info overlay
|
|
|
|
| 182 |
return frame
|
| 183 |
|
| 184 |
|
| 185 |
+
def draw_jump_reference_lines(frame, references, current_jump_height, user_height_cm):
|
| 186 |
+
"""Draw average and professional jump height reference lines."""
|
| 187 |
+
h, w, _ = frame.shape
|
|
|
|
| 188 |
|
| 189 |
+
# Calculate line positions (relative to frame height)
|
| 190 |
+
# Assume the person's height spans about 70% of frame height
|
| 191 |
+
person_height_pixels = int(h * 0.7)
|
| 192 |
+
pixels_per_cm = person_height_pixels / user_height_cm
|
|
|
|
|
|
|
| 193 |
|
| 194 |
+
# Base line (ground level) - bottom 10% of frame
|
| 195 |
+
ground_y = int(h * 0.9)
|
|
|
|
|
|
|
|
|
|
| 196 |
|
| 197 |
+
# Reference lines
|
| 198 |
+
avg_jump_pixels = int(references["average"] * pixels_per_cm)
|
| 199 |
+
pro_jump_pixels = int(references["professional"] * pixels_per_cm)
|
| 200 |
|
| 201 |
+
avg_line_y = ground_y - avg_jump_pixels
|
| 202 |
+
pro_line_y = ground_y - pro_jump_pixels
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
|
| 204 |
+
# Draw ground line
|
| 205 |
+
cv2.line(frame, (0, ground_y), (w, ground_y), (100, 100, 100), 2)
|
| 206 |
+
cv2.putText(frame, "Ground", (10, ground_y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (100, 100, 100), 2)
|
|
|
|
|
|
|
|
|
|
| 207 |
|
| 208 |
+
# Draw average line
|
| 209 |
+
if avg_line_y > 0:
|
| 210 |
+
cv2.line(frame, (0, avg_line_y), (w, avg_line_y), (255, 255, 0), 2)
|
| 211 |
+
cv2.putText(frame, f"Avg: {references['average']:.0f}cm",
|
| 212 |
+
(10, avg_line_y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 0), 2)
|
| 213 |
|
| 214 |
+
# Draw professional line
|
| 215 |
+
if pro_line_y > 0:
|
| 216 |
+
cv2.line(frame, (0, pro_line_y), (w, pro_line_y), (0, 255, 0), 2)
|
| 217 |
+
cv2.putText(frame, f"Pro: {references['professional']:.0f}cm",
|
| 218 |
+
(10, pro_line_y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
|
| 219 |
|
| 220 |
+
return frame
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
|
| 222 |
|
| 223 |
def calculate_peak_power_output(jump_height_m, body_mass_kg, flight_time_s):
|
|
|
|
| 473 |
# Draw pose landmarks with strain indicators
|
| 474 |
annotated_frame = draw_pose_landmarks(frame, results.pose_landmarks, knee_analysis)
|
| 475 |
|
| 476 |
+
# Draw jump reference lines
|
| 477 |
+
annotated_frame = draw_jump_reference_lines(
|
| 478 |
+
annotated_frame, jump_references, current_jump_height, user_height_cm
|
|
|
|
|
|
|
|
|
|
| 479 |
)
|
| 480 |
|
| 481 |
# Add performance info overlay
|