Niharmahesh commited on
Commit
206a6f8
·
verified ·
1 Parent(s): 9a605b3

Upload 12 files

Browse files
Files changed (12) hide show
  1. C_pushup.mp4 +0 -0
  2. README.md +1 -13
  3. W_58.mp4 +0 -0
  4. W_pushup.mp4 +0 -0
  5. demo.mp4 +0 -0
  6. ltt.json +1 -0
  7. main.py +205 -0
  8. packages.txt +2 -0
  9. pages/pushup.py +215 -0
  10. pages/squat.py +214 -0
  11. pose_landmarks_index.png +0 -0
  12. requirements.txt +7 -0
C_pushup.mp4 ADDED
Binary file (207 kB). View file
 
README.md CHANGED
@@ -1,13 +1 @@
1
- ---
2
- title: Wokrout Eaasz
3
- emoji: 🐠
4
- colorFrom: red
5
- colorTo: yellow
6
- sdk: streamlit
7
- sdk_version: 1.40.1
8
- app_file: app.py
9
- pinned: false
10
- short_description: 'corrects your pushup form and squat form '
11
- ---
12
-
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
+ # squat_easz
 
 
 
 
 
 
 
 
 
 
 
 
W_58.mp4 ADDED
Binary file (69.8 kB). View file
 
W_pushup.mp4 ADDED
Binary file (213 kB). View file
 
demo.mp4 ADDED
Binary file (562 kB). View file
 
