| 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
|
|
|
|
|
|
|
|
|
| mycolorFinder = ColorFinder(False)
|
|
|
|
|
| hsvVals = {
|
| "hmin": 10,
|
| "smin": 44,
|
| "vmin": 192,
|
| "hmax": 125,
|
| "smax": 114,
|
| "vmax": 255,
|
| }
|
|
|
|
|
| tuned_rgb_lower = np.array([112, 0, 181])
|
| tuned_rgb_upper = np.array([255, 255, 255])
|
| tuned_canny_threshold1 = 100
|
| tuned_canny_threshold2 = 200
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
|
|
|
|
|
|
|
| def detect_lbw(video):
|
| """Run the LBW detector on an uploaded video, return annotated clip + verdict."""
|
|
|
| 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))
|
|
|
|
|
| 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))
|
|
|
|
|
| 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()
|
|
|
|
|
| _, x, y = ball_detect(img, mycolorFinder, hsvVals)
|
| if x and y:
|
| cv2.circle(overlay, (x, y), 8, (255, 0, 0), -1)
|
|
|
|
|
| batsmanContours = batsman_detect(
|
| img,
|
| tuned_rgb_lower,
|
| tuned_rgb_upper,
|
| tuned_canny_threshold1,
|
| tuned_canny_threshold2,
|
| )
|
|
|
|
|
| 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
|
|
|
|
|
| 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
|
|
|
|
|
|
|
|
|
|
|
| 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() |