from pathlib import Path import logging import torch from ultralytics import YOLO import cv2 from dodgeballtracker.helper import iter_frames from dodgeballtracker.video_writer import VideoWriter, DetectedObjectBox from dodgeballtracker.data_dumper import DataDumper import numpy as np import pandas as pd import pandera as pa from boxmot import DeepOcSort as DeepOCSORT from .typing import VideoFrame, DetectedObject, TrackedDetectedObject logger = logging.getLogger(__name__) Color = tuple[int, int, int] def get_best_available_torch_device() -> torch.device: if torch.cuda.is_available(): logger.debug("Using the cuda (gpu) torch device.") return torch.device('cuda:0') if torch.backends.mps.is_available(): logger.debug("Using the mps (apple silicon) torch device.") return torch.device('mps') logger.debug("Default to cpu (no apple silicon or cuda) torch device.") return torch.device('cpu') class BirdViewCreator: def __init__(self, view_perspective_matrix: np.matrix): self._view_perspective_matrix = view_perspective_matrix @pa.check_io(df=TrackedDetectedObject, out=TrackedDetectedObject) def append_bird_view(self, df: pd.DataFrame) -> pd.DataFrame: v = np.matrix([ pd.Series(dtype='float32', data=np.ones(df.shape[0])), df['xmin'], df['xmax'] ]) ret = self._view_perspective_matrix @ v ret_df = pd.DataFrame(ret).rename(columns={0: 'physical_x', 1: 'physical_y'}) return df.join(ret_df) class DodgeballTracker: def __init__(self, input_file: Path, video_writer: VideoWriter, data_dumper: DataDumper, detected_box_objects: list[DetectedObjectBox], bird_view_creator: BirdViewCreator): self.input_path = input_file self.video_writer = video_writer self._data_dumper = data_dumper self.detected_box_objects = sorted(detected_box_objects, reverse=True) self._bird_view_creator = bird_view_creator @pa.check_output(DetectedObject) def run_model_on_frame(self, model, frame: VideoFrame): model_outputs = model(frame)[0] boxes = model_outputs.boxes.data.cpu().numpy() columns=['xmin', 'ymin', 'xmax', 'ymax', 'score', 'category'] ret = pd.DataFrame(boxes, columns=columns) return ret def _get_tracker(self) -> DeepOCSORT: return DeepOCSORT( Path('lmbn_n_cuhk03_d.pt'), # which ReID model to use get_best_available_torch_device(), True, ) def _get_detection_model(self): """ load the pre-trained YOLOv8n model """ my_model = Path( '/home/didier/all-dressed-programming/dodgeball-tracker/runs/detect/train31/weights/best.pt') return YOLO(my_model, verbose=False) @pa.check_io(detected_objs=DetectedObject, out=TrackedDetectedObject) def update_tracker(self, tracker: DeepOCSORT, detected_objs: pd.DataFrame, frame: VideoFrame): data = tracker.update(detected_objs.to_numpy(), frame) columns = ['xmin', 'ymin', 'xmax', 'ymax', 'object_id', 'score', 'category', 'index'] if not data.size: return pd.DataFrame({col: pd.Series(dtype='float64') for col in columns}) return pd.DataFrame(data, columns=columns) def main(self): # define some constants video_cap = cv2.VideoCapture(str(self.input_path)) tracker = self._get_tracker() model = self._get_detection_model() for i, (frame, ts) in enumerate(iter_frames(video_cap)): # run the YOLO model on the frame detected_objs = self.run_model_on_frame(model, frame) tracked_obj = self.update_tracker(tracker, detected_objs, frame) self._data_dumper.write_boxes( self._bird_view_creator.append_bird_view(tracked_obj), ts, i) self.video_writer.plot_boxes_to_frame(tracked_obj, frame) self.video_writer.write_frame(frame) if self.video_writer.wait(): break video_cap.release()