Irpan commited on
Commit
7fa2fcb
1 Parent(s): c74983a

init commit

Browse files
.DS_Store ADDED
Binary file (6.15 kB). View file
 
input_videos/.DS_Store ADDED
Binary file (6.15 kB). View file
 
models/.DS_Store ADDED
Binary file (6.15 kB). View file
 
output_videos/.DS_Store ADDED
Binary file (6.15 kB). View file
 
speed_and_distance_estimator/.DS_Store ADDED
Binary file (6.15 kB). View file
 
speed_and_distance_estimator/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from .speed_and_distance_estimator import SpeedAndDistance_Estimator
speed_and_distance_estimator/speed_and_distance_estimator.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import sys
3
+ sys.path.append('../')
4
+ from utils import measure_distance ,get_foot_position
5
+
6
+ class SpeedAndDistance_Estimator():
7
+ def __init__(self):
8
+ self.frame_window=5
9
+ self.frame_rate=24
10
+
11
+ def add_speed_and_distance_to_tracks(self,tracks, exclude_objects = ['ball', 'referees']):
12
+ total_distance= {}
13
+
14
+ for object, object_tracks in tracks.items():
15
+ if object in exclude_objects:
16
+ continue
17
+ number_of_frames = len(object_tracks)
18
+ for frame_num in range(0,number_of_frames, self.frame_window):
19
+ last_frame = min(frame_num+self.frame_window,number_of_frames-1 )
20
+
21
+ for track_id,_ in object_tracks[frame_num].items():
22
+ if track_id not in object_tracks[last_frame]:
23
+ continue
24
+
25
+ start_position = object_tracks[frame_num][track_id]['position_transformed']
26
+ end_position = object_tracks[last_frame][track_id]['position_transformed']
27
+
28
+ if start_position is None or end_position is None:
29
+ continue
30
+
31
+ distance_covered = measure_distance(start_position,end_position)
32
+ time_elapsed = (last_frame-frame_num)/self.frame_rate
33
+ speed_meteres_per_second = distance_covered/time_elapsed
34
+ speed_km_per_hour = speed_meteres_per_second*3.6
35
+
36
+ if object not in total_distance:
37
+ total_distance[object]= {}
38
+
39
+ if track_id not in total_distance[object]:
40
+ total_distance[object][track_id] = 0
41
+
42
+ total_distance[object][track_id] += distance_covered
43
+
44
+ for frame_num_batch in range(frame_num,last_frame):
45
+ if track_id not in tracks[object][frame_num_batch]:
46
+ continue
47
+ tracks[object][frame_num_batch][track_id]['speed'] = speed_km_per_hour
48
+ tracks[object][frame_num_batch][track_id]['distance'] = total_distance[object][track_id]
49
+
50
+ def draw_speed_and_distance(self,frames,tracks):
51
+ output_frames = []
52
+ for frame_num, frame in enumerate(frames):
53
+ for object, object_tracks in tracks.items():
54
+ if object == "referees":
55
+ continue
56
+ for _, track_info in object_tracks[frame_num].items():
57
+ if "speed" in track_info:
58
+ speed = track_info.get('speed',None)
59
+ distance = track_info.get('distance',None)
60
+ if speed is None or distance is None:
61
+ continue
62
+
63
+ bbox = track_info['bbox']
64
+ position = get_foot_position(bbox)
65
+ position = list(position)
66
+ position[1]+=40
67
+
68
+ position = tuple(map(int,position))
69
+ cv2.putText(frame, f"{speed:.2f} km/h",position,cv2.FONT_HERSHEY_SIMPLEX,0.5,(0,0,0),2)
70
+ cv2.putText(frame, f"{distance:.2f} m",(position[0],position[1]+20),cv2.FONT_HERSHEY_SIMPLEX,0.5,(0,0,0),2)
71
+ output_frames.append(frame)
72
+
73
+ return output_frames
stubs/.DS_Store ADDED
Binary file (6.15 kB). View file
 
stubs/camera_movement_stub_121364_0_small.pkl ADDED
Binary file (4.82 kB). View file
 
