ggirishg commited on
Commit
44be332
·
verified ·
1 Parent(s): 2df7b06

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +236 -357
app.py CHANGED
@@ -1,33 +1,49 @@
1
- import subprocess
2
-
3
- # Ensure setup.sh is executable and then run it using bash
4
- subprocess.run(['chmod', '+x', 'setup.sh'])
5
- subprocess.run(['bash', 'setup.sh'], check=True)
6
-
7
  import streamlit as st
8
  import cv2
9
  import mediapipe as mp
10
  import numpy as np
11
  import tempfile
12
- import os
 
13
 
14
  # Initialize MediaPipe Pose
15
  mp_pose = mp.solutions.pose
16
- pose = mp_pose.Pose(static_image_mode=False, model_complexity=1, enable_segmentation=True, min_detection_confidence=0.5, min_tracking_confidence=0.5)
17
- mp_drawing = mp.solutions.drawing_utils
 
 
 
 
 
18
 
19
  def calculate_angle_between_vectors(v1, v2):
 
 
 
20
  unit_vector_1 = v1 / np.linalg.norm(v1)
21
  unit_vector_2 = v2 / np.linalg.norm(v2)
22
  dot_product = np.dot(unit_vector_1, unit_vector_2)
23
- angle = np.arccos(dot_product)
24
  return np.degrees(angle)
25
 
26
  def process_video(video_path):
27
  cap = cv2.VideoCapture(video_path)
28
- output_dir = tempfile.mkdtemp()
29
 
30
- current_phase = "Not Setup phase"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  prev_wrist_left_y = None
32
  prev_wrist_right_y = None
33
  top_backswing_detected = False
@@ -37,13 +53,17 @@ def process_video(video_path):
37
  mid_downswing_frame = -2
38
  ball_impact_frame = -2
39
 
 
 
 
40
  BALL_IMPACT_DURATION = 2 # Duration in frames to display Ball Impact phase
41
 
42
  MIN_MOVEMENT_THRESHOLD = 0.01
43
  HIP_NEAR_THRESHOLD = 0.05
44
  MID_SWING_THRESHOLD = 0.05
45
 
46
- saved_phases = set()
 
47
 
48
  while cap.isOpened():
49
  ret, frame = cap.read()
@@ -53,15 +73,8 @@ def process_video(video_path):
53
  frame_no = int(cap.get(cv2.CAP_PROP_POS_FRAMES))
54
  image_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
55
  result = pose.process(image_rgb)
56
- h, w, c = frame.shape
57
 
58
  if result.pose_landmarks:
59
- mp_drawing.draw_landmarks(
60
- frame, result.pose_landmarks, mp_pose.POSE_CONNECTIONS,
61
- mp_drawing.DrawingSpec(color=(255, 0, 0), thickness=2, circle_radius=2),
62
- mp_drawing.DrawingSpec(color=(255, 0, 255), thickness=2, circle_radius=2)
63
- )
64
-
65
  landmarks = result.pose_landmarks.landmark
66
  wrist_left_y = landmarks[mp_pose.PoseLandmark.LEFT_WRIST].y
67
  wrist_right_y = landmarks[mp_pose.PoseLandmark.RIGHT_WRIST].y
@@ -74,369 +87,235 @@ def process_video(video_path):
74
  shoulder_y_avg = (shoulder_left_y + shoulder_right_y) / 2
75
  mid_swing_y = (shoulder_y_avg + hip_y_avg) / 2
76
 
77
- # Ensure the current phase persists for a few more milliseconds if it's Ball Impact
78
- if ball_impact_detected and frame_no <= ball_impact_frame + BALL_IMPACT_DURATION:
79
- current_phase = "Ball impact phase"
80
- elif (abs(wrist_left_y - hip_y_avg) < HIP_NEAR_THRESHOLD and abs(wrist_right_y - hip_y_avg) < HIP_NEAR_THRESHOLD):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  if prev_wrist_left_y is not None and prev_wrist_right_y is not None:
82
- if (abs(wrist_left_y - prev_wrist_left_y) < MIN_MOVEMENT_THRESHOLD and abs(wrist_right_y - prev_wrist_right_y) < MIN_MOVEMENT_THRESHOLD):
 
83
  if mid_downswing_detected and frame_no > mid_downswing_frame:
