|
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() |