stubs/track_stub_121364_0_small.pkl ADDED
Binary file (435 kB). View file
 
team_assigner/.DS_Store ADDED
Binary file (6.15 kB). View file
 
team_assigner/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from .team_assigner import TeamAssigner
team_assigner/team_assigner.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sklearn.cluster import KMeans
2
+
3
+ class TeamAssigner:
4
+ def __init__(self):
5
+ self.team_colors = {}
6
+ self.player_team_dict = {}
7
+
8
+ def get_clustering_model(self,image):
9
+ # Reshape the image to 2D array
10
+ image_2d = image.reshape(-1,3)
11
+
12
+ # Preform K-means with 2 clusters
13
+ kmeans = KMeans(n_clusters=2, init="k-means++",n_init=1)
14
+ kmeans.fit(image_2d)
15
+
16
+ return kmeans
17
+
18
+ def get_player_color(self,frame,bbox):
19
+ image = frame[int(bbox[1]):int(bbox[3]),int(bbox[0]):int(bbox[2])]
20
+
21
+ top_half_image = image[0:int(image.shape[0]/2),:]
22
+
23
+ # Get Clustering model
24
+ kmeans = self.get_clustering_model(top_half_image)
25
+
26
+ # Get the cluster labels forr each pixel
27
+ labels = kmeans.labels_
28
+
29
+ # Reshape the labels to the image shape
30
+ clustered_image = labels.reshape(top_half_image.shape[0],top_half_image.shape[1])
31
+
32
+ # Get the player cluster
33
+ corner_clusters = [clustered_image[0,0],clustered_image[0,-1],clustered_image[-1,0],clustered_image[-1,-1]]
34
+ non_player_cluster = max(set(corner_clusters),key=corner_clusters.count)
35
+ player_cluster = 1 - non_player_cluster
36
+
37
+ player_color = kmeans.cluster_centers_[player_cluster]
38
+
39
+ return player_color
40
+
41
+
42
+ def assign_team_color(self,frame, player_detections):
43
+
44
+ player_colors = []
45
+ for _, player_detection in player_detections.items():
46
+ bbox = player_detection["bbox"]
47
+ player_color = self.get_player_color(frame,bbox)
48
+ player_colors.append(player_color)
49
+
50
+ kmeans = KMeans(n_clusters=2, init="k-means++",n_init=10)
51
+ kmeans.fit(player_colors)
52
+
53
+ self.kmeans = kmeans
54
+
55
+ self.team_colors[1] = kmeans.cluster_centers_[0]
56
+ self.team_colors[2] = kmeans.cluster_centers_[1]
57
+
58
+
59
+ def get_player_team(self,frame,player_bbox,player_id):
60
+ if player_id in self.player_team_dict:
61
+ return self.player_team_dict[player_id]
62
+
63
+ player_color = self.get_player_color(frame,player_bbox)
64
+
65
+ team_id = self.kmeans.predict(player_color.reshape(1,-1))[0]
66
+ team_id+=1
67
+
68
+ if player_id ==91:
69
+ team_id=1
70
+
71
+ self.player_team_dict[player_id] = team_id
72
+
73
+ return team_id
trackers/.DS_Store ADDED
Binary file (6.15 kB). View file
 