84
- current_phase = "Ball impact phase"
85
  ball_impact_detected = True
86
  ball_impact_frame = frame_no
87
  else:
88
- current_phase = "Setup phase"
89
  top_backswing_detected = False
90
  mid_downswing_detected = False
91
  else:
92
  current_phase = ""
93
  else:
94
  if mid_downswing_detected and frame_no > mid_downswing_frame:
95
- current_phase = "Ball impact phase"
96
  ball_impact_detected = True
97
  ball_impact_frame = frame_no
98
  else:
99
- current_phase = "Setup phase"
100
  top_backswing_detected = False
101
  mid_downswing_detected = False
102
- elif (abs(wrist_left_y - mid_swing_y) < MID_SWING_THRESHOLD and abs(wrist_right_y - mid_swing_y) < MID_SWING_THRESHOLD and not top_backswing_detected and not ball_impact_detected):
103
- current_phase = "Mid backswing phase"
104
- elif (wrist_left_y < shoulder_left_y and wrist_right_y < shoulder_right_y and not mid_downswing_detected and not ball_impact_detected):
105
- current_phase = "Top backswing phase"
106
- top_backswing_detected = True
107
- top_backswing_frame = frame_no
108
- elif (abs(wrist_left_y - mid_swing_y) < MID_SWING_THRESHOLD and abs(wrist_right_y - mid_swing_y) < MID_SWING_THRESHOLD and top_backswing_detected and frame_no > top_backswing_frame):
109
- current_phase = "Mid downswing phase"
110
- mid_downswing_detected = True
111
- mid_downswing_frame = frame_no
112
- elif (wrist_left_y < shoulder_left_y and wrist_right_y < shoulder_right_y and ball_impact_detected and frame_no > ball_impact_frame):
113
- current_phase = "Follow through phase"
114
  else:
115
  current_phase = ""
116
 
117
  prev_wrist_left_y = wrist_left_y
118
  prev_wrist_right_y = wrist_right_y
119
 