ltt.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"v":"5.6.1","fr":29.9700012207031,"ip":0,"op":154.000006272549,"w":1000,"h":1000,"nm":"gym 2","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Layer 5 Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0,"y":0},"t":27,"s":[517,497.838,0],"to":[10,0,0],"ti":[-10,0,0]},{"i":{"x":0,"y":0},"o":{"x":0.167,"y":0.167},"t":56,"s":[577,497.838,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0,"y":1},"o":{"x":0,"y":0},"t":70,"s":[577,497.838,0],"to":[20,0,0],"ti":[-20,0,0]},{"t":94.0000038286985,"s":[697,497.838,0]}],"ix":2},"a":{"a":0,"k":[703,497.838,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[18.777,0],[0,0],[0,18.777],[0,0],[-18.777,0],[0,-18.778],[0,0]],"o":[[0,0],[-18.777,0],[0,0],[0,-18.778],[18.777,0],[0,0],[0,18.777]],"v":[[0,136],[0,136],[-34,102],[-34,-102],[0,-136],[34,-102],[34,102]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[703,497.838],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":27.0000010997325,"op":178.000007250089,"st":-3.00000012219251,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Layer 4 Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0,"y":0},"t":27,"s":[454,497.838,0],"to":[-10,0,0],"ti":[10,0,0]},{"i":{"x":0,"y":0},"o":{"x":0.167,"y":0.167},"t":56,"s":[394,497.838,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0,"y":1},"o":{"x":0,"y":0},"t":70,"s":[394,497.838,0],"to":[-20,0,0],"ti":[20,0,0]},{"t":94.0000038286985,"s":[274,497.838,0]}],"ix":2},"a":{"a":0,"k":[268,497.838,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[18.778,0],[0,0],[0,18.777],[0,0],[-18.778,0],[0,-18.778],[0,0]],"o":[[0,0],[-18.778,0],[0,0],[0,-18.778],[18.778,0],[0,0],[0,18.777]],"v":[[0,136],[0,136],[-34,102],[-34,-102],[0,-136],[34,-102],[34,102]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[268,497.838],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":27.0000010997325,"op":178.000007250089,"st":-3.00000012219251,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Layer 6 Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.165,"y":1},"o":{"x":0.014,"y":0},"t":9,"s":[487.941,497.838,0],"to":[-6.589,0,0],"ti":[13.944,0,0]},{"i":{"x":0.011,"y":1},"o":{"x":0.167,"y":0},"t":27,"s":[455.935,497.838,0],"to":[-42.489,0,0],"ti":[0,0,0]},{"i":{"x":0,"y":1},"o":{"x":0,"y":0},"t":70,"s":[455.935,497.838,0],"to":[0,0,0],"ti":[0,0,0]},{"t":94.0000038286985,"s":[335.935,497.838,0]}],"ix":2},"a":{"a":0,"k":[327.941,497.838,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[20.955,0],[0,0],[0,20.954],[0,0],[-20.955,0],[0,-20.955],[0,0]],"o":[[0,0],[-20.955,0],[0,0],[0,-20.955],[20.955,0],[0,0],[0,20.954]],"v":[[0,187.176],[0,187.176],[-37.941,149.235],[-37.941,-149.234],[0,-187.176],[37.941,-149.234],[37.941,149.235]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[327.941,497.838],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":9.00000036657752,"op":160.000006516934,"st":9.00000036657752,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Layer 3 Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.165,"y":1},"o":{"x":0.014,"y":0},"t":9,"s":[486.941,497.838,0],"to":[6.507,0,0],"ti":[-13.771,0,0]},{"i":{"x":0.011,"y":1},"o":{"x":0.167,"y":0},"t":27,"s":[518.551,497.838,0],"to":[41.958,0,0],"ti":[0,0,0]},{"i":{"x":0,"y":1},"o":{"x":0,"y":0},"t":70,"s":[518.551,497.838,0],"to":[0,0,0],"ti":[0,0,0]},{"t":94.0000038286985,"s":[638.551,497.838,0]}],"ix":2},"a":{"a":0,"k":[644.941,497.838,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":8.00000032584668,"s":[100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[20.954,0],[0,0],[0,20.954],[0,0],[-20.954,0],[0,-20.955],[0,0]],"o":[[0,0],[-20.954,0],[0,0],[0,-20.955],[20.954,0],[0,0],[0,20.954]],"v":[[0,187.176],[0,187.176],[-37.941,149.235],[-37.941,-149.234],[0,-187.176],[37.941,-149.234],[37.941,149.235]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[644.941,497.838],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":160.000006516934,"st":-6.00000024438501,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Layer 2 Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0,"y":0},"t":89,"s":[-354.944,492.522,0],"to":[140,0,0],"ti":[-140,0,0]},{"t":106.000004317469,"s":[485.056,492.522,0]}],"ix":2},"a":{"a":0,"k":[485.056,492.522,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[18.225,0],[0,0],[0,18.226],[-18.226,0],[0,0],[0,-18.225]],"o":[[0,0],[-18.226,0],[0,-18.225],[0,0],[18.225,0],[0,18.226]],"v":[[296.901,33],[-296.9,33],[-329.9,0],[-296.9,-33],[296.901,-33],[329.9,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[485.056,492.522],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":9.00000036657752,"op":160.000006516934,"st":9.00000036657752,"bm":0},{"ddd":0,"ind":6,"ty":1,"nm":"White Solid 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[500,500,0],"ix":2},"a":{"a":0,"k":[500,500,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"sw":1000,"sh":1000,"sc":"#ffffff","ip":0,"op":154.000006272549,"st":0,"bm":0}],"markers":[]}
main.py ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import json
3
+ from streamlit_lottie import st_lottie
4
+
5
+ # Load Lottie animation from a JSON file
6
+ def load_lottiefile(filepath: str):
7
+ with open(filepath, "r") as f:
8
+ return json.load(f)
9
+ st.set_page_config(layout="wide")
10
+
11
+ st.markdown("<h1 style='text-align: center;'>Workout Easz</h1>", unsafe_allow_html=True)
12
+ lottie_animation = load_lottiefile("ltt.json")
13
+ st_lottie(lottie_animation, height=300, key="header_animation")
14
+ # Disclaimer
15
+ st.markdown("""
16
+ <div style="text-align: center; color: #FF0000;">
17
+ <strong>Disclaimer:</strong>
18
+ The performance of pose detection depends on the model's accuracy. As of now, our application uses a model with approximately 50% pose detection accuracy. Results may vary based on the quality of the video input and the model's limitations.
19
+ </div>
20
+ """, unsafe_allow_html=True)
21
+ # Introduction and Key Points
22
+ with st.expander("Introduction to MediaPipe"):
23
+ st.header("Introduction to MediaPipe")
24
+ st.write("""
25
+ MediaPipe is an open-source framework developed by Google for building multimodal machine learning pipelines. Its Pose solution is designed for high-fidelity body pose tracking, detecting 33 key body landmarks in real-time[1][2].
26
+ """)
27
+
28
+ with st.expander("Extraction of 33 Key Points"):
29
+ st.header("Extraction of 33 Key Points")
30
+ st.write("""
31
+ MediaPipe Pose detects 33 key body landmarks, each with x, y, z coordinates, and a visibility score. These landmarks represent various parts of the body, such as the nose, eyes, ears, shoulders, elbows, wrists, hips, knees, ankles, and feet[1][2].
32
+ """)
33
+ st.image("pose_landmarks_index.png", caption="MediaPipe Pose Landmarks", width=400)
34
+
35
+ # Squat Analysis
36
+ squat_expander = st.expander("Squat Form Analysis")
37
+ with squat_expander:
38
+ st.header("Squat Form Analysis")
39
+
40
+ # Key Landmarks for Squat Analysis
41
+ st.subheader("1. Key Landmarks for Squat Analysis")
42
+ st.write("""
43
+ For squat analysis, we focus on the following key landmarks:
44
+ - Shoulder (left and right)
45
+ - Hip (left and right)
46
+ - Knee (left and right)
47
+ - Ankle (left and right)
48
+
49
+ These landmarks are essential for calculating the joint angles critical for analyzing squat form[3].
50
+ """)
51
+
52
+ # Calculation of Joint Angles for Squat
53
+ st.subheader("2. Calculation of Joint Angles for Squat")
54
+
55
+ # Knee Angle
56
+ st.write("**a. Knee Angle**")
57
+ st.write("""
58
+ The knee angle is formed by the hip, knee, and ankle. It is calculated using the arctangent function:
59
+ """)
60
+ st.latex(r"\theta_{\text{knee}} = \left| \frac{180}{\pi} \times \left( \arctan2(y_{\text{ankle}} - y_{\text{knee}}, x_{\text{ankle}} - x_{\text{knee}}) - \arctan2(y_{\text{hip}} - y_{\text{knee}}, x_{\text{hip}} - x_{\text{knee}}) \right) \right|")
61
+
62
+ # Hip Angle
63
+ st.write("**b. Hip Angle**")
64
+ st.write("""
65
+ The hip angle is formed by the shoulder, hip, and knee. It is calculated similarly:
66
+ """)
67
+ st.latex(r"\theta_{\text{hip}} = \left| \frac{180}{\pi} \times \left( \arctan2(y_{\text{knee}} - y_{\text{hip}}, x_{\text{knee}} - x_{\text{hip}}) - \arctan2(y_{\text{shoulder}} - y_{\text{hip}}, x_{\text{shoulder}} - x_{\text{hip}}) \right) \right|")
68
+
69
+ # Back Angle
70
+ st.write("**c. Back Angle**")
71
+ st.write("""
72
+ The back angle is formed by the shoulder, hip, and ankle:
73
+ """)
74
+ st.latex(r"\theta_{\text{back}} = \left| \frac{180}{\pi} \times \left( \arctan2(y_{\text{ankle}} - y_{\text{hip}}, x_{\text{ankle}} - x_{\text{hip}}) - \arctan2(y_{\text{shoulder}} - y_{\text{hip}}, x_{\text{shoulder}} - x_{\text{hip}}) \right) \right|")
75
+
76
+ # Form Issue Detection for Squat
77
+ st.subheader("3. Form Issue Detection for Squat")
78
+
79
+ st.write("**Ideal Angle Ranges:**")
80
+ st.write("""
81
+ - Knee Angle: 90 to 110 degrees
82
+ - Hip Angle: 80 to 100 degrees
83
+ - Back Angle: 70 to 90 degrees
84
+ """)
85
+
86
+ st.write("**Detection of Deviations:**")
87
+ st.write("""
88
+ - Knees Bending Too Much: Knee angle < 90 degrees
89
+ - Hips Bending Too Much: Hip angle < 80 degrees
90
+ - Back Leaning Too Much: Back angle < 70 degrees
91
+ """)
92
+
93
+ # Providing Suggestions for Squat
94
+ st.subheader("4. Providing Suggestions for Squat")
95
+ st.write("""
96
+ Based on the detected form issues, specific suggestions are provided:
97
+
98
+ 1. Knees Bending Too Much:
99
+ - Suggestion: "Watch your knee bend. Ensure your knees do not bend excessively and maintain proper alignment with your toes."
100
+
101
+ 2. Hips Bending Too Much:
102
+ - Suggestion: "Keep your hips higher. Avoid excessive hip bending by maintaining a more upright posture."
103
+
104
+ 3. Back Leaning Too Much:
105
+ - Suggestion: "Maintain a straighter back. Focus on keeping your back straight and avoid leaning forward excessively."
106
+ """)
107
+
108
+ # Push-up Analysis
109
+ pushup_expander = st.expander("Push-up Form Analysis")
110
+ with pushup_expander:
111
+ st.header("Push-up Form Analysis")
112
+
113
+ # Key Landmarks for Push-up Analysis
114
+ st.subheader("1. Key Landmarks for Push-up Analysis")
115
+ st.write("""
116
+ For push-up analysis, we focus on the following key landmarks:
117
+ - Shoulder (left and right)
118
+ - Elbow (left and right)
119
+ - Wrist (left and right)
120
+ - Hip (left and right)
121
+ - Ankle (left and right)
122
+
123
+ These landmarks are crucial for calculating the joint angles and body alignment in push-ups[3].
124
+ """)
125
+
126
+ # Calculation of Joint Angles for Push-up
127
+ st.subheader("2. Calculation of Joint Angles for Push-up")
128
+
129
+ # Elbow Angle
130
+ st.write("**a. Elbow Angle**")
131
+ st.write("""
132
+ The elbow angle is formed by the shoulder, elbow, and wrist. It is calculated using:
133
+ """)
134
+ st.latex(r"\theta_{\text{elbow}} = \left| \frac{180}{\pi} \times \left( \arctan2(y_{\text{wrist}} - y_{\text{elbow}}, x_{\text{wrist}} - x_{\text{elbow}}) - \arctan2(y_{\text{shoulder}} - y_{\text{elbow}}, x_{\text{shoulder}} - x_{\text{elbow}}) \right) \right|")
135
+
136
+ # Body Alignment Angle
137
+ st.write("**b. Body Alignment Angle**")
138
+ st.write("""
139
+ The body alignment angle is formed by the shoulder, hip, and ankle. It is calculated as:
140
+ """)
141
+ st.latex(r"\theta_{\text{alignment}} = \left| \frac{180}{\pi} \times \left( \arctan2(y_{\text{ankle}} - y_{\text{hip}}, x_{\text{ankle}} - x_{\text{hip}}) - \arctan2(y_{\text{shoulder}} - y_{\text{hip}}, x_{\text{shoulder}} - x_{\text{hip}}) \right) \right|")
142
+
143
+ # Form Issue Detection for Push-up
144
+ st.subheader("3. Form Issue Detection for Push-up")
145
+
146
+ st.write("**Ideal Angle Ranges:**")
147
+ st.write("""
148
+ - Elbow Angle: 90 to 110 degrees (at the bottom of the push-up)
149
+ - Body Alignment Angle: 170 to 180 degrees (straight body)
150
+ """)
151
+
152
+ st.write("**Detection of Deviations:**")
153
+ st.write("""
154
+ - Incomplete Push-up: Elbow angle > 110 degrees at the bottom
155
+ - Elbow Flaring: Elbow angle < 90 degrees
156
+ - Sagging Hips: Body alignment angle < 170 degrees
157
+ """)
158
+
159
+ # Providing Suggestions for Push-up
160
+ st.subheader("4. Providing Suggestions for Push-up")
161
+ st.write("""
162
+ Based on the detected form issues, specific suggestions are provided:
163
+
164
+ 1. Incomplete Push-up:
165
+ - Suggestion: "Lower your body further. Aim to bring your chest closer to the ground for a full range of motion."
166
+
167
+ 2. Elbow Flaring:
168
+ - Suggestion: "Keep your elbows closer to your body. Tuck them in to engage your chest and triceps more effectively."
169
+
170
+ 3. Sagging Hips:
171
+ - Suggestion: "Maintain a straight body line. Engage your core to keep your hips aligned with your shoulders and ankles."
172
+ """)
173
+
174
+ # Conclusion
175
+ with st.expander("Conclusion",expanded=False):
176
+ st.write("""
177
+ By leveraging MediaPipe's pose estimation capabilities, this application can effectively analyze both squat and push-up forms. It calculates key joint angles and body alignments, compares them to ideal ranges, and provides specific suggestions for improvement. This approach enables users to receive real-time feedback on their exercise technique, helping them to perform exercises correctly and reduce the risk of injury[1][2][3].
178
+ """)
179
+ # Version Updates
180
+ with st.expander("Version Updates", expanded=False):
181
+ st.header("Version Updates")
182
+
183
+ st.subheader("Current Version")
184
+ st.write("""
185
+ **Current Update:** As of today, our application now includes both squat and push-up form analysis.
186
+ """)
187
+
188
+ st.subheader("Previous Versions")
189
+ st.write("""
190
+ **Initial Release:** Squat workout analysis, released on July 19th.
191
+ """)
192
+
193
+ st.write("""
194
+ Stay tuned for more updates as we continue to enhance our application's features and performance.
195
+ """)
196
+ # References
197
+ with st.expander("References"):
198
+ st.header("References")
199
+ st.write("""
200
+ [1] [MediaPipe Pose Documentation](https://github.com/google-ai-edge/mediapipe/blob/master/docs/solutions/pose.md)
201
+ [2] [Google AI Edge - MediaPipe Pose Landmarker](https://ai.google.dev/edge/mediapipe/solutions/vision/pose_landmarker)
202
+ [3] [Real-time Human Pose Estimation using MediaPipe](https://sigmoidal.ai/en/real-time-human-pose-estimation-using-mediapipe/)
203
+ [4] [Kaggle Dataset for Push-Up Analysis](https://www.kaggle.com/datasets/mohamadashrafsalama/pushup)
204
+ """)
205
+
packages.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ libgl1-mesa-glx
2
+ libglib2.0-0
pages/pushup.py ADDED
@@ -0,0 +1,215 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import cv2
3
+ import mediapipe as mp
4
+ import numpy as np
5
+ import time
6
+ import json
7
+
8
+ # Cache the MediaPipe Pose model
9
+ @st.cache_resource
10
+ def load_pose_model():
11
+ mp_pose = mp.solutions.pose
12
+ pose = mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5)
13
+ return mp_pose, pose
14
+
15
+ mp_pose, pose = load_pose_model()
16
+ mp_drawing = mp.solutions.drawing_utils
17
+
18
+ # Function to calculate angle
19
+ def calculate_angle(a, b, c):
20
+ a = np.array(a) # First point
21
+ b = np.array(b) # Mid point
22
+ c = np.array(c) # End point
23
+ radians = np.arctan2(c[1] - b[1], c[0] - b[0]) - np.arctan2(a[1] - b[1], a[0] - b[0])
24
+ angle = np.abs(radians * 180.0 / np.pi)
25
+ if angle > 180.0:
26
+ angle = 360 - angle
27
+ return angle
28
+
29
+ # Function to calculate body angle
30
+ def calculate_body_angle(shoulder, hip, ankle):
31
+ shoulder = np.array(shoulder)
32
+ hip = np.array(hip)
33
+ ankle = np.array(ankle)
34
+ radians = np.arctan2(ankle[1] - hip[1], ankle[0] - hip[0]) - np.arctan2(shoulder[1] - hip[1], shoulder[0] - hip[0])
35
+ angle = np.abs(radians * 180.0 / np.pi)
36
+ if angle > 180.0:
37
+ angle = 360 - angle
38
+ return angle
39
+
40
+ # Function to calculate rep score
41
+ def calculate_rep_score(elbow_angles, body_angles):
42
+ ideal_elbow_range = (75, 90)
43
+ ideal_body_range = (0, 10)
44
+ 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
45
+ 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
46
+ return (elbow_score + body_score) / 2
47
+
48
+ # Function to generate workout report
49
+ def generate_workout_report(rep_scores, form_issues, analysis_time):
50
+ overall_efficiency = sum(rep_scores) / len(rep_scores) if rep_scores else 0
51
+ total_reps = len(rep_scores)
52
+ elbows_not_bending_enough = form_issues['elbows_not_bending_enough']
53
+ body_not_straight = form_issues['body_not_straight']
54
+
55
+ report = f"""
56
+ **Workout Report:**
57
+ -----------------
58
+ - **Total Push-ups:** {total_reps}
59
+ - **Overall Workout Efficiency:** {overall_efficiency * 100:.2f}%
60
+ - **Analysis Time:** {analysis_time:.2f} seconds
61
+
62
+ **Form Issues:**
63
+ - Elbows not bending enough: {elbows_not_bending_enough} reps ({(elbows_not_bending_enough / total_reps) * 100:.2f}% of reps)
64
+ - Body not straight: {body_not_straight} reps ({(body_not_straight / total_reps) * 100:.2f}% of reps)
65
+ """
66
+ return report
67
+
68
+ # Load Lottie animation from a JSON file
69
+ def load_lottiefile(filepath: str):
70
+ with open(filepath, "r") as f:
71
+ return json.load(f)
72
+
73
+ # Streamlit app
74
+ st.markdown("<h1 style='text-align: center;'>Push-Up Form Analysis</h1>", unsafe_allow_html=True)
75
+
76
+ # Center the "Try Demo" button
77
+ col1, col2, col3 = st.columns([1, 2, 1])
78
+ with col2:
79
+ demo_button = st.button("Try Demo")
80
+
81
+ # Path to the demo video
82
+ demo_video_path = "W_58.mp4"
83
+
84
+ # Video selection logic
85
+ video_path = None
86
+ if demo_button:
87
+ video_path = demo_video_path
88
+ st.success("Demo video loaded successfully!")
89
+
90
+ st.write("Or upload your own video:")
91
+ # File uploader for user's video
92
+ uploaded_file = st.file_uploader("Choose a video file", type=["mp4", "mov", "avi"])
93
+ if uploaded_file is not None:
94
+ with open("temp_video.mp4", "wb") as f:
95
+ f.write(uploaded_file.getvalue())
96
+ video_path = "temp_video.mp4"
97
+ st.success("Your video uploaded successfully!")
98
+
99
+ if video_path:
100
+ cap = cv2.VideoCapture(video_path)
101
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
102
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
103
+ fps = cap.get(cv2.CAP_PROP_FPS)
104
+ st.write(f"Frames per second: {fps}")
105
+
106
+ # Create placeholders for the three video outputs with titles
107
+ col1, col2, col3 = st.columns(3)
108
+ with col1:
109
+ st.subheader("Original Video")
110
+ original_video = st.empty()
111
+ with col2:
112
+ st.subheader("Pose Points")
113
+ points_video = st.empty()
114
+ with col3:
115
+ st.subheader("Form Guide")
116
+ guide_video = st.empty()
117
+
118
+ feedback_placeholder = st.empty()
119
+
120
+ rep_scores = []
121
+ current_rep_angles = {'elbow': [], 'body': []}
122
+ form_issues = {
123
+ "elbows_not_bending_enough": 0,
124
+ "body_not_straight": 0
125
+ }
126
+ stage = "UP"
127
+ pushup_count = 0
128
+ start_time = time.time()
129
+
130
+ # Initialize form issues for the current rep
131
+ current_rep_issues = {
132
+ "elbows_not_bending_enough": False,
133
+ "body_not_straight": False
134
+ }
135
+
136
+ try:
137
+ while cap.isOpened():
138
+ ret, frame = cap.read()
139
+ if not ret:
140
+ break
141
+
142
+ image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
143
+ image.flags.writeable = False
144
+ results = pose.process(image)
145
+ image.flags.writeable = True
146
+ image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
147
+
148
+ if results.pose_landmarks:
149
+ try:
150
+ landmarks = results.pose_landmarks.landmark
151
+ shoulder = [landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x * width,
152
+ landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y * height]
153
+ elbow = [landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].x * width,
154
+ landmarks[mp_pose.PoseLandmark.LEFT_ELBOW.value].y * height]
155
+ wrist = [landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x * width,
156
+ landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y * height]
157
+ hip = [landmarks[mp_pose.PoseLandmark.LEFT_HIP.value].x * width,
158
+ landmarks[mp_pose.PoseLandmark.LEFT_HIP.value].y * height]
159
+ ankle = [landmarks[mp_pose.PoseLandmark.LEFT_ANKLE.value].x * width,
160
+ landmarks[mp_pose.PoseLandmark.LEFT_ANKLE.value].y * height]
161
+
162
+ angle_elbow = calculate_angle(shoulder, elbow, wrist)
163
+ angle_body = calculate_body_angle(shoulder, hip, ankle)
164
+
165
+ current_rep_angles['elbow'].append(angle_elbow)
166
+ current_rep_angles['body'].append(angle_body)
167
+
168
+ points_image = np.zeros((height, width, 3), dtype=np.uint8)
169
+ guide_image = np.zeros((height, width, 3), dtype=np.uint8)
170
+ mp_drawing.draw_landmarks(points_image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS)
171
+ mp_drawing.draw_landmarks(guide_image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS)
172
+
173
+ if angle_elbow < 75:
174
+ current_rep_issues["elbows_not_bending_enough"] = True
175
+ cv2.line(guide_image, tuple(np.multiply(elbow, [1, 1]).astype(int)),
176
+ tuple(np.multiply(wrist, [1, 1]).astype(int)), (0, 255, 255), 5) # Yellow for elbow issues
177
+ if angle_body > 10:
178
+ current_rep_issues["body_not_straight"] = True
179
+ cv2.line(guide_image, tuple(np.multiply(shoulder, [1, 1]).astype(int)),
180
+ tuple(np.multiply(hip, [1, 1]).astype(int)), (0, 0, 255), 5) # Red for body issues
181
+
182
+ if angle_elbow > 90 and stage != "UP":
183
+ stage = "UP"
184
+ # Count issues for the completed rep
185
+ for issue, occurred in current_rep_issues.items():
186
+ if occurred:
187
+ form_issues[issue] += 1
188
+ # Reset current rep issues
189
+ current_rep_issues = {k: False for k in current_rep_issues}
190
+
191
+ if angle_elbow < 90 and stage == "UP":
192
+ stage = "DOWN"
193
+ pushup_count += 1
194
+ rep_score = calculate_rep_score(current_rep_angles['elbow'], current_rep_angles['body'])
195
+ rep_scores.append(rep_score)
196
+ current_rep_angles = {'elbow': [], 'body': []}
197
+
198
+ # Display the videos
199
+ original_video.image(image, channels="BGR")
200
+ points_video.image(points_image, channels="BGR")
201
+ guide_video.image(guide_image, channels="BGR")
202
+ except AttributeError as e:
203
+ st.error(f"Error processing frame: {e}")
204
+ else:
205
+ st.warning("No pose landmarks detected in this frame.")
206
+ except Exception as e:
207
+ st.error(f"Error occurred: {e}")
208
+ finally:
209
+ cap.release()
210
+
211
+ analysis_time = time.time() - start_time
212
+ report = generate_workout_report(rep_scores, form_issues, analysis_time)
213
+ feedback_placeholder.markdown(report)
214
+
215
+ #st.balloons() # Add a fun animation when the analysis is done
pages/squat.py ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import cv2
3
+ import mediapipe as mp
4
+ import numpy as np
5
+ import time
6
+ import json
7
+
8
+ @st.cache_resource
9
+ def load_pose_model():
10
+ mp_pose = mp.solutions.pose
11
+ pose = mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5)
12
+ return mp_pose, pose
13
+
14
+ mp_pose, pose = load_pose_model()
15
+ mp_drawing = mp.solutions.drawing_utils
16
+
17
+ def calculate_angle(a, b, c):
18
+ a = np.array(a) # First
19
+ b = np.array(b) # Mid
20
+ c = np.array(c) # End
21
+ radians = np.arctan2(c[1] - b[1], c[0] - b[0]) - np.arctan2(a[1] - b[1], a[0] - b[0])
22
+ angle = np.abs(radians * 180.0 / np.pi)
23
+ if angle > 180.0:
24
+ angle = 360 - angle
25
+ return angle
26
+
27
+ def calculate_rep_score(knee_angles, hip_angles, back_angles):
28
+ ideal_knee_range = (90, 110)
29
+ ideal_hip_range = (80, 100)
30
+ ideal_back_range = (70, 90)
31
+ 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
32
+ 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
33
+ 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
34
+ return (knee_score + hip_score + back_score) / 3
35
+
36
+ def generate_workout_report(rep_scores, form_issues, analysis_time):
37
+ if rep_scores:
38
+ overall_efficiency = sum(rep_scores) / len(rep_scores)
39
+ else:
40
+ overall_efficiency = 0
41
+
42
+ total_reps = len(rep_scores)
43
+
44
+ if total_reps > 0:
45
+ knee_percentage = form_issues['knees_bending_too_much'] / total_reps * 100
46
+ hip_percentage = form_issues['hips_bending_too_much'] / total_reps * 100
47
+ back_percentage = form_issues['back_leaning_too_much'] / total_reps * 100
48
+ else:
49
+ knee_percentage = 0
50
+ hip_percentage = 0
51
+ back_percentage = 0
52
+
53
+ report = f"""
54
+ Workout Report:
55
+ ---------------
56
+ Total Squats: {total_reps}
57
+ Overall Workout Efficiency: {overall_efficiency * 100:.2f}%
58
+ Analysis Time: {analysis_time:.2f} seconds
59
+ Form Issues:
60
+ - Knees bending too much: {form_issues['knees_bending_too_much']} reps ({knee_percentage:.2f}% of reps)
61
+ - Hips bending too much: {form_issues['hips_bending_too_much']} reps ({hip_percentage:.2f}% of reps)
62
+ - Back leaning too much: {form_issues['back_leaning_too_much']} reps ({back_percentage:.2f}% of reps)
63
+ """
64
+ return report
65
+
66
+ def load_lottiefile(filepath: str):
67
+ with open(filepath, "r") as f:
68
+ return json.load(f)
69
+
70
+ st.markdown("<h1 style='text-align: center;'>Squat Form Analysis</h1>", unsafe_allow_html=True)
71
+
72
+ col1, col2, col3 = st.columns([1,2,1])
73
+ with col2:
74
+ demo_button = st.button("Try Demo")
75
+
76
+ demo_video_path = "demo.mp4"
77
+
78
+ video_path = None
79
+ if demo_button:
80
+ video_path = demo_video_path
81
+ st.success("Demo video loaded successfully!")
82
+
83
+ st.write("Or upload your own video:")
84
+ uploaded_file = st.file_uploader("Choose a video file", type=["mp4", "mov", "avi"])
85
+ if uploaded_file is not None:
86
+ with open("temp_video.mp4", "wb") as f:
87
+ f.write(uploaded_file.getvalue())
88
+ video_path = "temp_video.mp4"
89
+ st.success("Your video uploaded successfully!")
90
+
91
+ if video_path:
92
+ cap = cv2.VideoCapture(video_path)
93
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
94
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
95
+ fps = cap.get(cv2.CAP_PROP_FPS)
96
+ st.write(f"Frames per second: {fps}")
97
+
98
+ col1, col2, col3 = st.columns(3)
99
+ with col1:
100
+ st.subheader("Original Video")
101
+ original_video = st.empty()
102
+ with col2:
103
+ st.subheader("Pose Points")
104
+ points_video = st.empty()
105
+ with col3:
106
+ st.subheader("Form Guide")
107
+ guide_video = st.empty()
108
+
109
+ squat_count_placeholder = st.empty()
110
+ feedback_placeholder = st.empty()
111
+
112
+ rep_scores = []
113
+ current_rep_angles = {'knee': [], 'hip': [], 'back': []}
114
+ form_issues = {
115
+ "knees_bending_too_much": 0,
116
+ "hips_bending_too_much": 0,
117
+ "back_leaning_too_much": 0
118
+ }
119
+ stage = None
120
+ squat_count = 0
121
+ start_time = time.time()
122
+
123
+ current_rep_issues = {
124
+ "knees_bending_too_much": False,
125
+ "hips_bending_too_much": False,
126
+ "back_leaning_too_much": False
127
+ }
128
+
129
+ while cap.isOpened():
130
+ ret, frame = cap.read()
131
+ if not ret:
132
+ break
133
+
134
+ image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
135
+ image.flags.writeable = False
136
+ results = pose.process(image)
137
+ image.flags.writeable = True
138
+ image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
139
+
140
+ if results.pose_landmarks is not None:
141
+ landmarks = results.pose_landmarks.landmark
142
+ shoulder = [landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x * width,
143
+ landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y * height]
144
+ hip = [landmarks[mp_pose.PoseLandmark.LEFT_HIP.value].x * width,
145
+ landmarks[mp_pose.PoseLandmark.LEFT_HIP.value].y * height]
146
+ knee = [landmarks[mp_pose.PoseLandmark.LEFT_KNEE.value].x * width,
147
+ landmarks[mp_pose.PoseLandmark.LEFT_KNEE.value].y * height]
148
+ ankle = [landmarks[mp_pose.PoseLandmark.LEFT_ANKLE.value].x * width,
149
+ landmarks[mp_pose.PoseLandmark.LEFT_ANKLE.value].y * height]
150
+
151
+ angle_knee = calculate_angle(hip, knee, ankle)
152
+ angle_hip = calculate_angle(shoulder, hip, knee)
153
+ angle_back = calculate_angle(shoulder, hip, ankle)
154
+
155
+ current_rep_angles['knee'].append(angle_knee)
156
+ current_rep_angles['hip'].append(angle_hip)
157
+ current_rep_angles['back'].append(angle_back)
158
+
159
+ points_image = np.zeros((height, width, 3), dtype=np.uint8)
160
+ guide_image = np.zeros((height, width, 3), dtype=np.uint8)
161
+ mp_drawing.draw_landmarks(points_image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS)
162
+
163
+ # Drawing on guide image with smaller markers
164
+ mp_drawing.draw_landmarks(
165
+ guide_image,
166
+ results.pose_landmarks,
167
+ mp_pose.POSE_CONNECTIONS,
168
+ mp_drawing.DrawingSpec(color=(0, 255, 0), thickness=1, circle_radius=2), # Green for upper arm
169
+ mp_drawing.DrawingSpec(color=(255, 0, 0), thickness=1, circle_radius=2) # Red for lower arm
170
+ )
171
+
172
+ if angle_knee < 90:
173
+ current_rep_issues["knees_bending_too_much"] = True
174
+ cv2.line(guide_image, tuple(np.multiply(knee, [1, 1]).astype(int)),
175
+ tuple(np.multiply(ankle, [1, 1]).astype(int)), (0, 255, 255), 2)
176
+ if angle_hip < 80:
177
+ current_rep_issues["hips_bending_too_much"] = True
178
+ cv2.line(guide_image, tuple(np.multiply(hip, [1, 1]).astype(int)),
179
+ tuple(np.multiply(knee, [1, 1]).astype(int)), (0, 0, 255), 2)
180
+ if angle_back < 70:
181
+ current_rep_issues["back_leaning_too_much"] = True
182
+ cv2.line(guide_image, tuple(np.multiply(shoulder, [1, 1]).astype(int)),
183
+ tuple(np.multiply(hip, [1, 1]).astype(int)), (255, 0, 0), 2)
184
+
185
+ # Rep counting logic
186
+ if angle_knee > 160 and stage != "UP":
187
+ stage = "UP"
188
+ for issue, occurred in current_rep_issues.items():
189
+ if occurred:
190
+ form_issues[issue] += 1
191
+ rep_scores.append(calculate_rep_score(
192
+ current_rep_angles['knee'],
193
+ current_rep_angles['hip'],
194
+ current_rep_angles['back']
195
+ ))
196
+ current_rep_angles = {'knee': [], 'hip': [], 'back': []}
197
+ current_rep_issues = {k: False for k in current_rep_issues}
198
+ elif angle_knee <= 90 and stage == "UP":
199
+ stage = "DOWN"
200
+ squat_count += 1
201
+ squat_count_placeholder.write(f"Squat Count: {squat_count}")
202
+
203
+ # Update video streams
204
+ original_video.image(frame, channels="BGR", use_column_width=True)
205
+ points_video.image(points_image, channels="BGR", use_column_width=True)
206
+ guide_video.image(guide_image, channels="BGR", use_column_width=True)
207
+
208
+ cap.release()
209
+ end_time = time.time()
210
+ analysis_time = end_time - start_time
211
+
212
+ # Generate and display the report
213
+ report = generate_workout_report(rep_scores, form_issues, analysis_time)
214
+ feedback_placeholder.text(report)
pose_landmarks_index.png ADDED
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ opencv-python-headless
2
+ mediapipe
3
+ numpy
4
+ streamlit
5
+ numpy
6
+ streamlit_webrtc
7
+ streamlit-lottie