dschandra commited on
Commit
70fce4e
·
verified ·
1 Parent(s): 640c086

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +54 -33
app.py CHANGED
@@ -16,9 +16,10 @@ BALL_DIAMETER = 0.073 # meters (approx. cricket ball diameter)
16
  FRAME_RATE = 20 # Input video frame rate
17
  SLOW_MOTION_FACTOR = 3 # Adjusted for 20 FPS
18
  CONF_THRESHOLD = 0.25 # Confidence threshold for detection
19
- IMPACT_ZONE_Y = 0.85 # Fraction of frame height for impact zone
20
- PITCH_ZONE_Y = 0.75 # Fraction of frame height for pitch zone
21
  IMPACT_DELTA_Y = 50 # Pixels for detecting sudden y-position change
 
22
 
23
  def process_video(video_path):
24
  if not os.path.exists(video_path):
@@ -26,7 +27,7 @@ def process_video(video_path):
26
  cap = cv2.VideoCapture(video_path)
27
  frames = []
28
  ball_positions = []
29
- detection_frames = [] # Track frames with exactly one detection
30
  debug_log = []
31
 
32
  frame_count = 0
@@ -37,11 +38,11 @@ def process_video(video_path):
37
  frame_count += 1
38
  frames.append(frame.copy())
39
  results = model.predict(frame, conf=CONF_THRESHOLD)
40
- detections = [det for det in results[0].boxes if det.cls == 0] # Class 0 is cricketBall
41
- if len(detections) == 1: # Only consider frames with exactly one detection
42
  x1, y1, x2, y2 = detections[0].xyxy[0].cpu().numpy()
43
  ball_positions.append([(x1 + x2) / 2, (y1 + y2) / 2])
44
- detection_frames.append(frame_count - 1) # 0-based index
45
  cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2)
46
  frames[-1] = frame
47
  debug_log.append(f"Frame {frame_count}: {len(detections)} ball detections")
@@ -59,12 +60,10 @@ def estimate_trajectory(ball_positions, detection_frames, frames):
59
  return None, None, None, None, None, None, "Error: Fewer than 2 valid single-ball detections for trajectory"
60
  frame_height = frames[0].shape[0]
61
 
62
- # Extract x, y coordinates
63
  x_coords = [pos[0] for pos in ball_positions]
64
  y_coords = [pos[1] for pos in ball_positions]
65
  times = np.array(detection_frames) / FRAME_RATE
66
 
67
- # Pitch point: first valid detection or when y exceeds PITCH_ZONE_Y
68
  pitch_idx = 0
69
  for i, y in enumerate(y_coords):
70
  if y > frame_height * PITCH_ZONE_Y:
@@ -73,10 +72,9 @@ def estimate_trajectory(ball_positions, detection_frames, frames):
73
  pitch_point = ball_positions[pitch_idx]
74
  pitch_frame = detection_frames[pitch_idx]
75
 
76
- # Impact point: sudden y-change or y exceeds IMPACT_ZONE_Y
77
  impact_idx = None
78
  for i in range(1, len(y_coords)):
79
- if (y_coords[i] > frame_height * IMPACT_ZONE_Y or
80
  abs(y_coords[i] - y_coords[i-1]) > IMPACT_DELTA_Y):
81
  impact_idx = i
82
  break
@@ -85,7 +83,6 @@ def estimate_trajectory(ball_positions, detection_frames, frames):
85
  impact_point = ball_positions[impact_idx]
86
  impact_frame = detection_frames[impact_idx]
87
 
88
- # Use only detected positions for trajectory
89
  x_coords = x_coords[:impact_idx + 1]
90
  y_coords = y_coords[:impact_idx + 1]
91
  times = times[:impact_idx + 1]
@@ -96,10 +93,8 @@ def estimate_trajectory(ball_positions, detection_frames, frames):
96
  except Exception as e:
97
  return None, None, None, None, None, None, f"Error in trajectory interpolation: {str(e)}"
98
 
99
- # Trajectory for visualization (detected frames only)
100
  vis_trajectory = list(zip(x_coords, y_coords))
101
 
102
- # Full trajectory for LBW (includes projection)
103
  t_full = np.linspace(times[0], times[-1] + 0.5, len(times) + 10)
104
  x_full = fx(t_full)
105
  y_full = fy(t_full)
