├── Create Zones.py ├── Vehicle Counting in Lanes.py ├── classes.txt ├── requirements.txt └── sort.py /Create Zones.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import numpy as np 3 | 4 | # Global variables 5 | polygon_points = [] 6 | 7 | # Read your video file 8 | video_path = r'C:\Users\Admin\Desktop\computervision videos/carsvid.mp4' 9 | cap = cv2.VideoCapture(video_path) 10 | 11 | 12 | # Callback function for mouse events 13 | def mouse_callback(event, x, y, flags, param): 14 | global polygon_points 15 | if event == cv2.EVENT_LBUTTONDOWN: 16 | polygon_points.append((x, y)) 17 | print(f"Point Added: (X: {x}, Y: {y})") 18 | 19 | 20 | 21 | while True: 22 | # Capture the first frame 23 | ret, frame = cap.read() 24 | frame = cv2.resize(frame, (1920, 1080)) 25 | # Create a window and set the mouse callback 26 | cv2.namedWindow('Frame') 27 | cv2.setMouseCallback('Frame', mouse_callback) 28 | 29 | # Draw the polygon on the frame 30 | if len(polygon_points) > 1: 31 | cv2.polylines(frame, [np.array(polygon_points)], isClosed=False, color=(0, 255, 0), thickness=2) 32 | 33 | cv2.imshow('Frame', frame) 34 | 35 | # Press 'Esc' to exit 36 | key = cv2.waitKey(0) 37 | if key == 27: 38 | break 39 | 40 | cv2.destroyAllWindows() 41 | cap.release() 42 | 43 | # Print the polygon points 44 | print("Polygon Points:") 45 | for point in polygon_points: 46 | print(f"X: {point[0]}, Y: {point[1]}") 47 | -------------------------------------------------------------------------------- /Vehicle Counting in Lanes.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import cvzone 3 | import math 4 | import numpy as np 5 | from ultralytics import YOLO 6 | from sort import * 7 | 8 | video_path = 'Place the path to your video file here' 9 | cap = cv2.VideoCapture(video_path) 10 | model = YOLO('yolov8n.pt') 11 | 12 | classnames = [] 13 | with open('classes.txt', 'r') as f: 14 | classnames = f.read().splitlines() 15 | 16 | road_zoneA = np.array([[308, 789], [711, 807], [694, 492], [415, 492], [309, 790]], np.int32) 17 | road_zoneB = np.array([[727, 797], [1123, 812], [1001, 516], [741, 525], [730, 795]], np.int32) 18 | road_zoneC = np.array([[1116, 701], [1533, 581], [1236, 367], [1009, 442], [1122, 698]], np.int32) 19 | 20 | zoneA_Line = np.array([road_zoneA[0],road_zoneA[1]]).reshape(-1) 21 | zoneB_Line = np.array([road_zoneB[0],road_zoneB[1]]).reshape(-1) 22 | zoneC_Line = np.array([road_zoneC[0],road_zoneC[1]]).reshape(-1) 23 | 24 | tracker = Sort() 25 | zoneAcounter = [] 26 | zoneBcounter = [] 27 | zoneCcounter = [] 28 | 29 | while True: 30 | ret, frame = cap.read() 31 | frame = cv2.resize(frame, (1920,1080)) 32 | results = model(frame) 33 | current_detections = np.empty([0,5]) 34 | 35 | for info in results: 36 | parameters = info.boxes 37 | for box in parameters: 38 | x1, y1, x2, y2 = box.xyxy[0] 39 | x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2) 40 | w, h = x2 - x1, y2 - y1 41 | confidence = box.conf[0] 42 | class_detect = box.cls[0] 43 | class_detect = int(class_detect) 44 | class_detect = classnames[class_detect] 45 | conf = math.ceil(confidence * 100) 46 | cvzone.putTextRect(frame, f'{class_detect}', [x1 + 8, y1 - 12], thickness=2, scale=1) 47 | cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) 48 | 49 | if class_detect == 'car' or class_detect == 'truck' or class_detect == 'bus'\ 50 | and conf > 60: 51 | detections = np.array([x1,y1,x2,y2,conf]) 52 | current_detections = np.vstack([current_detections,detections]) 53 | 54 | cv2.polylines(frame,[road_zoneA], isClosed=False, color=(0, 0, 255), thickness=8) 55 | cv2.polylines(frame, [road_zoneB], isClosed=False, color=(0, 255, 255), thickness=8) 56 | cv2.polylines(frame, [road_zoneC], isClosed=False, color=(255,0, 0), thickness=8) 57 | 58 | track_results = tracker.update(current_detections) 59 | for result in track_results: 60 | x1,y1,x2,y2,id = result 61 | x1,y1,x2,y2,id = int(x1),int(y1),int(x2),int(y2),int(id) 62 | w, h = x2 - x1, y2 - y1 63 | cx, cy = x1 + w // 2, y1 + h // 2 -40 64 | 65 | 66 | if zoneA_Line[0] < cx < zoneA_Line[2] and zoneA_Line[1] - 20 < cy < zoneA_Line[1] + 20: 67 | if zoneAcounter.count(id) == 0: 68 | zoneAcounter.append(id) 69 | 70 | if zoneB_Line[0] < cx < zoneB_Line[2] and zoneB_Line[1] - 20 < cy < zoneB_Line[1] + 20: 71 | if zoneBcounter.count(id) == 0: 72 | zoneBcounter.append(id) 73 | 74 | if zoneC_Line[0] < cx < zoneC_Line[2] and zoneC_Line[1] - 20 < cy < zoneC_Line[1] + 20: 75 | if zoneCcounter.count(id) == 0: 76 | zoneCcounter.append(id) 77 | 78 | cv2.circle(frame,(970,90),15,(0,0,255),-1) 79 | cv2.circle(frame,(970,130),15,(0,255,255),-1) 80 | cv2.circle(frame,(970,170),15,(255,0,0),-1) 81 | cvzone.putTextRect(frame, f'LANE A Vehicles ={len(zoneAcounter)}', [1000, 99], thickness=4, scale=2.3, border=2) 82 | cvzone.putTextRect(frame, f'LANE B Vehicles ={len(zoneBcounter)}', [1000, 140], thickness=4, scale=2.3, border=2) 83 | cvzone.putTextRect(frame, f'LANE C Vehicles ={len(zoneCcounter)}', [1000, 180], thickness=4, scale=2.3, border=2) 84 | 85 | cv2.imshow('frame', frame) 86 | cv2.waitKey(1) 87 | 88 | cap.release() 89 | cv2.destroyAllWindows() 90 | -------------------------------------------------------------------------------- /classes.txt: -------------------------------------------------------------------------------- 1 | person 2 | bicycle 3 | car 4 | motorbike 5 | aeroplane 6 | bus 7 | train 8 | truck 9 | boat 10 | traffic light 11 | fire hydrant 12 | stop sign 13 | parking meter 14 | bench 15 | bird 16 | cat 17 | dog 18 | horse 19 | sheep 20 | cow 21 | elephant 22 | bear 23 | zebra 24 | giraffe 25 | backpack 26 | umbrella 27 | handbag 28 | tie 29 | suitcase 30 | frisbee 31 | skis 32 | snowboard 33 | sports ball 34 | kite 35 | baseball bat 36 | baseball glove 37 | skateboard 38 | surfboard 39 | tennis racket 40 | bottle 41 | wine glass 42 | cup 43 | fork 44 | knife 45 | spoon 46 | bowl 47 | banana 48 | apple 49 | sandwich 50 | orange 51 | broccoli 52 | carrot 53 | hot dog 54 | pizza 55 | donut 56 | cake 57 | chair 58 | sofa 59 | pottedplant 60 | bed 61 | diningtable 62 | toilet 63 | tvmonitor 64 | laptop 65 | mouse 66 | remote 67 | keyboard 68 | cell phone 69 | microwave 70 | oven 71 | toaster 72 | sink 73 | refrigerator 74 | book 75 | clock 76 | vase 77 | scissors 78 | teddy bear 79 | hair drier 80 | toothbrush -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ultralytics 2 | cvzone 3 | opencv-python 4 | numpy 5 | -------------------------------------------------------------------------------- /sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | SORT: A Simple, Online and Realtime Tracker 3 | Copyright (C) 2016-2020 Alex Bewley alex@bewley.ai 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | """ 18 | from __future__ import print_function 19 | 20 | import os 21 | import numpy as np 22 | import matplotlib 23 | matplotlib.use('TkAgg') 24 | import matplotlib.pyplot as plt 25 | import matplotlib.patches as patches 26 | from skimage import io 27 | 28 | import glob 29 | import time 30 | import argparse 31 | from filterpy.kalman import KalmanFilter 32 | 33 | np.random.seed(0) 34 | 35 | 36 | def linear_assignment(cost_matrix): 37 | try: 38 | import lap 39 | _, x, y = lap.lapjv(cost_matrix, extend_cost=True) 40 | return np.array([[y[i],i] for i in x if i >= 0]) # 41 | except ImportError: 42 | from scipy.optimize import linear_sum_assignment 43 | x, y = linear_sum_assignment(cost_matrix) 44 | return np.array(list(zip(x, y))) 45 | 46 | 47 | def iou_batch(bb_test, bb_gt): 48 | """ 49 | From SORT: Computes IOU between two bboxes in the form [x1,y1,x2,y2] 50 | """ 51 | bb_gt = np.expand_dims(bb_gt, 0) 52 | bb_test = np.expand_dims(bb_test, 1) 53 | 54 | xx1 = np.maximum(bb_test[..., 0], bb_gt[..., 0]) 55 | yy1 = np.maximum(bb_test[..., 1], bb_gt[..., 1]) 56 | xx2 = np.minimum(bb_test[..., 2], bb_gt[..., 2]) 57 | yy2 = np.minimum(bb_test[..., 3], bb_gt[..., 3]) 58 | w = np.maximum(0., xx2 - xx1) 59 | h = np.maximum(0., yy2 - yy1) 60 | wh = w * h 61 | o = wh / ((bb_test[..., 2] - bb_test[..., 0]) * (bb_test[..., 3] - bb_test[..., 1]) 62 | + (bb_gt[..., 2] - bb_gt[..., 0]) * (bb_gt[..., 3] - bb_gt[..., 1]) - wh) 63 | return(o) 64 | 65 | 66 | def convert_bbox_to_z(bbox): 67 | """ 68 | Takes a bounding box in the form [x1,y1,x2,y2] and returns z in the form 69 | [x,y,s,r] where x,y is the centre of the box and s is the scale/area and r is 70 | the aspect ratio 71 | """ 72 | w = bbox[2] - bbox[0] 73 | h = bbox[3] - bbox[1] 74 | x = bbox[0] + w/2. 75 | y = bbox[1] + h/2. 76 | s = w * h #scale is just area 77 | r = w / float(h) 78 | return np.array([x, y, s, r]).reshape((4, 1)) 79 | 80 | 81 | def convert_x_to_bbox(x,score=None): 82 | """ 83 | Takes a bounding box in the centre form [x,y,s,r] and returns it in the form 84 | [x1,y1,x2,y2] where x1,y1 is the top left and x2,y2 is the bottom right 85 | """ 86 | w = np.sqrt(x[2] * x[3]) 87 | h = x[2] / w 88 | if(score==None): 89 | return np.array([x[0]-w/2.,x[1]-h/2.,x[0]+w/2.,x[1]+h/2.]).reshape((1,4)) 90 | else: 91 | return np.array([x[0]-w/2.,x[1]-h/2.,x[0]+w/2.,x[1]+h/2.,score]).reshape((1,5)) 92 | 93 | 94 | class KalmanBoxTracker(object): 95 | """ 96 | This class represents the internal state of individual tracked objects observed as bbox. 97 | """ 98 | count = 0 99 | def __init__(self,bbox): 100 | """ 101 | Initialises a tracker using initial bounding box. 102 | """ 103 | #define constant velocity model 104 | self.kf = KalmanFilter(dim_x=7, dim_z=4) 105 | self.kf.F = np.array([[1,0,0,0,1,0,0],[0,1,0,0,0,1,0],[0,0,1,0,0,0,1],[0,0,0,1,0,0,0], [0,0,0,0,1,0,0],[0,0,0,0,0,1,0],[0,0,0,0,0,0,1]]) 106 | self.kf.H = np.array([[1,0,0,0,0,0,0],[0,1,0,0,0,0,0],[0,0,1,0,0,0,0],[0,0,0,1,0,0,0]]) 107 | 108 | self.kf.R[2:,2:] *= 10. 109 | self.kf.P[4:,4:] *= 1000. #give high uncertainty to the unobservable initial velocities 110 | self.kf.P *= 10. 111 | self.kf.Q[-1,-1] *= 0.01 112 | self.kf.Q[4:,4:] *= 0.01 113 | 114 | self.kf.x[:4] = convert_bbox_to_z(bbox) 115 | self.time_since_update = 0 116 | self.id = KalmanBoxTracker.count 117 | KalmanBoxTracker.count += 1 118 | self.history = [] 119 | self.hits = 0 120 | self.hit_streak = 0 121 | self.age = 0 122 | 123 | def update(self,bbox): 124 | """ 125 | Updates the state vector with observed bbox. 126 | """ 127 | self.time_since_update = 0 128 | self.history = [] 129 | self.hits += 1 130 | self.hit_streak += 1 131 | self.kf.update(convert_bbox_to_z(bbox)) 132 | 133 | def predict(self): 134 | """ 135 | Advances the state vector and returns the predicted bounding box estimate. 136 | """ 137 | if((self.kf.x[6]+self.kf.x[2])<=0): 138 | self.kf.x[6] *= 0.0 139 | self.kf.predict() 140 | self.age += 1 141 | if(self.time_since_update>0): 142 | self.hit_streak = 0 143 | self.time_since_update += 1 144 | self.history.append(convert_x_to_bbox(self.kf.x)) 145 | return self.history[-1] 146 | 147 | def get_state(self): 148 | """ 149 | Returns the current bounding box estimate. 150 | """ 151 | return convert_x_to_bbox(self.kf.x) 152 | 153 | 154 | def associate_detections_to_trackers(detections,trackers,iou_threshold = 0.3): 155 | """ 156 | Assigns detections to tracked object (both represented as bounding boxes) 157 | 158 | Returns 3 lists of matches, unmatched_detections and unmatched_trackers 159 | """ 160 | if(len(trackers)==0): 161 | return np.empty((0,2),dtype=int), np.arange(len(detections)), np.empty((0,5),dtype=int) 162 | 163 | iou_matrix = iou_batch(detections, trackers) 164 | 165 | if min(iou_matrix.shape) > 0: 166 | a = (iou_matrix > iou_threshold).astype(np.int32) 167 | if a.sum(1).max() == 1 and a.sum(0).max() == 1: 168 | matched_indices = np.stack(np.where(a), axis=1) 169 | else: 170 | matched_indices = linear_assignment(-iou_matrix) 171 | else: 172 | matched_indices = np.empty(shape=(0,2)) 173 | 174 | unmatched_detections = [] 175 | for d, det in enumerate(detections): 176 | if(d not in matched_indices[:,0]): 177 | unmatched_detections.append(d) 178 | unmatched_trackers = [] 179 | for t, trk in enumerate(trackers): 180 | if(t not in matched_indices[:,1]): 181 | unmatched_trackers.append(t) 182 | 183 | #filter out matched with low IOU 184 | matches = [] 185 | for m in matched_indices: 186 | if(iou_matrix[m[0], m[1]]= self.min_hits or self.frame_count <= self.min_hits): 246 | ret.append(np.concatenate((d,[trk.id+1])).reshape(1,-1)) # +1 as MOT benchmark requires positive 247 | i -= 1 248 | # remove dead tracklet 249 | if(trk.time_since_update > self.max_age): 250 | self.trackers.pop(i) 251 | if(len(ret)>0): 252 | return np.concatenate(ret) 253 | return np.empty((0,5)) 254 | 255 | def parse_args(): 256 | """Parse input arguments.""" 257 | parser = argparse.ArgumentParser(description='SORT demo') 258 | parser.add_argument('--display', dest='display', help='Display online tracker output (slow) [False]',action='store_true') 259 | parser.add_argument("--seq_path", help="Path to detections.", type=str, default='data') 260 | parser.add_argument("--phase", help="Subdirectory in seq_path.", type=str, default='train') 261 | parser.add_argument("--max_age", 262 | help="Maximum number of frames to keep alive a track without associated detections.", 263 | type=int, default=1) 264 | parser.add_argument("--min_hits", 265 | help="Minimum number of associated detections before track is initialised.", 266 | type=int, default=3) 267 | parser.add_argument("--iou_threshold", help="Minimum IOU for match.", type=float, default=0.3) 268 | args = parser.parse_args() 269 | return args 270 | 271 | if __name__ == '__main__': 272 | # all train 273 | args = parse_args() 274 | display = args.display 275 | phase = args.phase 276 | total_time = 0.0 277 | total_frames = 0 278 | colours = np.random.rand(32, 3) #used only for display 279 | if(display): 280 | if not os.path.exists('mot_benchmark'): 281 | print('\n\tERROR: mot_benchmark link not found!\n\n Create a symbolic link to the MOT benchmark\n (https://motchallenge.net/data/2D_MOT_2015/#download). E.g.:\n\n $ ln -s /path/to/MOT2015_challenge/2DMOT2015 mot_benchmark\n\n') 282 | exit() 283 | plt.ion() 284 | fig = plt.figure() 285 | ax1 = fig.add_subplot(111, aspect='equal') 286 | 287 | if not os.path.exists('output'): 288 | os.makedirs('output') 289 | pattern = os.path.join(args.seq_path, phase, '*', 'det', 'det.txt') 290 | for seq_dets_fn in glob.glob(pattern): 291 | mot_tracker = Sort(max_age=args.max_age, 292 | min_hits=args.min_hits, 293 | iou_threshold=args.iou_threshold) #create instance of the SORT tracker 294 | seq_dets = np.loadtxt(seq_dets_fn, delimiter=',') 295 | seq = seq_dets_fn[pattern.find('*'):].split(os.path.sep)[0] 296 | 297 | with open(os.path.join('output', '%s.txt'%(seq)),'w') as out_file: 298 | print("Processing %s."%(seq)) 299 | for frame in range(int(seq_dets[:,0].max())): 300 | frame += 1 #detection and frame numbers begin at 1 301 | dets = seq_dets[seq_dets[:, 0]==frame, 2:7] 302 | dets[:, 2:4] += dets[:, 0:2] #convert to [x1,y1,w,h] to [x1,y1,x2,y2] 303 | total_frames += 1 304 | 305 | if(display): 306 | fn = os.path.join('mot_benchmark', phase, seq, 'img1', '%06d.jpg'%(frame)) 307 | im =io.imread(fn) 308 | ax1.imshow(im) 309 | plt.title(seq + ' Tracked Targets') 310 | 311 | start_time = time.time() 312 | trackers = mot_tracker.update(dets) 313 | cycle_time = time.time() - start_time 314 | total_time += cycle_time 315 | 316 | for d in trackers: 317 | print('%d,%d,%.2f,%.2f,%.2f,%.2f,1,-1,-1,-1'%(frame,d[4],d[0],d[1],d[2]-d[0],d[3]-d[1]),file=out_file) 318 | if(display): 319 | d = d.astype(np.int32) 320 | ax1.add_patch(patches.Rectangle((d[0],d[1]),d[2]-d[0],d[3]-d[1],fill=False,lw=3,ec=colours[d[4]%32,:])) 321 | 322 | if(display): 323 | fig.canvas.flush_events() 324 | plt.draw() 325 | ax1.cla() 326 | 327 | print("Total Tracking took: %.3f seconds for %d frames or %.1f FPS" % (total_time, total_frames, total_frames / total_time)) 328 | 329 | if(display): 330 | print("Note: to get real runtime results run without the option: --display") 331 | --------------------------------------------------------------------------------