Visualization#
This quick tutorial introduces the key concepts and basic features of visualization.
%load_ext autoreload
%autoreload 2
The autoreload extension is already loaded. To reload it, use:
%reload_ext autoreload
import sportslabkit as slk
root = slk.utils.get_git_root()
dataset_path = slk.datasets.get_path('wide_view')
path_to_csv = sorted(dataset_path.glob('annotations/*.csv'))[0]
path_to_mp4 = sorted(dataset_path.glob('videos/*.mp4'))[0]
df = slk.load_df(path_to_csv)
df.head()
| TeamID | 0 | ... | 1 | 3 | |||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| PlayerID | 1 | 10 | ... | 9 | 0 | ||||||||||||||||
| Attributes | bb_left | bb_top | bb_width | bb_height | conf | bb_left | bb_top | bb_width | bb_height | conf | ... | bb_left | bb_top | bb_width | bb_height | conf | bb_left | bb_top | bb_width | bb_height | conf |
| frame | |||||||||||||||||||||
| 0 | 3543.0 | 607.0 | 30.0 | 52.5 | 1.0 | 3536.42 | 555.93 | 13.57 | 42.39 | 1.0 | ... | 2919.31 | 538.44 | 23.59 | 47.18 | 1.0 | 3542.77 | 549.47 | 6.4 | 7.0 | 1.0 |
| 1 | 3542.0 | 609.0 | 32.0 | 51.0 | 1.0 | 3536.13 | 555.96 | 13.66 | 42.27 | 1.0 | ... | 2919.44 | 538.55 | 23.59 | 47.18 | 1.0 | 3548.55 | 549.43 | 6.4 | 7.0 | 1.0 |
| 2 | 3542.0 | 611.0 | 32.0 | 50.0 | 1.0 | 3535.85 | 555.99 | 13.73 | 42.16 | 1.0 | ... | 2919.57 | 538.66 | 23.59 | 47.18 | 1.0 | 3554.32 | 549.40 | 6.4 | 7.0 | 1.0 |
| 3 | 3542.0 | 613.0 | 32.0 | 49.0 | 1.0 | 3535.57 | 556.02 | 13.80 | 42.04 | 1.0 | ... | 2919.70 | 538.77 | 23.59 | 47.18 | 1.0 | 3560.10 | 549.36 | 6.4 | 7.0 | 1.0 |
| 4 | 3539.0 | 615.0 | 36.0 | 46.0 | 1.0 | 3535.28 | 556.04 | 13.88 | 41.94 | 1.0 | ... | 2919.84 | 538.88 | 23.59 | 47.18 | 1.0 | 3565.87 | 549.33 | 6.4 | 7.0 | 1.0 |
5 rows × 115 columns
from sportslabkit.utils import cv2pil
cam = slk.Camera(path_to_mp4)
frame = cam.get_frame(1)
h, w = frame.shape[:2]
cv2pil(frame, convert_bgr2rgb=False).resize((w//6, h//6))
df.visualize_frame(0, frame)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[9], line 1
----> 1 df.visualize_frame(0, frame)
File ~/Github/SportsLabKit/sportslabkit/dataframe/bboxdataframe.py:123, in BBoxDataFrame.visualize_frame(self, frame_idx, frame, draw_frame_id, use_cool_viz)
121 label = f"{team_id}_{player_id}"
122 player_id_int = sum([int(x) for x in str(hash(player_id))[1:]])
--> 123 color = _COLOR_NAMES[hash(player_id_int) % len(_COLOR_NAMES)]
125 logger.debug(f"x1: {x1}, y1: {y1}, x2: {x2}, y2: {y2}, label: {label}, color: {color}")
126 frame = add_bbox_to_frame(frame, x1, y1, x2, y2, label, color)
NameError: name '_COLOR_NAMES' is not defined
import cv2
import json
data_path = '/Users/atom/Github/SoccerTrack/soccertrack/datasets/fisheye_keypoints.json'
with open(data_path) as f:
keypoints = json.load(f)
def draw_f(frame):
draw = frame.copy()
# draw = cv2.cvtColor(draw, cv2.COLOR_RGB2BGR)
p1 = keypoints['(0.0, 0.0)']
p2 = keypoints['(0.0, 68.0)']
p3 = keypoints['(105.0, 0.0)']
p4 = keypoints['(105.0, 68.0)']
# draw the four corners
for p in [p1, p2, p3, p4]:
x, y = int(p[0]), int(p[1])
cv2.circle(draw, (x, y), 25, (255, 0, 0), -1)
cv2.line(draw, (int(p1[0]), int(p1[1])), (int(p2[0]), int(p2[1])), (255, 0, 0), 5)
cv2.line(draw, (int(p1[0]), int(p1[1])), (int(p3[0]), int(p3[1])), (255, 0, 0), 5)
cv2.line(draw, (int(p2[0]), int(p2[1])), (int(p4[0]), int(p4[1])), (255, 0, 0), 5)
cv2.line(draw, (int(p3[0]), int(p3[1])), (int(p4[0]), int(p4[1])), (255, 0, 0), 5)
for s, keypoint in keypoints.items():
x, y = int(keypoint[0]), int(keypoint[1])
cv2.circle(draw, (x, y), 5, (0, 255, 0), -1)
return draw
wide_view_mp4 = 'overlay_ball.mp4'
cam = Camera(wide_view_mp4)
cam._seek(0)
slk.utils.make_video((draw_f(frame) for frame in cam), 'test_with_lines_2.mp4', input_framerate=cam.frame_rate, custom_ffmpeg='/opt/homebrew/bin/ffmpeg')
# cv2pil(draw).resize((w//6, h//6))
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
Cell In[4], line 6
3 import json
4 data_path = '/Users/atom/Github/SoccerTrack/soccertrack/datasets/fisheye_keypoints.json'
----> 6 with open(data_path) as f:
7 keypoints = json.load(f)
9 def draw_f(frame):
File ~/miniconda3/envs/sportslabkit-test/lib/python3.10/site-packages/IPython/core/interactiveshell.py:284, in _modified_open(file, *args, **kwargs)
277 if file in {0, 1, 2}:
278 raise ValueError(
279 f"IPython won't let you open fd={file} by default "
280 "as it is likely to crash IPython. If you know what you are doing, "
281 "you can use builtins' open."
282 )
--> 284 return io_open(file, *args, **kwargs)
FileNotFoundError: [Errno 2] No such file or directory: '/Users/atom/Github/SoccerTrack/soccertrack/datasets/fisheye_keypoints.json'
import cv2
from soccertrack.utils import make_video
import json
data_path = '/Users/atom/Github/SoccerTrack/soccertrack/datasets/fisheye_keypoints.json'
with open(data_path) as f:
keypoints = json.load(f)
def draw_f(frame):
draw = frame.copy()
# draw = cv2.cvtColor(draw, cv2.COLOR_RGB2BGR)
p1 = keypoints['(0.0, 0.0)']
p2 = keypoints['(0.0, 68.0)']
p3 = keypoints['(105.0, 0.0)']
p4 = keypoints['(105.0, 68.0)']
# draw the four corners
for p in [p1, p2, p3, p4]:
x, y = int(p[0]), int(p[1])
cv2.circle(draw, (x, y), 25, (255, 0, 0), -1)
cv2.line(draw, (int(p1[0]), int(p1[1])), (int(p2[0]), int(p2[1])), (255, 0, 0), 5)
cv2.line(draw, (int(p1[0]), int(p1[1])), (int(p3[0]), int(p3[1])), (255, 0, 0), 5)
cv2.line(draw, (int(p2[0]), int(p2[1])), (int(p4[0]), int(p4[1])), (255, 0, 0), 5)
cv2.line(draw, (int(p3[0]), int(p3[1])), (int(p4[0]), int(p4[1])), (255, 0, 0), 5)
for s, keypoint in keypoints.items():
x, y = int(keypoint[0]), int(keypoint[1])
cv2.circle(draw, (x, y), 5, (0, 255, 0), -1)
return draw
wide_view_mp4 = 'overlay_ball.mp4'
cam = Camera(wide_view_mp4)
cam._seek(0)
make_video((draw_f(frame) for frame in cam), 'test_with_lines_2.mp4', input_framerate=cam.frame_rate, custom_ffmpeg='/opt/homebrew/bin/ffmpeg')
# cv2pil(draw).resize((w//6, h//6))
Plotting a single frame#
cam = slk.Camera(wide_view_mp4)
frame = cam.get_frame(1)
cv2pil(df.visualize_frame(frame_idx=1, frame=frame))
frame = cam.get_frame(1)
rect = Rect(10, 10, 4286, 42)
color = Color(0, 255, 0)
cv2pil(draw_ellipse(frame, rect, color, 2))
MARKER_CONTOUR_COLOR_HEX = "000000"
MARKER_CONTOUR_COLOR = Color.from_hex_string(MARKER_CONTOUR_COLOR_HEX)
# red
PLAYER_MARKER_FILL_COLOR_HEX = "FF0000"
PLAYER_MARKER_FILL_COLOR = Color.from_hex_string(PLAYER_MARKER_FILL_COLOR_HEX)
# green
BALL_MERKER_FILL_COLOR_HEX = "00FF00"
BALL_MARKER_FILL_COLOR = Color.from_hex_string(BALL_MERKER_FILL_COLOR_HEX)
MARKER_CONTOUR_THICKNESS = 2
MARKER_WIDTH = 20
MARKER_HEIGHT = 20
MARKER_MARGIN = 10
# distance in pixels from the player's bounding box where we consider the ball is in his possession
PLAYER_IN_POSSESSION_PROXIMITY = 30
# calculates coordinates of possession marker
def calculate_marker(anchor: Point) -> np.ndarray:
x, y = anchor.int_xy_tuple
return(np.array([
[x - MARKER_WIDTH // 2, y - MARKER_HEIGHT - MARKER_MARGIN],
[x, y - MARKER_MARGIN],
[x + MARKER_WIDTH // 2, y - MARKER_HEIGHT - MARKER_MARGIN]
]))
# draw single possession marker
def draw_marker(image: np.ndarray, anchor: Point, color: Color) -> np.ndarray:
possession_marker_countour = calculate_marker(anchor=anchor)
image = draw_filled_polygon(
image=image,
countour=possession_marker_countour,
color=color)
image = draw_polygon(
image=image,
countour=possession_marker_countour,
color=MARKER_CONTOUR_COLOR,
thickness=MARKER_CONTOUR_THICKNESS)
return image
# Colors are in BGR format
# Orange
BALL_COLOR_HEX = "#005AFF"
BALL_COLOR = Color.from_hex_string(BALL_COLOR_HEX)
# red
GOALKEEPER_1_COLOR_HEX = "#0000FF"
GOALKEEPER_COLOR_1 = Color.from_hex_string(GOALKEEPER_1_COLOR_HEX)
# yellow
GOALKEEPER_2_COLOR_HEX = "#00FFFF"
GOALKEEPER_COLOR_2 = Color.from_hex_string(GOALKEEPER_2_COLOR_HEX)
# Sky Blue
PLAYER_1_COLOR_HEX = "#C0C0C0"
PLAYER_COLOR_1 = Color.from_hex_string(PLAYER_1_COLOR_HEX)
# Silver
PLAYER_2_COLOR_HEX = "#FFFF00"
PLAYER_COLOR_2 = Color.from_hex_string(PLAYER_2_COLOR_HEX)
COLORS = [
BALL_COLOR,
GOALKEEPER_COLOR_1,
GOALKEEPER_COLOR_2,
PLAYER_COLOR_1,
PLAYER_COLOR_2,
]
THICKNESS = 4
CLASS_BALL = 0
CLASS_GOALKEEPER_1 = 1
CLASS_GOALKEEPER_2 = 2
CLASS_PLAYER_1 = 3
CLASS_PLAYER_2= 4
# initiate annotators
annotator = BaseAnnotator(
colors=COLORS,
thickness=THICKNESS)
text_annotator = TextAnnotator(background_color=Color(255, 255, 255), text_color=Color(0, 0, 0), text_thickness=2)
ball_marker_annotator = MarkerAnntator(color=BALL_COLOR)
# player_marker_annotator = MarkerAnntator(color=PLAYER_MARKER_FILL_COLOR)
def plot_frame(frame_number, _frame):
frame = _frame.copy()
detections = []
for (teamd_id, player_id), _detection in df[df.index == frame_number].iter_players():
team_id = int(teamd_id) if teamd_id != 'BALL' else 3
player_id = int(player_id) if player_id != 'BALL' else 0
if _detection.empty:
continue
x_min, y_min, w, h, confidence = _detection[['bb_left', 'bb_top', 'bb_width', 'bb_height', 'conf']].values.squeeze()
x_max = x_min + w
y_max = y_min + h
if team_id == 0:
class_id = CLASS_PLAYER_1 if player_id != 0 else CLASS_GOALKEEPER_1
elif team_id == 1:
class_id = CLASS_PLAYER_2 if player_id != 10 else CLASS_GOALKEEPER_2
elif team_id == 3:
class_id = CLASS_BALL
else:
raise ValueError(f"Unknown team id: {team_id}")
det = Detection(
rect=Rect(
x=float(x_min),
y=float(y_min),
width=float(x_max - x_min),
height=float(y_max - y_min),
),
tracker_id=player_id,
class_id=class_id,
class_name='',
confidence=float(confidence),
)
detections.append(det)
player_detections = list(filter(lambda d: d.class_id !=CLASS_BALL, detections))
ball_detections = list(filter(lambda d: d.class_id ==CLASS_BALL, detections))
# annotate video frame
annotated_image = annotator.annotate(image=frame, detections=player_detections)
annotated_image = text_annotator.annotate(image=annotated_image, detections=player_detections)
annotated_image = ball_marker_annotator.annotate(image=annotated_image, detections=ball_detections)
return annotated_image
cv2pil(plot_frame(1, cam.get_frame(1)), convert_bgr2rgb=False)
slk.utils.make_video((plot_frame(i+1, frame[:,:,::-1]) for i, frame in enumerate(cam.iter_frames())), 'assets/cool_vis.mp4', input_framerate=25)
Writing video: 74it [00:04, 14.84it/s]
---------------------------------------------------------------------------
KeyboardInterrupt Traceback (most recent call last)
Cell In[163], line 1
----> 1 slk.utils.make_video((plot_frame(i+1, frame[:,:,::-1]) for i, frame in enumerate(cam.iter_frames())), 'assets/cool_vis.mp4', input_framerate=25)
File ~/Github/SoccerTrack/sportslabkit/utils/utils.py:296, in make_video(frames, outpath, vcodec, pix_fmt, preset, crf, ss, t, c, height, width, input_framerate, logging, custom_ffmpeg)
287 writer = WriteGear(
288 output=outpath,
289 compression_mode=True,
(...)
292 **output_params,
293 )
295 # loop over
--> 296 for frame in tqdm(frames, desc=f"Writing video", level="INFO"):
297 writer.write(frame, rgb_mode=True) # activate RGB Mode
299 writer.close()
File ~/miniconda3/envs/soccertrack-test-env/lib/python3.10/site-packages/tqdm/std.py:1178, in tqdm.__iter__(self)
1175 time = self._time
1177 try:
-> 1178 for obj in iterable:
1179 yield obj
1180 # Update and possibly print the progressbar.
1181 # Note: does not call self.update(1) for speed optimisation.
Cell In[163], line 1, in <genexpr>(.0)
----> 1 slk.utils.make_video((plot_frame(i+1, frame[:,:,::-1]) for i, frame in enumerate(cam.iter_frames())), 'assets/cool_vis.mp4', input_framerate=25)
Cell In[124], line 3, in plot_frame(frame_number, _frame)
1 def plot_frame(frame_number, _frame):
----> 3 frame = _frame.copy()
5 detections = []
6 for (teamd_id, player_id), _detection in df[df.index == frame_number].iter_players():
KeyboardInterrupt:
import cv2
import pandas as pd
def write_dataframe_on_image(df, image, start_pos=(10, 10), font_scale=0.5, color=(255, 255, 255)):
# Define the starting position
x, y = start_pos
# Loop through DataFrame and write text on the image
counter = 1
for index, row in df.iterrows():
TeamID = index[1]
PlayerID = index[2]
text = f"TID: {TeamID} PID: {PlayerID}, x: {row['bb_top']:.2f}, y: {row['bb_left']:.2f}, w: {row['bb_width']:.2f}, h: {row['bb_height']:.2f}"
cv2.putText(image, text, (x, int(y + counter * 30 * font_scale * 1.3)), cv2.FONT_HERSHEY_SIMPLEX, font_scale, color, 2)
counter += 1
return image
# Example usage:
# for i, frame in enumerate(cam.iter_frames()):
# frame = plot_frame(i+1, frame[:,:,::-1]) # Ensure plot_frame is defined
# frame_df = df[df.index == i+1].to_long_df() # Ensure to_long_df is defined
# image = write_dataframe_on_image(frame_df, frame, start_pos=(50, 50), font_scale=1, color=(20,255,83))
def f(i, frame):
frame = plot_frame(i+1, frame[:,:,::-1]) # Ensure plot_frame is defined
frame_df = df[df.index == i+1].to_long_df() # Ensure to_long_df is defined
image = write_dataframe_on_image(frame_df, frame, start_pos=(1000, 50), font_scale=1, color=(20,255,83))
return image
cam = slk.Camera(wide_view_mp4)
slk.utils.make_video((f(i, frame) for i, frame in enumerate(cam.iter_frames())), "video.mp4", input_framerate=25)
Writing video: 750it [01:20, 9.30it/s]
# dedicated annotator to draw possession markers on video frames
def draw_circle(image, center, radius, color, thickness=1):
return cv2.circle(
img=image,
center=center,
radius=radius,
color=color,
thickness=thickness)
@dataclass
class TrackAnnotator:
def __init__(self, color) -> None:
self.color = color
self.centers = []
def annotate(self, image: np.ndarray, detections: List[Detection]) -> np.ndarray:
annotated_image = image.copy()
for detection in detections:
# FIXME: This breaks if there are multiple detections
annotated_image = draw_circle(
image=image,
center=detection.rect.bottom_center.int_xy_tuple,
radius=int(detection.rect.width * 1.5),
color=self.color.bgr_tuple,
thickness=3
)
# calculate text dimensions
size, _ = cv2.getTextSize(
"Ball Detected!!",
cv2.FONT_HERSHEY_SIMPLEX,
1.7,
thickness=4)
width, height = size
# calculate text background position
center_x, center_y = detection.rect.bottom_center.int_xy_tuple
x = center_x - width // 2 + 350
y = center_y - height // 2 + 10 - 75
# draw background
background_color = COLORS[detection.class_id]
annotated_image = draw_filled_rect(
image=annotated_image,
rect=Rect(x=x, y=y, width=width * 1, height=height * 1).pad(padding=25),
color=background_color)
annotated_image = draw_filled_rect(
image=annotated_image,
rect=Rect(x=x, y=y, width=width, height=height).pad(padding=15),
color=Color.from_hex_string("#202529"))
# draw text
annotated_image = cv2.putText(image, "Ball Detected!!", Point(x=x, y=y + height).int_xy_tuple, cv2.FONT_HERSHEY_SIMPLEX, 1.7, Color.from_hex_string("#ffffff").bgr_tuple, 4, 2, False)
# draw horizontal line from text
bx, by = detection.rect.bottom_center.int_xy_tuple
tx, ty = Point(x=x, y=y + height).int_xy_tuple
# make the line a bit shorter
tx = bx + int((tx - bx) * 0.8)
ty = by + int((ty - by) * 0.8)
bx = bx + int((tx - bx) * 0.2)
by = by + int((ty - by) * 0.2)
annotated_image = cv2.line(
img=annotated_image,
pt1=(bx, by),
pt2=(tx, ty),
color=self.color.bgr_tuple,
thickness=3)
if self.centers:
for i, center in enumerate(self.centers):
annotated_image = draw_circle(
image=image,
center=center,
radius=int(detection.rect.width * 0.5 * (1 - (len(self.centers) - i) / len(self.centers))),
color=self.color.bgr_tuple,
thickness=int(3 * (1 - (len(self.centers) - i) / len(self.centers)))
)
self.centers.append(detection.rect.bottom_center.int_xy_tuple)
self.centers = self.centers[-50:]
return annotated_image
def plot_ball(frame_number, _frame):
frame = _frame.copy()
detections = []
for (teamd_id, player_id), _detection in df[df.index == frame_number].iter_players():
team_id = int(teamd_id) if teamd_id != 'BALL' else 3
player_id = int(player_id) if player_id != 'BALL' else 0
if _detection.empty:
continue
x_min, y_min, w, h, confidence = _detection.values.squeeze()
x_max = x_min + w
y_max = y_min + h
if team_id == 0:
class_id = CLASS_PLAYER_1 if player_id != 0 else CLASS_GOALKEEPER_1
elif team_id == 1:
class_id = CLASS_PLAYER_2 if player_id != 0 else CLASS_GOALKEEPER_2
elif team_id == 3:
class_id = CLASS_BALL
else:
raise ValueError(f"Unknown team id: {team_id}")
det = Detection(
rect=Rect(
x=float(x_min),
y=float(y_min),
width=float(x_max - x_min),
height=float(y_max - y_min),
),
tracker_id=player_id,
class_id=class_id,
class_name='',
confidence=float(confidence),
)
detections.append(det)
ball_detections = list(filter(lambda d: d.class_id ==CLASS_BALL, detections))
# annotate video frame
annotated_image = track_annotator.annotate(image=frame, detections=ball_detections)
return annotated_image
track_annotator = TrackAnnotator(color=BALL_COLOR)
cv2pil(plot_ball(1, cam.get_frame(0)), convert_bgr2rgb=False)
from soccertrack.utils.utils import make_video
make_video((plot_ball(i+1, frame) for i, frame in enumerate(cam.iter_frames())), 'test.mp4', input_framerate=25)
Writing video: 750it [01:05, 11.45it/s]
def plot_frame(frame_number, _frame):
frame = _frame.copy()
detections = []
for (teamd_id, player_id), _detection in df[df.index == frame_number].iter_players():
team_id = int(teamd_id)
player_id = int(player_id)
if _detection.empty:
continue
x_min, y_min, w, h, confidence = _detection.values.squeeze()
x_max = x_min + w
y_max = y_min + h
if team_id == 0:
class_id = CLASS_PLAYER_1 if player_id != 0 else CLASS_GOALKEEPER_1
elif team_id == 1:
class_id = CLASS_PLAYER_2 if player_id != 0 else CLASS_GOALKEEPER_2
elif team_id == 3:
class_id = CLASS_BALL
else:
raise ValueError(f"Unknown team id: {team_id}")
det = Detection(
rect=Rect(
x=float(x_min),
y=float(y_min),
width=float(x_max - x_min),
height=float(y_max - y_min),
),
tracker_id=player_id,
class_id=class_id,
class_name='',
confidence=float(confidence),
)
detections.append(det)
# HD size bounding box
box = Rect(x=0, y=0, width=1280, height=720)
if not detections:
frame = frame[box.y:box.y + box.height, box.x:box.x + box.width]
return frame
ball_detection = list(filter(lambda d: d.class_id ==CLASS_BALL, detections))[0]
# center the box on the ball but keep it within the frame
bx = int(min(max(ball_detection.rect.center.x - box.width / 2, 0), frame.shape[1] - box.width))
by = int(min(max(ball_detection.rect.center.y - box.height / 2, 0), frame.shape[0] - box.height))
box = Rect(x=bx, y=by, width=box.width, height=box.height)
# Mask out the box but keep the background visible with a bit of transparency
opacity = 0.5
mask = np.zeros(frame.shape, dtype=np.uint8)
mask = cv2.rectangle(mask, (box.x, box.y), (box.x + box.width, box.y + box.height), (255, 255, 255), -1)
masked_frame = cv2.bitwise_and(frame, mask)
masked_frame = cv2.addWeighted(masked_frame, opacity, frame, 1 - opacity, 0)
if 1:
# crop the frame to the box
masked_frame = masked_frame[box.y:box.y + box.height, box.x:box.x + box.width]
return masked_frame
cv2pil(plot_frame(1, cam.get_frame(0)))
make_video((plot_frame(i+1, frame) for i, frame in enumerate(cam.iter_frames())), 'test.mp4', input_framerate=25)
Writing video: 750it [00:23, 31.36it/s]
Plotting a multiple frames#
from pathlib import Path
path_to_mp4 = Path('/Users/atom/Downloads') / 'A031C0024_20230211201927_0005.mp4'
path_to_mp4.exists()
True
import soccertrack
from soccertrack import detection_model
from soccertrack.utils import get_git_root
from soccertrack import Camera
root = get_git_root()
cam = Camera(path_to_mp4) # Camera object will be used to load frames
frame = cam[50]
model_name = "yolov5"
model_repo = root / "external" / "yolov5"
model_ckpt = root / "models" / "yolov5" / "yolov5m.pt"
det_model = detection_model.load(model_name, model_repo, model_ckpt)
det_model.model.conf = 0.01
det_model.model.max_det=30
det_result = det_model(frame, size = 1080)
print(det_result.to_df())
det_result.show(width=5)
YOLOv5 🚀 v6.2-239-gf33718f Python-3.10.9 torch-1.13.1 CPU
Fusing layers...
YOLOv5m summary: 290 layers, 21172173 parameters, 0 gradients
Adding AutoShape...
bbox_left bbox_top bbox_width bbox_height conf class
0 1360.600342 574.979919 52.461426 127.136475 0.862603 0.0
1 919.093811 381.701691 20.472778 55.379272 0.759889 0.0
2 983.026978 339.392700 15.257019 46.538910 0.574201 0.0
3 677.153809 371.058777 12.505981 34.700684 0.540997 0.0
4 786.935303 341.576477 11.174805 26.958282 0.533679 0.0
5 1680.884888 660.867188 52.098389 85.228455 0.507503 0.0
6 998.585815 328.584778 12.681824 27.968323 0.425303 0.0
7 612.843567 357.670410 10.662842 29.959564 0.421751 0.0
8 494.011841 400.206360 12.484100 35.074371 0.392153 0.0
9 402.424957 659.799438 73.990845 138.629578 0.390434 0.0
10 527.972778 371.210907 10.417969 31.941589 0.385738 0.0
11 956.745789 319.386902 11.761597 24.552277 0.381997 0.0
12 742.699341 340.571045 9.885437 25.210480 0.325300 0.0
13 415.444702 412.434967 14.028442 36.732788 0.316447 0.0
14 692.042175 332.803802 11.818115 30.942322 0.308820 0.0
15 600.605225 285.604980 8.357971 20.970306 0.238000 9.0
16 1703.069824 649.187378 78.077271 104.185608 0.227472 0.0
17 1777.966064 610.168823 52.442505 91.897583 0.214207 0.0
18 1828.611328 630.927002 47.544678 90.606750 0.211516 0.0
19 984.745972 332.606079 26.558838 41.515198 0.207436 0.0
20 569.484497 346.354980 12.116089 28.495483 0.205302 0.0
21 807.627014 326.712769 9.158691 16.909149 0.182252 0.0
22 715.789185 334.858063 11.805420 22.541382 0.175251 0.0
23 1725.585938 677.930176 70.131104 101.957092 0.158464 0.0
24 697.893005 333.006226 28.073486 28.604126 0.099073 0.0
25 1740.136963 630.464233 43.717529 74.713501 0.098868 0.0
26 388.227234 392.773193 8.991974 21.113007 0.091106 0.0
27 1794.209229 630.646240 61.527466 106.581116 0.084217 0.0
28 1373.451172 342.751099 9.210571 24.034241 0.083496 0.0
29 649.039368 241.906372 13.403198 23.178558 0.073765 9.0
from soccertrack.logger import tqdm
preds_list = []
for frame in tqdm(cam.iter_frames()):
det_result = det_model(frame, size = 500)
preds = det_result.pred
preds_list.append(preds)
14%|█▍ | 615/4432 [24:34<2:32:32, 2.40s/it]
---------------------------------------------------------------------------
KeyboardInterrupt Traceback (most recent call last)
Cell In[3], line 5
3 preds_list = []
4 for frame in tqdm(cam.iter_frames()):
----> 5 det_result = det_model(frame, size = 500)
7 preds = det_result.pred
8 preds_list.append(preds)
File ~/Github/SoccerTrack/soccertrack/detection_model/base.py:156, in BaseDetectionModel.__call__(self, inputs, **kwargs)
154 def __call__(self, inputs, **kwargs):
155 inputs = self._check_and_fix_inputs(inputs)
--> 156 results = self.forward(inputs, **kwargs)
157 results = self._check_and_fix_outputs(results)
158 detections = self._postprocess(results, inputs)
File ~/Github/SoccerTrack/soccertrack/detection_model/yolov5.py:29, in YOLOv5.forward(self, x, **kwargs)
19 return {}
20 return {
21 "bbox_left": r[:, 0] - r[:, 2] / 2,
22 "bbox_top": r[:, 1] - r[:, 3] / 2,
(...)
26 "class": r[:, 5],
27 }
---> 29 results = self.model(x, **kwargs).xywh
30 results = [to_dict(r) for r in results]
32 return results
File ~/miniconda3/envs/soccertrack-gui/lib/python3.10/site-packages/torch/nn/modules/module.py:1194, in Module._call_impl(self, *input, **kwargs)
1190 # If we don't have any hooks, we want to skip the rest of the logic in
1191 # this function, and just call forward.
1192 if not (self._backward_hooks or self._forward_hooks or self._forward_pre_hooks or _global_backward_hooks
1193 or _global_forward_hooks or _global_forward_pre_hooks):
-> 1194 return forward_call(*input, **kwargs)
1195 # Do not call functions when jit is used
1196 full_backward_hooks, non_full_backward_hooks = [], []
File ~/miniconda3/envs/soccertrack-gui/lib/python3.10/site-packages/torch/autograd/grad_mode.py:27, in _DecoratorContextManager.__call__.<locals>.decorate_context(*args, **kwargs)
24 @functools.wraps(func)
25 def decorate_context(*args, **kwargs):
26 with self.clone():
---> 27 return func(*args, **kwargs)
File ~/Github/SoccerTrack/external/yolov5/models/common.py:708, in AutoShape.forward(self, ims, size, augment, profile)
705 with amp.autocast(autocast):
706 # Inference
707 with dt[1]:
--> 708 y = self.model(x, augment=augment) # forward
710 # Post-process
711 with dt[2]:
File ~/miniconda3/envs/soccertrack-gui/lib/python3.10/site-packages/torch/nn/modules/module.py:1194, in Module._call_impl(self, *input, **kwargs)
1190 # If we don't have any hooks, we want to skip the rest of the logic in
1191 # this function, and just call forward.
1192 if not (self._backward_hooks or self._forward_hooks or self._forward_pre_hooks or _global_backward_hooks
1193 or _global_forward_hooks or _global_forward_pre_hooks):
-> 1194 return forward_call(*input, **kwargs)
1195 # Do not call functions when jit is used
1196 full_backward_hooks, non_full_backward_hooks = [], []
File ~/Github/SoccerTrack/external/yolov5/models/common.py:518, in DetectMultiBackend.forward(self, im, augment, visualize)
515 im = im.permute(0, 2, 3, 1) # torch BCHW to numpy BHWC shape(1,320,192,3)
517 if self.pt: # PyTorch
--> 518 y = self.model(im, augment=augment, visualize=visualize) if augment or visualize else self.model(im)
519 elif self.jit: # TorchScript
520 y = self.model(im)
File ~/miniconda3/envs/soccertrack-gui/lib/python3.10/site-packages/torch/nn/modules/module.py:1194, in Module._call_impl(self, *input, **kwargs)
1190 # If we don't have any hooks, we want to skip the rest of the logic in
1191 # this function, and just call forward.
1192 if not (self._backward_hooks or self._forward_hooks or self._forward_pre_hooks or _global_backward_hooks
1193 or _global_forward_hooks or _global_forward_pre_hooks):
-> 1194 return forward_call(*input, **kwargs)
1195 # Do not call functions when jit is used
1196 full_backward_hooks, non_full_backward_hooks = [], []
File ~/Github/SoccerTrack/external/yolov5/models/yolo.py:211, in DetectionModel.forward(self, x, augment, profile, visualize)
209 if augment:
210 return self._forward_augment(x) # augmented inference, None
--> 211 return self._forward_once(x, profile, visualize)
File ~/Github/SoccerTrack/external/yolov5/models/yolo.py:123, in BaseModel._forward_once(self, x, profile, visualize)
121 if profile:
122 self._profile_one_layer(m, x, dt)
--> 123 x = m(x) # run
124 y.append(x if m.i in self.save else None) # save output
125 if visualize:
File ~/miniconda3/envs/soccertrack-gui/lib/python3.10/site-packages/torch/nn/modules/module.py:1194, in Module._call_impl(self, *input, **kwargs)
1190 # If we don't have any hooks, we want to skip the rest of the logic in
1191 # this function, and just call forward.
1192 if not (self._backward_hooks or self._forward_hooks or self._forward_pre_hooks or _global_backward_hooks
1193 or _global_forward_hooks or _global_forward_pre_hooks):
-> 1194 return forward_call(*input, **kwargs)
1195 # Do not call functions when jit is used
1196 full_backward_hooks, non_full_backward_hooks = [], []
File ~/Github/SoccerTrack/external/yolov5/models/common.py:169, in C3.forward(self, x)
168 def forward(self, x):
--> 169 return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), 1))
File ~/miniconda3/envs/soccertrack-gui/lib/python3.10/site-packages/torch/nn/modules/module.py:1194, in Module._call_impl(self, *input, **kwargs)
1190 # If we don't have any hooks, we want to skip the rest of the logic in
1191 # this function, and just call forward.
1192 if not (self._backward_hooks or self._forward_hooks or self._forward_pre_hooks or _global_backward_hooks
1193 or _global_forward_hooks or _global_forward_pre_hooks):
-> 1194 return forward_call(*input, **kwargs)
1195 # Do not call functions when jit is used
1196 full_backward_hooks, non_full_backward_hooks = [], []
File ~/miniconda3/envs/soccertrack-gui/lib/python3.10/site-packages/torch/nn/modules/container.py:204, in Sequential.forward(self, input)
202 def forward(self, input):
203 for module in self:
--> 204 input = module(input)
205 return input
File ~/miniconda3/envs/soccertrack-gui/lib/python3.10/site-packages/torch/nn/modules/module.py:1194, in Module._call_impl(self, *input, **kwargs)
1190 # If we don't have any hooks, we want to skip the rest of the logic in
1191 # this function, and just call forward.
1192 if not (self._backward_hooks or self._forward_hooks or self._forward_pre_hooks or _global_backward_hooks
1193 or _global_forward_hooks or _global_forward_pre_hooks):
-> 1194 return forward_call(*input, **kwargs)
1195 # Do not call functions when jit is used
1196 full_backward_hooks, non_full_backward_hooks = [], []
File ~/Github/SoccerTrack/external/yolov5/models/common.py:122, in Bottleneck.forward(self, x)
121 def forward(self, x):
--> 122 return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
KeyboardInterrupt:
import numpy as np
import cv2
from soccertrack.utils import cv2pil, make_video
from scipy.signal import savgol_filter
def plot_frame(preds_list, camera):
frame = camera.get_frame(0).copy()
frame_xmin, frame_ymin = 0, 100
frame_xmax, frame_ymax = 1920, 800
frame_x = frame.shape[1]
frame_y = frame.shape[0]
new_frame_width = 1000#int(frame_x // 1.8)
new_frame_height = 500#int(frame_y // 1.8)
bxs, bys = [], []
for preds in preds_list:
preds = preds[(preds[:, 0] + preds[:, 2] < frame_xmax) & (preds[:, 1] + preds[:, 3] < frame_ymax)]
new_frame_bx = preds[:, 0].mean() - new_frame_width // 2
new_frame_by = preds[:, 1].mean() - new_frame_height // 2
# center the box on the ball but keep it within the frame
bx = int(min(max(new_frame_bx / 2, 0), frame_xmax - new_frame_width))
by = int(min(max(new_frame_by/ 2, 200), (frame_ymax - new_frame_height)))
bxs.append(bx)
bys.append(by)
bxs = savgol_filter(bxs, 501, 3).astype(int)
bys = savgol_filter(bys, 501, 3).astype(int)
for (bx, by), _frame in zip(zip(bxs, bys), camera.iter_frames()):
frame = _frame.copy()
# Mask out the box but keep the background visible with a bit of transparency
opacity = 0.5
mask = np.zeros(frame.shape, dtype=np.uint8)
mask = cv2.rectangle(mask, (bx, by), (bx + new_frame_width, by + new_frame_height), (255, 255, 255), -1)
masked_frame = cv2.bitwise_and(frame, mask)
masked_frame = cv2.addWeighted(masked_frame, opacity, frame, 1 - opacity, 0)
# crop the frame to the box
masked_frame = masked_frame[by:by + new_frame_height, bx:bx + new_frame_width]
yield masked_frame
make_video(plot_frame(preds_list, cam), 'test.mp4')
Writing video: 615it [00:40, 15.28it/s]
from IPython.display import Video
Video('test.mp4')
import matplotlib.font_manager as fm
fm.fontManager.addfont('/Users/atom/Downloads/xkcd-Regular-webfont/xkcd-Regular-webfont.ttf')
codf.visualize_frame(1, home_key='0', away_key='1', ball_key='3',
home_kwargs={'markerfacecolor': 'white'},
away_kwargs={'markerfacecolor': 'blue'})
# Importing the Seaborn library
import seaborn as sns
import matplotlib.pyplot as plt
data = pd.read_csv('/Users/atom/Downloads/readthedocs_traffic_analytics_soccertrack_2023-05-13_2023-08-11.csv')
# Converting the 'Date' column to a datetime object
data['Date'] = pd.to_datetime(data['Date'])
# Resampling the data by month and summing the views
monthly_views = data.resample('M', on='Date')['Views'].sum().reset_index()
# Setting the xkcd style
plt.xkcd()
# Creating the bar plot
plt.figure(figsize=(10, 6), facecolor='#0C1118')
sns.barplot(x=monthly_views['Date'].dt.strftime('%B %Y'), y=monthly_views['Views'], palette="Blues_d")
# Adding labels and title
plt.title('Monthly Views (Read the Docs - SoccerTrack Project)')
plt.xlabel('Month')
plt.ylabel('Number of Views')
plt.grid(True, color="white", linestyle="--", linewidth=0.5)
plt.xticks(rotation=45)
# Displaying the plot
plt.show()
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Humor Sans' not found.
findfont: Font family 'Comic Neue' not found.