trackers/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from .tracker import Tracker
trackers/tracker.py ADDED
@@ -0,0 +1,218 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from ultralytics import YOLO
2
+ import supervision as sv
3
+ import pickle
4
+ import os
5
+ import numpy as np
6
+ import pandas as pd
7
+ import cv2
8
+ import sys
9
+ sys.path.append('../')
10
+ from utils import get_center_of_bbox, get_bbox_width, get_foot_position
11
+
12
+ class Tracker:
13
+ def __init__(self, model_path):
14
+ self.model = YOLO(model_path)
15
+ self.tracker = sv.ByteTrack()
16
+
17
+ def add_position_to_tracks(sekf,tracks):
18
+ for object, object_tracks in tracks.items():
19
+ for frame_num, track in enumerate(object_tracks):
20
+ for track_id, track_info in track.items():
21
+ bbox = track_info['bbox']
22
+ if object == 'ball':
23
+ position= get_center_of_bbox(bbox)
24
+ else:
25
+ position = get_foot_position(bbox)
26
+ tracks[object][frame_num][track_id]['position'] = position
27
+
28
+ def interpolate_ball_positions(self,ball_positions):
29
+ ball_positions = [x.get(1,{}).get('bbox',[]) for x in ball_positions]
30
+ df_ball_positions = pd.DataFrame(ball_positions,columns=['x1','y1','x2','y2'])
31
+
32
+ # Interpolate missing values
33
+ df_ball_positions = df_ball_positions.interpolate()
34
+ df_ball_positions = df_ball_positions.bfill()
35
+
36
+ ball_positions = [{1: {"bbox":x}} for x in df_ball_positions.to_numpy().tolist()]
37
+
38
+ return ball_positions
39
+
40
+ def detect_frames(self, frames):
41
+ batch_size=20
42
+ detections = []
43
+ for i in range(0,len(frames),batch_size):
44
+ detections_batch = self.model.predict(frames[i:i+batch_size],conf=0.1)
45
+ detections += detections_batch
46
+ return detections
47
+
48
+ def get_object_tracks(self, frames, read_from_stub=False, stub_path=None):
49
+
50
+ if read_from_stub and stub_path is not None and os.path.exists(stub_path):
51
+ with open(stub_path,'rb') as f:
52
+ tracks = pickle.load(f)
53
+ return tracks
54
+
55
+ detections = self.detect_frames(frames)
56
+
57
+ tracks={
58
+ "players":[],
59
+ "referees":[],
60
+ "ball":[]
61
+ }
62
+ print(len(tracks['players']), len(tracks['referees']), len(tracks['ball']))
63
+
64
+ for frame_num, detection in enumerate(detections):
65
+ cls_names = detection.names
66
+ cls_names_inv = {v:k for k,v in cls_names.items()}
67
+
68
+ # Covert to supervision Detection format
69
+ detection_supervision = sv.Detections.from_ultralytics(detection)
70
+
71
+ # Convert GoalKeeper to player object
72
+ for object_ind , class_id in enumerate(detection_supervision.class_id):
73
+ if cls_names[class_id] == "goalkeeper":
74
+ detection_supervision.class_id[object_ind] = cls_names_inv["player"]
75
+
76
+ # Track Objects
77
+ detection_with_tracks = self.tracker.update_with_detections(detection_supervision)
78
+
79
+ tracks["players"].append({})
80
+ tracks["referees"].append({})
81
+ tracks["ball"].append({})
82
+
83
+ for frame_detection in detection_with_tracks:
84
+ bbox = frame_detection[0].tolist()
85
+ cls_id = frame_detection[3]
86
+ track_id = frame_detection[4]
87
+
88
+ if cls_id == cls_names_inv['player']:
89
+ tracks["players"][frame_num][track_id] = {"bbox":bbox}
90
+
91
+ if cls_id == cls_names_inv['referee']:
92
+ tracks["referees"][frame_num][track_id] = {"bbox":bbox}
93
+
94
+ for frame_detection in detection_supervision:
95
+ bbox = frame_detection[0].tolist()
96
+ cls_id = frame_detection[3]
97
+
98
+ if cls_id == cls_names_inv['ball']:
99
+ tracks["ball"][frame_num][1] = {"bbox":bbox}
100
+
101
+ if stub_path is not None:
102
+ with open(stub_path,'wb') as f:
103
+ pickle.dump(tracks,f)
104
+
105
+ return tracks
106
+
107
+ def draw_ellipse(self,frame,bbox,color,track_id=None):
108
+ y2 = int(bbox[3])
109
+ x_center, _ = get_center_of_bbox(bbox)
110
+ width = get_bbox_width(bbox)
111
+
112
+ cv2.ellipse(
113
+ frame,
114
+ center=(x_center,y2),
115
+ axes=(int(width), int(0.35*width)),
116
+ angle=0.0,
117
+ startAngle=-45,
118
+ endAngle=235,
119
+ color = color,
120
+ thickness=2,
121
+ lineType=cv2.LINE_4
122
+ )
123
+
124
+ rectangle_width = 40
125
+ rectangle_height=20
126
+ x1_rect = x_center - rectangle_width//2
127
+ x2_rect = x_center + rectangle_width//2
128
+ y1_rect = (y2- rectangle_height//2) +15
129
+ y2_rect = (y2+ rectangle_height//2) +15
130
+
131
+ if track_id is not None:
132
+ cv2.rectangle(frame,
133
+ (int(x1_rect),int(y1_rect) ),
134
+ (int(x2_rect),int(y2_rect)),
135
+ color,
136
+ cv2.FILLED)
137
+
138
+ x1_text = x1_rect+12
139
+ if track_id > 99:
140
+ x1_text -=10
141
+
142
+ cv2.putText(
143
+ frame,
144
+ f"{track_id}",
145
+ (int(x1_text),int(y1_rect+15)),
146
+ cv2.FONT_HERSHEY_SIMPLEX,
147
+ 0.6,
148
+ (0,0,0),
149
+ 2
150
+ )
151
+
152
+ return frame
153
+
154
+ def draw_traingle(self,frame,bbox,color):
155
+ y= int(bbox[1])
156
+ x,_ = get_center_of_bbox(bbox)
157
+
158
+ triangle_points = np.array([
159
+ [x,y],
160
+ [x-10,y-20],
161
+ [x+10,y-20],
162
+ ])
163
+ cv2.drawContours(frame, [triangle_points],0,color, cv2.FILLED)
164
+ cv2.drawContours(frame, [triangle_points],0,(0,0,0), 2)
165
+
166
+ return frame
167
+
168
+ def draw_team_ball_control(self,frame,frame_num,team_ball_control):
169
+ # Draw a semi-transparent rectaggle
170
+ overlay = frame.copy()
171
+ cv2.rectangle(overlay, (1350, 850), (1900,970), (255,255,255), -1 )
172
+ alpha = 0.4
173
+ cv2.addWeighted(overlay, alpha, frame, 1 - alpha, 0, frame)
174
+
175
+ team_ball_control_till_frame = team_ball_control[:frame_num+1]
176
+ # Get the number of time each team had ball control
177
+ team_1_num_frames = team_ball_control_till_frame[team_ball_control_till_frame==1].shape[0]
178
+ team_2_num_frames = team_ball_control_till_frame[team_ball_control_till_frame==2].shape[0]
179
+ team_1 = team_1_num_frames/(team_1_num_frames+team_2_num_frames)
180
+ team_2 = team_2_num_frames/(team_1_num_frames+team_2_num_frames)
181
+
182
+ cv2.putText(frame, f"Team 1 Possession: {team_1*100:.2f}%",(1400,900), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,0), 3)
183
+ cv2.putText(frame, f"Team 2 Possession: {team_2*100:.2f}%",(1400,950), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,0), 3)
184
+
185
+ return frame
186
+
187
+ def draw_annotations(self,video_frames, tracks,team_ball_control):
188
+ output_video_frames= []
189
+ for frame_num, frame in enumerate(video_frames):
190
+ frame = frame.copy()
191
+
192
+ player_dict = tracks["players"][frame_num]
193
+ ball_dict = tracks["ball"][frame_num]
194
+ referee_dict = tracks["referees"][frame_num]
195
+
196
+ # Draw Players
197
+ for track_id, player in player_dict.items():
198
+ color = player.get("team_color",(0,0,255))
199
+ frame = self.draw_ellipse(frame, player["bbox"],color, track_id)
200
+
201
+ if player.get('has_ball',False):
202
+ frame = self.draw_traingle(frame, player["bbox"],(0,0,255))
203
+
204
+ # Draw Referee
205
+ for _, referee in referee_dict.items():
206
+ frame = self.draw_ellipse(frame, referee["bbox"],(0,255,255))
207
+
208
+ # Draw ball
209
+ for track_id, ball in ball_dict.items():
210
+ frame = self.draw_traingle(frame, ball["bbox"],(255,255,50))
211
+
212
+
213
+ # Draw Team Ball Control
214
+ frame = self.draw_team_ball_control(frame, frame_num, team_ball_control)
215
+
216
+ output_video_frames.append(frame)
217
+
218
+ return output_video_frames
utils/.DS_Store ADDED
Binary file (6.15 kB). View file
 