@@ -120,55 +115,81 @@ def lbw_decision(ball_positions, full_trajectory, frames, pitch_point, impact_po
120
  stumps_x = frame_width / 2
121
  stumps_y = frame_height * 0.9
122
  stumps_width_pixels = frame_width * (STUMPS_WIDTH / 3.0)
 
123
 
124
  pitch_x, pitch_y = pitch_point
125
  impact_x, impact_y = impact_point
126
 
127
- # Check pitching point
128
- if pitch_x < stumps_x - stumps_width_pixels / 2 or pitch_x > stumps_x + stumps_width_pixels / 2:
 
129
  return f"Not Out (Pitched outside line at x: {pitch_x:.1f}, y: {pitch_y:.1f})", full_trajectory, pitch_point, impact_point
130
 
131
- # Check impact point
132
- if impact_x < stumps_x - stumps_width_pixels / 2 or impact_x > stumps_x + stumps_width_pixels / 2:
133
- return f"Not Out (Impact outside line at x: {impact_x:.1f}, y: {impact_y:.1f})", full_trajectory, pitch_point, impact_point
134
 
135
- # Check trajectory hitting stumps
136
  for x, y in full_trajectory:
137
- if abs(x - stumps_x) < stumps_width_pixels / 2 and abs(y - stumps_y) < frame_height * 0.1:
138
- return f"Out (Ball hits stumps, Pitch at x: {pitch_x:.1f}, y: {pitch_y:.1f}, Impact at x: {impact_x:.1f}, y: {impact_y:.1f})", full_trajectory, pitch_point, impact_point
 
 
 
 
 
 
 
 
139
  return f"Not Out (Missing stumps, Pitch at x: {pitch_x:.1f}, y: {pitch_y:.1f}, Impact at x: {impact_x:.1f}, y: {impact_y:.1f})", full_trajectory, pitch_point, impact_point
140
 
141
  def generate_slow_motion(frames, vis_trajectory, pitch_point, pitch_frame, impact_point, impact_frame, detection_frames, output_path):
142
  if not frames:
143
  return None
144
  frame_height, frame_width = frames[0].shape[:2]
 
 
 
 
145
 
146
  fourcc = cv2.VideoWriter_fourcc(*'mp4v')
147
  out = cv2.VideoWriter(output_path, fourcc, FRAME_RATE / SLOW_MOTION_FACTOR, (frame_width, frame_height))
148
 
149
- # Prepare trajectory points for visualization
150
  trajectory_points = np.array(vis_trajectory, dtype=np.int32).reshape((-1, 1, 2))
151
 
152
  for i, frame in enumerate(frames):
153
- # Draw trajectory (blue line) only for detected frames
 
 
 
 
 
 
 
 
 
 
 
154
  if i in detection_frames and trajectory_points.size > 0:
155
  idx = detection_frames.index(i) + 1
156
  if idx <= len(trajectory_points):
157
  cv2.polylines(frame, [trajectory_points[:idx]], False, (255, 0, 0), 2)
158
 
159
- # Draw pitch point (red circle) only in pitch frame
160
  if pitch_point and i == pitch_frame:
161
  x, y = pitch_point
162
- cv2.circle(frame, (int(x), int(y)), 8, (0, 0, 255), -1)
163
- cv2.putText(frame, "Pitch Point", (int(x) + 10, int(y) - 10),
164
- cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
165
 
166
- # Draw impact point (yellow circle) only in impact frame
167
  if impact_point and i == impact_frame:
168
  x, y = impact_point
169
- cv2.circle(frame, (int(x), int(y)), 8, (0, 255, 255), -1)
170
- cv2.putText(frame, "Impact Point", (int(x) + 10, int(y) + 20),
171
- cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 255), 2)
 
 
 
 
 
172
 
173
  for _ in range(SLOW_MOTION_FACTOR):
174
  out.write(frame)
@@ -194,10 +215,10 @@ iface = gr.Interface(
194
  inputs=gr.Video(label="Upload Video Clip"),
195
  outputs=[
196
  gr.Textbox(label="DRS Decision and Debug Log"),
197
- gr.Video(label="Very Slow-Motion Replay with Ball Detection (Green), Trajectory (Blue Line), Pitch Point (Red), Impact Point (Yellow)")
198
  ],
199
  title="AI-Powered DRS for LBW in Local Cricket",
200
- description="Upload a video clip of a cricket delivery to get an LBW decision and slow-motion replay showing ball detection (green boxes), trajectory (blue line), pitch point (red circle), and impact point (yellow circle)."
201
  )
202
 
203
  if __name__ == "__main__":
 
16
  FRAME_RATE = 20 # Input video frame rate
17
  SLOW_MOTION_FACTOR = 3 # Adjusted for 20 FPS
18
  CONF_THRESHOLD = 0.25 # Confidence threshold for detection
19
+ PITCH_ZONE_Y = 0.9 # Fraction of frame height for pitch zone (near stumps base)
20
+ IMPACT_ZONE_Y = 0.8 # Fraction of frame height for impact zone (near batsman)
21
  IMPACT_DELTA_Y = 50 # Pixels for detecting sudden y-position change
22
+ STUMPS_HEIGHT = 0.711 # meters (height of stumps)
23
 
24
  def process_video(video_path):
25
  if not os.path.exists(video_path):
 
27
  cap = cv2.VideoCapture(video_path)
28
  frames = []
29
  ball_positions = []
30
+ detection_frames = []
31
  debug_log = []
32
 
33
  frame_count = 0
 
38
  frame_count += 1
39
  frames.append(frame.copy())
40
  results = model.predict(frame, conf=CONF_THRESHOLD)
41
+ detections = [det for det in results[0].boxes if det.cls == 0]
42
+ if len(detections) == 1:
43
  x1, y1, x2, y2 = detections[0].xyxy[0].cpu().numpy()
44
  ball_positions.append([(x1 + x2) / 2, (y1 + y2) / 2])
45
+ detection_frames.append(frame_count - 1)
46
  cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2)
