Muscle_Memory / app.py
zforkash's picture
update 7
06ba967 verified
raw
history blame
9.16 kB
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("""
<style>
.main {
background: linear-gradient(135deg, #001f3f 0%, #00b4d8 100%);
}
.stButton > button {
background-color: #00b4d8;
color: white;
border: none;
padding: 0.5rem 2rem;
border-radius: 5px;
margin: 0.5rem;
transition: all 0.3s;
}
.stButton > button:hover {
background-color: #0077b6;
}
h1, h2, h3 {
color: #001f3f;
}
.workout-container {
background: rgba(0, 180, 216, 0.1);
padding: 2rem;
border-radius: 10px;
margin: 1rem 0;
}
.feedback-text {
background: rgba(0, 31, 63, 0.1);
padding: 1rem;
border-radius: 5px;
margin: 1rem 0;
}
</style>
""", 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("""
<div class='workout-container'>
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.
</div>
""", 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"""
<div class='workout-container'>
<h3>{selected_workout}</h3>
<p>{descriptions[selected_workout]}</p>
</div>
""", 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"""
<div class='feedback-text'>
<h4>Current Exercise: {selected_workout}</h4>
<p>Reps Completed: {state.counter}</p>
<p>Feedback: {state.feedback}</p>
</div>
""", unsafe_allow_html=True)
if __name__ == "__main__":
main()