utils/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ from .video_utils import read_video, save_video
2
+ from .bbox_utils import get_center_of_bbox, get_bbox_width, measure_distance,measure_xy_distance,get_foot_position
utils/bbox_utils.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def get_center_of_bbox(bbox):
2
+ x1,y1,x2,y2 = bbox
3
+ return int((x1+x2)/2),int((y1+y2)/2)
4
+
5
+ def get_bbox_width(bbox):
6
+ return bbox[2]-bbox[0]
7
+
8
+ def measure_distance(p1,p2):
9
+ return ((p1[0]-p2[0])**2 + (p1[1]-p2[1])**2)**0.5
10
+
11
+ def measure_xy_distance(p1,p2):
12
+ return p1[0]-p2[0],p1[1]-p2[1]
13
+
14
+ def get_foot_position(bbox):
15
+ x1,y1,x2,y2 = bbox
16
+ return int((x1+x2)/2),int(y2)
utils/video_utils.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+
3
+ def read_video(video_path):
4
+ cap = cv2.VideoCapture(video_path)
5
+ frames = []
6
+ while True:
7
+ ret, frame = cap.read()
8
+ if not ret:
9
+ break
10
+ frames.append(frame)
11
+ return frames
12
+
13
+ def save_video(ouput_video_frames,output_video_path):
14
+ fourcc = cv2.VideoWriter_fourcc(*'XVID')
15
+ out = cv2.VideoWriter(output_video_path, fourcc, 24, (ouput_video_frames[0].shape[1], ouput_video_frames[0].shape[0]))
16
+ for frame in ouput_video_frames:
17
+ out.write(frame)
18
+ out.release()
view_transformer/.DS_Store ADDED
Binary file (6.15 kB). View file
 