47
  frames[-1] = frame
48
  debug_log.append(f"Frame {frame_count}: {len(detections)} ball detections")
 
60
  return None, None, None, None, None, None, "Error: Fewer than 2 valid single-ball detections for trajectory"
61
  frame_height = frames[0].shape[0]
62
 
 
63
  x_coords = [pos[0] for pos in ball_positions]
64
  y_coords = [pos[1] for pos in ball_positions]
65
  times = np.array(detection_frames) / FRAME_RATE
66
 
 
67
  pitch_idx = 0
68
  for i, y in enumerate(y_coords):
69
  if y > frame_height * PITCH_ZONE_Y:
 
72
  pitch_point = ball_positions[pitch_idx]
73
  pitch_frame = detection_frames[pitch_idx]
74
 
 
75
  impact_idx = None
76
  for i in range(1, len(y_coords)):
77
+ if (y_coords[i] > frame_height * IMPACT_ZONE_Y and
78
  abs(y_coords[i] - y_coords[i-1]) > IMPACT_DELTA_Y):
79
  impact_idx = i
80
  break
 
83
  impact_point = ball_positions[impact_idx]
84
  impact_frame = detection_frames[impact_idx]
85
 
 
86
  x_coords = x_coords[:impact_idx + 1]
87
  y_coords = y_coords[:impact_idx + 1]
88
  times = times[:impact_idx + 1]
 
93
  except Exception as e:
94
  return None, None, None, None, None, None, f"Error in trajectory interpolation: {str(e)}"
95
 
 
96
  vis_trajectory = list(zip(x_coords, y_coords))
97
 
 
98
  t_full = np.linspace(times[0], times[-1] + 0.5, len(times) + 10)
99
  x_full = fx(t_full)
100
  y_full = fy(t_full)
 
115
  stumps_x = frame_width / 2
116
  stumps_y = frame_height * 0.9
117
  stumps_width_pixels = frame_width * (STUMPS_WIDTH / 3.0)
118
+ batsman_area_y = frame_height * 0.8 # Approximate batsman leg area
119
 
120
  pitch_x, pitch_y = pitch_point
121
  impact_x, impact_y = impact_point
122
 
123
+ # LBW Conditions
124
+ in_line_threshold = stumps_width_pixels / 2
125
+ if pitch_x < stumps_x - in_line_threshold or pitch_x > stumps_x + in_line_threshold:
126
  return f"Not Out (Pitched outside line at x: {pitch_x:.1f}, y: {pitch_y:.1f})", full_trajectory, pitch_point, impact_point
127
 
128
+ if impact_y < batsman_area_y or impact_x < stumps_x - in_line_threshold or impact_x > stumps_x + in_line_threshold:
129
+ return f"Not Out (Impact outside line or above batsman at x: {impact_x:.1f}, y: {impact_y:.1f})", full_trajectory, pitch_point, impact_point
 
130
 
131
+ hit_stumps = False
132
  for x, y in full_trajectory:
