Spaces:
Build error
Build error
Upload 8 files
Browse files- app.py +35 -0
- ball_tracker.py +21 -0
- gradio_app.py +53 -0
- lbw_engine.py +17 -0
- overlay_display.py +69 -0
- requirements.txt +0 -0
- test_app.py +2 -0
- trajectory.py +22 -0
app.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import cv2
|
| 2 |
+
from ball_tracker import detect_ball
|
| 3 |
+
from trajectory import predict_trajectory
|
| 4 |
+
from lbw_engine import evaluate_lbw
|
| 5 |
+
from overlay_display import draw_overlay
|
| 6 |
+
|
| 7 |
+
cap = cv2.VideoCapture(0)
|
| 8 |
+
ball_path = []
|
| 9 |
+
|
| 10 |
+
while True:
|
| 11 |
+
ret, frame = cap.read()
|
| 12 |
+
if not ret:
|
| 13 |
+
break
|
| 14 |
+
|
| 15 |
+
ball = detect_ball(frame)
|
| 16 |
+
if ball:
|
| 17 |
+
ball_path.append(ball)
|
| 18 |
+
|
| 19 |
+
if len(ball_path) > 5:
|
| 20 |
+
a, b, c = predict_trajectory(ball_path)
|
| 21 |
+
trajectory_func = lambda x: a * x**2 + b * x + c
|
| 22 |
+
|
| 23 |
+
pitch_x = ball_path[0][0]
|
| 24 |
+
impact_x = ball_path[-1][0]
|
| 25 |
+
wicket_x = int((impact_x + 100) / 2)
|
| 26 |
+
|
| 27 |
+
verdict, pitch, impact, wickets = evaluate_lbw(pitch_x, impact_x, wicket_x, ball_path)
|
| 28 |
+
frame = draw_overlay(frame, ball_path, trajectory_func, verdict, (pitch, impact, wickets))
|
| 29 |
+
|
| 30 |
+
cv2.imshow("Gully Cricket DRS", frame)
|
| 31 |
+
if cv2.waitKey(1) == ord('q'):
|
| 32 |
+
break
|
| 33 |
+
|
| 34 |
+
cap.release()
|
| 35 |
+
cv2.destroyAllWindows()
|
ball_tracker.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import cv2
|
| 2 |
+
import numpy as np
|
| 3 |
+
|
| 4 |
+
def detect_ball(frame):
|
| 5 |
+
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
|
| 6 |
+
lower_red1 = np.array([0, 100, 100])
|
| 7 |
+
upper_red1 = np.array([10, 255, 255])
|
| 8 |
+
lower_red2 = np.array([160, 100, 100])
|
| 9 |
+
upper_red2 = np.array([180, 255, 255])
|
| 10 |
+
|
| 11 |
+
mask1 = cv2.inRange(hsv, lower_red1, upper_red1)
|
| 12 |
+
mask2 = cv2.inRange(hsv, lower_red2, upper_red2)
|
| 13 |
+
mask = mask1 | mask2
|
| 14 |
+
|
| 15 |
+
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
| 16 |
+
if contours:
|
| 17 |
+
largest = max(contours, key=cv2.contourArea)
|
| 18 |
+
(x, y), radius = cv2.minEnclosingCircle(largest)
|
| 19 |
+
return (int(x), int(y))
|
| 20 |
+
return None
|
| 21 |
+
|
gradio_app.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import cv2
|
| 3 |
+
import numpy as np
|
| 4 |
+
from ball_tracker import detect_ball
|
| 5 |
+
from trajectory import predict_trajectory
|
| 6 |
+
from lbw_engine import evaluate_lbw
|
| 7 |
+
from overlay_display import draw_overlay
|
| 8 |
+
import tempfile
|
| 9 |
+
|
| 10 |
+
def process_video(video_file):
|
| 11 |
+
cap = cv2.VideoCapture(video_file)
|
| 12 |
+
ball_path = []
|
| 13 |
+
output_frames = []
|
| 14 |
+
|
| 15 |
+
while cap.isOpened():
|
| 16 |
+
ret, frame = cap.read()
|
| 17 |
+
if not ret:
|
| 18 |
+
break
|
| 19 |
+
|
| 20 |
+
ball = detect_ball(frame)
|
| 21 |
+
if ball:
|
| 22 |
+
ball_path.append(ball)
|
| 23 |
+
|
| 24 |
+
if len(ball_path) > 5:
|
| 25 |
+
try:
|
| 26 |
+
a, b, c = predict_trajectory(ball_path)
|
| 27 |
+
trajectory_func = lambda x: a * x**2 + b * x + c
|
| 28 |
+
pitch_x = ball_path[0][0]
|
| 29 |
+
impact_x = ball_path[-1][0]
|
| 30 |
+
wicket_x = int((impact_x + 100) / 2)
|
| 31 |
+
frame_width = frame.shape[1]
|
| 32 |
+
verdict, pitch, impact, wickets = evaluate_lbw(pitch_x, impact_x, wicket_x, ball_path, frame_width)
|
| 33 |
+
frame = draw_overlay(frame, ball_path, trajectory_func, verdict, (pitch, impact, wickets))
|
| 34 |
+
except Exception as e:
|
| 35 |
+
print("Prediction error:", e)
|
| 36 |
+
|
| 37 |
+
output_frames.append(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
|
| 38 |
+
|
| 39 |
+
cap.release()
|
| 40 |
+
|
| 41 |
+
return output_frames[-1] if output_frames else None
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
demo = gr.Interface(
|
| 45 |
+
fn=process_video,
|
| 46 |
+
inputs=gr.Video(label="Upload a Cricket Delivery Video"),
|
| 47 |
+
outputs=gr.Image(label="Decision Frame with Overlay"),
|
| 48 |
+
title="🏏 Gully Cricket DRS - LBW Analysis"
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
if __name__ == "__main__":
|
| 52 |
+
demo.launch(inbrowser=True, share=False)
|
| 53 |
+
|
lbw_engine.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
def evaluate_lbw(pitch_x, impact_x, wicket_x, predicted_path, frame_width):
|
| 2 |
+
# Use frame-relative zones instead of fixed pixel values
|
| 3 |
+
leg_boundary = int(frame_width * 0.33)
|
| 4 |
+
off_boundary = int(frame_width * 0.66)
|
| 5 |
+
|
| 6 |
+
# Determine zones
|
| 7 |
+
pitch_zone = "In Line" if leg_boundary < pitch_x < off_boundary else "Outside Leg"
|
| 8 |
+
impact_zone = "In Line" if leg_boundary < impact_x < off_boundary else "Outside Off"
|
| 9 |
+
hitting_wickets = leg_boundary < wicket_x < off_boundary
|
| 10 |
+
|
| 11 |
+
if pitch_zone == "Outside Leg":
|
| 12 |
+
return "Not Out", pitch_zone, impact_zone, "Missing"
|
| 13 |
+
if impact_zone != "In Line":
|
| 14 |
+
return "Not Out", pitch_zone, impact_zone, "Missing"
|
| 15 |
+
if hitting_wickets:
|
| 16 |
+
return "Out", pitch_zone, impact_zone, "Hitting"
|
| 17 |
+
return "Not Out", pitch_zone, impact_zone, "Missing"
|
overlay_display.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import cv2
|
| 2 |
+
|
| 3 |
+
def draw_overlay(frame, ball_points, trajectory_func, verdict, labels):
|
| 4 |
+
height, width = frame.shape[:2]
|
| 5 |
+
|
| 6 |
+
# 1. Draw smooth RED arc (trajectory)
|
| 7 |
+
draw_trajectory_arc(frame, trajectory_func, ball_points[0][0], ball_points[-1][0])
|
| 8 |
+
|
| 9 |
+
# 2. Draw BLUE prediction line from impact point forward
|
| 10 |
+
if len(ball_points) > 1:
|
| 11 |
+
draw_prediction_line(frame, ball_points[-1], trajectory_func)
|
| 12 |
+
|
| 13 |
+
# 3. Draw YELLOW stumps (centered)
|
| 14 |
+
draw_stumps(frame, width // 2)
|
| 15 |
+
|
| 16 |
+
# 4. Display Decision and Details
|
| 17 |
+
text_color = (0, 255, 0) if verdict == "Out" else (0, 255, 255)
|
| 18 |
+
cv2.putText(frame, f"Decision: {verdict}", (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 1.0, text_color, 3)
|
| 19 |
+
cv2.putText(frame, f"Pitching: {labels[0]}", (20, 75), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 0), 2)
|
| 20 |
+
cv2.putText(frame, f"Impact: {labels[1]}", (20, 105), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 0), 2)
|
| 21 |
+
cv2.putText(frame, f"Wickets: {labels[2]}", (20, 135), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 0), 2)
|
| 22 |
+
|
| 23 |
+
return frame
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
# ============================
|
| 27 |
+
# 📦 Helper Drawing Functions
|
| 28 |
+
# ============================
|
| 29 |
+
|
| 30 |
+
def draw_trajectory_arc(frame, trajectory_func, start_x, end_x, step=5, color=(0, 0, 255)):
|
| 31 |
+
height, width = frame.shape[:2]
|
| 32 |
+
for x in range(start_x, min(end_x, width - step), step):
|
| 33 |
+
try:
|
| 34 |
+
y1 = int(trajectory_func(x))
|
| 35 |
+
y2 = int(trajectory_func(x + step))
|
| 36 |
+
if 0 <= y1 < height and 0 <= y2 < height:
|
| 37 |
+
cv2.line(frame, (x, y1), (x + step, y2), color, 2)
|
| 38 |
+
except:
|
| 39 |
+
continue
|
| 40 |
+
|
| 41 |
+
def draw_prediction_line(frame, start_point, trajectory_func, distance=100, color=(255, 0, 0)):
|
| 42 |
+
x1 = start_point[0]
|
| 43 |
+
x2 = x1 + distance
|
| 44 |
+
try:
|
| 45 |
+
y2 = int(trajectory_func(x2))
|
| 46 |
+
height, width = frame.shape[:2]
|
| 47 |
+
if 0 <= y2 < height and x2 < width:
|
| 48 |
+
cv2.line(frame, start_point, (x2, y2), color, 2)
|
| 49 |
+
except:
|
| 50 |
+
pass
|
| 51 |
+
|
| 52 |
+
def draw_stumps(frame, center_x, color=(0, 255, 255)):
|
| 53 |
+
height = frame.shape[0]
|
| 54 |
+
stump_top = int(height * 0.55)
|
| 55 |
+
stump_bottom = int(height * 0.85)
|
| 56 |
+
for dx in [-10, 0, 10]:
|
| 57 |
+
cv2.line(frame, (center_x + dx, stump_top), (center_x + dx, stump_bottom), color, 2)
|
| 58 |
+
|
| 59 |
+
def draw_ball_path(frame, ball_path, color=(0, 255, 0)):
|
| 60 |
+
for i in range(1, len(ball_path)):
|
| 61 |
+
cv2.line(frame, ball_path[i - 1], ball_path[i], color, thickness=2)
|
| 62 |
+
|
| 63 |
+
def draw_ball(frame, position, radius=5, color=(0, 0, 255)):
|
| 64 |
+
cv2.circle(frame, position, radius, color, -1)
|
| 65 |
+
|
| 66 |
+
def draw_pitch_impact_wickets(frame, pitch, impact, wickets):
|
| 67 |
+
cv2.putText(frame, f"Pitch: {pitch}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 0), 2)
|
| 68 |
+
cv2.putText(frame, f"Impact: {impact}", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 0), 2)
|
| 69 |
+
cv2.putText(frame, f"Wickets: {wickets}", (10, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 0), 2)
|
requirements.txt
ADDED
|
Binary file (1.85 kB). View file
|
|
|
test_app.py
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
st.title("✅ Streamlit is working properly")
|
trajectory.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from scipy.optimize import curve_fit
|
| 3 |
+
|
| 4 |
+
def predict_trajectory(points):
|
| 5 |
+
if len(points) < 5:
|
| 6 |
+
raise ValueError("Not enough data points for trajectory prediction.")
|
| 7 |
+
|
| 8 |
+
points = np.array(points)
|
| 9 |
+
x = points[:, 0]
|
| 10 |
+
y = points[:, 1]
|
| 11 |
+
|
| 12 |
+
def poly2(x, a, b, c):
|
| 13 |
+
return a * x**2 + b * x + c
|
| 14 |
+
|
| 15 |
+
try:
|
| 16 |
+
params, _ = curve_fit(poly2, x, y)
|
| 17 |
+
except Exception as e:
|
| 18 |
+
print(f"Curve fitting failed: {e}")
|
| 19 |
+
raise e
|
| 20 |
+
|
| 21 |
+
return params
|
| 22 |
+
|