import streamlit as st
import cv2
import mediapipe as mp
import numpy as np
import time
import json
# Cache the MediaPipe Pose model
@st.cache_resource
def load_pose_model():
mp_pose = mp.solutions.pose
pose = mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5)
return mp_pose, pose
mp_pose, pose = load_pose_model()
mp_drawing = mp.solutions.drawing_utils
# Function to calculate angle
def calculate_angle(a, b, c):
a = np.array(a) # First point
b = np.array(b) # Mid point
c = np.array(c) # End point
radians = np.arctan2(c[1] - b[1], c[0] - b[0]) - np.arctan2(a[1] - b[1], a[0] - b[0])
angle = np.abs(radians * 180.0 / np.pi)
if angle > 180.0:
angle = 360 - angle
return angle
# Function to calculate body angle
def calculate_body_angle(shoulder, hip, ankle):
shoulder = np.array(shoulder)
hip = np.array(hip)
ankle = np.array(ankle)
radians = np.arctan2(ankle[1] - hip[1], ankle[0] - hip[0]) - np.arctan2(shoulder[1] - hip[1], shoulder[0] - hip[0])
angle = np.abs(radians * 180.0 / np.pi)
if angle > 180.0:
angle = 360 - angle
return angle
# Function to calculate rep score
def calculate_rep_score(elbow_angles, body_angles):
ideal_elbow_range = (75, 90)
ideal_body_range = (0, 10)
elbow_score = sum(1 for angle in elbow_angles if ideal_elbow_range[0] <= angle <= ideal_elbow_range[1]) / len(elbow_angles) if elbow_angles else 0
body_score = sum(1 for angle in body_angles if ideal_body_range[0] <= angle <= ideal_body_range[1]) / len(body_angles) if body_angles else 0
return (elbow_score + body_score) / 2
# Function to generate workout report
def generate_workout_report(rep_scores, form_issues, analysis_time):
overall_efficiency = sum(rep_scores) / len(rep_scores) if rep_scores else 0
total_reps = len(rep_scores)
elbows_not_bending_enough = form_issues['elbows_not_bending_enough']
body_not_straight = form_issues['body_not_straight']
report = f"""
**Workout Report:**
-----------------
- **Total Push-ups:** {total_reps}
- **Overall Workout Efficiency:** {overall_efficiency * 100:.2f}%
- **Analysis Time:** {analysis_time:.2f} seconds
**Form Issues:**
- Elbows not bending enough: {elbows_not_bending_enough} reps ({(elbows_not_bending_enough / total_reps) * 100:.2f}% of reps)
- Body not straight: {body_not_straight} reps ({(body_not_straight / total_reps) * 100:.2f}% of reps)
"""
return report
# Load Lottie animation from a JSON file
def load_lottiefile(filepath: str):
with open(filepath, "r") as f:
return json.load(f)
# Streamlit app
st.markdown("
Push-Up Form Analysis
", unsafe_allow_html=True)
# Center the "Try Demo" button
col1, col2, col3 = st.columns([1, 2, 1])
with col2:
demo_button = st.button("Try Demo")
# Path to the demo video
demo_video_path = "W_58.mp4"
# Video selection logic
video_path = None
if demo_button:
video_path = demo_video_path
st.success("Demo video loaded successfully!")
st.write("Or upload your own video:")
# File uploader for user's video
uploaded_file = st.file_uploader("Choose a video file", type=["mp4", "mov", "avi"])
if uploaded_file is not None:
with open("temp_video.mp4", "wb") as f:
f.write(uploaded_file.getvalue())
video_path = "temp_video.mp4"
st.success("Your video uploaded successfully!")
if video_path:
cap = cv2.VideoCapture(video_path)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = cap.get(cv2.CAP_PROP_FPS)
st.write(f"Frames per second: {fps}")
# Create placeholders for the three video outputs with titles
col1, col2, col3 = st.columns(3)
with col1:
st.subheader("Original Video")
original_video = st.empty()
with col2:
st.subheader("Pose Points")
points_video = st.empty()
with col3:
st.subheader("Form Guide")
guide_video = st.empty()
feedback_placeholder = st.empty()
rep_scores = []
current_rep_angles = {'elbow': [], 'body': []}
form_issues = {
"elbows_not_bending_enough": 0,
"body_not_straight": 0
}
stage = "UP"
pushup_count = 0
start_time = time.time()
# Initialize form issues for the current rep
current_rep_issues = {
"elbows_not_bending_enough": False,
"body_not_straight": False
}
try:
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
image.flags.writeable = False
results = pose.process(image)
image.flags.writeable = True
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
if results.pose_landmarks:
try:
landmarks = results.pose_landmarks.landmark
shoulder = [landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x * width,
landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y * height]
elbow = [landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].x * width,
landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].y * height]
wrist = [landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x * width,
landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y * height]
hip = [landmarks[mp_pose.PoseLandmark.LEFT_HIP.value].x * width,
landmarks[mp_pose.PoseLandmark.LEFT_HIP.value].y * height]
ankle = [landmarks[mp_pose.PoseLandmark.LEFT_ANKLE.value].x * width,
landmarks[mp_pose.PoseLandmark.LEFT_ANKLE.value].y * height]
angle_elbow = calculate_angle(shoulder, elbow, wrist)
angle_body = calculate_body_angle(shoulder, hip, ankle)
current_rep_angles['elbow'].append(angle_elbow)
current_rep_angles['body'].append(angle_body)
points_image = np.zeros((height, width, 3), dtype=np.uint8)
guide_image = np.zeros((height, width, 3), dtype=np.uint8)
mp_drawing.draw_landmarks(points_image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS)
mp_drawing.draw_landmarks(guide_image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS)
if angle_elbow < 75:
current_rep_issues["elbows_not_bending_enough"] = True
cv2.line(guide_image, tuple(np.multiply(elbow, [1, 1]).astype(int)),
tuple(np.multiply(wrist, [1, 1]).astype(int)), (0, 255, 255), 5) # Yellow for elbow issues
if angle_body > 10:
current_rep_issues["body_not_straight"] = True
cv2.line(guide_image, tuple(np.multiply(shoulder, [1, 1]).astype(int)),
tuple(np.multiply(hip, [1, 1]).astype(int)), (0, 0, 255), 5) # Red for body issues
if angle_elbow > 90 and stage != "UP":
stage = "UP"
# Count issues for the completed rep
for issue, occurred in current_rep_issues.items():
if occurred:
form_issues[issue] += 1
# Reset current rep issues
current_rep_issues = {k: False for k in current_rep_issues}
if angle_elbow < 90 and stage == "UP":
stage = "DOWN"
pushup_count += 1
rep_score = calculate_rep_score(current_rep_angles['elbow'], current_rep_angles['body'])
rep_scores.append(rep_score)
current_rep_angles = {'elbow': [], 'body': []}
# Display the videos
original_video.image(image, channels="BGR")
points_video.image(points_image, channels="BGR")
guide_video.image(guide_image, channels="BGR")
except AttributeError as e:
st.error(f"Error processing frame: {e}")
else:
st.warning("No pose landmarks detected in this frame.")
except Exception as e:
st.error(f"Error occurred: {e}")
finally:
cap.release()
analysis_time = time.time() - start_time
report = generate_workout_report(rep_scores, form_issues, analysis_time)
feedback_placeholder.markdown(report)
#st.balloons() # Add a fun animation when the analysis is done