import streamlit as st import cv2 import mediapipe as mp import numpy as np from streamlit_webrtc import webrtc_streamer, VideoTransformerBase import av import threading from dataclasses import dataclass from typing import List # Mediapipe setup mp_drawing = mp.solutions.drawing_utils mp_pose = mp.solutions.pose # Custom CSS st.markdown(""" """, unsafe_allow_html=True) @dataclass class ExerciseState: counter: int = 0 stage: str = None feedback: str = "" # Global state state = ExerciseState() lock = threading.Lock() def calculate_angle(a, b, c): """Calculate angle between three points.""" 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 def calculate_lateral_raise_angle(shoulder, wrist): """Calculate angle for lateral raise.""" horizontal_reference = np.array([1, 0]) 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) class VideoTransformer(VideoTransformerBase): def __init__(self): self.pose = mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) self.workout_type = "bicep_curl" # Default workout def process_bicep_curl(self, landmarks): """Process frame for bicep curl exercise.""" 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] angle = calculate_angle(shoulder, elbow, wrist) with lock: if angle > 160 and state.stage != "down": state.stage = "down" state.feedback = "Lower the weight" elif angle < 40 and state.stage == "down": state.stage = "up" state.counter += 1 state.feedback = f"Good rep! Count: {state.counter}" return angle def process_lateral_raise(self, landmarks): """Process frame for lateral raise exercise.""" shoulder = [landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x, landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y] wrist = [landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x, landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y] angle = calculate_lateral_raise_angle(shoulder, wrist) with lock: if angle < 20 and state.stage != "down": state.stage = "down" state.feedback = "Raise your arms" elif 70 <= angle <= 110 and state.stage == "down": state.stage = "up" state.counter += 1 state.feedback = f"Good rep! Count: {state.counter}" return angle def process_shoulder_press(self, landmarks): """Process frame for shoulder press exercise.""" 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] angle = calculate_angle(shoulder, elbow, wrist) with lock: if 80 <= angle <= 100 and state.stage != "down": state.stage = "down" state.feedback = "Press up!" elif angle > 160 and state.stage == "down": state.stage = "up" state.counter += 1 state.feedback = f"Good rep! Count: {state.counter}" return angle def recv(self, frame): img = frame.to_ndarray(format="bgr24") # Process the image image = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) results = self.pose.process(image) if results.pose_landmarks: # Draw pose landmarks mp_drawing.draw_landmarks( img, results.pose_landmarks, mp_pose.POSE_CONNECTIONS, mp_drawing.DrawingSpec(color=(0, 255, 0), thickness=2, circle_radius=2), mp_drawing.DrawingSpec(color=(0, 255, 0), thickness=2) ) # Process based on workout type if self.workout_type == "bicep_curl": angle = self.process_bicep_curl(results.pose_landmarks.landmark) elif self.workout_type == "lateral_raise": angle = self.process_lateral_raise(results.pose_landmarks.landmark) else: # shoulder_press angle = self.process_shoulder_press(results.pose_landmarks.landmark) # Draw angle and counter cv2.putText(img, f"Angle: {angle:.2f}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2) cv2.putText(img, f"Counter: {state.counter}", (10, 70), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2) cv2.putText(img, f"Feedback: {state.feedback}", (10, 110), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2) return av.VideoFrame.from_ndarray(img, format="bgr24") def main(): st.title("🏋️‍♂️ AI Workout Trainer") st.markdown("""
Welcome to your AI Workout Trainer! This app will help you perfect your form and track your exercises in real-time. Choose a workout and follow the feedback to improve your technique.
""", unsafe_allow_html=True) # Workout selection workout_options = { "Bicep Curl": "bicep_curl", "Lateral Raise": "lateral_raise", "Shoulder Press": "shoulder_press" } selected_workout = st.selectbox( "Choose your workout:", list(workout_options.keys()) ) # Reset state when workout changes if 'last_workout' not in st.session_state or st.session_state.last_workout != selected_workout: with lock: state.counter = 0 state.stage = None state.feedback = "" st.session_state.last_workout = selected_workout # Exercise descriptions descriptions = { "Bicep Curl": "Focus on keeping your upper arm still and curl the weight up smoothly.", "Lateral Raise": "Raise your arms to shoulder height, keeping them slightly bent.", "Shoulder Press": "Press the weight overhead, fully extending your arms." } st.markdown(f"""

{selected_workout}

{descriptions[selected_workout]}

""", unsafe_allow_html=True) # Initialize WebRTC streamer webrtc_ctx = webrtc_streamer( key="workout", video_transformer_factory=VideoTransformer, rtc_configuration={"iceServers": [{"urls": ["stun:stun.l.google.com:19302"]}]} ) if webrtc_ctx.video_transformer: webrtc_ctx.video_transformer.workout_type = workout_options[selected_workout] # Display feedback feedback_placeholder = st.empty() if webrtc_ctx.state.playing: feedback_placeholder.markdown(f"""

Current Exercise: {selected_workout}

Reps Completed: {state.counter}

Feedback: {state.feedback}

""", unsafe_allow_html=True) if __name__ == "__main__": main()