|
|
import cv2 |
|
|
import mediapipe as mp |
|
|
import numpy as np |
|
|
import gradio as gr |
|
|
|
|
|
|
|
|
mp_drawing = mp.solutions.drawing_utils |
|
|
mp_pose = mp.solutions.pose |
|
|
|
|
|
|
|
|
counter = 0 |
|
|
stage = None |
|
|
|
|
|
|
|
|
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(radians * 180.0 / np.pi) |
|
|
if angle > 180.0: |
|
|
angle = 360 - angle |
|
|
return angle |
|
|
|
|
|
|
|
|
def process_frames(cap): |
|
|
global counter, stage |
|
|
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: |
|
|
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) |
|
|
try: |
|
|
landmarks = results.pose_landmarks.landmark |
|
|
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) |
|
|
cv2.putText(image, str(round(angle, 2)), |
|
|
tuple(np.multiply(elbow, [frame.shape[1], frame.shape[0]]).astype(int)), |
|
|
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2, cv2.LINE_AA) |
|
|
if angle > 160: |
|
|
stage = "down" |
|
|
if angle < 30 and stage == "down": |
|
|
stage = "up" |
|
|
counter += 1 |
|
|
except: |
|
|
pass |
|
|
cv2.rectangle(image, (0, 0), (225, 73), (245, 117, 16), -1) |
|
|
cv2.putText(image, 'REPS', (15, 12), |
|
|
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1, cv2.LINE_AA) |
|
|
cv2.putText(image, str(counter), (10, 60), |
|
|
cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 255, 255), 2, cv2.LINE_AA) |
|
|
cv2.putText(image, 'STAGE', (65, 12), |
|
|
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1, cv2.LINE_AA) |
|
|
cv2.putText(image, stage if stage else "", (60, 60), |
|
|
cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 255, 255), 2, cv2.LINE_AA) |
|
|
mp_drawing.draw_landmarks(image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS, |
|
|
mp_drawing.DrawingSpec(color=(245, 117, 66), thickness=2, circle_radius=2), |
|
|
mp_drawing.DrawingSpec(color=(245, 66, 230), thickness=2, circle_radius=2)) |
|
|
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) |
|
|
yield image_rgb, f"Reps: {counter}", f"Stage: {stage if stage else 'None'}" |
|
|
cap.release() |
|
|
|
|
|
|
|
|
def process_webcam(): |
|
|
global counter, stage |
|
|
counter = 0 |
|
|
stage = None |
|
|
cap = cv2.VideoCapture(0) |
|
|
for frame, reps, stage in process_frames(cap): |
|
|
yield frame, reps, stage |
|
|
|
|
|
|
|
|
def process_uploaded_video(video_path): |
|
|
global counter, stage |
|
|
counter = 0 |
|
|
stage = None |
|
|
cap = cv2.VideoCapture(video_path) |
|
|
for frame, reps, stage in process_frames(cap): |
|
|
yield frame, reps, stage |
|
|
|
|
|
|
|
|
with gr.Blocks() as demo: |
|
|
gr.Markdown("# Exercise Pose Detection") |
|
|
gr.Markdown("Choose an input source to track bicep curls and pose.") |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
|
|
|
with gr.Column(scale=1): |
|
|
input_source = gr.Dropdown(choices=["Use Webcam", "Upload Video"], label="Select Input Source", value="Use Webcam", elem_classes="small-dropdown") |
|
|
start_webcam_btn = gr.Button("Start Webcam", visible=True, elem_classes="small-button") |
|
|
video_input = gr.Video(label="Upload Video", visible=False, elem_classes="small-video-input") |
|
|
|
|
|
|
|
|
with gr.Column(scale=3): |
|
|
with gr.Row(): |
|
|
video_output = gr.Image(label="Pose Detection Feed", streaming=True, elem_classes="large-video") |
|
|
with gr.Column(): |
|
|
rep_count = gr.Textbox(label="Rep Count", elem_classes="small-textbox") |
|
|
stage_output = gr.Textbox(label="Stage", elem_classes="small-textbox") |
|
|
|
|
|
|
|
|
demo.css = """ |
|
|
.small-dropdown { |
|
|
max-width: 200px !important; |
|
|
} |
|
|
.small-button { |
|
|
max-width: 200px !important; |
|
|
padding: 5px !important; |
|
|
font-size: 14px !important; |
|
|
} |
|
|
.small-video-input { |
|
|
max-width: 200px !important; |
|
|
} |
|
|
.large-video { |
|
|
width: 100% !important; |
|
|
max-width: 800px !important; |
|
|
margin: 0 auto !important; |
|
|
} |
|
|
.small-textbox { |
|
|
max-width: 150px !important; |
|
|
height: 50px !important; |
|
|
margin: 10px 0 !important; |
|
|
} |
|
|
""" |
|
|
|
|
|
|
|
|
def toggle_inputs(source): |
|
|
if source == "Use Webcam": |
|
|
return gr.update(visible=True), gr.update(visible=False), gr.update(value=None), gr.update(value=None), gr.update(value=None) |
|
|
else: |
|
|
return gr.update(visible=False), gr.update(visible=True), gr.update(value=None), gr.update(value=None), gr.update(value=None) |
|
|
|
|
|
|
|
|
input_source.change( |
|
|
toggle_inputs, |
|
|
inputs=[input_source], |
|
|
outputs=[start_webcam_btn, video_input, video_output, rep_count, stage_output] |
|
|
) |
|
|
|
|
|
|
|
|
start_webcam_btn.click( |
|
|
process_webcam, |
|
|
inputs=None, |
|
|
outputs=[video_output, rep_count, stage_output], |
|
|
queue=True |
|
|
) |
|
|
|
|
|
|
|
|
video_input.change( |
|
|
process_uploaded_video, |
|
|
inputs=[video_input], |
|
|
outputs=[video_output, rep_count, stage_output], |
|
|
queue=True |
|
|
) |
|
|
|
|
|
demo.launch() |