Spaces:
Sleeping
Sleeping
Upload 5 files
Browse files- ball_detect.py +94 -0
- batsman.py +140 -0
- main_gradio.py +168 -0
- pitch.py +65 -0
- requirements.txt +4 -0
ball_detect.py
ADDED
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import cv2
|
2 |
+
from cvzone.ColorModule import ColorFinder
|
3 |
+
|
4 |
+
|
5 |
+
def ball_detect(img, color_finder, hsv_values):
|
6 |
+
"""
|
7 |
+
Detects a ball in an image frame based on color.
|
8 |
+
Adapted to handle OpenCV-style contours (NumPy arrays of points).
|
9 |
+
|
10 |
+
Args:
|
11 |
+
img: The input image frame (BGR format).
|
12 |
+
color_finder: An instance of cvzone.ColorFinder for color detection.
|
13 |
+
hsv_values: A dictionary containing HSV range for ball color.
|
14 |
+
|
15 |
+
Returns:
|
16 |
+
tuple: (img_with_contours, ball_x, ball_y)
|
17 |
+
- img_with_contours: Image with contours drawn around the detected ball (or None if no ball detected).
|
18 |
+
- ball_x, ball_y: Center coordinates (x, y) of the detected ball (or 0, 0 if no ball detected).
|
19 |
+
"""
|
20 |
+
ball_x = 0
|
21 |
+
ball_y = 0
|
22 |
+
img_with_contours = None
|
23 |
+
|
24 |
+
if img is None:
|
25 |
+
return None, ball_x, ball_y
|
26 |
+
|
27 |
+
imggray_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
|
28 |
+
_, mask = color_finder.update(imggray_hsv, hsv_values)
|
29 |
+
|
30 |
+
contours, _ = cv2.findContours(
|
31 |
+
mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
|
32 |
+
) # Use standard cv2.findContours
|
33 |
+
|
34 |
+
# print(f"Type of contours: {type(contours)}") # Keep for debugging - should be NumPy array
|
35 |
+
if len(contours) > 0:
|
36 |
+
# Find the contour with the largest area using cv2.contourArea
|
37 |
+
largest_contour_index = 0
|
38 |
+
max_area = 0
|
39 |
+
for i, contour in enumerate(contours):
|
40 |
+
area = cv2.contourArea(contour)
|
41 |
+
if area > max_area:
|
42 |
+
max_area = area
|
43 |
+
largest_contour_index = i
|
44 |
+
largest_contour = contours[largest_contour_index]
|
45 |
+
|
46 |
+
# Calculate centroid using moments (OpenCV method for center of contour)
|
47 |
+
M = cv2.moments(largest_contour)
|
48 |
+
if M["m00"] != 0: # avoid division by zero
|
49 |
+
ball_x = int(M["m10"] / M["m00"])
|
50 |
+
ball_y = int(M["m01"] / M["m00"])
|
51 |
+
else:
|
52 |
+
ball_x = 0
|
53 |
+
ball_y = 0
|
54 |
+
|
55 |
+
img_with_contours = img.copy()
|
56 |
+
cv2.drawContours(
|
57 |
+
img_with_contours, [largest_contour], -1, (255, 0, 0), 2
|
58 |
+
) # Draw largest contour
|
59 |
+
cv2.circle(
|
60 |
+
img_with_contours, (ball_x, ball_y), 5, (255, 0, 0), -1
|
61 |
+
) # Draw center point
|
62 |
+
|
63 |
+
return img_with_contours, ball_x, ball_y
|
64 |
+
|
65 |
+
|
66 |
+
if __name__ == "__main__":
|
67 |
+
cap = cv2.VideoCapture(r"lbw.mp4")
|
68 |
+
color_finder = ColorFinder(False)
|
69 |
+
hsv_vals = {
|
70 |
+
"hmin": 10,
|
71 |
+
"smin": 44,
|
72 |
+
"vmin": 192,
|
73 |
+
"hmax": 125,
|
74 |
+
"smax": 114,
|
75 |
+
"vmax": 255,
|
76 |
+
}
|
77 |
+
|
78 |
+
while True:
|
79 |
+
frame, img = cap.read()
|
80 |
+
if not frame:
|
81 |
+
break
|
82 |
+
|
83 |
+
img_contours, x, y = ball_detect(img, color_finder, hsv_vals)
|
84 |
+
|
85 |
+
if img_contours is not None:
|
86 |
+
cv2.imshow("Ball Detection", img_contours)
|
87 |
+
else:
|
88 |
+
cv2.imshow("Ball Detection", img)
|
89 |
+
|
90 |
+
if cv2.waitKey(1) & 0xFF == ord("q"):
|
91 |
+
break
|
92 |
+
|
93 |
+
cap.release()
|
94 |
+
cv2.destroyAllWindows()
|
batsman.py
ADDED
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import cv2
|
2 |
+
import cvzone
|
3 |
+
import numpy as np
|
4 |
+
|
5 |
+
|
6 |
+
def batsman_detect(img, rgb_lower, rgb_upper, canny_threshold1=100, canny_threshold2=200):
|
7 |
+
"""
|
8 |
+
Detects a batsman in an image frame using color-based filtering and edge detection.
|
9 |
+
|
10 |
+
Args:
|
11 |
+
img: The input image frame (BGR format).
|
12 |
+
rgb_lower: NumPy array defining the lower bound of the RGB color range for batsman. e.g., np.array([112, 0, 181])
|
13 |
+
rgb_upper: NumPy array defining the upper bound of the RGB color range for batsman. e.g., np.array([255, 255, 255])
|
14 |
+
canny_threshold1: Lower threshold for Canny edge detection.
|
15 |
+
canny_threshold2: Upper threshold for Canny edge detection.
|
16 |
+
|
17 |
+
Returns:
|
18 |
+
contours: A list of contours detected in the color-masked and edge-processed image,
|
19 |
+
presumed to be the batsman. Returns an empty list if no contours are found.
|
20 |
+
"""
|
21 |
+
img_gray_rgb = cv2.cvtColor(
|
22 |
+
img, cv2.COLOR_BGR2RGB
|
23 |
+
) # Convert to RGB for color masking
|
24 |
+
img_blur = cv2.GaussianBlur(
|
25 |
+
img_gray_rgb, (5, 5), 1
|
26 |
+
) # Gaussian blur for noise reduction
|
27 |
+
|
28 |
+
# Edge Detection (Canny) - you can tune canny_threshold1 and canny_threshold2
|
29 |
+
img_canny = cv2.Canny(img_blur, canny_threshold1, canny_threshold2)
|
30 |
+
|
31 |
+
# Morphological Operations (Opening - Dilation followed by Erosion) - Consider experimenting with Closing (Erosion then Dilation)
|
32 |
+
kernel = np.ones((5, 5))
|
33 |
+
img_dilate = cv2.dilate(img_canny, kernel, iterations=2) # Dilate to thicken edges
|
34 |
+
img_threshold = cv2.erode(
|
35 |
+
img_dilate, kernel, iterations=2
|
36 |
+
) # Erode to remove noise and thin edges (Opening)
|
37 |
+
|
38 |
+
# Color Masking in RGB color space
|
39 |
+
mask = cv2.inRange(img_gray_rgb, rgb_lower, rgb_upper)
|
40 |
+
|
41 |
+
# Find contours in the color mask
|
42 |
+
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
43 |
+
|
44 |
+
return contours
|
45 |
+
|
46 |
+
|
47 |
+
if __name__ == "__main__":
|
48 |
+
cap = cv2.VideoCapture(r"lbw.mp4") # Adjust path if needed
|
49 |
+
|
50 |
+
# Default RGB color range - you should tune these using the trackbars below
|
51 |
+
default_rgb_lower = np.array([112, 0, 181])
|
52 |
+
default_rgb_upper = np.array([255, 255, 255])
|
53 |
+
|
54 |
+
# Canny thresholds - you can also tune these via trackbars if needed
|
55 |
+
default_canny_threshold1 = 100
|
56 |
+
default_canny_threshold2 = 200
|
57 |
+
|
58 |
+
def empty(a): # Dummy function for trackbar
|
59 |
+
pass
|
60 |
+
|
61 |
+
# Create a window for trackbars to tune RGB color range and Canny thresholds
|
62 |
+
cv2.namedWindow("Trackbars")
|
63 |
+
cv2.resizeWindow(
|
64 |
+
"Trackbars", 640, 480
|
65 |
+
) # Increased window height to fit more trackbars
|
66 |
+
cv2.createTrackbar("R Min", "Trackbars", default_rgb_lower[0], 255, empty)
|
67 |
+
cv2.createTrackbar("G Min", "Trackbars", default_rgb_lower[1], 255, empty)
|
68 |
+
cv2.createTrackbar("B Min", "Trackbars", default_rgb_lower[2], 255, empty)
|
69 |
+
cv2.createTrackbar("R Max", "Trackbars", default_rgb_upper[0], 255, empty)
|
70 |
+
cv2.createTrackbar("G Max", "Trackbars", default_rgb_upper[1], 255, empty)
|
71 |
+
cv2.createTrackbar("B Max", "Trackbars", default_rgb_upper[2], 255, empty)
|
72 |
+
cv2.createTrackbar(
|
73 |
+
"Canny Thresh 1", "Trackbars", default_canny_threshold1, 255, empty
|
74 |
+
) # Canny threshold 1
|
75 |
+
cv2.createTrackbar(
|
76 |
+
"Canny Thresh 2", "Trackbars", default_canny_threshold2, 255, empty
|
77 |
+
) # Canny threshold 2
|
78 |
+
|
79 |
+
while True:
|
80 |
+
frame, img = cap.read()
|
81 |
+
if not frame:
|
82 |
+
break
|
83 |
+
|
84 |
+
# Get trackbar positions for RGB color range
|
85 |
+
rgb_lower = np.array(
|
86 |
+
[
|
87 |
+
cv2.getTrackbarPos("R Min", "Trackbars"),
|
88 |
+
cv2.getTrackbarPos("G Min", "Trackbars"),
|
89 |
+
cv2.getTrackbarPos("B Min", "Trackbars"),
|
90 |
+
]
|
91 |
+
)
|
92 |
+
rgb_upper = np.array(
|
93 |
+
[
|
94 |
+
cv2.getTrackbarPos("R Max", "Trackbars"),
|
95 |
+
cv2.getTrackbarPos("G Max", "Trackbars"),
|
96 |
+
cv2.getTrackbarPos("B Max", "Trackbars"),
|
97 |
+
]
|
98 |
+
)
|
99 |
+
|
100 |
+
# Get trackbar positions for Canny thresholds
|
101 |
+
canny_threshold1 = cv2.getTrackbarPos("Canny Thresh 1", "Trackbars")
|
102 |
+
canny_threshold2 = cv2.getTrackbarPos("Canny Thresh 2", "Trackbars")
|
103 |
+
|
104 |
+
# Detect batsman using the function with tunable parameters
|
105 |
+
batsman_contours = batsman_detect(
|
106 |
+
img, rgb_lower, rgb_upper, canny_threshold1, canny_threshold2
|
107 |
+
)
|
108 |
+
|
109 |
+
img_contours = img.copy() # Copy image to draw contours on
|
110 |
+
for cnt in batsman_contours:
|
111 |
+
if (
|
112 |
+
cv2.contourArea(cnt) > 5000
|
113 |
+
): # Area filtering - you can adjust this threshold in main.py if needed
|
114 |
+
cv2.drawContours(
|
115 |
+
img_contours, cnt, -1, (0, 255, 0), 2
|
116 |
+
) # Draw batsman contours in green
|
117 |
+
|
118 |
+
img_mask = cv2.inRange(
|
119 |
+
cv2.cvtColor(img, cv2.COLOR_BGR2RGB), rgb_lower, rgb_upper
|
120 |
+
) # Show the mask for tuning
|
121 |
+
|
122 |
+
img_stack = cvzone.stackImages(
|
123 |
+
[img, img_mask, img_contours], 3, 0.5
|
124 |
+
) # Stack original, mask, and contours
|
125 |
+
cv2.imshow("Batsman Detection Tuning", img_stack) # Combined window for tuning
|
126 |
+
|
127 |
+
key = cv2.waitKey(1)
|
128 |
+
if key == ord("q"):
|
129 |
+
break
|
130 |
+
elif key == ord("s"): # Press 's' to save current RGB values to console
|
131 |
+
print("Saved RGB lower:", rgb_lower)
|
132 |
+
print("Saved RGB upper:", rgb_upper)
|
133 |
+
print("Saved Canny Threshold 1:", canny_threshold1)
|
134 |
+
print("Saved Canny Threshold 2:", canny_threshold2)
|
135 |
+
print(
|
136 |
+
"--- Copy these values to your main.py or default_rgb_lower/upper in batsman.py ---"
|
137 |
+
)
|
138 |
+
|
139 |
+
cap.release()
|
140 |
+
cv2.destroyAllWindows()
|
main_gradio.py
ADDED
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import cv2
|
2 |
+
import numpy as np
|
3 |
+
import gradio as gr
|
4 |
+
import tempfile
|
5 |
+
from pathlib import Path
|
6 |
+
from cvzone.ColorModule import ColorFinder
|
7 |
+
from batsman import batsman_detect
|
8 |
+
from ball_detect import ball_detect
|
9 |
+
|
10 |
+
# ---------------------------------------------------
|
11 |
+
# Static detection parameters (tune these if needed)
|
12 |
+
# ---------------------------------------------------
|
13 |
+
mycolorFinder = ColorFinder(False)
|
14 |
+
|
15 |
+
# HSV range for ball – tune for your footage
|
16 |
+
hsvVals = {
|
17 |
+
"hmin": 10,
|
18 |
+
"smin": 44,
|
19 |
+
"vmin": 192,
|
20 |
+
"hmax": 125,
|
21 |
+
"smax": 114,
|
22 |
+
"vmax": 255,
|
23 |
+
}
|
24 |
+
|
25 |
+
# RGB range & Canny thresholds for batsman – replace with tuned values
|
26 |
+
tuned_rgb_lower = np.array([112, 0, 181])
|
27 |
+
tuned_rgb_upper = np.array([255, 255, 255])
|
28 |
+
tuned_canny_threshold1 = 100
|
29 |
+
tuned_canny_threshold2 = 200
|
30 |
+
|
31 |
+
# ---------------------------------------------------
|
32 |
+
# Helper to classify each frame event
|
33 |
+
# ---------------------------------------------------
|
34 |
+
|
35 |
+
def ball_pitch_pad(x, x_prev, prev_x_diff, y, y_prev, prev_y_diff, batLeg):
|
36 |
+
"""Return 'Pad', 'Pitch' or 'Motion' based on ball & batsman coords."""
|
37 |
+
if x_prev == 0 and y_prev == 0:
|
38 |
+
return "Motion", 0, 0
|
39 |
+
|
40 |
+
if abs(x - x_prev) > 3 * abs(prev_x_diff) and abs(prev_x_diff) > 0:
|
41 |
+
if y < batLeg:
|
42 |
+
return "Pad", x - x_prev, y - y_prev
|
43 |
+
|
44 |
+
if y - y_prev < 0 and prev_y_diff > 0:
|
45 |
+
if y < batLeg:
|
46 |
+
return "Pad", x - x_prev, y - y_prev
|
47 |
+
else:
|
48 |
+
return "Pitch", x - x_prev, y - y_prev
|
49 |
+
|
50 |
+
return "Motion", x - x_prev, y - y_prev
|
51 |
+
|
52 |
+
# ---------------------------------------------------
|
53 |
+
# Main analysis routine wrapped for Gradio
|
54 |
+
# ---------------------------------------------------
|
55 |
+
|
56 |
+
def detect_lbw(video):
|
57 |
+
"""Run the LBW detector on an uploaded video, return annotated clip + verdict."""
|
58 |
+
# Accept both dict (gradio 4.x) or str
|
59 |
+
video_path = video if isinstance(video, (str, Path)) else video.get("name")
|
60 |
+
|
61 |
+
cap = cv2.VideoCapture(str(video_path))
|
62 |
+
if not cap.isOpened():
|
63 |
+
raise ValueError("Unable to open uploaded video.")
|
64 |
+
|
65 |
+
fps = cap.get(cv2.CAP_PROP_FPS) or 25
|
66 |
+
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
67 |
+
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
68 |
+
|
69 |
+
# Prepare temporary output file for annotated video
|
70 |
+
tmpfile = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4")
|
71 |
+
out_path = tmpfile.name
|
72 |
+
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
|
73 |
+
writer = cv2.VideoWriter(out_path, fourcc, fps, (width, height))
|
74 |
+
|
75 |
+
# Tracking variables
|
76 |
+
x = y = batLeg = 0
|
77 |
+
x_prev = y_prev = 0
|
78 |
+
prev_x_diff = prev_y_diff = 0
|
79 |
+
lbw_detected = False
|
80 |
+
|
81 |
+
while True:
|
82 |
+
x_prev, y_prev = x, y
|
83 |
+
success, img = cap.read()
|
84 |
+
if not success:
|
85 |
+
break
|
86 |
+
|
87 |
+
overlay = img.copy()
|
88 |
+
|
89 |
+
# 1️⃣ Ball detection
|
90 |
+
_, x, y = ball_detect(img, mycolorFinder, hsvVals)
|
91 |
+
if x and y:
|
92 |
+
cv2.circle(overlay, (x, y), 8, (255, 0, 0), -1)
|
93 |
+
|
94 |
+
# 2️⃣ Batsman detection
|
95 |
+
batsmanContours = batsman_detect(
|
96 |
+
img,
|
97 |
+
tuned_rgb_lower,
|
98 |
+
tuned_rgb_upper,
|
99 |
+
tuned_canny_threshold1,
|
100 |
+
tuned_canny_threshold2,
|
101 |
+
)
|
102 |
+
|
103 |
+
# Compute batsman's leg (lowest y among contours above the ball)
|
104 |
+
current_batLeg = float("inf")
|
105 |
+
for cnt in batsmanContours:
|
106 |
+
if cv2.contourArea(cnt) > 5000 and y != 0 and min(cnt[:, :, 1]) < y:
|
107 |
+
leg_candidate = max(cnt[:, :, 1])
|
108 |
+
current_batLeg = min(current_batLeg, leg_candidate)
|
109 |
+
cv2.drawContours(overlay, cnt, -1, (0, 255, 0), 3)
|
110 |
+
batLeg = current_batLeg if current_batLeg != float("inf") else batLeg
|
111 |
+
|
112 |
+
# 3️⃣ Classify the motion event for this frame
|
113 |
+
motion_type, prev_x_diff, prev_y_diff = ball_pitch_pad(
|
114 |
+
x, x_prev, prev_x_diff, y, y_prev, prev_y_diff, batLeg
|
115 |
+
)
|
116 |
+
|
117 |
+
if motion_type == "Pad":
|
118 |
+
lbw_detected = True
|
119 |
+
cv2.putText(
|
120 |
+
overlay,
|
121 |
+
"PAD CONTACT",
|
122 |
+
(50, 80),
|
123 |
+
cv2.FONT_HERSHEY_SIMPLEX,
|
124 |
+
1.6,
|
125 |
+
(0, 0, 255),
|
126 |
+
4,
|
127 |
+
cv2.LINE_AA,
|
128 |
+
)
|
129 |
+
elif motion_type == "Pitch":
|
130 |
+
cv2.putText(
|
131 |
+
overlay,
|
132 |
+
"Bounced",
|
133 |
+
(50, 80),
|
134 |
+
cv2.FONT_HERSHEY_SIMPLEX,
|
135 |
+
1.6,
|
136 |
+
(0, 255, 255),
|
137 |
+
4,
|
138 |
+
cv2.LINE_AA,
|
139 |
+
)
|
140 |
+
|
141 |
+
writer.write(overlay)
|
142 |
+
|
143 |
+
cap.release()
|
144 |
+
writer.release()
|
145 |
+
|
146 |
+
verdict = "✅ Potential LBW Detected!" if lbw_detected else "❌ No LBW Detected."
|
147 |
+
return out_path, verdict
|
148 |
+
|
149 |
+
# ---------------------------------------------------
|
150 |
+
# Gradio interface
|
151 |
+
# ---------------------------------------------------
|
152 |
+
|
153 |
+
demo = gr.Interface(
|
154 |
+
fn=detect_lbw,
|
155 |
+
inputs=gr.Video(label="Upload cricket clip (side-on view)"),
|
156 |
+
outputs=[
|
157 |
+
gr.Video(label="Annotated Review"),
|
158 |
+
gr.Textbox(label="Decision"),
|
159 |
+
],
|
160 |
+
title="Automated LBW Detector",
|
161 |
+
description=(
|
162 |
+
"Upload a short video of the delivery. The system analyses the ball & batsman "
|
163 |
+
"interaction frame-by-frame, overlays detections, and flags potential LBW instances."
|
164 |
+
),
|
165 |
+
)
|
166 |
+
|
167 |
+
if __name__ == "__main__":
|
168 |
+
demo.launch()
|
pitch.py
ADDED
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import cv2
|
2 |
+
import numpy as np
|
3 |
+
|
4 |
+
|
5 |
+
def pitch(img):
|
6 |
+
"""
|
7 |
+
Detects the cricket pitch in a given image frame using color-based segmentation and edge detection.
|
8 |
+
|
9 |
+
Args:
|
10 |
+
img: The input image frame (BGR format).
|
11 |
+
|
12 |
+
Returns:
|
13 |
+
contours: A list of contours detected in the color-masked and edge-processed image,
|
14 |
+
presumed to be the pitch. Returns an empty list if no contours are found.
|
15 |
+
"""
|
16 |
+
imgGray = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
17 |
+
imgBlur= cv2.GaussianBlur(imgGray, (5, 5), 1)
|
18 |
+
imgThreshold = cv2.Canny(imgBlur, 190, 167)
|
19 |
+
kernel = np.ones((5, 5))
|
20 |
+
imgDial = cv2.dilate(imgThreshold, kernel, iterations = 2)
|
21 |
+
imgThreshold = cv2.erode(imgDial, kernel, iterations = 2)
|
22 |
+
|
23 |
+
|
24 |
+
lower = np.array([190, 167, 99])
|
25 |
+
upper = np.array([255, 255, 184 ])
|
26 |
+
|
27 |
+
|
28 |
+
mask = cv2.inRange(imgGray, lower, upper)
|
29 |
+
# Find all contours
|
30 |
+
|
31 |
+
width = 264
|
32 |
+
height = 2256
|
33 |
+
|
34 |
+
imgContours = img.copy()
|
35 |
+
# imgWrap = img.copy()
|
36 |
+
contours, hierarchy = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
37 |
+
return contours
|
38 |
+
|
39 |
+
|
40 |
+
if __name__ == "__main__":
|
41 |
+
cap = cv2.VideoCapture(r"lbw.mp4") # Adjust path if needed
|
42 |
+
|
43 |
+
while True:
|
44 |
+
frame, img = cap.read()
|
45 |
+
if not frame:
|
46 |
+
break
|
47 |
+
|
48 |
+
pitch_contours = pitch(img) # Call the placeholder pitch detection
|
49 |
+
|
50 |
+
img_contours = img.copy()
|
51 |
+
for cnt in pitch_contours:
|
52 |
+
if (
|
53 |
+
cv2.contourArea(cnt) > 50000
|
54 |
+
): # Example area filtering - adjust as needed
|
55 |
+
cv2.drawContours(
|
56 |
+
img_contours, cnt, -1, (0, 255, 0), 10
|
57 |
+
) # Draw pitch contours in green
|
58 |
+
|
59 |
+
cv2.imshow("Pitch Detection (Placeholder)", img_contours)
|
60 |
+
|
61 |
+
if cv2.waitKey(1) & 0xFF == ord("q"):
|
62 |
+
break
|
63 |
+
|
64 |
+
cap.release()
|
65 |
+
cv2.destroyAllWindows()
|
requirements.txt
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
cvzone==1.6.1
|
2 |
+
numpy==2.2.1
|
3 |
+
opencv-python==4.10.0.84
|
4 |
+
gradio
|