Spaces:
Sleeping
Sleeping
Update gully_drs_core/ball_detection.py
Browse files- gully_drs_core/ball_detection.py +51 -18
gully_drs_core/ball_detection.py
CHANGED
@@ -5,14 +5,23 @@ import numpy as np
|
|
5 |
from .model_utils import load_model
|
6 |
|
7 |
def find_bounce_point(path):
|
8 |
-
"""
|
9 |
-
Finds the point where the ball bounces by detecting a Y-axis dip.
|
10 |
-
"""
|
11 |
for i in range(1, len(path)-1):
|
12 |
if path[i-1][1] > path[i][1] < path[i+1][1]: # y dips = bounce
|
13 |
return path[i]
|
14 |
return None
|
15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
def analyze_video(file_path):
|
17 |
model = load_model()
|
18 |
cap = cv2.VideoCapture(file_path)
|
@@ -23,44 +32,67 @@ def analyze_video(file_path):
|
|
23 |
ball_path = []
|
24 |
frames = []
|
25 |
|
|
|
|
|
|
|
|
|
|
|
26 |
while True:
|
27 |
ret, frame = cap.read()
|
28 |
if not ret:
|
29 |
break
|
30 |
|
31 |
results = model(frame)
|
|
|
|
|
32 |
for r in results:
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
|
41 |
frames.append(frame)
|
|
|
42 |
|
43 |
cap.release()
|
44 |
|
45 |
-
# Analyze trajectory
|
46 |
bounce_point = find_bounce_point(ball_path)
|
47 |
impact_point = ball_path[-1] if ball_path else None
|
48 |
|
49 |
-
# Stump zone: middle area at bottom
|
50 |
stump_zone = (
|
51 |
-
width // 2 - 30,
|
52 |
-
height - 100,
|
53 |
-
width // 2 + 30,
|
54 |
-
height
|
55 |
)
|
56 |
|
57 |
-
# LBW Decision: if impact is in stump zone
|
58 |
decision = "OUT" if (
|
59 |
impact_point and
|
60 |
stump_zone[0] <= impact_point[0] <= stump_zone[2] and
|
61 |
stump_zone[1] <= impact_point[1] <= stump_zone[3]
|
62 |
) else "NOT OUT"
|
63 |
|
|
|
|
|
64 |
return {
|
65 |
"trajectory": ball_path,
|
66 |
"fps": fps,
|
@@ -68,5 +100,6 @@ def analyze_video(file_path):
|
|
68 |
"bounce_point": bounce_point,
|
69 |
"impact_point": impact_point,
|
70 |
"decision": decision,
|
71 |
-
"stump_zone": stump_zone
|
|
|
72 |
}
|
|
|
5 |
from .model_utils import load_model
|
6 |
|
7 |
def find_bounce_point(path):
|
|
|
|
|
|
|
8 |
for i in range(1, len(path)-1):
|
9 |
if path[i-1][1] > path[i][1] < path[i+1][1]: # y dips = bounce
|
10 |
return path[i]
|
11 |
return None
|
12 |
|
13 |
+
def estimate_speed(ball_path, fps, px_to_m=0.01):
|
14 |
+
if len(ball_path) < 2:
|
15 |
+
return 0.0
|
16 |
+
p1 = ball_path[0]
|
17 |
+
p2 = ball_path[min(5, len(ball_path)-1)]
|
18 |
+
dx, dy = p2[0] - p1[0], p2[1] - p1[1]
|
19 |
+
dist_px = (dx**2 + dy**2)**0.5
|
20 |
+
dist_m = dist_px * px_to_m
|
21 |
+
time_s = (min(5, len(ball_path)-1)) / fps
|
22 |
+
speed_kmh = (dist_m / time_s) * 3.6 if time_s > 0 else 0
|
23 |
+
return round(speed_kmh, 1)
|
24 |
+
|
25 |
def analyze_video(file_path):
|
26 |
model = load_model()
|
27 |
cap = cv2.VideoCapture(file_path)
|
|
|
32 |
ball_path = []
|
33 |
frames = []
|
34 |
|
35 |
+
max_jump = 100 # max allowed jump (pixels) between consecutive ball detections
|
36 |
+
|
37 |
+
last_point = None
|
38 |
+
frame_idx = 0
|
39 |
+
|
40 |
while True:
|
41 |
ret, frame = cap.read()
|
42 |
if not ret:
|
43 |
break
|
44 |
|
45 |
results = model(frame)
|
46 |
+
valid_detection = None
|
47 |
+
|
48 |
for r in results:
|
49 |
+
# Accept only if exactly one detection of cricket ball class (e.g., class 0)
|
50 |
+
ball_detections = [box for box in r.boxes if int(box.cls[0]) == 0]
|
51 |
+
if len(ball_detections) == 1:
|
52 |
+
box = ball_detections[0]
|
53 |
+
x1, y1, x2, y2 = map(int, box.xyxy[0])
|
54 |
+
cx, cy = (x1 + x2) // 2, (y1 + y2) // 2
|
55 |
+
|
56 |
+
# Check jump threshold from last point
|
57 |
+
if last_point:
|
58 |
+
dx, dy = cx - last_point[0], cy - last_point[1]
|
59 |
+
jump = (dx**2 + dy**2)**0.5
|
60 |
+
if jump > max_jump:
|
61 |
+
# Reject outlier
|
62 |
+
frames.append(frame)
|
63 |
+
frame_idx += 1
|
64 |
+
continue
|
65 |
+
|
66 |
+
valid_detection = (cx, cy)
|
67 |
+
last_point = valid_detection
|
68 |
+
cv2.circle(frame, valid_detection, 6, (0, 255, 0), -1)
|
69 |
+
|
70 |
+
if valid_detection:
|
71 |
+
ball_path.append(valid_detection)
|
72 |
|
73 |
frames.append(frame)
|
74 |
+
frame_idx += 1
|
75 |
|
76 |
cap.release()
|
77 |
|
|
|
78 |
bounce_point = find_bounce_point(ball_path)
|
79 |
impact_point = ball_path[-1] if ball_path else None
|
80 |
|
|
|
81 |
stump_zone = (
|
82 |
+
width // 2 - 30,
|
83 |
+
height - 100,
|
84 |
+
width // 2 + 30,
|
85 |
+
height
|
86 |
)
|
87 |
|
|
|
88 |
decision = "OUT" if (
|
89 |
impact_point and
|
90 |
stump_zone[0] <= impact_point[0] <= stump_zone[2] and
|
91 |
stump_zone[1] <= impact_point[1] <= stump_zone[3]
|
92 |
) else "NOT OUT"
|
93 |
|
94 |
+
speed_kmh = estimate_speed(ball_path, fps)
|
95 |
+
|
96 |
return {
|
97 |
"trajectory": ball_path,
|
98 |
"fps": fps,
|
|
|
100 |
"bounce_point": bounce_point,
|
101 |
"impact_point": impact_point,
|
102 |
"decision": decision,
|
103 |
+
"stump_zone": stump_zone,
|
104 |
+
"speed_kmh": speed_kmh
|
105 |
}
|