├── .gitignore
├── LICENSE
├── README.md
├── camera_movement_estimator
├── __init__.py
└── camera_movement_estimator.py
├── development_and_analysis
└── color_assignment.ipynb
├── input_videos
├── NOTE.txt
└── match.mp4
├── main.py
├── models
└── NOTE.txt
├── output_images
└── player_2.jpg
├── output_videos
└── NOTE.txt
├── player_ball_assigner
├── __init__.py
└── player_ball_assigner.py
├── requirements.txt
├── speed_and_distance_estimator
├── __init__.py
└── speed_and_distance_estimator.py
├── stubs
├── camera_movement_stub.pkl
└── track_stubs.pkl
├── team_assigner
├── __init__.py
└── team_assigner.py
├── trackers
├── __init__.py
└── tracker.py
├── training
└── football_training_yolo_v5.ipynb
├── utils
├── __init__.py
├── bbox_utils.py
└── video_utils.py
├── view_transformer
├── __init__.py
└── view_transformer.py
└── yolo_inference.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *__pycahce__*
2 | *.pyc
3 | *.avi
4 | *.pt
5 | *football-players-detection-1*
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Rajveer Singh
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Football Analysis using YOLO
2 |
3 | This project employs YOLO (You Only Look Once) object detection to conduct comprehensive analysis of football matches. The goal is to provide detailed insights into player performance, team dynamics, ball possession, and camera movements during a match.
4 |
5 |
6 |
7 | ## Installation
8 |
9 | 1. **Clone the Repository:**
10 |
11 | ```bash
12 | git clone https://github.com/rajveersinghcse/football-analysis-using-yolo.git
13 | cd football-analysis-using-yolo
14 | ```
15 |
16 | 2. **Install Dependencies:**
17 | ```bash
18 | pip install -r requirements.txt
19 | ```
20 |
21 | The following libraries are used in this project:
22 |
23 | - ultralytics
24 | - numpy
25 | - opencv-python
26 | - roboflow
27 | - pandas
28 | - pickle
29 | - supervision
30 | - shutil
31 | - scikit-learn
32 |
33 | ## Usage
34 |
35 | 1. **Data Preparation:**
36 |
37 | - Place your video footage of the football match in the `input` directory.
38 |
39 | 2. **Running the Analysis:**
40 |
41 | - Execute the main script `python main.py` to initiate the analysis process.
42 | - The analysis encompasses the following key steps:
43 | - Object tracking using YOLO for players, referees, and the football.
44 | - Estimating camera movements to understand viewpoint changes.
45 | - Calculating player speed, distance traveled, and determining ball possession.
46 | - Visualizing analysis results on the video frames.
47 |
48 | 3. **Output:**
49 | - The annotated and analyzed video will be saved in the `output_videos` directory for review.
50 |
51 | ## Code Structure
52 |
53 | - **`utils.py`**: Contains utility functions for video I/O operations.
54 | - **`trackers.py`**: Implements the YOLO-based object tracker and interpolation techniques.
55 | - **`team_assigner.py`**: Assigns teams to players based on their visual appearance.
56 | - **`player_ball_assigner.py`**: Determines ball possession among players during the match.
57 | - **`camera_movement_estimator.py`**: Estimates camera movements to analyze perspective changes.
58 | - **`view_transformer.py`**: Transforms object positions based on the camera view for accurate analysis.
59 | - **`speed_and_distance_estimator.py`**: Calculates player speeds and distances traveled for performance evaluation.
60 |
61 | ## Contributing
62 |
63 | Contributions, feedback, and suggestions are highly encouraged! Please feel free to open an issue or submit a pull request with any improvements or new features.
64 |
65 | ## License
66 |
67 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
68 |
69 | ## Acknowledgements
70 |
71 | Special thanks to the YOLOv5 team and the contributors of the libraries used in this project for their valuable contributions to the field of object detection and analysis in computer vision.
72 |
--------------------------------------------------------------------------------
/camera_movement_estimator/__init__.py:
--------------------------------------------------------------------------------
1 | from .camera_movement_estimator import CameraMovementEstimator
--------------------------------------------------------------------------------
/camera_movement_estimator/camera_movement_estimator.py:
--------------------------------------------------------------------------------
1 | import pickle
2 | import cv2
3 | import numpy as np
4 | import os
5 | import sys
6 | sys.path.append('../')
7 | from utils import measure_distance,measure_xy_distance
8 |
9 | class CameraMovementEstimator():
10 | def __init__(self,frame):
11 | self.minimum_distance = 5
12 |
13 | self.lk_params = dict(
14 | winSize = (15,15),
15 | maxLevel = 2,
16 | criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT,10,0.03)
17 | )
18 |
19 | first_frame_grayscale = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
20 | mask_features = np.zeros_like(first_frame_grayscale)
21 | mask_features[:,0:20] = 1
22 | mask_features[:,900:1050] = 1
23 |
24 | self.features = dict(
25 | maxCorners = 100,
26 | qualityLevel = 0.3,
27 | minDistance =3,
28 | blockSize = 7,
29 | mask = mask_features
30 | )
31 |
32 | def add_adjust_positions_to_tracks(self,tracks, camera_movement_per_frame):
33 | for object, object_tracks in tracks.items():
34 | for frame_num, track in enumerate(object_tracks):
35 | for track_id, track_info in track.items():
36 | position = track_info['position']
37 | camera_movement = camera_movement_per_frame[frame_num]
38 | position_adjusted = (position[0]-camera_movement[0],position[1]-camera_movement[1])
39 | tracks[object][frame_num][track_id]['position_adjusted'] = position_adjusted
40 |
41 |
42 |
43 | def get_camera_movement(self,frames,read_from_stub=False, stub_path=None):
44 | # Read the stub
45 | if read_from_stub and stub_path is not None and os.path.exists(stub_path):
46 | with open(stub_path,'rb') as f:
47 | return pickle.load(f)
48 |
49 | camera_movement = [[0,0]]*len(frames)
50 |
51 | old_gray = cv2.cvtColor(frames[0],cv2.COLOR_BGR2GRAY)
52 | old_features = cv2.goodFeaturesToTrack(old_gray,**self.features)
53 |
54 | for frame_num in range(1,len(frames)):
55 | frame_gray = cv2.cvtColor(frames[frame_num],cv2.COLOR_BGR2GRAY)
56 | new_features, _,_ = cv2.calcOpticalFlowPyrLK(old_gray,frame_gray,old_features,None,**self.lk_params)
57 |
58 | max_distance = 0
59 | camera_movement_x, camera_movement_y = 0,0
60 |
61 | for i, (new,old) in enumerate(zip(new_features,old_features)):
62 | new_features_point = new.ravel()
63 | old_features_point = old.ravel()
64 |
65 | distance = measure_distance(new_features_point,old_features_point)
66 | if distance>max_distance:
67 | max_distance = distance
68 | camera_movement_x,camera_movement_y = measure_xy_distance(old_features_point, new_features_point )
69 |
70 | if max_distance > self.minimum_distance:
71 | camera_movement[frame_num] = [camera_movement_x,camera_movement_y]
72 | old_features = cv2.goodFeaturesToTrack(frame_gray,**self.features)
73 |
74 | old_gray = frame_gray.copy()
75 |
76 | if stub_path is not None:
77 | with open(stub_path,'wb') as f:
78 | pickle.dump(camera_movement,f)
79 |
80 | return camera_movement
81 |
82 | def draw_camera_movement(self,frames, camera_movement_per_frame):
83 | output_frames=[]
84 |
85 | for frame_num, frame in enumerate(frames):
86 | frame= frame.copy()
87 |
88 | overlay = frame.copy()
89 | cv2.rectangle(overlay,(0,0),(500,100),(255,255,255),-1)
90 | alpha =0.6
91 | cv2.addWeighted(overlay,alpha,frame,1-alpha,0,frame)
92 |
93 | x_movement, y_movement = camera_movement_per_frame[frame_num]
94 | frame = cv2.putText(frame,f"Camera Movement X: {x_movement:.2f}",(10,30), cv2.FONT_HERSHEY_SIMPLEX,1,(0,0,0),3)
95 | frame = cv2.putText(frame,f"Camera Movement Y: {y_movement:.2f}",(10,60), cv2.FONT_HERSHEY_SIMPLEX,1,(0,0,0),3)
96 |
97 | output_frames.append(frame)
98 |
99 | return output_frames
--------------------------------------------------------------------------------
/development_and_analysis/color_assignment.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 1,
6 | "metadata": {},
7 | "outputs": [],
8 | "source": [
9 | "import cv2 \n",
10 | "import matplotlib.pyplot as plt\n",
11 | "import numpy as np\n",
12 | "from sklearn.cluster import KMeans"
13 | ]
14 | },
15 | {
16 | "cell_type": "code",
17 | "execution_count": 5,
18 | "metadata": {},
19 | "outputs": [],
20 | "source": [
21 | "image_path = '../output_images/player_2.jpg'\n",
22 | "image = cv2.imread(image_path)\n",
23 | "image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)"
24 | ]
25 | },
26 | {
27 | "cell_type": "code",
28 | "execution_count": 6,
29 | "metadata": {},
30 | "outputs": [
31 | {
32 | "data": {
33 | "text/plain": [
34 | ""
35 | ]
36 | },
37 | "execution_count": 6,
38 | "metadata": {},
39 | "output_type": "execute_result"
40 | },
41 | {
42 | "data": {
43 | "image/png": "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",
44 | "text/plain": [
45 | ""
46 | ]
47 | },
48 | "metadata": {},
49 | "output_type": "display_data"
50 | }
51 | ],
52 | "source": [
53 | "plt.imshow(image)"
54 | ]
55 | },
56 | {
57 | "cell_type": "markdown",
58 | "metadata": {},
59 | "source": [
60 | "#### We need only top half of the image"
61 | ]
62 | },
63 | {
64 | "cell_type": "code",
65 | "execution_count": 7,
66 | "metadata": {},
67 | "outputs": [
68 | {
69 | "data": {
70 | "text/plain": [
71 | ""
72 | ]
73 | },
74 | "execution_count": 7,
75 | "metadata": {},
76 | "output_type": "execute_result"
77 | },
78 | {
79 | "data": {
80 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAd0AAAGeCAYAAADR8dusAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAAA/W0lEQVR4nO3de2xc9Z3//9c5c/FlPI5jgu0QAmRL2DYEyBby+1FUSMo2bFpWZdNKTZuuSNltu4CiLaK/jZKo2hBtQ7TsT4FeAhVaiaIKKiF1s70pKBTSiN2ktGTVdAk0mxIIYOwhV9/Gnuv5/pFv3DWJP+/PxPaJ7T4f0khkzsefc+Yz55y3x8z7/Q4kRQIAABMuvNAHAADAHwuCLgAAMSHoAgAQE4IuAAAxIegCABATgi4AADEh6AIAEBOCLgAAMSHoAgAQk+SFPoBzueSSS9TX13ehDwMAAC/ZbFbvvvuuOW7Cgu4999yjf/iHf9Ds2bN14MAB3XffffqP//gP8+cuueQSdXZ2TtRhAQAwIebMmWMG3gkJup/97Gf1yCOP6N5779V//ud/6u/+7u+0Y8cOLViwQG+//bbzZ898wr3//1+loeLgeR9DFFXO+2fPCEP7r++JMGGOCYIxH4oUuiepVu0S2tWqvZtK1b1u5Yr9YqrGjqztkhRF9uspG29xpVIe837qG+xLJJGwzwHJ/ZoDj5PEWrdk0j7W0Ot8Hfv/daoY62qdZ5JUqZTMMda6Jar2a7HOgcjj+vU5XyPjGva53wTB2EvlJ2S/IPP1BPY1bI7weC1Vn3Udh/trGHncHB3q04167P/7N6+/0AaagIYHv/zlL/Vf//Vfuvfee4efe/XVV/Xv//7v2rBhg/Nns9msent7de+DKzRUyJ/3McQWdD1uuD43VPtgrKDrcRH4BN2Ke91KBN2zTKWg63e++rwet7JxE7POs9NjCLrvR9A922QIug11jXpiw7Nqbm42A++4f5EqlUrp+uuv186dO0c8v3PnTt10001njU+n08pmsyMeAABMR+MedGfNmqVkMqlcLjfi+Vwup46OjrPGr1+/Xr29vcMP/n8uAGC6mrCUoff/eSIIgnP+yWLLli1qbm4efsyZM2eiDgkAgAtq3L9IdezYMZXL5bM+1ba1tZ316VeSisWiisXieB8GAACTzrh/0i2VStq3b5+WLVs24vlly5Zpz5494707AACmjAlJGdq6dau+//3v6+WXX9bevXv1la98RZdddpm++93ves8RBqHz23zlsvubqZHHt9Gsbwv6fKPU51uL46FYGPtfA3y+lToO30k018Tn28tlj5QSRe7X4/P+WWMqFY9vT0b2t6SLxSHndut89hpTtV9vXUO9OSadTDm3J1Jjv234fIs6GfikQLm3+6TSWcbrGrfOe5/9WN9e9sqUGIeXE4Qex2psr3jco32+vWweh8+ajEdap6cJCbrPPPOMLrroIv3jP/6jZs+erVdeeUWf/OQn9dZbb03E7gAAmBImrCLVY489pscee2yipgcAYMqh4QEAADEh6AIAEBOCLgAAMSHoAgAQE4IuAAAxmZRN7CWpGlW98jlHk0ymzTGplDsf0Scn1aeaVrHs7pYSV65vVXbuq30s9u9pVSMRMEjYc6Q8xkRGB5mqRxcTK90w8lgzn/ZN+X53m8pCoWDOUSq5z6PAI0+3WLTzgZNGa79knX1tpdPuMZlMgzmHV568+fb43EOs82S8rk+jm5HHbszmP14dzcbWUUeSfC4L61iqPlUBYsrTDTw6PLlENbQ64pMuAAAxIegCABATgi4AADEh6AIAEBOCLgAAMSHoAgAQE4IuAAAxIegCABCTyVscozq24hg+BSfsptJ2BrhPI2YrOdsvod3Noy6CV0K79Xr8Gm2b7avNOaoevw8mkkYRB491TRgFNMpln+IY9n5KQ+7iF2WPohWhkYDv894kPZL4kwl30Zi60L5tJI33zyrAIdnXpyRVI/e6BYmEOYfFo1+7fGojhMb5GFuRnDHcV8dT5PORz2NNxmPVKhWP63ycfp5PugAAxISgCwBATAi6AADEhKALAEBMCLoAAMSEoAsAQEwIugAAxGTy5ukqMpuhO3++6pFjW3LnqwUeTdB9WPl35XHI9Q098h4TSTuZMAyMU8Ijuc5KW/XKR6x45AOH7vfYrwG2tSb2exN5vH8K3GNSRs6xJNWl3I3hg8DOSc1kMuaYZHLst4Vq1Z0/Wy4MmXOEHo3F7TRc+72pmNe5z7no0Sjd2u6R62tdO+OV62vn8o49X7+Wxu9jEUf+cy374JMuAAAxIegCABATgi4AADEh6AIAEBOCLgAAMSHoAgAQE4IuAAAxIegCABCTSVscI4oiZ8Kxlbzt06jZSt5OehQs8GkeXzIKdRQK7gbnPsKkXRgh4dHQO7KKcHg0MLeKGvgkkkdGMQlJqsq9rpFHAr91mngVSPE41oa6euf2VMJe10y9u7BFJpM150il3A3qJSk0yjgUy/b5ajWXr1RK5hw+54lVAMWnX3uQcJ+v1n1C8mtiHxjr6lHfRpFxw6kYRUkkv9djDalWx15wwqeehN+xGuvqM4d9KE4+hVyGx45xX2fZuHHjcMA88+jq6hrv3QAAMOVMyCfdV155RR//+MeH/12p2CUZAQCY7iYk6JbLZeVyuYmYGgCAKWtCvkg1f/58dXZ26vDhw/rBD36gefPmjTo2nU4rm82OeAAAMB2Ne9B96aWXdOedd+ov/uIv9OUvf1kdHR3as2ePWltbzzl+/fr16u3tHX50dnaO9yEBADApjHvQffbZZ/Vv//ZveuWVV/T888/r9ttvlyStXr36nOO3bNmi5ubm4cecOXPG+5AAAJgUJjxlKJ/P67//+781f/78c24vFosqFosTfRgAAFxwE14cI51O60Mf+hBpQwCAP3rj/kn3X/7lX/STn/xEb731ltra2vT1r39dzc3NevLJJ2uaJ5LkymsvG1nv45GmVPXIVvcpSpFMuwsSWNslKQzd6ds+RTp8CoZUzDEeGe2he4xXIrpH8n01ch9rYKyZJEXGfrx+K/V4QU1Njc7tDXXu7afH1Dm3Zz2KY/gUCkgbhTrKkbvQx+n9uNe1r7/XnGNgoM8cMzg46D6OlH19phLudfUpKmNfN1JkFccwZ5BkFGHweHvNgiKSFBnXTuBTDSQmoXGVhh73cZ/32CWVTHuPHfege+mll+oHP/iBZs2apaNHj+qXv/ylbrzxRr311lvjvSsAAKaUcQ+6n//858d7SgAApgUaHgAAEBOCLgAAMSHoAgAQE4IuAAAxIegCABCTKdvE3mo8nfbIu7Lm8GlO7pP7ajU4Tvg0Fk8YeboeuXc+uctR2d0E26fxtJXC57Nm5cgnz9o9j09+Xmjkk3r0QPfKr6xrcOeCNtbbebpJ4zwaGhoy54jK9roWk+79FCt2o/SkMUdPz0lzjkKhYI4ZLLnzdJORnT8ZpI3cV48UzorHmWKe96HHPcu6J3ldnx5ntXGh+zRtd92/Jb97lhfjAvQ51lqa0J/z5z3uNcNjx7QnAADgjaALAEBMCLoAAMSEoAsAQEwIugAAxISgCwBATAi6AADEhKALAEBMJm1xDIXB6ccoSkYRh8Y6u9G2T+P38RAYhTqqHhntgZEB7lNwwqeBearOXUzAp8DGUKno3J5O2wULyoWSOSZhNVsvu49DkgK535tkyucSsd+/pPGafXqCW+eRV/ETn/0k3cVaWmc0m3OkjObxvQOnzDkaPda+IXQXFSlW7HOgYhRiKVY8rk+P4gpWkY2qPArCGIdSrngU6/Eo5xIaxU18qsZY96ykce1JdoENnzFlI1aMh0rVp5jPaXzSBQAgJgRdAABiQtAFACAmBF0AAGJC0AUAICYEXQAAYkLQBQAgJpM2TzcKA0WOPF1LoWzneVoSRuN4yS/HtmzkcFVKdo6XlWIbJu2cN59GzflBd1PwhJErKkmNje7cyaGhvDlHut7d9F2ym5yHRr6pJFWNvEafRts+ubxh0hjj0cA8TFv7sd/fTCZjjpnRlHVun9k6w5yjoanBub1jboc5h0/ueb7Q59z+3slj5hxvvP2mex9D/eYcQcJe+/p6o3ZAaJ9HpZL7vhZ6XJ9Wg3ofUeCRqGsWQhh7Dq4kVcr+ObKjGWsubxDRxB4AgEmHoAsAQEwIugAAxISgCwBATAi6AADEhKALAEBMCLoAAMSEoAsAQEwmbXGMqirOps6JlPX7gp1UbSXfl0t2ArhPAn/CSJy3imecnsOd9B4ZjbglKWk1ppZU12A0W/dIVj/Ve9K5vaHBXThBko6fcs8h2UU40gn3a5GkitE4PFVnF+lIJ+wiHAmz+IVd1KBQcSfwV0p2gn+iZB/rRQ3u1zxv/pXmHJdeeolze1PWPgesQhCSdKznqHP7gf85YO8n4V63Ez2nzDl6+nrNMcWq8Xo8ijxYzdLr0/b768O6zIOqfS+x7hShx2c+n/trYFQO8rlnWYV2LGENobTmT7o333yzfvzjH6uzs1NRFOmOO+44a8zGjRvV2dmpfD6vXbt2acGCBbXuBgCAaafmoJvJZLR//36tWbPmnNvXrl2r+++/X2vWrNHixYvV3d2t5557Tk1NTWM+WAAAprKa/7z87LPP6tlnnx11+3333afNmzdr+/btkqTVq1crl8tp1apVevzxx8//SAEAmOLG9YtU8+bN0+zZs7Vz587h54rFonbv3q2bbrrpnD+TTqeVzWZHPAAAmI7GNeh2dJzuHJLL5UY8n8vlhre93/r169Xb2zv86OzsHM9DAgBg0piQlKH3f1ssCIJRv0G2ZcsWNTc3Dz/mzJkzEYcEAMAFN64pQ93d3ZJOf+I989+S1NbWdtan3zOKxaKKxeJ4HgYAAJPSuH7SfeONN9TV1aVly5YNP5dKpbRkyRLt2bNnPHcFAMCUU/Mn3Uwmoyuv/ENy/Lx583TdddfpxIkTevvtt/XII49ow4YNOnTokA4dOqQNGzYon8/r6aefrmk/QRA4k56TSfeh+yREVyruRHOfORS6E7MlKTKGVO0pFEXuJPGURyGIctUunpAK3fP4FCxomtE85jl8/jfDBz7wAef29vZ2cw7rHEjI4/21axro5LHjzu2db9vfZTh16pRze7Fgr2t/YcAc0zHn3N+/OGNmW6s5R0Nzxrk99CgGUp+2b09tF7c5t0f19jXc0FLv3H78pF2o5X9e/7055r333nNu7+vPm3NECffrKVbsvxpahXYkKRka91eP4kPWEOueJtmFL3zHTPQctfx8zUH3hhtu0C9+8Yvhfz/88MOSpO9973u666679NBDD6mhoUGPPvqoZs6cqZdeekm33Xab+vv7a90VAADTSs1Bd/fu3WZU37RpkzZt2nTeBwUAwHREwwMAAGJC0AUAICYEXQAAYkLQBQAgJgRdAABiMmmb2Kfr61QNHU3sjVwzK/9SksKK+3eORMrOZxuPfGCPHvbjchxW03dJStW7G5jXNXo0Hy+784ELpSFzjkpk5xTPmetulL5w4UJzjpTZgN5eV59m3G+/dcS5vXfAboLe+Z47lzfyOJHKxnsjSams+7bQ0GKfR5HcOZg95T77OIxcfEmqyv2aw5T93lw6b65z+5w/udyco7HFbtTyu9/9zrn9wIFXzTms7vKVwD4HKmU7P1ZJ97qlAvveGFg1DDwKFFQDj2MNrGvUvoatug/2z9vrcQafdAEAiAlBFwCAmBB0AQCICUEXAICYEHQBAIgJQRcAgJgQdAEAiAlBFwCAmEza4hj19XVSOHpitE8jZkux6G74XCgUzDlKHgUJikV3QQKfBsgJI3k7DO3fnyIrWV12kngq7W5yL0kfXrDAub3foxBEYCa8Sx0d7gbmgUdifbnqfo99ikn4JNZffMnFzu1/es18c44T/cec23tO2M3Wq1X7WOuz7gIp+ZLdGzsKjXWtejRbl12IpSzj/bFqn0hKRu5BoUfhg4s73O+vJB0/5X5/yq++Ys5RrbjP6XTaPtZKxT6nFbqvc6/7r3X5lcfnM1/kU0DDEHrcG90/7/9a+KQLAEBMCLoAAMSEoAsAQEwIugAAxISgCwBATAi6AADEhKALAEBMJm2ersXKbQ08cuuqJXeu4JCxXZJKpZI5ZmBgwLndJ/e13sgD88kTGxqym8d/4AMfcG7/kJGDK0n19fXO7Y1NV5pzNGXtRukpo0F5sTxozmEum0c64lDJ3k99nXtNZlyUMedobWt2bs8dfcecY8aMGeaYuiZ33mpR9utNGkmaFdnXVt4rn9Sdz+2Tw1kouq+LlEeyb/OMJnPM3MsvdW7PtmTNObre6XJuP37ihDlHW6udUxxF7nWtVj1yY60hPlMYxyFJ8qhzYPPYzzjhky4AADEh6AIAEBOCLgAAMSHoAgAQE4IuAAAxIegCABATgi4AADEh6AIAEJNJWxyjWCmr6EiOd22TpETFfmmDRrGIgtHkXpIqFbuJvbWfikdut5WM3jyzxZzjirlX2GPmzXNub2tzN46XpPpGdxN0nwbYgdeZ6S5M4tNn2+Txa2nCozBJ2SgGkWyw57jqandRkYYmdwEOSUom7YXNtroLk1QiuyBMFLr3EyXtyghDZfv6SyXchWXKFftYlXCvfSJpF8colQrmmCDpvtDT9e7rRpJSje7X69POvRTZRUfCqnvdqvZtT9Wy+2gCr9on9nURGhVsgsCewyq2NJ4/X/Mn3Ztvvlk//vGP1dnZqSiKdMcdd4zY/sQTTyiKohGPvXv31robAACmnZqDbiaT0f79+7VmzZpRx+zYsUMdHR3Dj09+8pNjOkgAAKaDmv+8/Oyzz+rZZ591jikUCsrlcud9UAAATEcT8kWqpUuXKpfL6eDBg3r88cd18cWjF9hOp9PKZrMjHgAATEfjHnR37NihL3zhC7r11lv1ta99TYsXL9YLL7yg9CiddNavX6/e3t7hR2dn53gfEgAAk8K4f3v5mWeeGf7vAwcO6OWXX9aRI0d0++23a/v27WeN37Jli7Zu3Tr872w2S+AFAExLE54y1N3drSNHjmj+/Pnn3F4sFlX0SM0BAGCqm/DiGK2trZo7d666utzNlwEAmO5q/qSbyWR05ZV/SNKfN2+errvuOp04cUInTpzQAw88oB/+8Ifq6urSFVdcoQcffFDHjh0755+WXQaLJQ06PgGHRkGC8tCguY9CwZ3QXqjan8CD0E6KzrY2O7dbhS8kO/m6IWMXRrh07lxzzIIFC5zbk2m74kRF7sz5yJxBqhiFL05P5M6ujzz2FATuMdXIrgJQDXxKErilG+1LsbWtxbm9rX3WmI9DkurT7iINyTr7HCha147PmqXtzwRFq1CHR4GUqlHpoWqcz5IUJexzLTSKY1x2hX19Dgz2O7efjHrMOSqB/XqsdS2W7MoW5YJ7TFJ20ZHGVMYcE1gFNKoehSuM+8B4qjno3nDDDfrFL34x/O+HH35YkvS9731P99xzj6655hrdeeedamlpUVdXl3bt2qWVK1eqv999sgAAMN3VHHR3797t/NS1fPnyMR0QAADTFQ0PAACICUEXAICYEHQBAIgJQRcAgJgQdAEAiMnkbWJvVaqyUrM8cl+tnDefSln1jQ3mmFkzL3JudzWEOKO52Z3re9FF7n1I0p/8yZ+YYxIp98L65L6WKu78vNAjt7nisZ9U4E7CNHNFJUVVI0839Oi07ZXjZ4zx+PW3IeM+1+qTdq521aPNeULu98enXXfVzH/2yKH22I/dPNyjCXrKvSYln5xxjybm1r2i9eJWc46LT7Q7txfL9vk60DtgjinJyLFN2jm2qcDI9654JFF7cb/HyaS9n2rV4zp3mNAm9gAA4PwQdAEAiAlBFwCAmBB0AQCICUEXAICYEHQBAIgJQRcAgJgQdAEAiMmkLY6hakWquJotu39fsBpxS1Jq5kzndqvJvSRFHg2uW2e59/Nn1y+y52h1NyhvaLCLdIQev2INDg46t5eqdqGAVF3aub1qFKSQpCi0xxSM4heBx+u1ktprSXp3qUZjb3RvvX9lj/dmPFTHYU0qHsUxPOqjmO9PMrCLOFgFQ6oV+71LJtznvCRlMu6G7PPnzx/zfnr7+8w5+vvz5phq2X1fSyXt0JFMu4tSJMoehUsi+1yLyu73x+dcG5+r3A+fdAEAiAlBFwCAmBB0AQCICUEXAICYEHQBAIgJQRcAgJgQdAEAiMmkzdMNg0ChIwcvitx5ZOXikLmPphlNzu1t7e7cWEkqlOz9zJrlnsenAX1bm7vRfeSRi9bb22uOCZLujLVq1c5ZPHr0Pef2fMFeM5+c4iB0nwMtM7PmHA0N7nzuskdT8NCjF3dg5B0HRtN3SbLe4pKRtyzJeU2dYeXhBh5ZjYHcixIFPnnLPieBe56C7HOtZ8DIbbVT8ZWdMcMck5I7x3ZGkz1H6yz3vcLKBZakdNrOKbby8RMJ+6RPGBdG4LGuyYR9DiRCdxirGnm8pwd5JIU70MQeAIBJiKALAEBMCLoAAMSEoAsAQEwIugAAxISgCwBATAi6AADEhKALAEBMJm1xjKBaUVAdPXu6IdPo/PnsjGZzH/OunOfcftHFreYcb73zljkmk3E3mK96FAqwktV9imMY9QokSYVCwbndJwn8nXfecW6v+uSRB3ZRikTKPVGqwT696zLu4hhVxzn4hzH22odWtQ+jeIYks9N2GPoUrfDhPpZq5FFswDgfI6Nx/Okp7KMtR+7zZHBw0JyjK9ft3D7QZzd9v2TOXHPM7LZLnNutgiKSVF9f79y+YMECc46+nn5zzImjJ5zbfe43UcX9HpdLHoVnPM7pinEueRXhMApsWCasOMa6dev0q1/9Sr29vcrlctq+fbuuuuqqs8Zt3LhRnZ2dyufz2rVrl9eJAADAdFdT0F2yZIm2bdumG2+8UcuWLVMymdTOnTvV2PiHT51r167V/fffrzVr1mjx4sXq7u7Wc889p6Ymd8lFAACmu5o+U3/iE58Y8e+77rpLR48e1fXXX68XX3xRknTfffdp8+bN2r59uyRp9erVyuVyWrVqlR5//PFxOmwAAKaeMX2Rasb/LfJ94sTpv//PmzdPs2fP1s6dO4fHFItF7d69WzfddNM550in08pmsyMeAABMR2MKulu3btWLL76oAwcOSJI6OjokSblcbsS4XC43vO391q9fr97e3uFHZ2fnWA4JAIBJ67yD7ne+8x1de+21+vznP3/Wtvd/sy0IglG/7bZlyxY1NzcPP+bMmXO+hwQAwKR2Xt+T/ta3vqVPfepTuuWWW0Z8Mu3uPv3V+46OjuH/lqS2trazPv2eUSwWVSzafUABAJjqav6k++1vf1uf/vSndeutt+rNN98cse2NN95QV1eXli1bNvxcKpXSkiVLtGfPnjEfLAAAU1lNn3S3bdumVatW6Y477lBfX5/a29slST09PRoaGpIkPfLII9qwYYMOHTqkQ4cOacOGDcrn83r66adrO7BkqGR19N8J6uvTzp9vbW0x9zFrlrv4RXtHuzlHlLSTxKtVd/J2T/9Jc45i2f3XAJ9k9VQqZY458+W40VQqdqb5UGlozHMk6+xCAfmiu5BHdtAuApApuwuXhEn799LIKNAgSZHcY6rGdklKBO5j8Snk4XOeJBPu8yQ0jkOyi18EHmU6qh4FNIpD7nNgsGAXtnjrrTed28vuujSSpMaM/QXQpib3mObGFnOOTIO7KFBTo30cl196uTmm93ivc3uh332NS1KQdp8nidC+xs2iMpICo4hKkLDPebO2hbWPGopj1BR07733XknS7t27Rzz/xS9+UU8++aQk6aGHHlJDQ4MeffRRzZw5Uy+99JJuu+029ffbN0AAAKazmoKubzTftGmTNm3adF4HBADAdEXDAwAAYkLQBQAgJgRdAABiQtAFACAmBF0AAGIyaZvYDxUGNeTIsZt96blrOZ/xoQVn9/l9v4s6Zjm3Nza5czgl6arWK80xhZI7x/btd+16068efNW5PZPJmHNY+cI+zuRjO/dj5K365Ir2F+zkSCuXt2/IztFsMvI46wJ3PrgkRYFHM26jMXwQejR1N3J5o9Be11LJXteK8f755E5WjLzGRMLO0Sx75B33DQ04t7934pg9R949x/GcnUd//Lg9prHe3d50xmXuugGSFEbuW/YHLvuAOUfvcTt9Mxn8j3P7qcEec46w4n6PU4EdflJpe4yVvz4uAuPasrb/L3zSBQAgJgRdAABiQtAFACAmBF0AAGJC0AUAICYEXQAAYkLQBQAgJgRdAABiMmmLY9SlU6pq9MIEg4PuhPakR/PxtJF4XanYhQSMft+SpHLknmew4H4tktSXP+Xc3n3ULrDh05rRGlMt2wULgoR77Rsb3Y24Jam5pdkc0zjDXbykvrHOnCNMuY+1FLmbpEt+TewTCfe6+lyIFaM4RujxO3S+aBdGKBXc+ymX7ddbLLnPk1TKvnDKkV0wpPeku9l6X4/9eo8dPeHcXira94HQ4x3s63Vf5+WS/XpTxg0nU2dfN5d12E3sL2m/xLk9tJfEvFdUhuzzKLJvNwrtOis2475n3TlraWLPJ10AAGJC0AUAICYEXQAAYkLQBQAgJgRdAABiQtAFACAmBF0AAGIyafN0K1FZleroyWDJpPvQyxV343hJiowksFRdvTlHpWrnmiWNXNBBoxG3JA0MuPMN3zjypjlHPm83dW9ocOe+RhU7lzBMuhPn5syZY86RacmYY4KkOzeu7Dh/zujpO+XcXvVoUB8k7AbWiaTRxN6jCbbV0LtQGDTnGBiwz7XeU33GHPZ5lEiOnmMvSXV1HjnUof2ZoLfPfazH33Pn4ErS0aNHndsb0va5GBo51JKU6+x2bv9/Fv6/5hyFIfc9K+lxS29vbTfHfOSGjzi3v3DyBXOOd99+x7m9sd59r5GkhHyScN3nScLjPBprrm9Qw8dXPukCABATgi4AADEh6AIAEBOCLgAAMSHoAgAQE4IuAAAxIegCABATgi4AADGZvMUxKhVVKqMnglvJzImEne1sFbbwyKlW6JFVXS27C0r47KdUdhc+8CmM0NRkN4/3KaBhSYfuwgiVil20Im0UgpDsIhyRRxP0wYK7SX3k0a3bp4DGUMldlCL0KLBRNc5Xn/fOZ0y56C7AEAb2e9NgvH8lj8IllZK9Jv1597r29PTYc/T2Orenmt3nsySVIvv15PuGnNuDyL4RlAfd52u63i46ks7aRX/qrnQXrvjvl18x5ziZO+ncngrt88gqgiRJ5aJ77QOPOYLQaGJfQ5N6S02fdNetW6df/epX6u3tVS6X0/bt23XVVVeNGPPEE08oiqIRj717947bAQMAMFXVFHSXLFmibdu26cYbb9SyZcuUTCa1c+dONTaO/AS1Y8cOdXR0DD8++clPjutBAwAwFdX05+VPfOITI/5911136ejRo7r++uv14osvDj9fKBSUy+XG5wgBAJgmxvRFqhkzZkiSTpwYWVR86dKlyuVyOnjwoB5//HFdfPHFo86RTqeVzWZHPAAAmI7GFHS3bt2qF198UQcOHBh+bseOHfrCF76gW2+9VV/72te0ePFivfDCC0qnz/1lhPXr16u3t3f40dnZOZZDAgBg0jrvby9/5zvf0bXXXquPfvSjI55/5plnhv/7wIEDevnll3XkyBHdfvvt2r59+1nzbNmyRVu3bh3+dzabJfACAKal8wq63/rWt/SpT31Kt9xyixkgu7u7deTIEc2fP/+c24vFoopFu/ctAABTXc1B99vf/rZWrFihpUuX6s033zTHt7a2au7cuerq6jqf4wMAYNqoKehu27ZNq1at0h133KG+vj61t7dLOp2APjQ0pEwmowceeEA//OEP1dXVpSuuuEIPPvigjh07ds4/LbvUpdOqavRiAIOD7mIQp06dMveRzriT3rMzmsw5imW7MIKlrW2WOebYsdG/jCbJ668FPr/4hIG7IMHQkDvBX5Ia6t3r2jKz2ZzD5wt1jVn3+5NI2QntlcCdWB8mU+Ychaq9Jv0Fd5GGYsEuWtHbe8q53afwRaFgF3FIJdy3hUzGfm+qcp9H1nkmScm0XXimocFdxOGKK64w5zj1nrs4RinvsWbGOS9J1ZK7WEsysM+1OTOte4W9rgOD9nkyM3ORe3tTizlHaci9bulG+/WWPM7XROD+alLCo/qQVfvCKo5RS/GMmoLuvffeK0navXv3iOe/+MUv6sknn1SlUtE111yjO++8Uy0tLerq6tKuXbu0cuVK9ff317IrAACmnZqCrhXNh4aGtHz58jEdEAAA0xUNDwAAiAlBFwCAmBB0AQCICUEXAICYEHQBAIjJpG1iXyxWVCyMngPb2+NOQTp69Ki5j1S9O0+srcOdGytJSaORuiSFkTt3rtkjJ3V2R4dz+9H33jPnsPIvJTsPt77eboA9Wp3tM5KhvWaJhD3GSvX0yZ0LjRy+IOkxh0eqdlOTO6d4YMjOrwwH3ed8qepuPi9JhbK7CbokVSrueRJJOyc1mXCPMS4JSVIYuvNaJals5MmX8vab02Cc0wOn3DUBJKm/Z8Ack29wr/17XcfNOS5rb3Rurw/d2yUpFdrnSVrue2Nz00xzjmRo5OFGHvmzRnN5ySPH1uOzZVR1n5CBfTvyxiddAABiQtAFACAmBF0AAGJC0AUAICYEXQAAYkLQBQAgJgRdAABiQtAFACAmk7Y4RhiGCh1FFE6edDcFf/utTnMfTc1GM/WqnZhtNaY+PY17TItHQ+j8THfj6WyDXWAj9EhGrxivJ4g8CjAMFse0D0lK+BS2MAosJEP79Lbe4qEhu+F332CfOaYuW+fcXt/obsYuSa2Bu7F4Q6NdGKG/zz7WqOxe2HTKLpDSWJ9xbg8i+/0dHLQLefQZRXLyJ+2iFVaBjb4ed5N7SVKdXT3heHjMuf13rx4057hy9nzn9pTs9yZrFAWSpGrkvkYzDe5iL5I0I9vi3N4/YK9rU5N9TheL7oI+1vsrSYmU8f5Z906Pe+sZfNIFACAmBF0AAGJC0AUAICYEXQAAYkLQBQAgJgRdAABiQtAFACAmBF0AAGIyaYtjVKunH6NJBu5k5nz/oLmPU8dPObcP9NmJ9TNmzjDHWFUcihV3MQlJam50Fxtoyri3S1JjvV2AwUok90k0b2hw7yeKjKoWktJJdzEJSWpqdBcEqWtKm3OUI/faF/rdifeSVCrZBUOCons/QcJekzqjgEZ9vUfRika7qEFUtAqk2L+rW4VJikYBFUmqluxruFIsubdX7Pfm+PHjzu319XaBhmTCvpUWh9zFPvpO2cUiBvLuNUk02ud8SnZxjELJfawXtbSac1jr1tPjLnAk+d0rEgn360kkPArtOAoxnd7uniMMKY4BAMCkQ9AFACAmBF0AAGJC0AUAICYEXQAAYkLQBQAgJgRdAABiMmnzdIMoNPIB3b8vFAp2HmBg5PqGgb08gUez9ciVcCy/5vJWbmRD2s7BLQ25cxolqfeUu8l5XZ2dPzto5Ehf1OJuxi5J9Wl7P41GXmoyaecjJozzqKHOzn1tbm42x+RL7pxvj9NIVSNnMSF7Ep9c3kTavSaVkvt8Pj3GfayFgn0ulgp2Tnho5QMb+dGS1NTkzl3uN5qkS1LVuMYlKVXvPqff686Zc7z7Tqdz+6yrLjbn8FFnXH8tLS32JBX3mvjk4PpIpNznQBDY+7HevyhyX1sVj/f/jJo+6d59993av3+/enp61NPToz179mj58uUjxmzcuFGdnZ3K5/PatWuXFixYUMsuAACYtmoKuu+8847WrVunG264QTfccINeeOEF/ehHPxoOrGvXrtX999+vNWvWaPHixeru7tZzzz1n/iYJAMAfg5qC7k9/+lPt2LFDhw4d0qFDh/T1r39d/f39uvHGGyVJ9913nzZv3qzt27frwIEDWr16tRobG7Vq1aoJOXgAAKaS8/4iVRiGWrlypTKZjPbu3at58+Zp9uzZ2rlz5/CYYrGo3bt366abbhp1nnQ6rWw2O+IBAMB0VHPQXbhwofr6+lQoFPTd735XK1as0GuvvaaOjg5JUi438ssAuVxueNu5rF+/Xr29vcOPzk73FwUAAJiqag66Bw8e1KJFi3TjjTfqscce05NPPqkPfehDw9vf/420IAic31LbsmWLmpubhx9z5syp9ZAAAJgSak4ZKpVKev311yVJ+/bt0+LFi/XVr35V//zP/yxJ6ujoUHd39/D4tra2sz79/m/FYtHra/0AAEx1Yy6OEQSB6urq9MYbb6irq0vLli0b3pZKpbRkyRLt2bNnrLsBAGDKq+mT7ubNm7Vjxw69/fbbymaz+tznPqelS5cO5+o+8sgj2rBhw/C3mzds2KB8Pq+nn3665gMLg6SzOEUy4W7WHJXthOhy0Z18Xxmyk/MTVbsgQdJoyF6R3Wg7mXH/ftTS1GLO0XFxmzlmaNBd2KJUto+1tWWmc3vKaDotSc0Zu+BEXcoo9OCRFF8xCkr4FAOZUTfDHDN0wr2uPsUVimX3+Rh4FBtIp+0m50YdAFUj+1jLxnmSz+fNOfp7+s0x+X73PD4FYQLjGvY4jZRMuAvtSFLSaIR+4uRxc47Dhw87t7fNGv37M2f43CusYjuDeXeTe0nK593n/KBxr5Gk1otazDGFkrt4SaVinwNWkRWrCFIisGPFGTUF3fb2dn3/+9/X7Nmz1dPTo9/+9rdavny5fv7zn0uSHnroITU0NOjRRx/VzJkz9dJLL+m2225Tf7998QAAMN3VFHS/9KUvmWM2bdqkTZs2nfcBAQAwXdHwAACAmBB0AQCICUEXAICYEHQBAIgJQRcAgJhM2ib2YSKpMDn64aXkztGsVux80sFed47fqWMnzTlSgf17Szabcc+RtnP8MkZOanPKbmLf1txqjjmWes+9faDHnOOy2Zc6t8/usEt9zmhuMcdYuZGDcjeOl6Rq1Z1f55M/W/bJszaagucLdt5qpTL2pt9Rwc6vtHISfZrYFwfcVeaGhuzG8D55nPk+I698yD7WqpHGmU7Yt8mksWaSVCobazJgn6+/f/13zu2XdMw250jPm2+O+d9VBc/lN7/5jTlHxUj4bm525/NLUsEjz7pYco/xy9N1X1th6L7PJ+Wfp8snXQAAYkLQBQAgJgRdAABiQtAFACAmBF0AAGJC0AUAICYEXQAAYkLQBQAgJpO3OIYSCjV68YPQKEgQGM3JJTvJ30rwl6RcwZ1ELkn5pibn9tbWFnOOxsZG5/agbP/+lKnLmmMuap5ljLD3M7v9Euf2i2faRTpSHgUJIuMc8EmKL1fcSe0ljzkqHsUxosg4X6t24YvIaFJvbZekinEckv0ODxnNySWpYDQ57/MoBDGYtwtoFAru9yfhcb5GgftcqwQeBTY8iqiEkTEmYd+zentPObcf/J/XzDnSCXehFknKD7jX/vCRt8w5ko7iRpLU2uq+T0jSseM5c0zFKIRULtuFKxoa3GtiFcdIGIV6RszlPRIAAIwJQRcAgJgQdAEAiAlBFwCAmBB0AQCICUEXAICYEHQBAIgJQRcAgJhM2uIYCUVKavRk/4RRPKEa2Inmfaf6nNvfPHzEnMPImZYkzTKKQQz05M05rOTtct5OAD/a9Z45ZsgoajB7Voc5h1X8oqm+wZyjQSlzzIDs4iUW6/1LeBRZSQb2SZBIuMeEob2fwChsEXqc86WyR7EPo1BHoeBRHKPgfm+qZfu9i6p2IQ9r7SseBUPC0H0vSSbtwheyl9XkU2BhwCgqcvDgQXOO9949bo55t9Nd9KfkUcxFcq/be+/Z9yN5FCZJhO57RZCyrwur+MVYt48Y6z0SAACMCUEXAICYEHQBAIgJQRcAgJgQdAEAiAlBFwCAmBB0AQCIyaTN01W1qqjikR83ioRH7qTVjLvzSKc5R3NzszlmRmOLc3ufR55uELlfT1+fnTvZmM6YY1qy7tzITKbJnCM0jtWnCXqq0c7TLVvJkXZ6ntlou05pc47QY0fFqpFnXXLnR0tS0cjjDB157Wf45FdWI3fOd7lo59iWjDzdYtHOK/dpPl41Xk+16pP/7D5f00m76XvVyKGWpMDIo04k7HNexuvt7++3p/BIb7fWPpGyQ0epZNy/PXJwkx65y6mUe0yx5JNTHJ+aPunefffd2r9/v3p6etTT06M9e/Zo+fLlw9ufeOIJRVE04rF3795xP2gAAKaimj7pvvPOO1q3bp1+//vfS5JWr16tH/3oR/qzP/szvfrqq5KkHTt26K677hr+maLHb8UAAPwxqCno/vSnPx3x769//eu65557dOONNw4H3UKhoFwuN35HCADANHHeX6QKw1ArV65UJpMZ8SfkpUuXKpfL6eDBg3r88cd18cUXO+dJp9PKZrMjHgAATEc1B92FCxeqr69PhUJB3/3ud7VixQq99tprkk7/afkLX/iCbr31Vn3ta1/T4sWL9cILLyidHv3LKOvXr1dvb+/wo7PT/vISAABTUc3fXj548KAWLVqklpYWfeYzn9GTTz6pJUuW6LXXXtMzzzwzPO7AgQN6+eWXdeTIEd1+++3avn37OefbsmWLtm7dOvzvbDZL4AUATEs1B91SqaTXX39dkrRv3z4tXrxYX/3qV3X33XefNba7u1tHjhzR/PnzR52vWCzyZSsAwB+FMRfHCIJAdXXnzmNrbW3V3Llz1dXVNdbdAAAw5dX0SXfz5s3asWOH3n77bWWzWX3uc5/T0qVLtXz5cmUyGT3wwAP64Q9/qK6uLl1xxRV68MEHdezYsVH/tOwUSK5ccit520pE9xmTz9tFK1Ipu3hCX687Yb1asZO3K2V3InloNHKWpI72S80xVzS7CwGk6u39REbjcJ+Gz9WqnTg/ZBRgqKTt4goycu8T1gBJVaNZtySlE0YRjqS9riVjTCLwKI5RsItwlEru68Kjt7wqRmGbwuCQOUexaHeGrxrHGlbt9886XxPGeydJycgeExpjgsi+Z1mN7j1OAQ0V7PtaXb37PtCXHzDnsO7RmYxdrEceBV9SKfd1YRXAkaRS2X1dWLHCJ94MH4/3SEnt7e36/ve/r9mzZ6unp0e//e1vtXz5cv385z9XfX29rrnmGt15551qaWlRV1eXdu3apZUrV3pVSQEAYLqrKeh+6UtfGnXb0NDQiOpUAABgJBoeAAAQE4IuAAAxIegCABATgi4AADEh6AIAEJNJ28Q+UqSqI0crmXbnZvk0wDZ7j3vkk+YLdr5hZ1e3c3tjY6M5R3t7u3P7iRPHzDmaZ84wx1TT7kVpsNMelTTWbaho54qmSnbjcCutMQjsg7Uav5c9GtTbJ5IUGr/f+jRKb0gZzeU9zvnQI+84qrjzY62m75IUGmNKJZ9kX58G9EaebuhxizOWLah6vL+hx7lmrZvHklg5xT7nYuCRzDtUcOfh1ht5vJI0MOBe2IEBO5W0qcm+N1o5/cmUfb5Wqu4xVh6uT+2B4bHeIwEAwJgQdAEAiAlBFwCAmBB0AQCICUEXAICYEHQBAIgJQRcAgJgQdAEAiMmkLY5RiqoqRaMnPaesZPSknaxesZKqjYbRkhQl7N9b+o1E874hO0k8d+I993GYSfNS6pRdQGNu1d3o/srWeeYcgVFgo+hRxKHs0Sk9lap3bq8m7DWJZDVKH3vhC0lKhkaT+pRHEQ736ap82W5OnvC45AOjUEDSoziGjIISpUGPQh4V+1gTxtpbr+X0IPd54tWgvOLRPd4s5uJTiMU4CTzO14THPcu6tgYL1nUjpVLu+2fC4/6aSttjrGXzKhpjFrdwr7tPwZHhfXmPBAAAY0LQBQAgJgRdAABiQtAFACAmBF0AAGJC0AUAICYEXQAAYkLQBQAgJpO2OEYimVSiOvrhFcvu5GyfxOvASBL3qQFQiewiDlUjcTo/aBc1iIxCHuWKfRwtyRZzTKFYdG4/3nPKnCM7s9m5PV21E8lLJfv11KXdBScC+RTHsIoJ2CdB6FGQIB2mnduTKY9CEEZdhES9faxBxSquIKnsHlNMus8RSUrIPaZctN/fZGRfw1atiMC4biQpGRgFNjyKVvgUp7GLY9hzhMax+iyZPM6BatUoKBHYBScyTXXGPswp7OOQlEi47wOJhP3+Vc2Dcc/hV9jkND7pAgAQE4IuAAAxIegCABATgi4AADEh6AIAEBOCLgAAMSHoAgAQk0mbp1tRpIojzzI08hojs9mzLQp9cu98mhe7xyQ9GjVHRlNw2WmPKpSGzDHFiju/slAomHM0GTmLRSMX2HdMst7Iz/NY11LFnQcYeuT4hR6XUWS8QWFk7ydIuvdTl3KvhyQlzWbdUip0z1Pot9+b3mjAuT3yyBWtGO+NJDNPN+lxeVq52tXII9c3YZ8DwdhvSWbeauDxOcorp9S4r1kN6iX7WMPQXjO7ubxk3V99cn2tNbHzsP3f3DF90l23bp2iKNLDDz884vmNGzeqs7NT+Xxeu3bt0oIFC8ayGwAApoXzDro33HCDvvKVr2j//v0jnl+7dq3uv/9+rVmzRosXL1Z3d7eee+45NTU1jflgAQCYys4r6GYyGT311FP68pe/rJMnT47Ydt9992nz5s3avn27Dhw4oNWrV6uxsVGrVq0alwMGAGCqOq+gu23bNv3sZz/T888/P+L5efPmafbs2dq5c+fwc8ViUbt379ZNN910zrnS6bSy2eyIBwAA01HNX6RauXKlPvzhD2vx4sVnbevo6JAk5XK5Ec/ncjldfvnl55xv/fr1euCBB2o9DAAAppyaPuleeuml+uY3v6m//uu/dn6L9f3f9AqCYNRvf23ZskXNzc3Djzlz5tRySAAATBk1fdK9/vrr1d7ern379v1hgmRSt9xyi9asWaM//dM/lXT6E293d/fwmLa2trM+/Z5RLBa90kMAAJjqavqk+/zzz2vhwoVatGjR8OPXv/61nnrqKS1atEiHDx9WV1eXli1bNvwzqVRKS5Ys0Z49e8b94AEAmEpq+qTb39+vAwcOjHhuYGBAx48fH37+kUce0YYNG3To0CEdOnRIGzZsUD6f19NPP13TgaXChMrh6AnYVq2IIPBoYm8kgPs0lfboX67QGJPyKOJQLrknCT2aaBeKg+aYnp4e5/bsDHeDekmaYTSgD+Txest2QnthyP16Uh7J99WEUVXE4xww+opLkhLGaw6sk0RSwjjZfOoIJBvt1L2g4n7N6aRPEQ736034FGjwKEphFSWIIo9iEcZ+vJrYe1SniYwCKIHHzcQ6HX2OdXzG2OtqFZTwur/6rIkxj0+BDbuJ/fgZ94pUDz30kBoaGvToo49q5syZeumll3Tbbbepv79/vHcFAMCUMuag+7GPfeys5zZt2qRNmzaNdWoAAKYVGh4AABATgi4AADEh6AIAEBOCLgAAMSHoAgAQk0nbxF5VOVPw7BxbexdW+pbPHPLIj62a+Wr2jtJGg/Jkws59HRiwc18rpZJze7Vs5yOWC+4KY6WCex+SFIQe+bFJI7fOI/85NPJjQ4/m1JHHmITRaDv0Svh2v56kR/6zz5Cowf0et2TtXO3BZncOdcLj5eYH8+aYVKLOvR+P/GevRTHE9enFqwG9wapxcHo/1gifHFv3mMhrDo8caZ8XZO7G51jG5+f5pAsAQEwIugAAxISgCwBATAi6AADEhKALAEBMCLoAAMSEoAsAQEwmbZ5ufbrBuT0y83Q9+jBaeboeuaI+IqNXo9V3UpICoy+oTz/Iqp2mq/p0o3N7XbLenMPKnUyFaXOOpEcv3GRg5C57/E5p5cf65OD65Ru65xmP336tnr2+knK/P6nAPgfqEu7rtzFt9/VVnf16UqH7XKsztktSwni9CY/e3D450kHVPSY0+u2eHmOdKT59bsen5669n7HfP72Ow+P1mFN4XOcu1n3zfwvkl+Ucm0suuUSdnZ0X+jAAAKjJnDlz9O677zrHTLqgK50OvH19fcP/zmaz6uzs1Jw5c0Y8j7FhXScG6zoxWNeJwbqOj2w2awZcaZL+eXm0A+/r6+OkmACs68RgXScG6zoxWNex8V07vkgFAEBMCLoAAMRkSgTdQqGgBx54QIVC4UIfyrTCuk4M1nVisK4Tg3WN16T8IhUAANPRlPikCwDAdEDQBQAgJgRdAABiQtAFACAmBF0AAGIy6YPuPffco8OHD2twcFAvv/yyPvrRj17oQ5pSbr75Zv34xz9WZ2enoijSHXfccdaYjRs3qrOzU/l8Xrt27dKCBQsuwJFOLevWrdOvfvUr9fb2KpfLafv27brqqqvOGsfa1ubuu+/W/v371dPTo56eHu3Zs0fLly8fMYY1HZt169YpiiI9/PDDI55nXeMTTdbHZz/72ahQKER/+7d/G33wgx+MHn744aivry+aO3fuBT+2qfJYvnx59E//9E/RihUroiiKojvuuGPE9rVr10Y9PT3RihUroquvvjr6wQ9+EHV2dkZNTU0X/Ngn82PHjh3R6tWrowULFkTXXntt9JOf/CR68803o8bGRtZ2DI+//Mu/jD7xiU9E8+fPj+bPnx994xvfiAqFQrRgwQLWdBweN9xwQ3T48OHoN7/5TfTwww8PP8+6xvq44Acw6uOXv/xl9Oijj4547tVXX40efPDBC35sU/FxrqD77rvvRmvXrh3+dzqdjk6ePBl95StfueDHO5Ues2bNiqIoim6++WbWdpwfx48fj/7mb/6GNR3jI5PJRAcPHoz+/M//PNq1a9eIoMu6xveYtH9eTqVSuv7667Vz584Rz+/cuVM33XTTBTqq6WXevHmaPXv2iDUuFovavXs3a1yjGTNmSJJOnDghibUdD2EYauXKlcpkMtq7dy9rOkbbtm3Tz372Mz3//PMjnmdd4zUpuwxJ0qxZs5RMJpXL5UY8n8vl1NHRcYGOano5s47nWuPLL7/8QhzSlLV161a9+OKLOnDggCTWdiwWLlyovXv3qr6+Xv39/VqxYoVee+01feQjH5HEmp6PlStX6sMf/rAWL1581jbO1XhN2qB7RhRFI/4dBMFZz2FsWOOx+c53vqNrr732nF/yY21rd/DgQS1atEgtLS36zGc+oyeffFJLliwZ3s6a1ubSSy/VN7/5Td12223O+sqsazwm7Z+Xjx07pnK5fNan2ra2trN+I8P56e7uliTWeAy+9a1v6VOf+pQ+9rGPqbOzc/h51vb8lUolvf7669q3b582bNig/fv366tf/Sprep6uv/56tbe3a9++fSqVSiqVSlq6dKn+/u//XqVSaXjtWNd4TNqgWyqVtG/fPi1btmzE88uWLdOePXsu0FFNL2+88Ya6urpGrHEqldKSJUtYYw/f/va39elPf1q33nqr3nzzzRHbWNvxEwSB6urqWNPz9Pzzz2vhwoVatGjR8OPXv/61nnrqKS1atEiHDx9mXWN2wb/NNdrjTMrQXXfdFX3wgx+Mtm7dGvX19UWXXXbZBT+2qfLIZDLRddddF1133XVRFEXRfffdF1133XXDaVdr166NTp48Gf3VX/1VdPXVV0dPPfUUqQIej23btkUnT56Mbrnllqi9vX34UV9fPzyGta39sXnz5uijH/1odPnll0cLFy6MvvGNb0Tlcjn6+Mc/zpqO4+P9315mXWN9XPADcD7uueee6I033oiGhoail19+eURKBg/7sWTJkuhcnnjiieExGzdujN59991ocHAw+sUvfhFdffXVF/y4J/tjNKtXrx4xjrWt7fGv//qvw9d7LpeLnnvuueGAy5qO3+P9QZd1je9BP10AAGIyaf+fLgAA0w1BFwCAmBB0AQCICUEXAICYEHQBAIgJQRcAgJgQdAEAiAlBFwCAmBB0AQCICUEXAICYEHQBAIjJ/wGLJfCkO9wdbAAAAABJRU5ErkJggg==",
81 | "text/plain": [
82 | ""
83 | ]
84 | },
85 | "metadata": {},
86 | "output_type": "display_data"
87 | }
88 | ],
89 | "source": [
90 | "top_half_image = image[0: int(image.shape[0]/2)]\n",
91 | "plt.imshow(top_half_image)"
92 | ]
93 | },
94 | {
95 | "cell_type": "markdown",
96 | "metadata": {},
97 | "source": [
98 | "#### Cluster the image into two clusters"
99 | ]
100 | },
101 | {
102 | "cell_type": "code",
103 | "execution_count": 8,
104 | "metadata": {},
105 | "outputs": [
106 | {
107 | "name": "stderr",
108 | "output_type": "stream",
109 | "text": [
110 | "c:\\ProgramData\\Anaconda3\\Lib\\site-packages\\sklearn\\cluster\\_kmeans.py:1412: FutureWarning: The default value of `n_init` will change from 10 to 'auto' in 1.4. Set the value of `n_init` explicitly to suppress the warning\n",
111 | " super()._check_params_vs_input(X, default_n_init=10)\n"
112 | ]
113 | },
114 | {
115 | "data": {
116 | "text/plain": [
117 | ""
118 | ]
119 | },
120 | "execution_count": 8,
121 | "metadata": {},
122 | "output_type": "execute_result"
123 | },
124 | {
125 | "data": {
126 | "image/png": "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eDwqLi4OfE1SUpIKCwvV2toa9uIBAIgnIc10e3t71dnZGXTsP//5j77++uvA8R07dqi6ulpdXV3q6upSdXW1+vr6tG/fvvBVjagZzaYHo9k8AQCuRWHfkWrbtm2aPHmydu7cqalTp+ro0aNatmyZent7w/1RAADEFYekoe9wihKXyyWv16vvrLskm6COR8x0wTaQuKY4UjUh4yOlpaXJ5/NdtSsPPAAAwBBCFwAAQwhdAAAMIXQBADCE0AUAwBBCFwAAQwhdAAAMIXQBADCE0AUAwBBCFwAAQwhdAAAMIXQBADCE0AUAwJCwP9ovbBwp0a4AY5TsmhztEhBtjtRoVwCYE0JexVzoulwuSdKE9PeiXAnGan9PtCsAAPNcLteIj/aLuefpStKMGTOCCne5XHK73crOzh7xG8LoMa6RwbhGBuMaGYxreLhcLp07d27EfjE305U0bOE+n48/FBHAuEYG4xoZjGtkMK7jM9qx40YqAAAMIXQBADAkLkK3v79fmzZtUn9/f7RLSSiMa2QwrpHBuEYG42pWTN5IBQBAIoqLmS4AAImA0AUAwBBCFwAAQwhdAAAMIXQBADAk5kN33bp1OnnypL755hsdO3ZMixYtinZJcWXx4sV688035Xa7Zdu2li9fPqhPTU2N3G63+vr61NzcrLy8vChUGl8qKyv1wQcfyOv1yrIsNTQ06NZbbx3Uj7ENzdq1a9XR0aGenh719PSotbVVJSUlQX0Y0/GprKyUbduqq6sLOs64mmPHanvkkUfs/v5++1e/+pV922232XV1dbbP57NnzpwZ9dripZWUlNi///3v7RUrVti2bdvLly8POl9RUWH39PTYK1assO+44w77tddes91ut52amhr12mO5NTY22mVlZXZeXp49Z84c+29/+5t9+vRpOzk5mbEdR/v5z39u33fffXZubq6dm5trP/fcc3Z/f7+dl5fHmIahzZ8/3z558qT98ccf23V1dYHjjKvRFvUChm3vv/++vXPnzqBjn332mb1ly5ao1xaPbajQPXfunF1RURF47XQ67YsXL9pPPfVU1OuNpzZ9+nTbtm178eLFjG2Y29dff23/8pe/ZEzH2VJSUuzjx4/bP/3pT+3m5uag0GVczbWYvbyclJSkefPmqampKeh4U1OTFi5cGKWqEktOTo6ysrKCxtjv96ulpYUxDtH1118vSbpw4YIkxjYcJkyYoNLSUqWkpKitrY0xHaf6+nq9/fbbOnToUNBxxtWsmHzKkCRNnz5dEydOlGVZQccty1JmZmaUqkosV8ZxqDG++eabo1FS3Nq+fbuOHDmizs5OSYzteMyePVttbW267rrr1NvbqxUrVujzzz/Xj3/8Y0mM6ViUlpbq7rvvVkFBwaBz/Fk1K2ZD9wrbtoNeOxyOQccwPozx+Lz00kuaM2fOkDf5MbahO378uPLz8zVlyhQ9/PDD2rNnjwoLCwPnGdPQ3HjjjXr++ee1bNmyq+6vzLiaEbOXl8+fP6/Lly8PmtWmp6cP+j8yjE13d7ckMcbj8MILL+iBBx7QT37yE7nd7sBxxnbsBgYGdOLECbW3t6u6ulodHR16+umnGdMxmjdvnjIyMtTe3q6BgQENDAyoqKhIv/nNbzQwMBAYO8bVjJgN3YGBAbW3t6u4uDjoeHFxsVpbW6NUVWI5deqUPB5P0BgnJSWpsLCQMR6FF198UQ899JCWLl2q06dPB51jbMPH4XBo0qRJjOkYHTp0SLNnz1Z+fn6gffjhh9q7d6/y8/N18uRJxtWwqN/NNVy7smRozZo19m233WZv377d9vl89k033RT12uKlpaSk2HPnzrXnzp1r27Ztb9iwwZ47d25g2VVFRYV98eJF+8EHH7TvuOMOe+/evSwVGEWrr6+3L168aC9ZssTOyMgItOuuuy7Qh7ENvdXW1tqLFi2yb775Znv27Nn2c889Z1++fNm+5557GNMwtv+9e5lxNdqiXsBV27p16+xTp07Zly5dso8dOxa0JIM2cissLLSHsnv37kCfmpoa+9y5c/Y333xjv/vuu/Ydd9wR9bpjvQ2nrKwsqB9jG1r785//HPj7blmWffDgwUDgMqbha/8buoyrucbzdAEAMCRmf6cLAECiIXQBADCE0AUAwBBCFwAAQwhdAAAMIXQBADCE0AUAwBBCFwAAQwhdAAAMIXQBADCE0AUAwJD/B1AROO7Ia/rTAAAAAElFTkSuQmCC",
127 | "text/plain": [
128 | ""
129 | ]
130 | },
131 | "metadata": {},
132 | "output_type": "display_data"
133 | }
134 | ],
135 | "source": [
136 | "# Reshape the image into 2d array\n",
137 | "image_2d = top_half_image.reshape(-1, 3)\n",
138 | "\n",
139 | "# perform kmeans clustering\n",
140 | "Kmeans = KMeans(n_clusters=2, random_state=0).fit(image_2d) \n",
141 | "\n",
142 | "# get the cluster labels\n",
143 | "labels = Kmeans.labels_\n",
144 | "\n",
145 | "# reshape the labels to the original image shape\n",
146 | "clustered_image = labels.reshape(top_half_image.shape[0], top_half_image.shape[1])\n",
147 | "\n",
148 | "# Display the clustered image\n",
149 | "plt.imshow(clustered_image)"
150 | ]
151 | },
152 | {
153 | "cell_type": "code",
154 | "execution_count": 9,
155 | "metadata": {},
156 | "outputs": [
157 | {
158 | "name": "stdout",
159 | "output_type": "stream",
160 | "text": [
161 | "Non player cluster: 1\n"
162 | ]
163 | }
164 | ],
165 | "source": [
166 | "corner_clusters = [clustered_image[0, 0], clustered_image[0, -1], clustered_image[-1, 0], clustered_image[-1, -1]]\n",
167 | "non_player_cluster = max(set(corner_clusters), key=corner_clusters.count)\n",
168 | "print('Non player cluster:', non_player_cluster)"
169 | ]
170 | },
171 | {
172 | "cell_type": "code",
173 | "execution_count": 10,
174 | "metadata": {},
175 | "outputs": [
176 | {
177 | "name": "stdout",
178 | "output_type": "stream",
179 | "text": [
180 | "Player cluster: 0\n"
181 | ]
182 | }
183 | ],
184 | "source": [
185 | "player_cluster = 1-non_player_cluster\n",
186 | "print('Player cluster:', player_cluster)"
187 | ]
188 | },
189 | {
190 | "cell_type": "code",
191 | "execution_count": 11,
192 | "metadata": {},
193 | "outputs": [
194 | {
195 | "data": {
196 | "text/plain": [
197 | "array([171.1701847 , 235.37862797, 142.83641161])"
198 | ]
199 | },
200 | "execution_count": 11,
201 | "metadata": {},
202 | "output_type": "execute_result"
203 | }
204 | ],
205 | "source": [
206 | "Kmeans.cluster_centers_[player_cluster]"
207 | ]
208 | },
209 | {
210 | "cell_type": "code",
211 | "execution_count": null,
212 | "metadata": {},
213 | "outputs": [],
214 | "source": []
215 | }
216 | ],
217 | "metadata": {
218 | "kernelspec": {
219 | "display_name": "base",
220 | "language": "python",
221 | "name": "python3"
222 | },
223 | "language_info": {
224 | "codemirror_mode": {
225 | "name": "ipython",
226 | "version": 3
227 | },
228 | "file_extension": ".py",
229 | "mimetype": "text/x-python",
230 | "name": "python",
231 | "nbconvert_exporter": "python",
232 | "pygments_lexer": "ipython3",
233 | "version": "3.11.9"
234 | }
235 | },
236 | "nbformat": 4,
237 | "nbformat_minor": 2
238 | }
239 |
--------------------------------------------------------------------------------
/input_videos/NOTE.txt:
--------------------------------------------------------------------------------
1 | You have to put your Input Video here.
--------------------------------------------------------------------------------
/input_videos/match.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rajveersinghcse/Football-Analysis-using-YOLO/5fd79c081a31d922ab30bc3d832d83d636008df6/input_videos/match.mp4
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | from utils import read_video, save_video
2 | from trackers import Tracker
3 | import cv2
4 | import numpy as np
5 | from team_assigner import TeamAssigner
6 | from player_ball_assigner import PlayerBallAssigner
7 | from camera_movement_estimator import CameraMovementEstimator
8 | from view_transformer import ViewTransformer
9 | from speed_and_distance_estimator import SpeedAndDistance_Estimator
10 |
11 |
12 | def main():
13 | # Read Video
14 | video_frames = read_video("./input_videos/match.mp4")
15 |
16 | # Initialize Tracker
17 | tracker = Tracker("./models/best.pt")
18 |
19 | tracks = tracker.get_object_tracks(
20 | video_frames, read_from_stub=True, stub_path="stubs/track_stubs.pkl"
21 | )
22 | # Get object positions
23 | tracker.add_position_to_tracks(tracks)
24 |
25 | # camera movement estimator
26 | camera_movement_estimator = CameraMovementEstimator(video_frames[0])
27 | camera_movement_per_frame = camera_movement_estimator.get_camera_movement(
28 | video_frames, read_from_stub=True, stub_path="stubs/camera_movement_stub.pkl"
29 | )
30 | camera_movement_estimator.add_adjust_positions_to_tracks(
31 | tracks, camera_movement_per_frame
32 | )
33 |
34 | # View Trasnformer
35 | view_transformer = ViewTransformer()
36 | view_transformer.add_transformed_position_to_tracks(tracks)
37 |
38 | # Interpolate Ball Positions
39 | tracks["ball"] = tracker.interpolate_ball_positions(tracks["ball"])
40 |
41 | # Speed and distance estimator
42 | speed_and_distance_estimator = SpeedAndDistance_Estimator()
43 | speed_and_distance_estimator.add_speed_and_distance_to_tracks(tracks)
44 |
45 | # Assign Player Teams
46 | team_assigner = TeamAssigner()
47 | team_assigner.assign_team_color(video_frames[0], tracks["players"][0])
48 |
49 | for frame_num, player_track in enumerate(tracks["players"]):
50 | for player_id, track in player_track.items():
51 | team = team_assigner.get_player_team(
52 | video_frames[frame_num], track["bbox"], player_id
53 | )
54 | tracks["players"][frame_num][player_id]["team"] = team
55 | tracks["players"][frame_num][player_id]["team_color"] = (
56 | team_assigner.team_colors[team]
57 | )
58 |
59 | # Assign Ball Aquisition
60 | player_assigner = PlayerBallAssigner()
61 | team_ball_control = []
62 | for frame_num, player_track in enumerate(tracks["players"]):
63 | ball_bbox = tracks["ball"][frame_num][1]["bbox"]
64 | assigned_player = player_assigner.assign_ball_to_player(player_track, ball_bbox)
65 |
66 | if assigned_player != -1:
67 | tracks["players"][frame_num][assigned_player]["has_ball"] = True
68 | team_ball_control.append(
69 | tracks["players"][frame_num][assigned_player]["team"]
70 | )
71 | else:
72 | team_ball_control.append(team_ball_control[-1])
73 | team_ball_control = np.array(team_ball_control)
74 |
75 | # Draw output
76 | ## Draw object Tracks
77 | output_video_frames = tracker.draw_annotations(
78 | video_frames, tracks, team_ball_control
79 | )
80 |
81 | ## Draw Camera movement
82 | output_video_frames = camera_movement_estimator.draw_camera_movement(
83 | output_video_frames, camera_movement_per_frame
84 | )
85 |
86 | ## Draw Speed and Distance
87 | speed_and_distance_estimator.draw_speed_and_distance(output_video_frames, tracks)
88 |
89 | # Save video
90 | save_video(output_video_frames, "./output_videos/output_video.avi")
91 |
92 |
93 | if __name__ == "__main__":
94 | main()
95 |
--------------------------------------------------------------------------------
/models/NOTE.txt:
--------------------------------------------------------------------------------
1 | You need to run Football_training_yolo_v5.ipynb file to get the models. You will find the models into training folder.
--------------------------------------------------------------------------------
/output_images/player_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rajveersinghcse/Football-Analysis-using-YOLO/5fd79c081a31d922ab30bc3d832d83d636008df6/output_images/player_2.jpg
--------------------------------------------------------------------------------
/output_videos/NOTE.txt:
--------------------------------------------------------------------------------
1 | Your output Video will be here after pasting the Input Video
--------------------------------------------------------------------------------
/player_ball_assigner/__init__.py:
--------------------------------------------------------------------------------
1 | from .player_ball_assigner import PlayerBallAssigner
--------------------------------------------------------------------------------
/player_ball_assigner/player_ball_assigner.py:
--------------------------------------------------------------------------------
1 | import sys
2 | sys.path.append('../')
3 | from utils import get_center_of_bbox, measure_distance
4 |
5 | class PlayerBallAssigner():
6 | def __init__(self):
7 | self.max_player_ball_distance = 70
8 |
9 | def assign_ball_to_player(self,players,ball_bbox):
10 | ball_position = get_center_of_bbox(ball_bbox)
11 |
12 | miniumum_distance = 99999
13 | assigned_player=-1
14 |
15 | for player_id, player in players.items():
16 | player_bbox = player['bbox']
17 |
18 | distance_left = measure_distance((player_bbox[0],player_bbox[-1]),ball_position)
19 | distance_right = measure_distance((player_bbox[2],player_bbox[-1]),ball_position)
20 | distance = min(distance_left,distance_right)
21 |
22 | if distance < self.max_player_ball_distance:
23 | if distance < miniumum_distance:
24 | miniumum_distance = distance
25 | assigned_player = player_id
26 |
27 | return assigned_player
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | ultralytics
2 | numpy
3 | opencv-python
4 | roboflow
5 | pandas
6 | pickle
7 | supervision
8 | shutil
9 | scikit-learn
10 |
--------------------------------------------------------------------------------
/speed_and_distance_estimator/__init__.py:
--------------------------------------------------------------------------------
1 | from .speed_and_distance_estimator import SpeedAndDistance_Estimator
--------------------------------------------------------------------------------
/speed_and_distance_estimator/speed_and_distance_estimator.py:
--------------------------------------------------------------------------------
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):
12 | total_distance= {}
13 |
14 | for object, object_tracks in tracks.items():
15 | if object == "ball" or object == "referees":
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 == "ball" or 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/camera_movement_stub.pkl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rajveersinghcse/Football-Analysis-using-YOLO/5fd79c081a31d922ab30bc3d832d83d636008df6/stubs/camera_movement_stub.pkl
--------------------------------------------------------------------------------
/stubs/track_stubs.pkl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rajveersinghcse/Football-Analysis-using-YOLO/5fd79c081a31d922ab30bc3d832d83d636008df6/stubs/track_stubs.pkl
--------------------------------------------------------------------------------
/team_assigner/__init__.py:
--------------------------------------------------------------------------------
1 | from .team_assigner import TeamAssigner
--------------------------------------------------------------------------------
/team_assigner/team_assigner.py:
--------------------------------------------------------------------------------
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).fit(image_2d)
14 |
15 | return kmeans
16 |
17 | def get_player_color(self,frame,bbox):
18 | image = frame[int(bbox[1]):int(bbox[3]),int(bbox[0]):int(bbox[2])]
19 |
20 | top_half_image = image[0:int(image.shape[0]/2),:]
21 |
22 | # Get Clustering model
23 | kmeans = self.get_clustering_model(top_half_image)
24 |
25 | # Get the cluster labels forr each pixel
26 | labels = kmeans.labels_
27 |
28 | # Reshape the labels to the image shape
29 | clustered_image = labels.reshape(top_half_image.shape[0],top_half_image.shape[1])
30 |
31 | # Get the player cluster
32 | corner_clusters = [clustered_image[0,0],clustered_image[0,-1],clustered_image[-1,0],clustered_image[-1,-1]]
33 | non_player_cluster = max(set(corner_clusters),key=corner_clusters.count)
34 | player_cluster = 1 - non_player_cluster
35 |
36 | player_color = kmeans.cluster_centers_[player_cluster]
37 |
38 | return player_color
39 |
40 |
41 | def assign_team_color(self,frame, player_detections):
42 |
43 | player_colors = []
44 | for _, player_detection in player_detections.items():
45 | bbox = player_detection["bbox"]
46 | player_color = self.get_player_color(frame,bbox)
47 | player_colors.append(player_color)
48 |
49 | kmeans = KMeans(n_clusters=2, init="k-means++",n_init=10).fit(player_colors)
50 |
51 | self.kmeans = kmeans
52 |
53 | self.team_colors[1] = kmeans.cluster_centers_[0]
54 | self.team_colors[2] = kmeans.cluster_centers_[1]
55 |
56 |
57 | def get_player_team(self,frame,player_bbox,player_id):
58 | if player_id in self.player_team_dict:
59 | return self.player_team_dict[player_id]
60 |
61 | player_color = self.get_player_color(frame,player_bbox)
62 |
63 | team_id = self.kmeans.predict(player_color.reshape(1,-1))[0]
64 | team_id+=1
65 |
66 | if player_id == 91:
67 | team_id=1
68 |
69 | self.player_team_dict[player_id] = team_id
70 |
71 | return team_id
72 |
--------------------------------------------------------------------------------
/trackers/__init__.py:
--------------------------------------------------------------------------------
1 | from .tracker import Tracker
--------------------------------------------------------------------------------
/trackers/tracker.py:
--------------------------------------------------------------------------------
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 |
63 | for frame_num, detection in enumerate(detections):
64 | cls_names = detection.names
65 | cls_names_inv = {v:k for k,v in cls_names.items()}
66 |
67 | # Covert to supervision Detection format
68 | detection_supervision = sv.Detections.from_ultralytics(detection)
69 |
70 | # Convert GoalKeeper to player object
71 | for object_ind , class_id in enumerate(detection_supervision.class_id):
72 | if cls_names[class_id] == "goalkeeper":
73 | detection_supervision.class_id[object_ind] = cls_names_inv["player"]
74 |
75 | # Track Objects
76 | detection_with_tracks = self.tracker.update_with_detections(detection_supervision)
77 |
78 | tracks["players"].append({})
79 | tracks["referees"].append({})
80 | tracks["ball"].append({})
81 |
82 | for frame_detection in detection_with_tracks:
83 | bbox = frame_detection[0].tolist()
84 | cls_id = frame_detection[3]
85 | track_id = frame_detection[4]
86 |
87 | if cls_id == cls_names_inv['player']:
88 | tracks["players"][frame_num][track_id] = {"bbox":bbox}
89 |
90 | if cls_id == cls_names_inv['referee']:
91 | tracks["referees"][frame_num][track_id] = {"bbox":bbox}
92 |
93 | for frame_detection in detection_supervision:
94 | bbox = frame_detection[0].tolist()
95 | cls_id = frame_detection[3]
96 |
97 | if cls_id == cls_names_inv['ball']:
98 | tracks["ball"][frame_num][1] = {"bbox":bbox}
99 |
100 | if stub_path is not None:
101 | with open(stub_path,'wb') as f:
102 | pickle.dump(tracks,f)
103 |
104 | return tracks
105 |
106 | def draw_ellipse(self,frame,bbox,color,track_id=None):
107 | y2 = int(bbox[3])
108 | x_center, _ = get_center_of_bbox(bbox)
109 | width = get_bbox_width(bbox)
110 |
111 | cv2.ellipse(
112 | frame,
113 | center=(x_center,y2),
114 | axes=(int(width), int(0.35*width)),
115 | angle=0.0,
116 | startAngle=-45,
117 | endAngle=235,
118 | color = color,
119 | thickness=2,
120 | lineType=cv2.LINE_4
121 | )
122 |
123 | rectangle_width = 40
124 | rectangle_height=20
125 | x1_rect = x_center - rectangle_width//2
126 | x2_rect = x_center + rectangle_width//2
127 | y1_rect = (y2- rectangle_height//2) +15
128 | y2_rect = (y2+ rectangle_height//2) +15
129 |
130 | if track_id is not None:
131 | cv2.rectangle(frame,
132 | (int(x1_rect),int(y1_rect) ),
133 | (int(x2_rect),int(y2_rect)),
134 | color,
135 | cv2.FILLED)
136 |
137 | x1_text = x1_rect+12
138 | if track_id > 99:
139 | x1_text -=10
140 |
141 | cv2.putText(
142 | frame,
143 | f"{track_id}",
144 | (int(x1_text),int(y1_rect+15)),
145 | cv2.FONT_HERSHEY_SIMPLEX,
146 | 0.6,
147 | (0,0,0),
148 | 2
149 | )
150 |
151 | return frame
152 |
153 | def draw_traingle(self,frame,bbox,color):
154 | y= int(bbox[1])
155 | x,_ = get_center_of_bbox(bbox)
156 |
157 | triangle_points = np.array([
158 | [x,y],
159 | [x-10,y-20],
160 | [x+10,y-20],
161 | ])
162 | cv2.drawContours(frame, [triangle_points],0,color, cv2.FILLED)
163 | cv2.drawContours(frame, [triangle_points],0,(0,0,0), 2)
164 |
165 | return frame
166 |
167 | def draw_team_ball_control(self,frame,frame_num,team_ball_control):
168 | # Draw a semi-transparent rectaggle
169 | overlay = frame.copy()
170 | cv2.rectangle(overlay, (1350, 850), (1900,970), (255,255,255), -1 )
171 | alpha = 0.4
172 | cv2.addWeighted(overlay, alpha, frame, 1 - alpha, 0, frame)
173 |
174 | team_ball_control_till_frame = team_ball_control[:frame_num+1]
175 | # Get the number of time each team had ball control
176 | team_1_num_frames = team_ball_control_till_frame[team_ball_control_till_frame==1].shape[0]
177 | team_2_num_frames = team_ball_control_till_frame[team_ball_control_till_frame==2].shape[0]
178 | team_1 = team_1_num_frames/(team_1_num_frames+team_2_num_frames)
179 | team_2 = team_2_num_frames/(team_1_num_frames+team_2_num_frames)
180 |
181 | cv2.putText(frame, f"Team 1 Ball Control: {team_1*100:.2f}%",(1400,900), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,0), 3)
182 | cv2.putText(frame, f"Team 2 Ball Control: {team_2*100:.2f}%",(1400,950), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,0), 3)
183 |
184 | return frame
185 |
186 | def draw_annotations(self,video_frames, tracks,team_ball_control):
187 | output_video_frames= []
188 | for frame_num, frame in enumerate(video_frames):
189 | frame = frame.copy()
190 |
191 | player_dict = tracks["players"][frame_num]
192 | ball_dict = tracks["ball"][frame_num]
193 | referee_dict = tracks["referees"][frame_num]
194 |
195 | # Draw Players
196 | for track_id, player in player_dict.items():
197 | color = player.get("team_color",(0,0,255))
198 | frame = self.draw_ellipse(frame, player["bbox"],color, track_id)
199 |
200 | if player.get('has_ball',False):
201 | frame = self.draw_traingle(frame, player["bbox"],(0,0,255))
202 |
203 | # Draw Referee
204 | for _, referee in referee_dict.items():
205 | frame = self.draw_ellipse(frame, referee["bbox"],(0,255,255))
206 |
207 | # Draw ball
208 | for track_id, ball in ball_dict.items():
209 | frame = self.draw_traingle(frame, ball["bbox"],(0,255,0))
210 |
211 |
212 | # Draw Team Ball Control
213 | frame = self.draw_team_ball_control(frame, frame_num, team_ball_control)
214 |
215 | output_video_frames.append(frame)
216 |
217 | return output_video_frames
--------------------------------------------------------------------------------
/training/football_training_yolo_v5.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 2,
6 | "metadata": {
7 | "id": "5_sdU4m-d7dK"
8 | },
9 | "outputs": [],
10 | "source": [
11 | "import ultralytics\n",
12 | "from roboflow import Roboflow"
13 | ]
14 | },
15 | {
16 | "cell_type": "code",
17 | "execution_count": 3,
18 | "metadata": {
19 | "colab": {
20 | "base_uri": "https://localhost:8080/"
21 | },
22 | "id": "MzQwV91LdslB",
23 | "outputId": "5a73340a-7a6f-44d9-af68-7fc48d7ff953"
24 | },
25 | "outputs": [
26 | {
27 | "name": "stdout",
28 | "output_type": "stream",
29 | "text": [
30 | "loading Roboflow workspace...\n",
31 | "loading Roboflow project...\n"
32 | ]
33 | },
34 | {
35 | "name": "stderr",
36 | "output_type": "stream",
37 | "text": [
38 | "Downloading Dataset Version Zip in football-players-detection-1 to yolov5pytorch:: 100%|██████████| 148663/148663 [00:02<00:00, 66787.03it/s]"
39 | ]
40 | },
41 | {
42 | "name": "stdout",
43 | "output_type": "stream",
44 | "text": [
45 | "\n"
46 | ]
47 | },
48 | {
49 | "name": "stderr",
50 | "output_type": "stream",
51 | "text": [
52 | "\n",
53 | "Extracting Dataset Version Zip to football-players-detection-1 in yolov5pytorch:: 100%|██████████| 1338/1338 [00:00<00:00, 2278.21it/s]\n"
54 | ]
55 | }
56 | ],
57 | "source": [
58 | "rf = Roboflow(api_key=\"\")\n",
59 | "project = rf.workspace(\"roboflow-jvuqo\").project(\"football-players-detection-3zvbc\")\n",
60 | "version = project.version(1)\n",
61 | "dataset = version.download(\"yolov5\")"
62 | ]
63 | },
64 | {
65 | "cell_type": "code",
66 | "execution_count": 4,
67 | "metadata": {
68 | "colab": {
69 | "base_uri": "https://localhost:8080/",
70 | "height": 35
71 | },
72 | "id": "IyXs_n72dslD",
73 | "outputId": "8ea1de3e-5280-4fa2-8d10-6b59a03f7bbf"
74 | },
75 | "outputs": [
76 | {
77 | "data": {
78 | "application/vnd.google.colaboratory.intrinsic+json": {
79 | "type": "string"
80 | },
81 | "text/plain": [
82 | "'/content/football-players-detection-1'"
83 | ]
84 | },
85 | "execution_count": 4,
86 | "metadata": {},
87 | "output_type": "execute_result"
88 | }
89 | ],
90 | "source": [
91 | "dataset.location"
92 | ]
93 | },
94 | {
95 | "cell_type": "code",
96 | "execution_count": 5,
97 | "metadata": {
98 | "colab": {
99 | "base_uri": "https://localhost:8080/",
100 | "height": 35
101 | },
102 | "id": "tqk2jYzHdslD",
103 | "outputId": "bc81d4c4-8200-4dae-d554-59c35587127a"
104 | },
105 | "outputs": [
106 | {
107 | "data": {
108 | "application/vnd.google.colaboratory.intrinsic+json": {
109 | "type": "string"
110 | },
111 | "text/plain": [
112 | "'./football-players-detection-1/football-players-detection-1/test'"
113 | ]
114 | },
115 | "execution_count": 5,
116 | "metadata": {},
117 | "output_type": "execute_result"
118 | }
119 | ],
120 | "source": [
121 | "import shutil\n",
122 | "\n",
123 | "shutil.move('./football-players-detection-1/train', './football-players-detection-1/football-players-detection-1/train')\n",
124 | "shutil.move('./football-players-detection-1/valid', './football-players-detection-1/football-players-detection-1/valid')\n",
125 | "shutil.move('./football-players-detection-1/test', './football-players-detection-1/football-players-detection-1/test')"
126 | ]
127 | },
128 | {
129 | "cell_type": "markdown",
130 | "metadata": {
131 | "id": "iuBKL0DJdslE"
132 | },
133 | "source": [
134 | "## Training"
135 | ]
136 | },
137 | {
138 | "cell_type": "code",
139 | "execution_count": 6,
140 | "metadata": {
141 | "colab": {
142 | "base_uri": "https://localhost:8080/"
143 | },
144 | "id": "X6d6DMGrdslG",
145 | "outputId": "a4d3912a-fc10-4a81-9f2b-1f743e331d30"
146 | },
147 | "outputs": [
148 | {
149 | "name": "stdout",
150 | "output_type": "stream",
151 | "text": [
152 | "PRO TIP 💡 Replace 'model=yolov5l.pt' with new 'model=yolov5lu.pt'.\n",
153 | "YOLOv5 'u' models are trained with https://github.com/ultralytics/ultralytics and feature improved performance vs standard YOLOv5 models trained with https://github.com/ultralytics/yolov5.\n",
154 | "\n",
155 | "Downloading https://github.com/ultralytics/assets/releases/download/v8.2.0/yolov5lu.pt to 'yolov5lu.pt'...\n",
156 | "100% 102M/102M [00:00<00:00, 292MB/s] \n",
157 | "Ultralytics YOLOv8.2.2 🚀 Python-3.10.12 torch-2.2.1+cu121 CUDA:0 (Tesla T4, 15102MiB)\n",
158 | "\u001b[34m\u001b[1mengine/trainer: \u001b[0mtask=detect, mode=train, model=yolov5l.pt, data=/content/football-players-detection-1/data.yaml, epochs=100, time=None, patience=100, batch=16, imgsz=640, save=True, save_period=-1, cache=False, device=None, workers=8, project=None, name=train, exist_ok=False, pretrained=True, optimizer=auto, verbose=True, seed=0, deterministic=True, single_cls=False, rect=False, cos_lr=False, close_mosaic=10, resume=False, amp=True, fraction=1.0, profile=False, freeze=None, multi_scale=False, overlap_mask=True, mask_ratio=4, dropout=0.0, val=True, split=val, save_json=False, save_hybrid=False, conf=None, iou=0.7, max_det=300, half=False, dnn=False, plots=True, source=None, vid_stride=1, stream_buffer=False, visualize=False, augment=False, agnostic_nms=False, classes=None, retina_masks=False, embed=None, show=False, save_frames=False, save_txt=False, save_conf=False, save_crop=False, show_labels=True, show_conf=True, show_boxes=True, line_width=None, format=torchscript, keras=False, optimize=False, int8=False, dynamic=False, simplify=False, opset=None, workspace=4, nms=False, lr0=0.01, lrf=0.01, momentum=0.937, weight_decay=0.0005, warmup_epochs=3.0, warmup_momentum=0.8, warmup_bias_lr=0.1, box=7.5, cls=0.5, dfl=1.5, pose=12.0, kobj=1.0, label_smoothing=0.0, nbs=64, hsv_h=0.015, hsv_s=0.7, hsv_v=0.4, degrees=0.0, translate=0.1, scale=0.5, shear=0.0, perspective=0.0, flipud=0.0, fliplr=0.5, bgr=0.0, mosaic=1.0, mixup=0.0, copy_paste=0.0, auto_augment=randaugment, erasing=0.4, crop_fraction=1.0, cfg=None, tracker=botsort.yaml, save_dir=runs/detect/train\n",
159 | "Downloading https://ultralytics.com/assets/Arial.ttf to '/root/.config/Ultralytics/Arial.ttf'...\n",
160 | "100% 755k/755k [00:00<00:00, 26.5MB/s]\n",
161 | "2024-04-25 14:25:13.387233: E external/local_xla/xla/stream_executor/cuda/cuda_dnn.cc:9261] Unable to register cuDNN factory: Attempting to register factory for plugin cuDNN when one has already been registered\n",
162 | "2024-04-25 14:25:13.387290: E external/local_xla/xla/stream_executor/cuda/cuda_fft.cc:607] Unable to register cuFFT factory: Attempting to register factory for plugin cuFFT when one has already been registered\n",
163 | "2024-04-25 14:25:13.389213: E external/local_xla/xla/stream_executor/cuda/cuda_blas.cc:1515] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered\n",
164 | "Overriding model.yaml nc=80 with nc=4\n",
165 | "\n",
166 | " from n params module arguments \n",
167 | " 0 -1 1 7040 ultralytics.nn.modules.conv.Conv [3, 64, 6, 2, 2] \n",
168 | " 1 -1 1 73984 ultralytics.nn.modules.conv.Conv [64, 128, 3, 2] \n",
169 | " 2 -1 3 156928 ultralytics.nn.modules.block.C3 [128, 128, 3] \n",
170 | " 3 -1 1 295424 ultralytics.nn.modules.conv.Conv [128, 256, 3, 2] \n",
171 | " 4 -1 6 1118208 ultralytics.nn.modules.block.C3 [256, 256, 6] \n",
172 | " 5 -1 1 1180672 ultralytics.nn.modules.conv.Conv [256, 512, 3, 2] \n",
173 | " 6 -1 9 6433792 ultralytics.nn.modules.block.C3 [512, 512, 9] \n",
174 | " 7 -1 1 4720640 ultralytics.nn.modules.conv.Conv [512, 1024, 3, 2] \n",
175 | " 8 -1 3 9971712 ultralytics.nn.modules.block.C3 [1024, 1024, 3] \n",
176 | " 9 -1 1 2624512 ultralytics.nn.modules.block.SPPF [1024, 1024, 5] \n",
177 | " 10 -1 1 525312 ultralytics.nn.modules.conv.Conv [1024, 512, 1, 1] \n",
178 | " 11 -1 1 0 torch.nn.modules.upsampling.Upsample [None, 2, 'nearest'] \n",
179 | " 12 [-1, 6] 1 0 ultralytics.nn.modules.conv.Concat [1] \n",
180 | " 13 -1 3 2757632 ultralytics.nn.modules.block.C3 [1024, 512, 3, False] \n",
181 | " 14 -1 1 131584 ultralytics.nn.modules.conv.Conv [512, 256, 1, 1] \n",
182 | " 15 -1 1 0 torch.nn.modules.upsampling.Upsample [None, 2, 'nearest'] \n",
183 | " 16 [-1, 4] 1 0 ultralytics.nn.modules.conv.Concat [1] \n",
184 | " 17 -1 3 690688 ultralytics.nn.modules.block.C3 [512, 256, 3, False] \n",
185 | " 18 -1 1 590336 ultralytics.nn.modules.conv.Conv [256, 256, 3, 2] \n",
186 | " 19 [-1, 14] 1 0 ultralytics.nn.modules.conv.Concat [1] \n",
187 | " 20 -1 3 2495488 ultralytics.nn.modules.block.C3 [512, 512, 3, False] \n",
188 | " 21 -1 1 2360320 ultralytics.nn.modules.conv.Conv [512, 512, 3, 2] \n",
189 | " 22 [-1, 10] 1 0 ultralytics.nn.modules.conv.Concat [1] \n",
190 | " 23 -1 3 9971712 ultralytics.nn.modules.block.C3 [1024, 1024, 3, False] \n",
191 | " 24 [17, 20, 23] 1 7060444 ultralytics.nn.modules.head.Detect [4, [256, 512, 1024]] \n",
192 | "YOLOv5l summary: 416 layers, 53166428 parameters, 53166412 gradients, 135.3 GFLOPs\n",
193 | "\n",
194 | "Transferred 685/691 items from pretrained weights\n",
195 | "\u001b[34m\u001b[1mTensorBoard: \u001b[0mStart with 'tensorboard --logdir runs/detect/train', view at http://localhost:6006/\n",
196 | "Freezing layer 'model.24.dfl.conv.weight'\n",
197 | "\u001b[34m\u001b[1mAMP: \u001b[0mrunning Automatic Mixed Precision (AMP) checks with YOLOv8n...\n",
198 | "Downloading https://github.com/ultralytics/assets/releases/download/v8.2.0/yolov8n.pt to 'yolov8n.pt'...\n",
199 | "100% 6.23M/6.23M [00:00<00:00, 105MB/s]\n",
200 | "\u001b[34m\u001b[1mAMP: \u001b[0mchecks passed ✅\n",
201 | "\u001b[34m\u001b[1mtrain: \u001b[0mScanning /content/football-players-detection-1/football-players-detection-1/train/labels... 612 images, 0 backgrounds, 0 corrupt: 100% 612/612 [00:00<00:00, 1805.13it/s]\n",
202 | "\u001b[34m\u001b[1mtrain: \u001b[0mNew cache created: /content/football-players-detection-1/football-players-detection-1/train/labels.cache\n",
203 | "\u001b[34m\u001b[1malbumentations: \u001b[0mBlur(p=0.01, blur_limit=(3, 7)), MedianBlur(p=0.01, blur_limit=(3, 7)), ToGray(p=0.01), CLAHE(p=0.01, clip_limit=(1, 4.0), tile_grid_size=(8, 8))\n",
204 | "/usr/lib/python3.10/multiprocessing/popen_fork.py:66: RuntimeWarning: os.fork() was called. os.fork() is incompatible with multithreaded code, and JAX is multithreaded, so this will likely lead to a deadlock.\n",
205 | " self.pid = os.fork()\n",
206 | "\u001b[34m\u001b[1mval: \u001b[0mScanning /content/football-players-detection-1/football-players-detection-1/valid/labels... 38 images, 0 backgrounds, 0 corrupt: 100% 38/38 [00:00<00:00, 1101.57it/s]\n",
207 | "\u001b[34m\u001b[1mval: \u001b[0mNew cache created: /content/football-players-detection-1/football-players-detection-1/valid/labels.cache\n",
208 | "Plotting labels to runs/detect/train/labels.jpg... \n",
209 | "\u001b[34m\u001b[1moptimizer:\u001b[0m 'optimizer=auto' found, ignoring 'lr0=0.01' and 'momentum=0.937' and determining best 'optimizer', 'lr0' and 'momentum' automatically... \n",
210 | "\u001b[34m\u001b[1moptimizer:\u001b[0m AdamW(lr=0.00125, momentum=0.9) with parameter groups 113 weight(decay=0.0), 120 weight(decay=0.0005), 119 bias(decay=0.0)\n",
211 | "\u001b[34m\u001b[1mTensorBoard: \u001b[0mmodel graph visualization added ✅\n",
212 | "Image sizes 640 train, 640 val\n",
213 | "Using 2 dataloader workers\n",
214 | "Logging results to \u001b[1mruns/detect/train\u001b[0m\n",
215 | "Starting training for 100 epochs...\n",
216 | "\n",
217 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
218 | " 1/100 10.8G 1.266 1.486 0.8403 194 640: 100% 39/39 [00:41<00:00, 1.07s/it]\n",
219 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:03<00:00, 1.54s/it]\n",
220 | " all 38 905 0.274 0.285 0.219 0.124\n",
221 | "\n",
222 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
223 | " 2/100 10.4G 1.16 0.7307 0.8124 235 640: 100% 39/39 [00:30<00:00, 1.28it/s]\n",
224 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.60it/s]\n",
225 | " all 38 905 0.622 0.516 0.547 0.324\n",
226 | "\n",
227 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
228 | " 3/100 10.4G 1.234 0.6746 0.8196 219 640: 100% 39/39 [00:30<00:00, 1.27it/s]\n",
229 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.40it/s]\n",
230 | " all 38 905 0.692 0.536 0.545 0.289\n",
231 | "\n",
232 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
233 | " 4/100 10.4G 1.22 0.6634 0.8191 77 640: 100% 39/39 [00:30<00:00, 1.27it/s]\n",
234 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.41it/s]\n",
235 | " all 38 905 0.757 0.682 0.689 0.39\n",
236 | "\n",
237 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
238 | " 5/100 10.4G 1.155 0.6372 0.812 177 640: 100% 39/39 [00:30<00:00, 1.29it/s]\n",
239 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:01<00:00, 1.82it/s]\n",
240 | " all 38 905 0.631 0.727 0.633 0.399\n",
241 | "\n",
242 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
243 | " 6/100 10.4G 1.083 0.6184 0.8074 178 640: 100% 39/39 [00:30<00:00, 1.28it/s]\n",
244 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.59it/s]\n",
245 | " all 38 905 0.743 0.683 0.695 0.426\n",
246 | "\n",
247 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
248 | " 7/100 10.5G 1.102 0.6218 0.8087 104 640: 100% 39/39 [00:30<00:00, 1.29it/s]\n",
249 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.43it/s]\n",
250 | " all 38 905 0.848 0.645 0.748 0.458\n",
251 | "\n",
252 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
253 | " 8/100 10.4G 1.035 0.5491 0.8034 234 640: 100% 39/39 [00:30<00:00, 1.30it/s]\n",
254 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.32it/s]\n",
255 | " all 38 905 0.889 0.644 0.734 0.467\n",
256 | "\n",
257 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
258 | " 9/100 10.7G 1.08 0.5593 0.808 170 640: 100% 39/39 [00:30<00:00, 1.28it/s]\n",
259 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.53it/s]\n",
260 | " all 38 905 0.836 0.641 0.708 0.455\n",
261 | "\n",
262 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
263 | " 10/100 10.8G 1.041 0.5286 0.8014 203 640: 100% 39/39 [00:30<00:00, 1.29it/s]\n",
264 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.46it/s]\n",
265 | " all 38 905 0.856 0.699 0.754 0.493\n",
266 | "\n",
267 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
268 | " 11/100 10.7G 1.083 0.5396 0.8054 132 640: 100% 39/39 [00:30<00:00, 1.27it/s]\n",
269 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.06it/s]\n",
270 | " all 38 905 0.878 0.686 0.76 0.471\n",
271 | "\n",
272 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
273 | " 12/100 10.7G 0.9954 0.5095 0.7989 121 640: 100% 39/39 [00:30<00:00, 1.29it/s]\n",
274 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.67it/s]\n",
275 | " all 38 905 0.844 0.697 0.751 0.488\n",
276 | "\n",
277 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
278 | " 13/100 10.7G 1 0.487 0.7966 165 640: 100% 39/39 [00:30<00:00, 1.28it/s]\n",
279 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.50it/s]\n",
280 | " all 38 905 0.858 0.692 0.745 0.493\n",
281 | "\n",
282 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
283 | " 14/100 10.7G 1.008 0.4831 0.7995 120 640: 100% 39/39 [00:30<00:00, 1.30it/s]\n",
284 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:01<00:00, 1.73it/s]\n",
285 | " all 38 905 0.891 0.698 0.762 0.521\n",
286 | "\n",
287 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
288 | " 15/100 10.7G 0.959 0.4669 0.7942 122 640: 100% 39/39 [00:29<00:00, 1.30it/s]\n",
289 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.62it/s]\n",
290 | " all 38 905 0.88 0.708 0.746 0.493\n",
291 | "\n",
292 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
293 | " 16/100 10.7G 0.9878 0.4772 0.7952 247 640: 100% 39/39 [00:29<00:00, 1.30it/s]\n",
294 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.62it/s]\n",
295 | " all 38 905 0.78 0.694 0.74 0.455\n",
296 | "\n",
297 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
298 | " 17/100 10.8G 0.9391 0.4605 0.7953 129 640: 100% 39/39 [00:30<00:00, 1.29it/s]\n",
299 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.74it/s]\n",
300 | " all 38 905 0.876 0.66 0.758 0.496\n",
301 | "\n",
302 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
303 | " 18/100 10.7G 0.9693 0.4725 0.7935 222 640: 100% 39/39 [00:30<00:00, 1.28it/s]\n",
304 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.71it/s]\n",
305 | " all 38 905 0.82 0.728 0.748 0.503\n",
306 | "\n",
307 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
308 | " 19/100 10.7G 0.8985 0.4436 0.7923 131 640: 100% 39/39 [00:30<00:00, 1.29it/s]\n",
309 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.56it/s]\n",
310 | " all 38 905 0.805 0.687 0.752 0.486\n",
311 | "\n",
312 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
313 | " 20/100 10.7G 0.9006 0.4337 0.7921 128 640: 100% 39/39 [00:30<00:00, 1.27it/s]\n",
314 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.48it/s]\n",
315 | " all 38 905 0.915 0.731 0.784 0.533\n",
316 | "\n",
317 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
318 | " 21/100 10.5G 0.9134 0.4385 0.7904 200 640: 100% 39/39 [00:30<00:00, 1.28it/s]\n",
319 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:01<00:00, 1.89it/s]\n",
320 | " all 38 905 0.901 0.753 0.787 0.53\n",
321 | "\n",
322 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
323 | " 22/100 10.4G 0.9397 0.4435 0.7935 238 640: 100% 39/39 [00:30<00:00, 1.28it/s]\n",
324 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.57it/s]\n",
325 | " all 38 905 0.905 0.716 0.775 0.511\n",
326 | "\n",
327 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
328 | " 23/100 10.7G 0.9286 0.4472 0.7929 129 640: 100% 39/39 [00:30<00:00, 1.28it/s]\n",
329 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.61it/s]\n",
330 | " all 38 905 0.909 0.716 0.786 0.539\n",
331 | "\n",
332 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
333 | " 24/100 10.8G 0.89 0.4373 0.7943 133 640: 100% 39/39 [00:30<00:00, 1.29it/s]\n",
334 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.67it/s]\n",
335 | " all 38 905 0.875 0.711 0.788 0.542\n",
336 | "\n",
337 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
338 | " 25/100 10.4G 0.9148 0.4435 0.7901 257 640: 100% 39/39 [00:29<00:00, 1.30it/s]\n",
339 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:01<00:00, 1.57it/s]\n",
340 | " all 38 905 0.875 0.724 0.776 0.529\n",
341 | "\n",
342 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
343 | " 26/100 10.4G 0.889 0.423 0.7898 167 640: 100% 39/39 [00:29<00:00, 1.30it/s]\n",
344 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.75it/s]\n",
345 | " all 38 905 0.923 0.737 0.801 0.53\n",
346 | "\n",
347 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
348 | " 27/100 10.7G 0.8876 0.4289 0.7897 109 640: 100% 39/39 [00:30<00:00, 1.29it/s]\n",
349 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.38it/s]\n",
350 | " all 38 905 0.838 0.742 0.792 0.537\n",
351 | "\n",
352 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
353 | " 28/100 10.7G 0.9303 0.4417 0.7912 216 640: 100% 39/39 [00:29<00:00, 1.30it/s]\n",
354 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:01<00:00, 1.85it/s]\n",
355 | " all 38 905 0.929 0.731 0.812 0.532\n",
356 | "\n",
357 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
358 | " 29/100 10.7G 0.9325 0.4455 0.7943 201 640: 100% 39/39 [00:30<00:00, 1.28it/s]\n",
359 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.26it/s]\n",
360 | " all 38 905 0.893 0.743 0.794 0.522\n",
361 | "\n",
362 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
363 | " 30/100 10.7G 0.909 0.4442 0.7889 150 640: 100% 39/39 [00:30<00:00, 1.28it/s]\n",
364 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.73it/s]\n",
365 | " all 38 905 0.918 0.708 0.785 0.505\n",
366 | "\n",
367 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
368 | " 31/100 10.7G 0.8851 0.4287 0.7916 114 640: 100% 39/39 [00:30<00:00, 1.28it/s]\n",
369 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:01<00:00, 1.88it/s]\n",
370 | " all 38 905 0.919 0.737 0.8 0.542\n",
371 | "\n",
372 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
373 | " 32/100 10.7G 0.8521 0.4116 0.7887 137 640: 100% 39/39 [00:29<00:00, 1.30it/s]\n",
374 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.30it/s]\n",
375 | " all 38 905 0.922 0.731 0.814 0.563\n",
376 | "\n",
377 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
378 | " 33/100 10.4G 0.8674 0.4204 0.7899 241 640: 100% 39/39 [00:30<00:00, 1.29it/s]\n",
379 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.83it/s]\n",
380 | " all 38 905 0.838 0.698 0.771 0.532\n",
381 | "\n",
382 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
383 | " 34/100 10.6G 0.8881 0.415 0.7879 121 640: 100% 39/39 [00:30<00:00, 1.27it/s]\n",
384 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.60it/s]\n",
385 | " all 38 905 0.872 0.659 0.764 0.514\n",
386 | "\n",
387 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
388 | " 35/100 10.7G 0.8724 0.4107 0.7891 206 640: 100% 39/39 [00:30<00:00, 1.30it/s]\n",
389 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.50it/s]\n",
390 | " all 38 905 0.867 0.768 0.78 0.539\n",
391 | "\n",
392 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
393 | " 36/100 10.6G 0.8448 0.4047 0.7861 118 640: 100% 39/39 [00:29<00:00, 1.30it/s]\n",
394 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.48it/s]\n",
395 | " all 38 905 0.842 0.748 0.8 0.538\n",
396 | "\n",
397 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
398 | " 37/100 10.7G 0.8604 0.413 0.7907 127 640: 100% 39/39 [00:30<00:00, 1.30it/s]\n",
399 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.37it/s]\n",
400 | " all 38 905 0.89 0.737 0.799 0.544\n",
401 | "\n",
402 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
403 | " 38/100 10.4G 0.8537 0.408 0.789 117 640: 100% 39/39 [00:30<00:00, 1.29it/s]\n",
404 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.48it/s]\n",
405 | " all 38 905 0.87 0.728 0.786 0.548\n",
406 | "\n",
407 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
408 | " 39/100 11.1G 0.8424 0.4031 0.787 101 640: 100% 39/39 [00:30<00:00, 1.29it/s]\n",
409 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.22it/s]\n",
410 | " all 38 905 0.921 0.758 0.827 0.577\n",
411 | "\n",
412 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
413 | " 40/100 10.7G 0.8266 0.3936 0.7847 82 640: 100% 39/39 [00:30<00:00, 1.28it/s]\n",
414 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.49it/s]\n",
415 | " all 38 905 0.92 0.766 0.814 0.57\n",
416 | "\n",
417 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
418 | " 41/100 10.7G 0.8407 0.3989 0.7869 188 640: 100% 39/39 [00:30<00:00, 1.27it/s]\n",
419 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.29it/s]\n",
420 | " all 38 905 0.915 0.771 0.823 0.568\n",
421 | "\n",
422 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
423 | " 42/100 10.4G 0.8431 0.4069 0.785 207 640: 100% 39/39 [00:30<00:00, 1.27it/s]\n",
424 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.55it/s]\n",
425 | " all 38 905 0.917 0.712 0.8 0.568\n",
426 | "\n",
427 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
428 | " 43/100 10.8G 0.8663 0.4132 0.7863 94 640: 100% 39/39 [00:30<00:00, 1.27it/s]\n",
429 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.52it/s]\n",
430 | " all 38 905 0.902 0.743 0.806 0.551\n",
431 | "\n",
432 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
433 | " 44/100 10.6G 0.8219 0.3898 0.7829 113 640: 100% 39/39 [00:30<00:00, 1.29it/s]\n",
434 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.67it/s]\n",
435 | " all 38 905 0.9 0.734 0.804 0.561\n",
436 | "\n",
437 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
438 | " 45/100 10.4G 0.8253 0.3947 0.7851 292 640: 100% 39/39 [00:30<00:00, 1.29it/s]\n",
439 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:01<00:00, 1.53it/s]\n",
440 | " all 38 905 0.897 0.737 0.782 0.549\n",
441 | "\n",
442 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
443 | " 46/100 10.4G 0.8441 0.4042 0.7865 202 640: 100% 39/39 [00:30<00:00, 1.29it/s]\n",
444 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.74it/s]\n",
445 | " all 38 905 0.895 0.743 0.8 0.562\n",
446 | "\n",
447 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
448 | " 47/100 10.7G 0.883 0.4083 0.7861 186 640: 100% 39/39 [00:30<00:00, 1.26it/s]\n",
449 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 3.27it/s]\n",
450 | " all 38 905 0.885 0.694 0.787 0.526\n",
451 | "\n",
452 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
453 | " 48/100 10.7G 0.8379 0.3935 0.7877 122 640: 100% 39/39 [00:30<00:00, 1.28it/s]\n",
454 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:01<00:00, 1.69it/s]\n",
455 | " all 38 905 0.917 0.753 0.819 0.558\n",
456 | "\n",
457 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
458 | " 49/100 10.7G 0.8275 0.3889 0.7853 157 640: 100% 39/39 [00:30<00:00, 1.30it/s]\n",
459 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.44it/s]\n",
460 | " all 38 905 0.906 0.738 0.806 0.555\n",
461 | "\n",
462 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
463 | " 50/100 10.8G 0.8338 0.3924 0.7846 133 640: 100% 39/39 [00:30<00:00, 1.27it/s]\n",
464 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.62it/s]\n",
465 | " all 38 905 0.875 0.733 0.788 0.564\n",
466 | "\n",
467 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
468 | " 51/100 10.7G 0.8114 0.3827 0.787 184 640: 100% 39/39 [00:30<00:00, 1.28it/s]\n",
469 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:01<00:00, 1.63it/s]\n",
470 | " all 38 905 0.939 0.746 0.814 0.577\n",
471 | "\n",
472 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
473 | " 52/100 10.9G 0.8092 0.3846 0.7861 96 640: 100% 39/39 [00:30<00:00, 1.29it/s]\n",
474 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.37it/s]\n",
475 | " all 38 905 0.936 0.719 0.807 0.536\n",
476 | "\n",
477 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
478 | " 53/100 10.4G 0.8238 0.3861 0.787 167 640: 100% 39/39 [00:29<00:00, 1.30it/s]\n",
479 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.51it/s]\n",
480 | " all 38 905 0.903 0.749 0.82 0.554\n",
481 | "\n",
482 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
483 | " 54/100 10.4G 0.8358 0.3891 0.7845 245 640: 100% 39/39 [00:30<00:00, 1.29it/s]\n",
484 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:01<00:00, 1.71it/s]\n",
485 | " all 38 905 0.939 0.764 0.815 0.575\n",
486 | "\n",
487 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
488 | " 55/100 10.7G 0.7808 0.3683 0.7839 79 640: 100% 39/39 [00:30<00:00, 1.29it/s]\n",
489 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 3.00it/s]\n",
490 | " all 38 905 0.903 0.754 0.815 0.565\n",
491 | "\n",
492 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
493 | " 56/100 10.6G 0.8 0.3703 0.7823 210 640: 100% 39/39 [00:29<00:00, 1.30it/s]\n",
494 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.65it/s]\n",
495 | " all 38 905 0.902 0.755 0.814 0.572\n",
496 | "\n",
497 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
498 | " 57/100 10.4G 0.771 0.3614 0.7841 210 640: 100% 39/39 [00:29<00:00, 1.31it/s]\n",
499 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:01<00:00, 1.69it/s]\n",
500 | " all 38 905 0.939 0.753 0.814 0.582\n",
501 | "\n",
502 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
503 | " 58/100 10.4G 0.7918 0.3675 0.7824 194 640: 100% 39/39 [00:30<00:00, 1.30it/s]\n",
504 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.50it/s]\n",
505 | " all 38 905 0.913 0.744 0.808 0.552\n",
506 | "\n",
507 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
508 | " 59/100 10.4G 0.7813 0.3616 0.7828 100 640: 100% 39/39 [00:29<00:00, 1.30it/s]\n",
509 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.49it/s]\n",
510 | " all 38 905 0.876 0.757 0.809 0.577\n",
511 | "\n",
512 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
513 | " 60/100 10.7G 0.7627 0.3557 0.7819 155 640: 100% 39/39 [00:30<00:00, 1.30it/s]\n",
514 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:01<00:00, 1.96it/s]\n",
515 | " all 38 905 0.888 0.752 0.796 0.555\n",
516 | "\n",
517 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
518 | " 61/100 10.4G 0.798 0.3683 0.7815 123 640: 100% 39/39 [00:30<00:00, 1.29it/s]\n",
519 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.54it/s]\n",
520 | " all 38 905 0.931 0.734 0.795 0.551\n",
521 | "\n",
522 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
523 | " 62/100 10.4G 0.7997 0.3691 0.7853 118 640: 100% 39/39 [00:30<00:00, 1.28it/s]\n",
524 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.25it/s]\n",
525 | " all 38 905 0.888 0.724 0.794 0.548\n",
526 | "\n",
527 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
528 | " 63/100 10.4G 0.7678 0.359 0.7826 202 640: 100% 39/39 [00:30<00:00, 1.30it/s]\n",
529 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:01<00:00, 2.00it/s]\n",
530 | " all 38 905 0.884 0.757 0.812 0.563\n",
531 | "\n",
532 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
533 | " 64/100 10.7G 0.7701 0.3609 0.7826 269 640: 100% 39/39 [00:29<00:00, 1.31it/s]\n",
534 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.75it/s]\n",
535 | " all 38 905 0.922 0.746 0.808 0.578\n",
536 | "\n",
537 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
538 | " 65/100 10.4G 0.7587 0.356 0.7822 158 640: 100% 39/39 [00:30<00:00, 1.30it/s]\n",
539 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.77it/s]\n",
540 | " all 38 905 0.943 0.738 0.822 0.575\n",
541 | "\n",
542 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
543 | " 66/100 10.5G 0.7547 0.3567 0.7811 151 640: 100% 39/39 [00:30<00:00, 1.29it/s]\n",
544 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:01<00:00, 1.76it/s]\n",
545 | " all 38 905 0.894 0.747 0.814 0.561\n",
546 | "\n",
547 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
548 | " 67/100 10.7G 0.7432 0.3529 0.7782 234 640: 100% 39/39 [00:30<00:00, 1.30it/s]\n",
549 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.43it/s]\n",
550 | " all 38 905 0.905 0.733 0.793 0.556\n",
551 | "\n",
552 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
553 | " 68/100 10.4G 0.7363 0.3495 0.7827 185 640: 100% 39/39 [00:29<00:00, 1.31it/s]\n",
554 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.78it/s]\n",
555 | " all 38 905 0.924 0.747 0.808 0.564\n",
556 | "\n",
557 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
558 | " 69/100 10.4G 0.7471 0.3522 0.7821 129 640: 100% 39/39 [00:30<00:00, 1.29it/s]\n",
559 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:01<00:00, 1.86it/s]\n",
560 | " all 38 905 0.957 0.756 0.814 0.574\n",
561 | "\n",
562 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
563 | " 70/100 10.4G 0.7696 0.3587 0.7795 202 640: 100% 39/39 [00:29<00:00, 1.30it/s]\n",
564 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.61it/s]\n",
565 | " all 38 905 0.917 0.722 0.798 0.569\n",
566 | "\n",
567 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
568 | " 71/100 10.4G 0.7302 0.3436 0.782 145 640: 100% 39/39 [00:30<00:00, 1.28it/s]\n",
569 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.67it/s]\n",
570 | " all 38 905 0.957 0.744 0.821 0.58\n",
571 | "\n",
572 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
573 | " 72/100 10.3G 0.7441 0.3477 0.7802 104 640: 100% 39/39 [00:29<00:00, 1.31it/s]\n",
574 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:01<00:00, 1.72it/s]\n",
575 | " all 38 905 0.959 0.753 0.82 0.573\n",
576 | "\n",
577 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
578 | " 73/100 10.6G 0.7635 0.3554 0.7815 197 640: 100% 39/39 [00:30<00:00, 1.30it/s]\n",
579 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.86it/s]\n",
580 | " all 38 905 0.905 0.714 0.795 0.55\n",
581 | "\n",
582 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
583 | " 74/100 10.4G 0.7466 0.3499 0.7787 151 640: 100% 39/39 [00:31<00:00, 1.26it/s]\n",
584 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.49it/s]\n",
585 | " all 38 905 0.913 0.727 0.794 0.576\n",
586 | "\n",
587 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
588 | " 75/100 10.7G 0.7165 0.3359 0.7785 123 640: 100% 39/39 [00:30<00:00, 1.29it/s]\n",
589 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:01<00:00, 1.62it/s]\n",
590 | " all 38 905 0.932 0.757 0.811 0.579\n",
591 | "\n",
592 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
593 | " 76/100 10.6G 0.7554 0.3494 0.7804 140 640: 100% 39/39 [00:29<00:00, 1.31it/s]\n",
594 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.58it/s]\n",
595 | " all 38 905 0.921 0.739 0.826 0.602\n",
596 | "\n",
597 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
598 | " 77/100 10.7G 0.7221 0.3368 0.7773 161 640: 100% 39/39 [00:30<00:00, 1.28it/s]\n",
599 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.64it/s]\n",
600 | " all 38 905 0.927 0.76 0.817 0.578\n",
601 | "\n",
602 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
603 | " 78/100 10.4G 0.7283 0.3409 0.7779 206 640: 100% 39/39 [00:30<00:00, 1.28it/s]\n",
604 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.58it/s]\n",
605 | " all 38 905 0.91 0.766 0.822 0.585\n",
606 | "\n",
607 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
608 | " 79/100 10.7G 0.7277 0.3365 0.7782 165 640: 100% 39/39 [00:30<00:00, 1.30it/s]\n",
609 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.75it/s]\n",
610 | " all 38 905 0.937 0.74 0.829 0.595\n",
611 | "\n",
612 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
613 | " 80/100 10.4G 0.716 0.3354 0.7798 89 640: 100% 39/39 [00:29<00:00, 1.30it/s]\n",
614 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.54it/s]\n",
615 | " all 38 905 0.935 0.763 0.826 0.593\n",
616 | "\n",
617 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
618 | " 81/100 10.4G 0.7175 0.3317 0.7773 115 640: 100% 39/39 [00:30<00:00, 1.28it/s]\n",
619 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.56it/s]\n",
620 | " all 38 905 0.937 0.76 0.825 0.596\n",
621 | "\n",
622 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
623 | " 82/100 10.7G 0.6918 0.3245 0.7777 162 640: 100% 39/39 [00:29<00:00, 1.32it/s]\n",
624 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.49it/s]\n",
625 | " all 38 905 0.944 0.754 0.827 0.598\n",
626 | "\n",
627 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
628 | " 83/100 10.9G 0.6849 0.3205 0.777 141 640: 100% 39/39 [00:30<00:00, 1.29it/s]\n",
629 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.86it/s]\n",
630 | " all 38 905 0.957 0.745 0.824 0.593\n",
631 | "\n",
632 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
633 | " 84/100 10.8G 0.697 0.323 0.7785 179 640: 100% 39/39 [00:30<00:00, 1.27it/s]\n",
634 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.46it/s]\n",
635 | " all 38 905 0.964 0.759 0.828 0.588\n",
636 | "\n",
637 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
638 | " 85/100 10.6G 0.7172 0.329 0.7774 216 640: 100% 39/39 [00:29<00:00, 1.31it/s]\n",
639 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.38it/s]\n",
640 | " all 38 905 0.958 0.741 0.812 0.574\n",
641 | "\n",
642 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
643 | " 86/100 10.5G 0.7137 0.328 0.7771 147 640: 100% 39/39 [00:29<00:00, 1.30it/s]\n",
644 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.61it/s]\n",
645 | " all 38 905 0.961 0.751 0.835 0.594\n",
646 | "\n",
647 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
648 | " 87/100 10.4G 0.6739 0.3182 0.7764 122 640: 100% 39/39 [00:30<00:00, 1.28it/s]\n",
649 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.42it/s]\n",
650 | " all 38 905 0.952 0.756 0.83 0.588\n",
651 | "\n",
652 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
653 | " 88/100 10.8G 0.693 0.322 0.7768 288 640: 100% 39/39 [00:29<00:00, 1.30it/s]\n",
654 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:01<00:00, 1.81it/s]\n",
655 | " all 38 905 0.95 0.726 0.828 0.594\n",
656 | "\n",
657 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
658 | " 89/100 10.4G 0.7022 0.3246 0.7769 181 640: 100% 39/39 [00:30<00:00, 1.28it/s]\n",
659 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.61it/s]\n",
660 | " all 38 905 0.949 0.746 0.833 0.595\n",
661 | "\n",
662 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
663 | " 90/100 10.4G 0.6736 0.3127 0.7774 79 640: 100% 39/39 [00:29<00:00, 1.31it/s]\n",
664 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.55it/s]\n",
665 | " all 38 905 0.912 0.756 0.825 0.591\n",
666 | "Closing dataloader mosaic\n",
667 | "\u001b[34m\u001b[1malbumentations: \u001b[0mBlur(p=0.01, blur_limit=(3, 7)), MedianBlur(p=0.01, blur_limit=(3, 7)), ToGray(p=0.01), CLAHE(p=0.01, clip_limit=(1, 4.0), tile_grid_size=(8, 8))\n",
668 | "/usr/lib/python3.10/multiprocessing/popen_fork.py:66: RuntimeWarning: os.fork() was called. os.fork() is incompatible with multithreaded code, and JAX is multithreaded, so this will likely lead to a deadlock.\n",
669 | " self.pid = os.fork()\n",
670 | "\n",
671 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
672 | " 91/100 10.9G 0.6377 0.3075 0.7749 92 640: 100% 39/39 [00:36<00:00, 1.07it/s]\n",
673 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.33it/s]\n",
674 | " all 38 905 0.934 0.751 0.823 0.587\n",
675 | "\n",
676 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
677 | " 92/100 11G 0.627 0.3039 0.7768 92 640: 100% 39/39 [00:28<00:00, 1.35it/s]\n",
678 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.45it/s]\n",
679 | " all 38 905 0.941 0.747 0.822 0.575\n",
680 | "\n",
681 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
682 | " 93/100 11.1G 0.6178 0.3001 0.7761 94 640: 100% 39/39 [00:28<00:00, 1.37it/s]\n",
683 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.50it/s]\n",
684 | " all 38 905 0.957 0.735 0.825 0.583\n",
685 | "\n",
686 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
687 | " 94/100 11.1G 0.6152 0.2984 0.7757 89 640: 100% 39/39 [00:28<00:00, 1.36it/s]\n",
688 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.52it/s]\n",
689 | " all 38 905 0.956 0.752 0.827 0.578\n",
690 | "\n",
691 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
692 | " 95/100 11.1G 0.618 0.2995 0.7742 88 640: 100% 39/39 [00:28<00:00, 1.35it/s]\n",
693 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:01<00:00, 2.00it/s]\n",
694 | " all 38 905 0.961 0.748 0.821 0.585\n",
695 | "\n",
696 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
697 | " 96/100 11.2G 0.6089 0.2968 0.775 95 640: 100% 39/39 [00:28<00:00, 1.36it/s]\n",
698 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.69it/s]\n",
699 | " all 38 905 0.944 0.733 0.818 0.575\n",
700 | "\n",
701 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
702 | " 97/100 11.1G 0.5971 0.2918 0.7733 92 640: 100% 39/39 [00:28<00:00, 1.35it/s]\n",
703 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.59it/s]\n",
704 | " all 38 905 0.961 0.753 0.827 0.593\n",
705 | "\n",
706 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
707 | " 98/100 11.2G 0.6029 0.2922 0.7725 94 640: 100% 39/39 [00:28<00:00, 1.36it/s]\n",
708 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.42it/s]\n",
709 | " all 38 905 0.943 0.745 0.827 0.583\n",
710 | "\n",
711 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
712 | " 99/100 11.1G 0.6011 0.291 0.7738 93 640: 100% 39/39 [00:28<00:00, 1.35it/s]\n",
713 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.67it/s]\n",
714 | " all 38 905 0.955 0.752 0.823 0.582\n",
715 | "\n",
716 | " Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size\n",
717 | " 100/100 11G 0.5881 0.2825 0.7747 92 640: 100% 39/39 [00:28<00:00, 1.36it/s]\n",
718 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 3.27it/s]\n",
719 | " all 38 905 0.955 0.755 0.827 0.589\n",
720 | "\n",
721 | "100 epochs completed in 0.965 hours.\n",
722 | "Optimizer stripped from runs/detect/train/weights/last.pt, 106.8MB\n",
723 | "Optimizer stripped from runs/detect/train/weights/best.pt, 106.8MB\n",
724 | "\n",
725 | "Validating runs/detect/train/weights/best.pt...\n",
726 | "Ultralytics YOLOv8.2.2 🚀 Python-3.10.12 torch-2.2.1+cu121 CUDA:0 (Tesla T4, 15102MiB)\n",
727 | "YOLOv5l summary (fused): 303 layers, 53134492 parameters, 0 gradients, 134.7 GFLOPs\n",
728 | " Class Images Instances Box(P R mAP50 mAP50-95): 100% 2/2 [00:00<00:00, 2.43it/s]\n",
729 | " all 38 905 0.921 0.739 0.827 0.6\n",
730 | " ball 38 35 1 0.228 0.448 0.209\n",
731 | " goalkeeper 38 27 0.868 0.852 0.936 0.749\n",
732 | " player 38 754 0.959 0.956 0.984 0.791\n",
733 | " referee 38 89 0.858 0.921 0.939 0.653\n",
734 | "Speed: 0.1ms preprocess, 10.1ms inference, 0.0ms loss, 1.4ms postprocess per image\n",
735 | "Results saved to \u001b[1mruns/detect/train\u001b[0m\n",
736 | "💡 Learn more at https://docs.ultralytics.com/modes/train\n"
737 | ]
738 | }
739 | ],
740 | "source": [
741 | "!yolo task=detect mode=train model=yolov5l.pt data=\"{dataset.location}/data.yaml\" epochs=100 imgsz=640"
742 | ]
743 | },
744 | {
745 | "cell_type": "code",
746 | "execution_count": 6,
747 | "metadata": {
748 | "id": "-1Mi_LTWdslG"
749 | },
750 | "outputs": [],
751 | "source": []
752 | }
753 | ],
754 | "metadata": {
755 | "accelerator": "GPU",
756 | "colab": {
757 | "gpuType": "T4",
758 | "provenance": []
759 | },
760 | "kernelspec": {
761 | "display_name": "Python 3",
762 | "name": "python3"
763 | },
764 | "language_info": {
765 | "codemirror_mode": {
766 | "name": "ipython",
767 | "version": 3
768 | },
769 | "file_extension": ".py",
770 | "mimetype": "text/x-python",
771 | "name": "python",
772 | "nbconvert_exporter": "python",
773 | "pygments_lexer": "ipython3",
774 | "version": "3.11.9"
775 | }
776 | },
777 | "nbformat": 4,
778 | "nbformat_minor": 0
779 | }
780 |
--------------------------------------------------------------------------------
/utils/__init__.py:
--------------------------------------------------------------------------------
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:
--------------------------------------------------------------------------------
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:
--------------------------------------------------------------------------------
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()
19 |
--------------------------------------------------------------------------------
/view_transformer/__init__.py:
--------------------------------------------------------------------------------
1 | from .view_transformer import ViewTransformer
--------------------------------------------------------------------------------
/view_transformer/view_transformer.py:
--------------------------------------------------------------------------------
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
--------------------------------------------------------------------------------
/yolo_inference.py:
--------------------------------------------------------------------------------
1 | from ultralytics import YOLO
2 |
3 | model = YOLO('./models/best.pt')
4 |
5 | results = model.predict('./input_videos/match.mp4',save=True)
6 | print(results[0])
7 |
8 | for box in results[0].boxes:
9 | print(box)
--------------------------------------------------------------------------------