133
+ if (abs(x - stumps_x) < in_line_threshold and
134
+ abs(y - stumps_y) < frame_height * 0.1):
135
+ hit_stumps = True
136
+ break
137
+
138
+ if hit_stumps:
139
+ # Check if clipping (Umpire's Call)
140
+ if abs(x - stumps_x) < in_line_threshold * 0.1: # Arbitrary small margin for clipping
141
+ return f"Umpire's Call - Not Out (Ball clips stumps, Pitch at x: {pitch_x:.1f}, y: {pitch_y:.1f}, Impact at x: {impact_x:.1f}, y: {impact_y:.1f})", full_trajectory, pitch_point, impact_point
142
+ return f"Out (Ball hits stumps, Pitch at x: {pitch_x:.1f}, y: {pitch_y:.1f}, Impact at x: {impact_x:.1f}, y: {impact_y:.1f})", full_trajectory, pitch_point, impact_point
143
  return f"Not Out (Missing stumps, Pitch at x: {pitch_x:.1f}, y: {pitch_y:.1f}, Impact at x: {impact_x:.1f}, y: {impact_y:.1f})", full_trajectory, pitch_point, impact_point
144
 
145
  def generate_slow_motion(frames, vis_trajectory, pitch_point, pitch_frame, impact_point, impact_frame, detection_frames, output_path):
146
  if not frames:
147
  return None
148
  frame_height, frame_width = frames[0].shape[:2]
149
+ stumps_x = frame_width / 2
150
+ stumps_y = frame_height * 0.9
151
+ stumps_width_pixels = frame_width * (STUMPS_WIDTH / 3.0)
152
+ stumps_height_pixels = frame_height * (STUMPS_HEIGHT / 3.0)
153
 
154
  fourcc = cv2.VideoWriter_fourcc(*'mp4v')
155
  out = cv2.VideoWriter(output_path, fourcc, FRAME_RATE / SLOW_MOTION_FACTOR, (frame_width, frame_height))
156
 
 
157
  trajectory_points = np.array(vis_trajectory, dtype=np.int32).reshape((-1, 1, 2))
158
 
159
  for i, frame in enumerate(frames):
160
+ # Draw stumps (three white vertical lines)
161
+ stump_positions = [
162
+ (stumps_x - stumps_width_pixels / 2, stumps_y),
163
+ (stumps_x, stumps_y),
164
+ (stumps_x + stumps_width_pixels / 2, stumps_y)
165
+ ]
166
+ for x, y in stump_positions:
167
+ cv2.line(frame, (int(x), int(y)), (int(x), int(y - stumps_height_pixels)), (255, 255, 255), 2)
168
+
169
+ # Draw crease line (striker to non-striker)
170
+ cv2.line(frame, (0, int(stumps_y)), (frame_width, int(stumps_y)), (255, 255, 0), 2) # Yellow line
171
+
172
  if i in detection_frames and trajectory_points.size > 0:
173
  idx = detection_frames.index(i) + 1
174
  if idx <= len(trajectory_points):
175
  cv2.polylines(frame, [trajectory_points[:idx]], False, (255, 0, 0), 2)
176
 
 
177
  if pitch_point and i == pitch_frame:
178
  x, y = pitch_point
179
+ cv2.circle(frame, (int(x), int(y)), 8, (0, 255, 0), -1) # Green for pitch
180
+ cv2.putText(frame, "Pitching Factor", (int(x) + 10, int(y) - 10),
181
+ cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
182
 
 
183
  if impact_point and i == impact_frame:
184
  x, y = impact_point
185
+ cv2.circle(frame, (int(x), int(y)), 8, (0, 0, 255), -1) # Red for impact
186
+ cv2.putText(frame, "Impact Factor", (int(x) + 10, int(y) + 20),
187
+ cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
188
+
189
+ # Wicket factor (show at impact frame if hitting stumps)
190
+ if impact_point and i == impact_frame and "Out" in lbw_decision(ball_positions, full_trajectory, frames, pitch_point, impact_point)[0]:
191
+ cv2.putText(frame, "Wicket Factor", (int(stumps_x) - 50, int(stumps_y) - 20),
192
+ cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 165, 255), 2) # Orange
193
 
194
  for _ in range(SLOW_MOTION_FACTOR):
195
  out.write(frame)
 
215
  inputs=gr.Video(label="Upload Video Clip"),
216
  outputs=[
217
  gr.Textbox(label="DRS Decision and Debug Log"),
218
+ gr.Video(label="Very Slow-Motion Replay with Pitching Factor (Green), Impact Factor (Red), Wicket Factor (Orange), Stumps (White), Crease (Yellow)")
219
  ],
220
  title="AI-Powered DRS for LBW in Local Cricket",
221
+ description="Upload a video clip of a cricket delivery to get an LBW decision and slow-motion replay showing pitching factor (green circle), impact factor (red circle), wicket factor (orange text), stumps (white lines), and crease line (yellow line)."
222
  )
223
 
224
  if __name__ == "__main__":