Spaces:
Sleeping
Sleeping
| import cv2 | |
| import numpy as np | |
| import gradio as gr | |
| import tempfile | |
| from pathlib import Path | |
| from cvzone.ColorModule import ColorFinder | |
| from batsman import batsman_detect | |
| from ball_detect import ball_detect | |
| # --------------------------------------------------- | |
| # Static detection parameters (tune these if needed) | |
| # --------------------------------------------------- | |
| mycolorFinder = ColorFinder(False) | |
| # HSV range for ball – tune for your footage | |
| hsvVals = { | |
| "hmin": 10, | |
| "smin": 44, | |
| "vmin": 192, | |
| "hmax": 125, | |
| "smax": 114, | |
| "vmax": 255, | |
| } | |
| # RGB range & Canny thresholds for batsman – replace with tuned values | |
| tuned_rgb_lower = np.array([112, 0, 181]) | |
| tuned_rgb_upper = np.array([255, 255, 255]) | |
| tuned_canny_threshold1 = 100 | |
| tuned_canny_threshold2 = 200 | |
| # --------------------------------------------------- | |
| # Helper to classify each frame event | |
| # --------------------------------------------------- | |
| def ball_pitch_pad(x, x_prev, prev_x_diff, y, y_prev, prev_y_diff, batLeg): | |
| """Return 'Pad', 'Pitch' or 'Motion' based on ball & batsman coords.""" | |
| if x_prev == 0 and y_prev == 0: | |
| return "Motion", 0, 0 | |
| if abs(x - x_prev) > 3 * abs(prev_x_diff) and abs(prev_x_diff) > 0: | |
| if y < batLeg: | |
| return "Pad", x - x_prev, y - y_prev | |
| if y - y_prev < 0 and prev_y_diff > 0: | |
| if y < batLeg: | |
| return "Pad", x - x_prev, y - y_prev | |
| else: | |
| return "Pitch", x - x_prev, y - y_prev | |
| return "Motion", x - x_prev, y - y_prev | |
| # --------------------------------------------------- | |
| # Main analysis routine wrapped for Gradio | |
| # --------------------------------------------------- | |
| def detect_lbw(video): | |
| """Run the LBW detector on an uploaded video, return annotated clip + verdict.""" | |
| # Accept both dict (gradio 4.x) or str | |
| video_path = video if isinstance(video, (str, Path)) else video.get("name") | |
| cap = cv2.VideoCapture(str(video_path)) | |
| if not cap.isOpened(): | |
| raise ValueError("Unable to open uploaded video.") | |
| fps = cap.get(cv2.CAP_PROP_FPS) or 25 | |
| width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) | |
| height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) | |
| # Prepare temporary output file for annotated video | |
| tmpfile = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") | |
| out_path = tmpfile.name | |
| fourcc = cv2.VideoWriter_fourcc(*"mp4v") | |
| writer = cv2.VideoWriter(out_path, fourcc, fps, (width, height)) | |
| # Tracking variables | |
| x = y = batLeg = 0 | |
| x_prev = y_prev = 0 | |
| prev_x_diff = prev_y_diff = 0 | |
| lbw_detected = False | |
| while True: | |
| x_prev, y_prev = x, y | |
| success, img = cap.read() | |
| if not success: | |
| break | |
| overlay = img.copy() | |
| # 1️⃣ Ball detection | |
| _, x, y = ball_detect(img, mycolorFinder, hsvVals) | |
| if x and y: | |
| cv2.circle(overlay, (x, y), 8, (255, 0, 0), -1) | |
| # 2️⃣ Batsman detection | |
| batsmanContours = batsman_detect( | |
| img, | |
| tuned_rgb_lower, | |
| tuned_rgb_upper, | |
| tuned_canny_threshold1, | |
| tuned_canny_threshold2, | |
| ) | |
| # Compute batsman's leg (lowest y among contours above the ball) | |
| current_batLeg = float("inf") | |
| for cnt in batsmanContours: | |
| if cv2.contourArea(cnt) > 5000 and y != 0 and min(cnt[:, :, 1]) < y: | |
| leg_candidate = max(cnt[:, :, 1]) | |
| current_batLeg = min(current_batLeg, leg_candidate) | |
| cv2.drawContours(overlay, cnt, -1, (0, 255, 0), 3) | |
| batLeg = current_batLeg if current_batLeg != float("inf") else batLeg | |
| # 3️⃣ Classify the motion event for this frame | |
| motion_type, prev_x_diff, prev_y_diff = ball_pitch_pad( | |
| x, x_prev, prev_x_diff, y, y_prev, prev_y_diff, batLeg | |
| ) | |
| if motion_type == "Pad": | |
| lbw_detected = True | |
| cv2.putText( | |
| overlay, | |
| "PAD CONTACT", | |
| (50, 80), | |
| cv2.FONT_HERSHEY_SIMPLEX, | |
| 1.6, | |
| (0, 0, 255), | |
| 4, | |
| cv2.LINE_AA, | |
| ) | |
| elif motion_type == "Pitch": | |
| cv2.putText( | |
| overlay, | |
| "Bounced", | |
| (50, 80), | |
| cv2.FONT_HERSHEY_SIMPLEX, | |
| 1.6, | |
| (0, 255, 255), | |
| 4, | |
| cv2.LINE_AA, | |
| ) | |
| writer.write(overlay) | |
| cap.release() | |
| writer.release() | |
| verdict = "✅ Potential LBW Detected!" if lbw_detected else "❌ No LBW Detected." | |
| return out_path, verdict | |
| # --------------------------------------------------- | |
| # Gradio interface | |
| # --------------------------------------------------- | |
| demo = gr.Interface( | |
| fn=detect_lbw, | |
| inputs=gr.Video(label="Upload cricket clip (side-on view)"), | |
| outputs=[ | |
| gr.Video(label="Annotated Review"), | |
| gr.Textbox(label="Decision"), | |
| ], | |
| title="Automated LBW Detector", | |
| description=( | |
| "Upload a short video of the delivery. The system analyses the ball & batsman " | |
| "interaction frame-by-frame, overlays detections, and flags potential LBW instances." | |
| ), | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() |