├── .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 |

demo

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": "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", 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) --------------------------------------------------------------------------------