Spaces:
Runtime error
Runtime error
Upload 9 files
Browse files- athletic_performance.py +120 -47
athletic_performance.py
CHANGED
|
@@ -182,52 +182,126 @@ def draw_pose_landmarks(frame, landmarks, knee_analysis=None):
|
|
| 182 |
return frame
|
| 183 |
|
| 184 |
|
| 185 |
-
def
|
| 186 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
h, w, _ = frame.shape
|
| 188 |
|
| 189 |
-
#
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
#
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
#
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
|
| 228 |
return frame
|
| 229 |
|
| 230 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 231 |
def calculate_peak_power_output(jump_height_m, body_mass_kg, flight_time_s):
|
| 232 |
"""Calculate peak power output using biomechanical models."""
|
| 233 |
if jump_height_m <= 0 or flight_time_s <= 0:
|
|
@@ -481,19 +555,18 @@ def generate_annotated_video(video_path, user_height_cm, user_weight_kg, gender,
|
|
| 481 |
# Draw pose landmarks with strain indicators
|
| 482 |
annotated_frame = draw_pose_landmarks(frame, results.pose_landmarks, knee_analysis)
|
| 483 |
|
| 484 |
-
#
|
| 485 |
-
|
| 486 |
-
|
|
|
|
|
|
|
|
|
|
| 487 |
)
|
| 488 |
|
| 489 |
# Add performance info overlay
|
| 490 |
info_y = 30
|
| 491 |
cv2.putText(annotated_frame, f"Frame: {frame_idx}/{total_frames}",
|
| 492 |
(10, info_y), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
|
| 493 |
-
|
| 494 |
-
if current_jump_height > 0:
|
| 495 |
-
cv2.putText(annotated_frame, f"Current Jump: {current_jump_height:.1f}cm",
|
| 496 |
-
(10, info_y + 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
|
| 497 |
|
| 498 |
# Add knee strain warnings
|
| 499 |
if knee_analysis["strain_detected"]:
|
|
|
|
| 182 |
return frame
|
| 183 |
|
| 184 |
|
| 185 |
+
def draw_jump_reference_arrows(frame, references, person_landmarks, is_jumping, user_height_cm):
|
| 186 |
+
"""
|
| 187 |
+
Draw reference arrows for average and professional jump heights near the person.
|
| 188 |
+
Only shows when the person is actively jumping.
|
| 189 |
+
|
| 190 |
+
Args:
|
| 191 |
+
frame: Video frame to draw on
|
| 192 |
+
references (dict): Jump height references {'average': float, 'professional': float}
|
| 193 |
+
person_landmarks: MediaPipe pose landmarks
|
| 194 |
+
is_jumping (bool): Whether the person is currently jumping
|
| 195 |
+
user_height_cm (float): User's height for scaling
|
| 196 |
+
|
| 197 |
+
Returns:
|
| 198 |
+
frame: Frame with reference arrows drawn
|
| 199 |
+
"""
|
| 200 |
+
if not is_jumping or not person_landmarks:
|
| 201 |
+
return frame
|
| 202 |
+
|
| 203 |
h, w, _ = frame.shape
|
| 204 |
|
| 205 |
+
# Get person's hip position (center point)
|
| 206 |
+
left_hip = person_landmarks.landmark[mp.solutions.pose.PoseLandmark.LEFT_HIP]
|
| 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 |
return frame
|
| 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 |
+
Args:
|
| 262 |
+
landmarks: MediaPipe pose landmarks
|
| 263 |
+
frame_height (int): Height of the video frame
|
| 264 |
+
|
| 265 |
+
Returns:
|
| 266 |
+
bool: True if person appears to be jumping
|
| 267 |
+
"""
|
| 268 |
+
if not landmarks:
|
| 269 |
+
return False
|
| 270 |
+
|
| 271 |
+
try:
|
| 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):
|
| 306 |
"""Calculate peak power output using biomechanical models."""
|
| 307 |
if jump_height_m <= 0 or flight_time_s <= 0:
|
|
|
|
| 555 |
# Draw pose landmarks with strain indicators
|
| 556 |
annotated_frame = draw_pose_landmarks(frame, results.pose_landmarks, knee_analysis)
|
| 557 |
|
| 558 |
+
# Detect if person is currently jumping
|
| 559 |
+
is_jumping = detect_jumping_phase(results.pose_landmarks, h)
|
| 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
|
| 567 |
info_y = 30
|
| 568 |
cv2.putText(annotated_frame, f"Frame: {frame_idx}/{total_frames}",
|
| 569 |
(10, info_y), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 570 |
|
| 571 |
# Add knee strain warnings
|
| 572 |
if knee_analysis["strain_detected"]:
|