File size: 6,338 Bytes
4efd841
 
239b12e
88fe94b
 
 
 
de70a25
88fe94b
de70a25
 
88fe94b
de70a25
88fe94b
 
de70a25
185b0b9
0fb91cc
88fe94b
 
 
0fb91cc
 
88fe94b
 
 
0fb91cc
88fe94b
0fb91cc
88fe94b
 
 
 
0fb91cc
de70a25
 
0fb91cc
 
de70a25
 
0fb91cc
de70a25
88fe94b
de70a25
 
 
0fb91cc
88fe94b
 
0fb91cc
 
 
 
 
 
88fe94b
 
 
 
0fb91cc
88fe94b
 
 
 
 
 
e7cb6e4
88fe94b
 
0fb91cc
 
88fe94b
de70a25
88fe94b
 
 
 
0fb91cc
88fe94b
 
 
0fb91cc
 
de70a25
0fb91cc
88fe94b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
de70a25
0fb91cc
 
88fe94b
de70a25
88fe94b
 
 
 
de70a25
 
 
88fe94b
 
 
 
 
0fb91cc
 
 
 
88fe94b
 
0fb91cc
88fe94b
 
 
0fb91cc
 
 
4efd841
 
 
88fe94b
 
 
0fb91cc
 
11562f0
88fe94b
0fb91cc
4efd841
a2aa653
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import cv2
import numpy as np
import torch
from ultralytics import YOLO
import gradio as gr
from scipy.interpolate import interp1d
import uuid
import os

# Load the trained YOLOv8n model from the Space's root directory
model = YOLO("best.pt")  # Assumes best.pt is in the same directory as app.py

# Constants for LBW decision and video processing
STUMPS_WIDTH = 0.2286  # meters (width of stumps)
BALL_DIAMETER = 0.073  # meters (approx. cricket ball diameter)
FRAME_RATE = 30  # Input video frame rate
SLOW_MOTION_FACTOR = 2  # For slow motion (6x slower)
CONF_THRESHOLD = 0.3  # Lowered confidence threshold for better detection

def process_video(video_path):
    # Initialize video capture
    if not os.path.exists(video_path):
        return [], [], "Error: Video file not found"
    cap = cv2.VideoCapture(video_path)
    frames = []
    ball_positions = []
    debug_log = []

    frame_count = 0
    while cap.isOpened():
        ret, frame = cap.read()
        if not ret:
            break
        frame_count += 1
        frames.append(frame.copy())  # Store original frame
        # Detect ball using the trained YOLOv8n model
        results = model.predict(frame, conf=CONF_THRESHOLD)
        detections = 0
        for detection in results[0].boxes:
            if detection.cls == 0:  # Assuming class 0 is the ball
                detections += 1
                x1, y1, x2, y2 = detection.xyxy[0].cpu().numpy()
                ball_positions.append([(x1 + x2) / 2, (y1 + y2) / 2])
                # Draw bounding box on frame for visualization
                cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2)
        frames[-1] = frame  # Update frame with bounding box
        debug_log.append(f"Frame {frame_count}: {detections} ball detections")
    cap.release()

    if not ball_positions:
        debug_log.append("No balls detected in any frame")
    else:
        debug_log.append(f"Total ball detections: {len(ball_positions)}")

    return frames, ball_positions, "\n".join(debug_log)

def estimate_trajectory(ball_positions, frames):
    # Simplified physics-based trajectory projection
    if len(ball_positions) < 2:
        return None, None, "Error: Fewer than 2 ball detections for trajectory"
    # Extract x, y coordinates
    x_coords = [pos[0] for pos in ball_positions]
    y_coords = [pos[1] for pos in ball_positions]
    times = np.arange(len(ball_positions)) / FRAME_RATE

    # Interpolate to smooth trajectory
    try:
        fx = interp1d(times, x_coords, kind='linear', fill_value="extrapolate")
        fy = interp1d(times, y_coords, kind='quadratic', fill_value="extrapolate")
    except Exception as e:
        return None, None, f"Error in trajectory interpolation: {str(e)}"

    # Project trajectory forward (0.5 seconds post-impact)
    t_future = np.linspace(times[-1], times[-1] + 0.5, 10)
    x_future = fx(t_future)
    y_future = fy(t_future)

    return list(zip(x_future, y_future)), t_future, "Trajectory estimated successfully"

