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