120
- cv2.putText(frame, f"Phase: {current_phase}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2, cv2.LINE_AA)
 
 
 
 
 
121
 
122
- # Save the frame for each detected phase
123
- if current_phase and current_phase not in saved_phases:
124
- phase_filename = os.path.join(output_dir, f"{current_phase.replace(' ', '_')}.png")
125
- cv2.imwrite(phase_filename, frame)
126
- saved_phases.add(current_phase)
 
127
 
128
  cap.release()
129
- cv2.destroyAllWindows()
130
  pose.close()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
 
132
- return output_dir
133
-
134
- st.title("Golf Swing Phase Detection")
135
- st.write("Upload a video to detect different phases of a golf swing.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
 
137
- video_file = st.file_uploader("Upload Video", type=["mp4", "avi", "mov", "mkv"])
 
138
 
139
- if video_file is not None:
140
- tfile = tempfile.NamedTemporaryFile(delete=False)
141
- tfile.write(video_file.read())
142
- tfile_path = tfile.name
143
-
144
- st.write("Processing video...")
145
- output_dir = process_video(tfile_path)
146
-
147
- st.write("Detected phases saved to:", output_dir)
148
- st.write("Example frames from detected phases:")
149
-
150
- for phase_image in os.listdir(output_dir):
151
- st.image(os.path.join(output_dir, phase_image), caption=phase_image)
152
- # import streamlit as st
153
- # import cv2
154
- # import mediapipe as mp
155
- # import numpy as np
156
- # import tempfile
157
- # import os
158
- # from collections import deque
159
-
160
- # # Initialize MediaPipe Pose
161
- # mp_pose = mp.solutions.pose
162
- # mp_drawing = mp.solutions.drawing_utils
163
-
164
- # # Define states for the state machine
165
- # SETUP = "Setup phase"
166
- # MID_BACKSWING = "Mid backswing phase"
167
- # TOP_BACKSWING = "Top backswing phase"
168
- # MID_DOWNSWING = "Mid downswing phase"
169
- # BALL_IMPACT = "Ball impact phase"
170
- # FOLLOW_THROUGH = "Follow through phase"
171
- # UNKNOWN = "Unknown"
172
-
173
- # # Parameters for logic
174
- # NUM_FRAMES_STABLE = 5 # Number of frames to confirm a state transition
175
- # VEL_THRESHOLD = 0.003 # Velocity threshold to confirm direction (tune as needed)
176
- # MID_POINT_RATIO = 0.5 # Ratio for mid-swing line (between shoulders and hips)
177
- # BALL_IMPACT_DURATION = 5 # Frames to keep Ball Impact state stable
178
-
179
- # def smooth_positions(positions, window=5):
180
- # """Simple smoothing by averaging the last `window` positions."""
181
- # if len(positions) < window:
182
- # return positions[-1]
183
- # arr = np.array(positions[-window:])
184
- # return np.mean(arr, axis=0)
185
-
186
- # def process_video(video_path):
187
- # pose = mp_pose.Pose(
188
- # static_image_mode=False,
189
- # model_complexity=1,
190
- # enable_segmentation=True,
191
- # min_detection_confidence=0.5,
192
- # min_tracking_confidence=0.5,
193
- # )
194
-
195
- # cap = cv2.VideoCapture(video_path)
196
- # output_dir = tempfile.mkdtemp()
197
-
198
- # # State machine variables
199
- # current_state = UNKNOWN
200
- # last_confirmed_state = UNKNOWN
201
- # state_confirmation_count = 0
202
-
203
- # # To store positions and smoothing
204
- # wrist_left_positions = deque(maxlen=30)
205
- # wrist_right_positions = deque(maxlen=30)
206
-
207
- # # For saving phases once
208
- # saved_phases = set()
209
-
210
- # # Reference positions (will be recorded from initial frames)
211
- # initial_hip_y = None
212
- # initial_shoulder_y = None
213
- # detected_initial_setup = False
214
-
215
- # # Variables to track top backswing peak
216
- # # We'll store the max height reached during backswing
217
- # max_wrist_height = None
218
- # top_backswing_reached = False
219
-
220
- # # For Ball impact stable frames
221
- # ball_impact_frame_no = -1
222
- # frame_count = 0
223
-
224
- # while cap.isOpened():
225
- # ret, frame = cap.read()
226
- # if not ret:
227
- # break
228
- # frame_count += 1
229
- # image_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
230
- # result = pose.process(image_rgb)
231
- # h, w, c = frame.shape
232
-
233
- # if result.pose_landmarks:
234
- # landmarks = result.pose_landmarks.landmark
235
- # # Extract relevant landmarks
236
- # wrist_left_y = landmarks[mp_pose.PoseLandmark.LEFT_WRIST].y
237
- # wrist_right_y = landmarks[mp_pose.PoseLandmark.RIGHT_WRIST].y
238
- # hip_left_y = landmarks[mp_pose.PoseLandmark.LEFT_HIP].y
239
- # hip_right_y = landmarks[mp_pose.PoseLandmark.RIGHT_HIP].y
240
- # shoulder_left_y = landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER].y
241
- # shoulder_right_y = landmarks[mp_pose.PoseLandmark.RIGHT_SHOULDER].y
242
-
243
- # hip_y_avg = (hip_left_y + hip_right_y) / 2
244
- # shoulder_y_avg = (shoulder_left_y + shoulder_right_y) / 2
245
-
246
- # # Record initial reference once at the start if not done
247
- # if initial_hip_y is None:
248
- # initial_hip_y = hip_y_avg
249
- # if initial_shoulder_y is None:
250
- # initial_shoulder_y = shoulder_y_avg
251
-
252
- # # Mid swing line (between shoulder and hip)
253
- # mid_swing_y = (shoulder_y_avg + hip_y_avg) / 2
254
-
255
- # # Append current positions
256
- # wrist_left_positions.append(wrist_left_y)
257
- # wrist_right_positions.append(wrist_right_y)
258
-
259
- # # Smooth positions
260
- # smoothed_left_y = smooth_positions(list(wrist_left_positions))
261
- # smoothed_right_y = smooth_positions(list(wrist_right_positions))
262
-
263
- # # Average wrist height
264
- # avg_wrist_y = (smoothed_left_y + smoothed_right_y) / 2.0
265
-
266
- # # Compute velocity as difference from last frame (if possible)
267
- # if len(wrist_left_positions) > 1:
268
- # vel_wrist_y = avg_wrist_y - ((wrist_left_positions[-2] + wrist_right_positions[-2]) / 2.0)
269
- # else:
270
- # vel_wrist_y = 0.0
271
-
272
- # # Define conditions for each phase based on relative positions and movement:
273
- # # We'll define logical checks:
274
- # # 1. Setup: wrists near hip level and minimal movement
275
- # # 2. Mid backswing: wrists have started moving upward from hip level toward shoulder
276
- # # 3. Top backswing: wrists reach a peak (highest point) and start descending
277
- # # 4. Mid downswing: wrists cross mid line going downward
278
- # # 5. Ball impact: wrists around hip level again with downward movement stabilized
279
- # # 6. Follow through: wrists go above shoulders again after impact
280
-
281
- # # Detect initial Setup:
282
- # # Setup if wrists near hips and minimal vertical movement for a few frames
283
- # near_hip = abs(avg_wrist_y - initial_hip_y) < 0.05
284
- # low_velocity = abs(vel_wrist_y) < VEL_THRESHOLD
285
-
286
- # # Mid Backswing check:
287
- # # Movement upward from hip towards shoulder
288
- # # Condition: wrist higher than hip but not yet at top, positive upward velocity
289
- # going_up = (vel_wrist_y < -VEL_THRESHOLD) # remember y is normalized [0..1], top is smaller
290
- # mid_backswing_cond = (avg_wrist_y < mid_swing_y) and (avg_wrist_y < initial_hip_y) and going_up
291
-
292
- # # Top Backswing:
293
- # # Detecting a peak: we track max height during backswing.
294
- # # If currently going_up, update max_wrist_height.
295
- # # Once we detect a change from going_up to going_down, we mark top backswing.
296
- # if max_wrist_height is None or avg_wrist_y < max_wrist_height:
297
- # max_wrist_height = avg_wrist_y
298
- # going_down = (vel_wrist_y > VEL_THRESHOLD)
299
- # # Top backswing if we previously were going up and now start going down
300
- # # and wrists are near or above shoulder level (or at least higher than mid swing).
301
- # top_backswing_cond = top_backswing_reached is False and going_down and (max_wrist_height < mid_swing_y)
302
-
303
- # # Mid Downswing:
304
- # # After top backswing, as we go down again and cross mid swing line downward
305
- # mid_downswing_cond = top_backswing_reached and (avg_wrist_y > mid_swing_y) and going_down
306
-
307
- # # Ball Impact:
308
- # # When wrists return to near hip level while still going down or stabilizing
309
- # # We'll consider ball impact when avg_wrist_y ~ hip level and we've come down from top backswing
310
- # ball_impact_cond = top_backswing_reached and (abs(avg_wrist_y - initial_hip_y) < 0.05) and going_down
311
-
312
- # # Follow Through:
313
- # # After impact, if wrists go up again above shoulder level
314
- # follow_through_cond = (ball_impact_frame_no > 0 and frame_count > ball_impact_frame_no + BALL_IMPACT_DURATION
315
- # and avg_wrist_y < mid_swing_y and going_up)
316
-
317
- # # State machine transitions:
318
- # desired_state = UNKNOWN
319
-
320
- # # Prioritize states in a logical order
321
- # if current_state == UNKNOWN:
322
- # # Try to find a stable setup as a start
323
- # if near_hip and low_velocity:
324
- # desired_state = SETUP
325
- # else:
326
- # desired_state = UNKNOWN
327
-
328
- # elif current_state == SETUP:
329
- # # From setup, if we start going up and cross mid line:
330
- # if mid_backswing_cond:
331
- # desired_state = MID_BACKSWING
332
- # else:
333
- # desired_state = SETUP
334
-
335
- # elif current_state == MID_BACKSWING:
336
- # # If we detect a top backswing condition (peak reached):
337
- # if top_backswing_cond:
338
- # desired_state = TOP_BACKSWING
339
- # top_backswing_reached = True
340
- # else:
341
- # desired_state = MID_BACKSWING
342
-
343
- # elif current_state == TOP_BACKSWING:
344
- # # After top backswing, going down past mid line means mid downswing
345
- # if mid_downswing_cond:
346
- # desired_state = MID_DOWNSWING
347
- # else:
348
- # desired_state = TOP_BACKSWING
349
-
350
- # elif current_state == MID_DOWNSWING:
351
- # # Reaching ball impact condition
352
- # if ball_impact_cond:
353
- # desired_state = BALL_IMPACT
354
- # ball_impact_frame_no = frame_count
355
- # else:
356
- # desired_state = MID_DOWNSWING
357
-
358
- # elif current_state == BALL_IMPACT:
359
- # # After ball impact, potentially follow through if going upward again
360
- # if follow_through_cond:
361
- # desired_state = FOLLOW_THROUGH
362
- # else:
363
- # # Keep showing ball impact for a few frames
364
- # if frame_count <= ball_impact_frame_no + BALL_IMPACT_DURATION:
365
- # desired_state = BALL_IMPACT
366
- # else:
367
- # desired_state = BALL_IMPACT # could default to unknown if no follow through detected
368
-
369
- # elif current_state == FOLLOW_THROUGH:
370
- # # Final phase, usually no more transitions expected
371
- # desired_state = FOLLOW_THROUGH
372
-
373
- # # If we are UNKNOWN and can't find a better match:
374
- # if desired_state == UNKNOWN:
375
- # # Try to match any phase heuristics if no known logic fits
376
- # if near_hip and low_velocity:
377
- # desired_state = SETUP
378
- # else:
379
- # desired_state = UNKNOWN
380
-
381
- # # Confirm state transitions only if stable for several frames
382
- # if desired_state == current_state:
383
- # state_confirmation_count += 1
384
- # else:
385
- # # Different desired state
386
- # if desired_state != UNKNOWN:
387
- # # Start counting from scratch for the new state
388
- # current_state = desired_state
389
- # state_confirmation_count = 1
390
- # else:
391
- # # If unknown requested, just switch immediately
392
- # current_state = UNKNOWN
393
- # state_confirmation_count = 1
394
-
395
- # # Once stable enough in a state, set last_confirmed_state
396
- # if state_confirmation_count >= NUM_FRAMES_STABLE:
397
- # last_confirmed_state = current_state
398
-
399
- # # Draw Landmarks
400
- # mp_drawing.draw_landmarks(
401
- # frame,
402
- # result.pose_landmarks,
403
- # mp_pose.POSE_CONNECTIONS,
404
- # mp_drawing.DrawingSpec(color=(255, 0, 0), thickness=2, circle_radius=2),
405
- # mp_drawing.DrawingSpec(color=(255, 0, 255), thickness=2, circle_radius=2)
406
- # )
407
-
408
- # cv2.putText(frame, f"Phase: {last_confirmed_state}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 2, cv2.LINE_AA)
409
-
410
- # # Save the frame for each detected phase (once)
411
- # if last_confirmed_state not in saved_phases and last_confirmed_state != UNKNOWN:
412
- # phase_filename = os.path.join(output_dir, f"{last_confirmed_state.replace(' ', '_')}.png")
413
- # cv2.imwrite(phase_filename, frame)
414
- # saved_phases.add(last_confirmed_state)
415
-
416
- # cv2.imshow("Pose Estimation", frame)
417
- # if cv2.waitKey(1) & 0xFF == ord('q'):
418
- # break
419
-
420
- # cap.release()
421
- # cv2.destroyAllWindows()
422
- # pose.close()
423
- # return output_dir
424
-
425
- # st.title("Golf Swing Phase Detection - Improved Logic")
426
- # st.write("Upload a video to detect different phases of a golf swing with improved accuracy.")
427
-
428
- # video_file = st.file_uploader("Upload Video", type=["mp4", "avi", "mov", "mkv"])
429
-
430
- # if video_file is not None:
431
- # tfile = tempfile.NamedTemporaryFile(delete=False)
432
- # tfile.write(video_file.read())
433
- # tfile_path = tfile.name
434
-
435
- # st.write("Processing video...")
436
- # output_dir = process_video(tfile_path)
437
-
438
- # st.write("Detected phases saved to:", output_dir)
439
- # st.write("Example frames from detected phases:")
440
-
441
- # for phase_image in os.listdir(output_dir):
442
- # st.image(os.path.join(output_dir, phase_image), caption=phase_image)
 
 
 
 
 
 
 
1
  import streamlit as st
2
  import cv2
3
  import mediapipe as mp
4
  import numpy as np
5
  import tempfile
6
+ import time
7
+ import base64
8
 
9
  # Initialize MediaPipe Pose
10
  mp_pose = mp.solutions.pose
11
+ pose = mp_pose.Pose(
12
+ static_image_mode=False,
13
+ model_complexity=1,
14
+ enable_segmentation=True,
15
+ min_detection_confidence=0.5,
16
+ min_tracking_confidence=0.5
17
+ )
18
 
19
  def calculate_angle_between_vectors(v1, v2):
20
+ """
21
+ Calculates the angle between two vectors in degrees.
22
+ """
23
  unit_vector_1 = v1 / np.linalg.norm(v1)
24
  unit_vector_2 = v2 / np.linalg.norm(v2)
25
  dot_product = np.dot(unit_vector_1, unit_vector_2)
26
+ angle = np.arccos(np.clip(dot_product, -1.0, 1.0)) # Clip for numerical stability
27
  return np.degrees(angle)
28
 
29
  def process_video(video_path):
30
  cap = cv2.VideoCapture(video_path)
 
31
 
32
+ # Define the order of phases
33
+ phases_order = [
34
+ "Setup Phase",
35
+ "Mid Backswing Phase",
36
+ "Top Backswing Phase",
37
+ "Mid Downswing Phase",
38
+ "Ball Impact Phase",
39
+ "Follow Through Phase"
40
+ ]
41
+
42
+ # Initialize a dictionary to store the first detected frame for each phase
43
+ phase_images = {phase: None for phase in phases_order}
44
+
45
+ # Initialize variables for phase detection
46
+ current_phase = "Not Setup Phase"
47
  prev_wrist_left_y = None
48
  prev_wrist_right_y = None
49
  top_backswing_detected = False
 
53
  mid_downswing_frame = -2
54
  ball_impact_frame = -2
55
 
56
+ mid_backswing_wrist_left_y = None
57
+ mid_backswing_wrist_right_y = None
58
+
59
  BALL_IMPACT_DURATION = 2 # Duration in frames to display Ball Impact phase
60
 
61
  MIN_MOVEMENT_THRESHOLD = 0.01
62
  HIP_NEAR_THRESHOLD = 0.05
63
  MID_SWING_THRESHOLD = 0.05
64
 
65
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
66
+ processed_frames = 0
67
 
68
  while cap.isOpened():
69
  ret, frame = cap.read()
 
73
  frame_no = int(cap.get(cv2.CAP_PROP_POS_FRAMES))
74
  image_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
75
  result = pose.process(image_rgb)
 
76
 
77
  if result.pose_landmarks:
 
 
 
 
 
 
78
  landmarks = result.pose_landmarks.landmark
79
  wrist_left_y = landmarks[mp_pose.PoseLandmark.LEFT_WRIST].y
80
  wrist_right_y = landmarks[mp_pose.PoseLandmark.RIGHT_WRIST].y
 
87
  shoulder_y_avg = (shoulder_left_y + shoulder_right_y) / 2
88
  mid_swing_y = (shoulder_y_avg + hip_y_avg) / 2
89
 
90
+ # Phase Detection Logic
91
+ if ball_impact_detected and frame_no > ball_impact_frame + BALL_IMPACT_DURATION:
92
+ current_phase = "Follow Through Phase"
93
+ elif abs(wrist_left_y - mid_swing_y) < MID_SWING_THRESHOLD and \
94
+ abs(wrist_right_y - mid_swing_y) < MID_SWING_THRESHOLD and \
95
+ not top_backswing_detected and not ball_impact_detected:
96
+ current_phase = "Mid Backswing Phase"
97
+ mid_backswing_wrist_left_y = wrist_left_y
98
+ mid_backswing_wrist_right_y = wrist_right_y
99
+ elif wrist_left_y < shoulder_left_y and wrist_right_y < shoulder_right_y and \
100
+ not mid_downswing_detected and not ball_impact_detected:
101
+ current_phase = "Top Backswing Phase"
102
+ top_backswing_detected = True
103
+ top_backswing_frame = frame_no
104
+ elif mid_backswing_wrist_left_y is not None and mid_backswing_wrist_right_y is not None and \
105
+ abs(wrist_left_y - mid_backswing_wrist_left_y) < MID_SWING_THRESHOLD and \
106
+ abs(wrist_right_y - mid_backswing_wrist_right_y) < MID_SWING_THRESHOLD and \
107
+ top_backswing_detected and frame_no > top_backswing_frame:
108
+ current_phase = "Mid Downswing Phase"
109
+ mid_downswing_detected = True
110
+ mid_downswing_frame = frame_no
111
+ elif abs(wrist_left_y - hip_y_avg) < HIP_NEAR_THRESHOLD and \
112
+ abs(wrist_right_y - hip_y_avg) < HIP_NEAR_THRESHOLD:
113
  if prev_wrist_left_y is not None and prev_wrist_right_y is not None:
114
+ if abs(wrist_left_y - prev_wrist_left_y) < MIN_MOVEMENT_THRESHOLD and \
115
+ abs(wrist_right_y - prev_wrist_right_y) < MIN_MOVEMENT_THRESHOLD:
116
  if mid_downswing_detected and frame_no > mid_downswing_frame:
117
+ current_phase = "Ball Impact Phase"
118
  ball_impact_detected = True
119
  ball_impact_frame = frame_no
120
  else:
121
+ current_phase = "Setup Phase"
122
  top_backswing_detected = False
123
  mid_downswing_detected = False
124
  else:
125
  current_phase = ""
126
  else:
127
  if mid_downswing_detected and frame_no > mid_downswing_frame:
128
+ current_phase = "Ball Impact Phase"
129
  ball_impact_detected = True
130
  ball_impact_frame = frame_no
131
  else:
132
+ current_phase = "Setup Phase"
133
  top_backswing_detected = False
134
  mid_downswing_detected = False
 
 
 
 
 
 
 
 
 
 
 
 
135
  else:
136
  current_phase = ""
137
 
138
  prev_wrist_left_y = wrist_left_y
139
  prev_wrist_right_y = wrist_right_y
140
 
141
+ # **Removed Overlay of Phase Information on Video Frames**
142
+
143
+ # Encode frame for display
144
+ frame_bgr = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
145
+ ret, buffer = cv2.imencode('.jpg', frame_bgr)
146
+ frame_jpg = buffer.tobytes()
147
 
148
+ # Store the first detected frame for each phase
149
+ if current_phase in phases_order and phase_images[current_phase] is None:
150
+ phase_images[current_phase] = frame_jpg
151
+
152
+ processed_frames += 1
153
+ yield frame_jpg, current_phase, processed_frames, total_frames
154
 
155
  cap.release()
 
156
  pose.close()
157
+ yield phase_images, None, processed_frames, total_frames
158
+
159
+ # Streamlit UI Configuration
160
+
161
+ # Custom CSS for styling
162
+ st.markdown("""
163
+ <style>
164
+ /* General Styles */
165
+ body {
166
+ background-color: #f5f5f5;
167
+ }
168
+ .title {
169
+ font-size: 36px;
170
+ font-weight: bold;
171
+ color: #2E86C1;
172
+ text-align: center;
173
+ margin-bottom: 10px;
174
+ }
175
+ .subtitle {
176
+ font-size: 18px;
177
+ color: #555;
178
+ text-align: center;
179
+ margin-bottom: 30px;
180
+ }
181
+ .phase-placeholder {
182
+ border: 2px solid #2E86C1;
183
+ background-color: #D6EAF8;
184
+ padding: 15px;
185
+ text-align: center;
186
+ font-size: 24px;
187
+ border-radius: 10px;
188
+ height: 60px;
189
+ margin-bottom: 20px;
190
+ color: #1B4F72;
191
+ }
192
+ .detected-phases {
193
+ display: flex;
194
+ flex-wrap: wrap;
195
+ justify-content: center;
196
+ gap: 30px; /* Increased gap for more space between phase cards */
197
+ }
198
+ .phase-card {
199
+ border: 1px solid #ddd;
200
+ border-radius: 10px;
201
+ padding: 15px;
202
+ background-color: #fff;
203
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
204
+ text-align: center;
205
+ width: 250px;
206
+ height: 300px; /* Fixed height to accommodate image and text */
207
+ display: flex;
208
+ flex-direction: column;
209
+ justify-content: center;
210
+ align-items: center;
211
+ }
212
+ .phase-card img {
213
+ width: 200px; /* Fixed width */
214
+ height: 200px; /* Fixed height */
215
+ object-fit: cover; /* Ensures image covers the area without distortion */
216
+ border-radius: 10px;
217
+ margin-bottom: 10px;
218
+ }
219
+ .footer {
220
+ text-align: center;
221
+ font-size: 14px;
222
+ color: #aaa;
223
+ margin-top: 40px;
224
+ }
225
+ </style>
226
+ """, unsafe_allow_html=True)
227
+
228
+ # Title and Description
229
+ st.markdown("<div class='title'>🏌️‍♂️ Golf Swing Phase Detection</div><hr>", unsafe_allow_html=True)
230
+ st.markdown("<div class='subtitle'>Upload a golf swing video to automatically detect and visualize its different phases.</div>", unsafe_allow_html=True)
231
+
232
+ # File Uploader
233
+ video_file = st.file_uploader("🎥 **Upload Your Golf Swing Video**", type=["mp4", "avi", "mov", "mkv"])
234
 
235
+ if video_file is not None:
236
+ with st.spinner("🔍 Processing video..."):
237
+ try:
238
+ # Save uploaded video to a temporary file
239
+ tfile = tempfile.NamedTemporaryFile(delete=False)
240
+ tfile.write(video_file.read())
241
+ tfile_path = tfile.name
242
+
243
+ phase_placeholder = st.empty()
244
+ video_placeholder = st.empty()
245
+ progress_bar = st.progress(0)
246
+ progress_text = st.empty()
247
+
248
+ phase_images = {}
249
+ detected_phases = []
250
+
251
+ for result, current_phase, processed, total in process_video(tfile_path):
252
+ if current_phase is not None:
253
+ # Update phase placeholder
254
+ phase_placeholder.markdown(
255
+ f"<div class='phase-placeholder'>{current_phase}</div>",
256
+ unsafe_allow_html=True
257
+ )
258
+ # Display video frame
259
+ video_placeholder.image(result, channels='BGR', use_column_width=True)
260
+ # Update progress bar
261
+ progress = processed / total if total > 0 else 0
262
+ progress_bar.progress(progress)
263
+ progress_text.text(f"Processing frame {processed} of {total}")
264
+ # Collect detected phases
265
+ if current_phase not in detected_phases and current_phase != "Not Setup Phase":
266
+ detected_phases.append(current_phase)
267
+ time.sleep(0.01) # Adjust delay as needed for smoother progress
268
+ else:
269
+ phase_images = result
270
+
271
+ # Cleanup placeholders
272
+ phase_placeholder.empty()
273
+ video_placeholder.empty()
274
+ progress_bar.empty()
275
+ progress_text.empty()
276
+
277
+ st.success("✅ Video processing complete!")
278
+
279
+ # Define the order of phases for fixed grid positions
280
+ phases_order = [
281
+ "Setup Phase",
282
+ "Mid Backswing Phase",
283
+ "Top Backswing Phase",
284
+ "Mid Downswing Phase",
285
+ "Ball Impact Phase",
286
+ "Follow Through Phase"
287
+ ]
288
+
289
+ # Display Detected Phases in Fixed Grid Layout
290
+ st.markdown("### 🏆 Detected Phases:")
291
+ if phases_order:
292
+ st.markdown("<div class='detected-phases'>", unsafe_allow_html=True)
293
+ for phase in phases_order:
294
+ image = phase_images.get(phase)
295
+ if image:
296
+ # Proper Base64 encoding
297
+ encoded_image = base64.b64encode(image).decode('utf-8')
298
+ st.markdown(f"""
299
+ <div class='phase-card'>
300
+ <img src="data:image/jpeg;base64,{encoded_image}" alt="{phase}">
301
+ <h3>{phase}</h3>
302
+ </div>
303
+ """, unsafe_allow_html=True)
304
+ else:
305
+ # Placeholder for missing phases
306
+ st.markdown(f"""
307
+ <div class='phase-card'>
308
+ <img src="https://via.placeholder.com/200x200.png?text=No+Image" alt="{phase}">
309
+ <h3>{phase}</h3>
310
+ <p style="color: #aaa; font-size: 14px;">Not Detected</p>
311
+ </div>
312
+ """, unsafe_allow_html=True)
313
+ st.markdown("</div>", unsafe_allow_html=True)
314
+ else:
315
+ st.warning("⚠️ No distinct phases detected.")
316
 
317
+ except Exception as e:
318
+ st.error(f"An error occurred during video processing: {e}")
319
 
320
+ # Footer
321
+ st.markdown("<div class='footer'>Developed by Your Name | © 2024 Golf Swing Analyzer</div>", unsafe_allow_html=True)