import streamlit as st
import cv2
import mediapipe as mp
import numpy as np
import time
import json
@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
def calculate_angle(a, b, c):
a = np.array(a) # First
b = np.array(b) # Mid
c = np.array(c) # End
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
def calculate_rep_score(knee_angles, hip_angles, back_angles):
ideal_knee_range = (90, 110)
ideal_hip_range = (80, 100)
ideal_back_range = (70, 90)
knee_score = sum(1 for angle in knee_angles if ideal_knee_range[0] <= angle <= ideal_knee_range[1]) / len(knee_angles) if knee_angles else 0
hip_score = sum(1 for angle in hip_angles if ideal_hip_range[0] <= angle <= ideal_hip_range[1]) / len(hip_angles) if hip_angles else 0
back_score = sum(1 for angle in back_angles if ideal_back_range[0] <= angle <= ideal_back_range[1]) / len(back_angles) if back_angles else 0
return (knee_score + hip_score + back_score) / 3
def generate_workout_report(rep_scores, form_issues, analysis_time):
if rep_scores:
overall_efficiency = sum(rep_scores) / len(rep_scores)
else:
overall_efficiency = 0
total_reps = len(rep_scores)
if total_reps > 0:
knee_percentage = form_issues['knees_bending_too_much'] / total_reps * 100
hip_percentage = form_issues['hips_bending_too_much'] / total_reps * 100
back_percentage = form_issues['back_leaning_too_much'] / total_reps * 100
else:
knee_percentage = 0
hip_percentage = 0
back_percentage = 0
report = f"""
Workout Report:
---------------
Total Squats: {total_reps}
Overall Workout Efficiency: {overall_efficiency * 100:.2f}%
Analysis Time: {analysis_time:.2f} seconds
Form Issues:
- Knees bending too much: {form_issues['knees_bending_too_much']} reps ({knee_percentage:.2f}% of reps)
- Hips bending too much: {form_issues['hips_bending_too_much']} reps ({hip_percentage:.2f}% of reps)
- Back leaning too much: {form_issues['back_leaning_too_much']} reps ({back_percentage:.2f}% of reps)
"""
return report
def load_lottiefile(filepath: str):
with open(filepath, "r") as f:
return json.load(f)
st.markdown("
Squat Form Analysis
", unsafe_allow_html=True)
col1, col2, col3 = st.columns([1,2,1])
with col2:
demo_button = st.button("Try Demo")
demo_video_path = "demo.mp4"
video_path = None
if demo_button:
video_path = demo_video_path
st.success("Demo video loaded successfully!")
st.write("Or upload your own 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}")
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()
squat_count_placeholder = st.empty()
feedback_placeholder = st.empty()
rep_scores = []
current_rep_angles = {'knee': [], 'hip': [], 'back': []}
form_issues = {
"knees_bending_too_much": 0,
"hips_bending_too_much": 0,
"back_leaning_too_much": 0
}
stage = None
squat_count = 0
start_time = time.time()
current_rep_issues = {
"knees_bending_too_much": False,
"hips_bending_too_much": False,
"back_leaning_too_much": False
}
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 is not None:
landmarks = results.pose_landmarks.landmark
shoulder = [landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x * width,
landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y * height]
hip = [landmarks[mp_pose.PoseLandmark.LEFT_HIP.value].x * width,
landmarks[mp_pose.PoseLandmark.LEFT_HIP.value].y * height]
knee = [landmarks[mp_pose.PoseLandmark.LEFT_KNEE.value].x * width,
landmarks[mp_pose.PoseLandmark.LEFT_KNEE.value].y * height]
ankle = [landmarks[mp_pose.PoseLandmark.LEFT_ANKLE.value].x * width,
landmarks[mp_pose.PoseLandmark.LEFT_ANKLE.value].y * height]
angle_knee = calculate_angle(hip, knee, ankle)
angle_hip = calculate_angle(shoulder, hip, knee)
angle_back = calculate_angle(shoulder, hip, ankle)
current_rep_angles['knee'].append(angle_knee)
current_rep_angles['hip'].append(angle_hip)
current_rep_angles['back'].append(angle_back)
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)
# Drawing on guide image with smaller markers
mp_drawing.draw_landmarks(
guide_image,
results.pose_landmarks,
mp_pose.POSE_CONNECTIONS,
mp_drawing.DrawingSpec(color=(0, 255, 0), thickness=1, circle_radius=2), # Green for upper arm
mp_drawing.DrawingSpec(color=(255, 0, 0), thickness=1, circle_radius=2) # Red for lower arm
)
if angle_knee < 90:
current_rep_issues["knees_bending_too_much"] = True
cv2.line(guide_image, tuple(np.multiply(knee, [1, 1]).astype(int)),
tuple(np.multiply(ankle, [1, 1]).astype(int)), (0, 255, 255), 2)
if angle_hip < 80:
current_rep_issues["hips_bending_too_much"] = True
cv2.line(guide_image, tuple(np.multiply(hip, [1, 1]).astype(int)),
tuple(np.multiply(knee, [1, 1]).astype(int)), (0, 0, 255), 2)
if angle_back < 70:
current_rep_issues["back_leaning_too_much"] = True
cv2.line(guide_image, tuple(np.multiply(shoulder, [1, 1]).astype(int)),
tuple(np.multiply(hip, [1, 1]).astype(int)), (255, 0, 0), 2)
# Rep counting logic
if angle_knee > 160 and stage != "UP":
stage = "UP"
for issue, occurred in current_rep_issues.items():
if occurred:
form_issues[issue] += 1
rep_scores.append(calculate_rep_score(
current_rep_angles['knee'],
current_rep_angles['hip'],
current_rep_angles['back']
))
current_rep_angles = {'knee': [], 'hip': [], 'back': []}
current_rep_issues = {k: False for k in current_rep_issues}
elif angle_knee <= 90 and stage == "UP":
stage = "DOWN"
squat_count += 1
squat_count_placeholder.write(f"Squat Count: {squat_count}")
# Update video streams
original_video.image(frame, channels="BGR", use_column_width=True)
points_video.image(points_image, channels="BGR", use_column_width=True)
guide_video.image(guide_image, channels="BGR", use_column_width=True)
cap.release()
end_time = time.time()
analysis_time = end_time - start_time
# Generate and display the report
report = generate_workout_report(rep_scores, form_issues, analysis_time)
feedback_placeholder.text(report)