view_transformer/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from .view_transformer import ViewTransformer
view_transformer/view_transformer.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import cv2
3
+
4
+ class ViewTransformer():
5
+ def __init__(self):
6
+ court_width = 68
7
+ court_length = 23.32
8
+
9
+ self.pixel_vertices = np.array([[110, 1035],
10
+ [265, 275],
11
+ [910, 260],
12
+ [1640, 915]])
13
+
14
+ self.target_vertices = np.array([
15
+ [0,court_width],
16
+ [0, 0],
17
+ [court_length, 0],
18
+ [court_length, court_width]
19
+ ])
20
+
21
+ self.pixel_vertices = self.pixel_vertices.astype(np.float32)
22
+ self.target_vertices = self.target_vertices.astype(np.float32)
23
+
24
+ self.persepctive_trasnformer = cv2.getPerspectiveTransform(self.pixel_vertices, self.target_vertices)
25
+
26
+ def transform_point(self,point):
27
+ p = (int(point[0]),int(point[1]))
28
+ is_inside = cv2.pointPolygonTest(self.pixel_vertices,p,False) >= 0
29
+ if not is_inside:
30
+ return None
31
+
32
+ reshaped_point = point.reshape(-1,1,2).astype(np.float32)
33
+ tranform_point = cv2.perspectiveTransform(reshaped_point,self.persepctive_trasnformer)
34
+ return tranform_point.reshape(-1,2)
35
+
36
+ def add_transformed_position_to_tracks(self,tracks):
37
+ for object, object_tracks in tracks.items():
38
+ for frame_num, track in enumerate(object_tracks):
39
+ for track_id, track_info in track.items():
40
+ position = track_info['position_adjusted']
41
+ position = np.array(position)
42
+ position_trasnformed = self.transform_point(position)
43
+ if position_trasnformed is not None:
44
+ position_trasnformed = position_trasnformed.squeeze().tolist()
45
+ tracks[object][frame_num][track_id]['position_transformed'] = position_trasnformed