def lbw_decision(ball_positions, trajectory, frames):
    # Simplified LBW logic
    if not frames:
        return "Error: No frames processed", None
    if not trajectory or len(ball_positions) < 2:
        return "Not enough data (insufficient ball detections)", None

    # Assume stumps are at the bottom center of the frame (calibration needed)
    frame_height, frame_width = frames[0].shape[:2]
    stumps_x = frame_width / 2
    stumps_y = frame_height * 0.9  # Approximate stumps position
    stumps_width_pixels = frame_width * (STUMPS_WIDTH / 3.0)  # Assume 3m pitch width

    # Check pitching point (first detected position)
    pitch_x, pitch_y = ball_positions[0]
    if pitch_x < stumps_x - stumps_width_pixels / 2 or pitch_x > stumps_x + stumps_width_pixels / 2:
        return "Not Out (Pitched outside line)", None

    # Check impact point (last detected position)
    impact_x, impact_y = ball_positions[-1]
    if impact_x < stumps_x - stumps_width_pixels / 2 or impact_x > stumps_x + stumps_width_pixels / 2:
        return "Not Out (Impact outside line)", None

    # Check trajectory hitting stumps
    for x, y in trajectory:
        if abs(x - stumps_x) < stumps_width_pixels / 2 and abs(y - stumps_y) < frame_height * 0.1:
            return "Out", trajectory
    return "Not Out (Missing stumps)", trajectory

def generate_slow_motion(frames, trajectory, output_path):
    # Generate very slow-motion video with ball detection and trajectory overlay
    if not frames:
        return None
    fourcc = cv2.VideoWriter_fourcc(*'mp4v')
    out = cv2.VideoWriter(output_path, fourcc, FRAME_RATE / SLOW_MOTION_FACTOR, (frames[0].shape[1], frames[0].shape[0]))

    for frame in frames:
        if trajectory:
            for x, y in trajectory:
                cv2.circle(frame, (int(x), int(y)), 5, (255, 0, 0), -1)  # Blue dots for trajectory
        for _ in range(SLOW_MOTION_FACTOR):  # Duplicate frames for very slow motion
            out.write(frame)
    out.release()
    return output_path

def drs_review(video):
    # Process video and generate DRS output
    frames, ball_positions, debug_log = process_video(video)
    if not frames:
        return f"Error: Failed to process video\nDebug Log:\n{debug_log}", None
    trajectory, _, trajectory_log = estimate_trajectory(ball_positions, frames)
    decision, trajectory = lbw_decision(ball_positions, trajectory, frames)

    # Generate slow-motion replay even if Trajectory fails
    output_path = f"output_{uuid.uuid4()}.mp4"
    slow_motion_path = generate_slow_motion(frames, trajectory, output_path)

    # Combine debug logs for output
    debug_output = f"{debug_log}\n{trajectory_log}"
    return f"DRS Decision: {decision}\nDebug Log:\n{debug_output}", slow_motion_path

# Gradio interface
iface = gr.Interface(
    fn=drs_review,
    inputs=gr.Video(label="Upload Video Clip"),
    outputs=[
        gr.Textbox(label="DRS Decision and Debug Log"),
        gr.Video(label="Very Slow-Motion Replay with Ball Detection and Trajectory")
    ],
    title="AI-Powered DRS for LBW in Local Cricket",
    description="Upload a video clip of a cricket delivery to get an LBW decision and very slow-motion replay showing ball detection (green boxes) and trajectory (blue dots)."
)

if __name__ == "__main__":
    iface.launch()