Spaces:
Sleeping
Sleeping
import streamlit as st | |
import cv2 | |
import mediapipe as mp | |
import numpy as np | |
import time | |
from sklearn.ensemble import IsolationForest | |
# Encapsulated workout functions | |
def bicep_curl(): | |
import cv2 | |
import mediapipe as mp | |
import numpy as np | |
import time | |
from sklearn.ensemble import IsolationForest | |
# Mediapipe utilities | |
mp_drawing = mp.solutions.drawing_utils | |
mp_pose = mp.solutions.pose | |
# Function to calculate angles between three points | |
def calculate_angle(a, b, c): | |
a = np.array(a) | |
b = np.array(b) | |
c = np.array(c) | |
radians = np.arctan2(c[1] - b[1], c[0] - b[0]) - np.arctan2(a[1] - b[1], a[0] - b[0]) | |
angle = np.abs(np.degrees(radians)) | |
if angle > 180.0: | |
angle = 360 - angle | |
return angle | |
# Function to draw text with a background | |
def draw_text_with_background(image, text, position, font, font_scale, color, thickness, bg_color, padding=10): | |
text_size = cv2.getTextSize(text, font, font_scale, thickness)[0] | |
text_x, text_y = position | |
box_coords = ( | |
(text_x - padding, text_y - padding), | |
(text_x + text_size[0] + padding, text_y + text_size[1] + padding), | |
) | |
cv2.rectangle(image, box_coords[0], box_coords[1], bg_color, cv2.FILLED) | |
cv2.putText(image, text, (text_x, text_y + text_size[1]), font, font_scale, color, thickness) | |
# Real-time feedback for single rep | |
def analyze_single_rep(rep, rep_data): | |
"""Provide actionable feedback for a single rep.""" | |
feedback = [] | |
avg_rom = np.mean([r["ROM"] for r in rep_data]) | |
avg_tempo = np.mean([r["Tempo"] for r in rep_data]) | |
avg_smoothness = np.mean([r["Smoothness"] for r in rep_data]) | |
if rep["ROM"] < avg_rom * 0.8: | |
feedback.append("Extend arm more") | |
if rep["Tempo"] < avg_tempo * 0.8: | |
feedback.append("Slow down") | |
if rep["Smoothness"] > avg_smoothness * 1.2: | |
feedback.append("Move smoothly") | |
return " | ".join(feedback) if feedback else "Good rep!" | |
# Post-workout feedback function with Isolation Forest | |
def analyze_workout_with_isolation_forest(rep_data): | |
if not rep_data: | |
print("No reps completed.") | |
return | |
print("\n--- Post-Workout Summary ---") | |
# Convert rep_data to a feature matrix | |
features = np.array([[rep["ROM"], rep["Tempo"], rep["Smoothness"]] for rep in rep_data]) | |
# Train Isolation Forest | |
model = IsolationForest(contamination=0.2, random_state=42) | |
predictions = model.fit_predict(features) | |
# Analyze reps | |
for i, (rep, prediction) in enumerate(zip(rep_data, predictions), 1): | |
status = "Good" if prediction == 1 else "Anomalous" | |
reason = [] | |
if prediction == -1: # If anomalous | |
if rep["ROM"] < np.mean(features[:, 0]) - np.std(features[:, 0]): | |
reason.append("Low ROM") | |
if rep["Tempo"] < np.mean(features[:, 1]) - np.std(features[:, 1]): | |
reason.append("Too Fast") | |
if rep["Smoothness"] > np.mean(features[:, 2]) + np.std(features[:, 2]): | |
reason.append("Jerky Movement") | |
reason_str = ", ".join(reason) if reason else "None" | |
print(f"Rep {i}: {status} | ROM: {rep['ROM']:.2f}, Tempo: {rep['Tempo']:.2f}s, Smoothness: {rep['Smoothness']:.2f} | Reason: {reason_str}") | |
# Main workout tracking function | |
def main(): | |
cap = cv2.VideoCapture(0) | |
counter = 0 # Rep counter | |
stage = None # Movement stage | |
max_reps = 10 | |
rep_data = [] # Store metrics for each rep | |
feedback = "" # Real-time feedback for the video feed | |
workout_start_time = None # Timer start | |
with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose: | |
while cap.isOpened(): | |
ret, frame = cap.read() | |
if not ret: | |
print("Failed to grab frame.") | |
break | |
# Initialize workout start time | |
if workout_start_time is None: | |
workout_start_time = time.time() | |
# Timer | |
elapsed_time = time.time() - workout_start_time | |
timer_text = f"Timer: {int(elapsed_time)}s" | |
# Convert frame to RGB | |
image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) | |
image.flags.writeable = False | |
results = pose.process(image) | |
# Convert back to BGR | |
image.flags.writeable = True | |
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) | |
# Check if pose landmarks are detected | |
if results.pose_landmarks: | |
landmarks = results.pose_landmarks.landmark | |
# Extract key joints | |
shoulder = [ | |
landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x, | |
landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y | |
] | |
elbow = [ | |
landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].x, | |
landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].y | |
] | |
wrist = [ | |
landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x, | |
landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y | |
] | |
# Check visibility of key joints | |
visibility_threshold = 0.5 | |
if (landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].visibility < visibility_threshold or | |
landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].visibility < visibility_threshold or | |
landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].visibility < visibility_threshold): | |
draw_text_with_background(image, "Ensure all key joints are visible!", (50, 150), | |
cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 255, 255), 5, (0, 0, 255)) | |
cv2.imshow('Workout Feedback', image) | |
continue # Skip processing if joints are not visible | |
# Calculate the angle | |
angle = calculate_angle(shoulder, elbow, wrist) | |
# Stage logic for counting reps | |
if angle > 160 and stage != "down": | |
stage = "down" | |
start_time = time.time() # Start timing for the rep | |
start_angle = angle # Record the starting angle | |
# Stop the program if it's the 10th rep down stage | |
if counter == max_reps: | |
print("Workout complete at rep 10 (down stage)!") | |
break | |
elif angle < 40 and stage == "down": | |
stage = "up" | |
counter += 1 | |
end_time = time.time() # End timing for the rep | |
end_angle = angle # Record the ending angle | |
# Calculate rep metrics | |
rom = start_angle - end_angle # Range of Motion | |
tempo = end_time - start_time # Duration of the rep | |
smoothness = np.std([start_angle, end_angle]) # Dummy smoothness metric | |
rep_data.append({"ROM": rom, "Tempo": tempo, "Smoothness": smoothness}) | |
# Analyze the rep using Isolation Forest | |
feedback = analyze_single_rep(rep_data[-1], rep_data) | |
# Wireframe color based on form | |
wireframe_color = (0, 255, 0) if stage == "up" or stage == "down" else (0, 0, 255) | |
# Draw wireframe | |
mp_drawing.draw_landmarks( | |
image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS, | |
mp_drawing.DrawingSpec(color=wireframe_color, thickness=5, circle_radius=4), | |
mp_drawing.DrawingSpec(color=wireframe_color, thickness=5, circle_radius=4) | |
) | |
# Display reps, stage, timer, and feedback | |
draw_text_with_background(image, f"Reps: {counter}", (50, 150), | |
cv2.FONT_HERSHEY_SIMPLEX, 3, (255, 255, 255), 5, (0, 0, 0)) | |
draw_text_with_background(image, f"Stage: {stage if stage else 'N/A'}", (50, 300), | |
cv2.FONT_HERSHEY_SIMPLEX, 3, (255, 255, 255), 5, (0, 0, 0)) | |
draw_text_with_background(image, timer_text, (1000, 50), # Timer in the top-right corner | |
cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 3, (0, 0, 0)) | |
draw_text_with_background(image, feedback, (50, 450), | |
cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 3, (0, 0, 0)) | |
# Show video feed | |
cv2.imshow('Workout Feedback', image) | |
# Break if 'q' is pressed | |
if cv2.waitKey(10) & 0xFF == ord('q'): | |
break | |
cap.release() | |
cv2.destroyAllWindows() | |
# Post-workout analysis | |
analyze_workout_with_isolation_forest(rep_data) | |
if __name__ == "__main__": | |
main() | |
def lateral_raise(): | |
import cv2 | |
import mediapipe as mp | |
import numpy as np | |
import time | |
from sklearn.ensemble import IsolationForest | |
# Mediapipe utilities | |
mp_drawing = mp.solutions.drawing_utils | |
mp_pose = mp.solutions.pose | |
# Function to calculate lateral raise angle | |
def calculate_angle_for_lateral_raise(shoulder, wrist): | |
""" | |
Calculate the angle of the arm relative to the horizontal plane | |
passing through the shoulder. | |
""" | |
horizontal_reference = np.array([1, 0]) # Horizontal vector | |
arm_vector = np.array([wrist[0] - shoulder[0], wrist[1] - shoulder[1]]) | |
dot_product = np.dot(horizontal_reference, arm_vector) | |
magnitude_reference = np.linalg.norm(horizontal_reference) | |
magnitude_arm = np.linalg.norm(arm_vector) | |
if magnitude_arm == 0 or magnitude_reference == 0: | |
return 0 | |
cos_angle = dot_product / (magnitude_reference * magnitude_arm) | |
angle = np.arccos(np.clip(cos_angle, -1.0, 1.0)) | |
return np.degrees(angle) | |
# Function to draw text with a background | |
def draw_text_with_background(image, text, position, font, font_scale, color, thickness, bg_color, padding=10): | |
text_size = cv2.getTextSize(text, font, font_scale, thickness)[0] | |
text_x, text_y = position | |
box_coords = ( | |
(text_x - padding, text_y - padding), | |
(text_x + text_size[0] + padding, text_y + text_size[1] + padding), | |
) | |
cv2.rectangle(image, box_coords[0], box_coords[1], bg_color, cv2.FILLED) | |
cv2.putText(image, text, (text_x, text_y + text_size[1]), font, font_scale, color, thickness) | |
# Function to check if all required joints are visible | |
def are_key_joints_visible(landmarks, visibility_threshold=0.5): | |
""" | |
Ensure that all required joints are visible based on their visibility scores. | |
""" | |
required_joints = [ | |
mp_pose.PoseLandmark.LEFT_SHOULDER.value, | |
mp_pose.PoseLandmark.RIGHT_SHOULDER.value, | |
mp_pose.PoseLandmark.LEFT_WRIST.value, | |
mp_pose.PoseLandmark.RIGHT_WRIST.value, | |
] | |
for joint in required_joints: | |
if landmarks[joint].visibility < visibility_threshold: | |
return False | |
return True | |
# Real-time feedback for single rep | |
def analyze_single_rep(rep, rep_data): | |
"""Provide actionable feedback for a single rep.""" | |
feedback = [] | |
# Calculate averages from previous reps | |
avg_rom = np.mean([r["ROM"] for r in rep_data]) if rep_data else 0 | |
avg_tempo = np.mean([r["Tempo"] for r in rep_data]) if rep_data else 0 | |
# Dynamic tempo thresholds | |
lower_tempo_threshold = 2.0 # Minimum grace threshold for faster tempo | |
upper_tempo_threshold = 9.0 # Maximum grace threshold for slower tempo | |
# Adjust thresholds after a few reps | |
if len(rep_data) > 3: | |
lower_tempo_threshold = max(2.0, avg_tempo * 0.7) | |
upper_tempo_threshold = min(9.0, avg_tempo * 1.3) | |
# Feedback for ROM | |
if rep["ROM"] < 30: # Minimum ROM threshold | |
feedback.append("Lift arm higher") | |
elif rep_data and rep["ROM"] < avg_rom * 0.8: | |
feedback.append("Increase ROM") | |
# Feedback for Tempo | |
if rep["Tempo"] < lower_tempo_threshold: # Tempo too fast | |
feedback.append("Slow down") | |
elif rep["Tempo"] > upper_tempo_threshold: # Tempo too slow | |
feedback.append("Speed up") | |
return feedback | |
# Post-workout feedback function | |
def analyze_workout_with_isolation_forest(rep_data): | |
if not rep_data: | |
print("No reps completed.") | |
return | |
print("\n--- Post-Workout Summary ---") | |
# Filter valid reps for recalculating thresholds | |
valid_reps = [rep for rep in rep_data if rep["ROM"] > 20] # Ignore very low ROM reps | |
if not valid_reps: | |
print("No valid reps to analyze.") | |
return | |
features = np.array([[rep["ROM"], rep["Tempo"]] for rep in valid_reps]) | |
avg_rom = np.mean(features[:, 0]) | |
avg_tempo = np.mean(features[:, 1]) | |
std_rom = np.std(features[:, 0]) | |
std_tempo = np.std(features[:, 1]) | |
# Adjusted bounds for anomalies | |
rom_lower_bound = max(20, avg_rom - std_rom * 2) | |
tempo_lower_bound = max(1.0, avg_tempo - std_tempo * 2) | |
tempo_upper_bound = min(10.0, avg_tempo + std_tempo * 2) | |
print(f"ROM Lower Bound: {rom_lower_bound}") | |
print(f"Tempo Bounds: {tempo_lower_bound}-{tempo_upper_bound}") | |
# Anomaly detection | |
for i, rep in enumerate(valid_reps, 1): | |
feedback = [] | |
if rep["ROM"] < rom_lower_bound: | |
feedback.append("Low ROM") | |
if rep["Tempo"] < tempo_lower_bound: | |
feedback.append("Too Fast") | |
elif rep["Tempo"] > tempo_upper_bound: | |
feedback.append("Too Slow") | |
if feedback: | |
print(f"Rep {i}: Anomalous | Feedback: {', '.join(feedback[:1])}") | |
# Use Isolation Forest for secondary anomaly detection | |
model = IsolationForest(contamination=0.1, random_state=42) # Reduced contamination | |
predictions = model.fit_predict(features) | |
for i, prediction in enumerate(predictions, 1): | |
if prediction == -1: # Outlier | |
print(f"Rep {i}: Isolation Forest flagged this rep as anomalous.") | |
# Main workout tracking function | |
def main(): | |
cap = cv2.VideoCapture(0) | |
counter = 0 # Rep counter | |
stage = None # Movement stage | |
feedback = [] # Real-time feedback for the video feed | |
rep_data = [] # Store metrics for each rep | |
angles_during_rep = [] # Track angles during a single rep | |
workout_start_time = None # Timer start | |
with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose: | |
while cap.isOpened(): | |
ret, frame = cap.read() | |
if not ret: | |
print("Failed to grab frame.") | |
break | |
# Initialize workout start time | |
if workout_start_time is None: | |
workout_start_time = time.time() | |
# Timer | |
elapsed_time = time.time() - workout_start_time | |
timer_text = f"Timer: {int(elapsed_time)}s" | |
# Convert the image to RGB | |
image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) | |
image.flags.writeable = False | |
results = pose.process(image) | |
# Convert back to BGR | |
image.flags.writeable = True | |
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) | |
# Check if pose landmarks are detected | |
if results.pose_landmarks: | |
landmarks = results.pose_landmarks.landmark | |
# Check if key joints are visible | |
if not are_key_joints_visible(landmarks): | |
draw_text_with_background( | |
image, "Ensure all joints are visible", (50, 50), | |
cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 255) | |
) | |
cv2.imshow("Lateral Raise Tracker", image) | |
continue | |
# Extract key joints | |
left_shoulder = [ | |
landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x, | |
landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y, | |
] | |
left_wrist = [ | |
landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x, | |
landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y, | |
] | |
# Calculate angle for lateral raise | |
angle = calculate_angle_for_lateral_raise(left_shoulder, left_wrist) | |
# Track angles during a rep | |
if stage == "up" or stage == "down": | |
angles_during_rep.append(angle) | |
# Stage logic for counting reps | |
if angle < 20 and stage != "down": | |
stage = "down" | |
if counter == 10: # Stop on the down stage of the 10th rep | |
print("Workout complete! 10 reps reached.") | |
break | |
# Calculate ROM for the completed rep | |
if len(angles_during_rep) > 1: | |
rom = max(angles_during_rep) - min(angles_during_rep) | |
else: | |
rom = 0.0 | |
tempo = elapsed_time | |
print(f"Rep {counter + 1}: ROM={rom:.2f}, Tempo={tempo:.2f}s") | |
# Record metrics for the rep | |
rep_data.append({ | |
"ROM": rom, | |
"Tempo": tempo, | |
}) | |
# Reset angles and timer for the next rep | |
angles_during_rep = [] | |
workout_start_time = time.time() # Reset timer | |
if 70 <= angle <= 110 and stage == "down": | |
stage = "up" | |
counter += 1 | |
# Analyze feedback | |
feedback = analyze_single_rep(rep_data[-1], rep_data) | |
# Determine wireframe color | |
wireframe_color = (0, 255, 0) if not feedback else (0, 0, 255) | |
# Display feedback | |
draw_text_with_background(image, f"Reps: {counter}", (50, 50), | |
cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0)) | |
draw_text_with_background(image, " | ".join(feedback), (50, 120), | |
cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0)) | |
draw_text_with_background(image, timer_text, (50, 190), | |
cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0)) | |
# Render detections with wireframe color | |
mp_drawing.draw_landmarks( | |
image, | |
results.pose_landmarks, | |
mp_pose.POSE_CONNECTIONS, | |
mp_drawing.DrawingSpec(color=wireframe_color, thickness=2, circle_radius=2), | |
mp_drawing.DrawingSpec(color=wireframe_color, thickness=2, circle_radius=2), | |
) | |
# Display the image | |
cv2.imshow("Lateral Raise Tracker", image) | |
if cv2.waitKey(10) & 0xFF == ord("q"): | |
break | |
cap.release() | |
cv2.destroyAllWindows() | |
# Post-workout analysis | |
analyze_workout_with_isolation_forest(rep_data) | |
if __name__ == "__main__": | |
main() | |
def shoulder_press(): | |
import cv2 | |
import mediapipe as mp | |
import numpy as np | |
import time | |
# Mediapipe utilities | |
mp_drawing = mp.solutions.drawing_utils | |
mp_pose = mp.solutions.pose | |
# Function to calculate angles | |
def calculate_angle(point_a, point_b, point_c): | |
vector_ab = np.array([point_a[0] - point_b[0], point_a[1] - point_b[1]]) | |
vector_cb = np.array([point_c[0] - point_b[0], point_c[1] - point_b[1]]) | |
dot_product = np.dot(vector_ab, vector_cb) | |
magnitude_ab = np.linalg.norm(vector_ab) | |
magnitude_cb = np.linalg.norm(vector_cb) | |
if magnitude_ab == 0 or magnitude_cb == 0: | |
return 0 | |
cos_angle = dot_product / (magnitude_ab * magnitude_cb) | |
angle = np.arccos(np.clip(cos_angle, -1.0, 1.0)) | |
return np.degrees(angle) | |
# Function to check if all required joints are visible | |
def are_key_joints_visible(landmarks, visibility_threshold=0.5): | |
required_joints = [ | |
mp_pose.PoseLandmark.LEFT_SHOULDER.value, | |
mp_pose.PoseLandmark.RIGHT_SHOULDER.value, | |
mp_pose.PoseLandmark.LEFT_ELBOW.value, | |
mp_pose.PoseLandmark.RIGHT_ELBOW.value, | |
mp_pose.PoseLandmark.LEFT_WRIST.value, | |
mp_pose.PoseLandmark.RIGHT_WRIST.value, | |
] | |
for joint in required_joints: | |
if landmarks[joint].visibility < visibility_threshold: | |
return False | |
return True | |
# Function to draw text with a background | |
def draw_text_with_background(image, text, position, font, font_scale, color, thickness, bg_color, padding=10): | |
text_size = cv2.getTextSize(text, font, font_scale, thickness)[0] | |
text_x, text_y = position | |
box_coords = ( | |
(text_x - padding, text_y - padding), | |
(text_x + text_size[0] + padding, text_y + text_size[1] + padding), | |
) | |
cv2.rectangle(image, box_coords[0], box_coords[1], bg_color, cv2.FILLED) | |
cv2.putText(image, text, (text_x, text_y + text_size[1]), font, font_scale, color, thickness) | |
# Main workout tracking function | |
def main(): | |
cap = cv2.VideoCapture(0) | |
counter = 0 | |
stage = None | |
feedback = "" | |
workout_start_time = None | |
rep_start_time = None | |
with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose: | |
while cap.isOpened(): | |
ret, frame = cap.read() | |
if not ret: | |
print("Failed to grab frame.") | |
break | |
# Initialize workout start time | |
if workout_start_time is None: | |
workout_start_time = time.time() | |
# Timer | |
elapsed_time = time.time() - workout_start_time | |
timer_text = f"Timer: {int(elapsed_time)}s" | |
# Convert the image to RGB | |
image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) | |
image.flags.writeable = False | |
results = pose.process(image) | |
# Convert back to BGR | |
image.flags.writeable = True | |
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) | |
# Check if pose landmarks are detected | |
if results.pose_landmarks: | |
landmarks = results.pose_landmarks.landmark | |
# Check if key joints are visible | |
if not are_key_joints_visible(landmarks): | |
feedback = "Ensure all joints are visible" | |
draw_text_with_background( | |
image, feedback, (50, 50), | |
cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 255) | |
) | |
cv2.imshow("Shoulder Press Tracker", image) | |
continue | |
# Extract key joints for both arms | |
left_shoulder = [ | |
landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x, | |
landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y, | |
] | |
left_elbow = [ | |
landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].x, | |
landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].y, | |
] | |
left_wrist = [ | |
landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x, | |
landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y, | |
] | |
right_shoulder = [ | |
landmarks[mp_pose.PoseLandmark.RIGHT_SHOULDER.value].x, | |
landmarks[mp_pose.PoseLandmark.RIGHT_SHOULDER.value].y, | |
] | |
right_elbow = [ | |
landmarks[mp_pose.PoseLandmark.RIGHT_ELBOW.value].x, | |
landmarks[mp_pose.PoseLandmark.RIGHT_ELBOW.value].y, | |
] | |
right_wrist = [ | |
landmarks[mp_pose.PoseLandmark.RIGHT_WRIST.value].x, | |
landmarks[mp_pose.PoseLandmark.RIGHT_WRIST.value].y, | |
] | |
# Calculate angles | |
left_elbow_angle = calculate_angle(left_shoulder, left_elbow, left_wrist) | |
right_elbow_angle = calculate_angle(right_shoulder, right_elbow, right_wrist) | |
# Check starting and ending positions | |
if 80 <= left_elbow_angle <= 100 and 80 <= right_elbow_angle <= 100 and stage != "down": | |
stage = "down" | |
if counter == 10: | |
feedback = "Workout complete! 10 reps done." | |
draw_text_with_background(image, feedback, (50, 120), | |
cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 255)) | |
cv2.imshow("Shoulder Press Tracker", image) | |
break | |
if rep_start_time is not None: | |
tempo = time.time() - rep_start_time | |
feedback = f"Rep {counter} completed! Tempo: {tempo:.2f}s" | |
rep_start_time = None | |
elif left_elbow_angle > 160 and right_elbow_angle > 160 and stage == "down": | |
stage = "up" | |
counter += 1 | |
rep_start_time = time.time() | |
# Wireframe color | |
wireframe_color = (0, 255, 0) if "completed" in feedback or "Good" in feedback else (0, 0, 255) | |
# Display feedback | |
draw_text_with_background(image, f"Reps: {counter}", (50, 50), | |
cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0)) | |
draw_text_with_background(image, feedback, (50, 120), | |
cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0)) | |
draw_text_with_background(image, timer_text, (50, 190), | |
cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255, 255, 255), 2, (0, 0, 0)) | |
# Render detections with wireframe color | |
mp_drawing.draw_landmarks( | |
image, | |
results.pose_landmarks, | |
mp_pose.POSE_CONNECTIONS, | |
mp_drawing.DrawingSpec(color=wireframe_color, thickness=2, circle_radius=2), | |
mp_drawing.DrawingSpec(color=wireframe_color, thickness=2, circle_radius=2), | |
) | |
# Display the image | |
cv2.imshow("Shoulder Press Tracker", image) | |
if cv2.waitKey(10) & 0xFF == ord("q"): | |
break | |
cap.release() | |
cv2.destroyAllWindows() | |
if __name__ == "__main__": | |
main() | |
# Streamlit configuration | |
st.set_page_config(page_title="Workout Tracker", page_icon="💪", layout="centered") | |
# Custom CSS for styling | |
st.markdown( | |
''' | |
<style> | |
body { | |
background-color: #001f3f; | |
color: #7FDBFF; | |
font-family: Arial, sans-serif; | |
} | |
.stButton > button { | |
background-color: #0074D9; | |
color: white; | |
border-radius: 5px; | |
padding: 10px 20px; | |
font-size: 18px; | |
} | |
.stButton > button:hover { | |
background-color: #7FDBFF; | |
color: #001f3f; | |
} | |
</style> | |
''', | |
unsafe_allow_html=True | |
) | |
# Title and Introduction | |
st.title("Workout Tracker") | |
st.markdown("Welcome to the **Workout Tracker App**! Select your desired workout below and receive real-time feedback as you exercise.") | |
# Check webcam availability | |
def check_webcam(): | |
try: | |
cap = cv2.VideoCapture(0) | |
if not cap.isOpened(): | |
st.error("Webcam not detected! Please ensure a webcam is connected.") | |
return False | |
cap.release() | |
return True | |
except Exception as e: | |
st.error(f"Error accessing webcam: {e}") | |
return False | |
# Workout Selection | |
workout_option = st.radio("Select Your Workout:", ["Bicep Curl", "Lateral Raise", "Shoulder Press"]) | |
# Start Button | |
if st.button("Start Workout"): | |
if not check_webcam(): | |
st.stop() | |
if workout_option == "Bicep Curl": | |
st.write("Launching Bicep Curl Tracker...") | |
bicep_curl() | |
elif workout_option == "Lateral Raise": | |
st.write("Launching Lateral Raise Tracker...") | |
lateral_raise() | |
elif workout_option == "Shoulder Press": | |
st.write("Launching Shoulder Press Tracker...") | |
shoulder_press() | |
# Footer | |
st.markdown("---") | |
st.markdown("**Note**: Close the workout window or press 'q' in the camera feed to stop the workout.") | |