Spaces:
Running
Running
Irpan
commited on
Commit
•
7fa2fcb
1
Parent(s):
c74983a
init commit
Browse files- .DS_Store +0 -0
- input_videos/.DS_Store +0 -0
- models/.DS_Store +0 -0
- output_videos/.DS_Store +0 -0
- speed_and_distance_estimator/.DS_Store +0 -0
- speed_and_distance_estimator/__init__.py +1 -0
- speed_and_distance_estimator/speed_and_distance_estimator.py +73 -0
- stubs/.DS_Store +0 -0
- stubs/camera_movement_stub_121364_0_small.pkl +0 -0
- stubs/track_stub_121364_0_small.pkl +0 -0
- team_assigner/.DS_Store +0 -0
- team_assigner/__init__.py +1 -0
- team_assigner/team_assigner.py +73 -0
- trackers/.DS_Store +0 -0
- trackers/__init__.py +1 -0
- trackers/tracker.py +218 -0
- utils/.DS_Store +0 -0
- utils/__init__.py +2 -0
- utils/bbox_utils.py +16 -0
- utils/video_utils.py +18 -0
- view_transformer/.DS_Store +0 -0
- view_transformer/__init__.py +1 -0
- view_transformer/view_transformer.py +45 -0
.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
|