dschandra commited on
Commit
2649d00
·
verified ·
1 Parent(s): 0bce49d

Upload 5 files

Browse files
Files changed (5) hide show
  1. ball_detect.py +94 -0
  2. batsman.py +140 -0
  3. main_gradio.py +168 -0
  4. pitch.py +65 -0
  5. 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