Yolov8 provides two object tracking algorithms. Therefore, there is no need to separately implement Strong SORT, DeepSORT, and ByteTrack.
The tracking models currently supported by YOLOv8 are BoT-SORT and ByteTrack.
VOLOv8 Built-In Tracking models
The following figure is from the BOT-SORT Github site. It can be seen that the performance of BoT_SORT is superior to ByteTrack and StrongSORT as a whole.
However, there is no mention of processing speed (FPS) in this graph.
- MOTA : Multiple Object Tracking Accuracy
- IDF1 : Identification F1 score
VOLOv8 Video Tracking
Now let's implement the tracking function provided by YOLOV8 with Python.
Before starting, upgrade yolov8 to the fexin version.
(base) spypiggy@spypiggy-NX:~/sconda activate yolov8 (yolov8) spypiggy@spypiggy-NX:~/src/yolov8$ pip install ultralytics --upgrade
You can test the tracking provided by yolov8 with the following simple Python code.
import argparse from ultralytics import YOLO def main(): parser = argparse.ArgumentParser() parser.add_argument('--track', type=str, default="botsort.yaml" ) #botsort.yaml or bytetrack.yaml args = parser.parse_args() model = YOLO("yolov8m.pt") model.to('cuda') for result in model.track(source="./sample.mp4", show=True, stream=True, agnostic_nms=True, tracker=args.track): pass if __name__ == "__main__": main()
<sample_track.py>
Before you rune the program, copy the sample.mp4 file to the proper directory.
If you run the proggram, you might see this screen shot. The first id value in the text at the top of the box is the unique number of the object tracked by tracking. You can change the tracking model using the --track parameter. If you provide bytetrack.yaml as a parameter, you will find that a slightly faster FPS comes out. However, the tracking accuracy is lower than that of botsort.
Detailed usage of the track function is at https://docs.ultralytics.com/modes/track/#available-trackers.
VOLOv8 Video Tracking with OpenCV
As always I like to use OpenCV. And if you're a developer, like me, you'll want to do additional work with the extracted results.
In the example code above, the result of the model.track function is stored in the result variable. Presumably, recognition results and tracking id values are also stored in this variable.
I prefer using VSCode's debugging features rather than print statements to check the values of these variables. This is because it has the advantage of being able to examine the values of desired variables in more detail.
import cv2 import argparse from ultralytics import YOLO import numpy as np def main(): parser = argparse.ArgumentParser() parser.add_argument('--track', type=str, default="botsort.yaml" ) #botsort.yaml or bytetrack.yaml args = parser.parse_args() model = YOLO("yolov8m.pt") model.to('cuda') colors = [(255,255 , 0), (0,255,0), (0,0,255)] font = cv2.FONT_HERSHEY_SIMPLEX label_map = model.names for result in model.track(source="./sample.mp4", show=False, stream=True, agnostic_nms=True, tracker=args.track): frame = result.orig_img for box, conf, cls in zip(result.boxes.data, result.boxes.conf, result.boxes.cls): index = 0 p1 = (int(box[0].item()), int(box[1].item())) p2 = (int(box[2].item()), int(box[3].item())) id = int(box[4].item()) cv2.rectangle(frame, p1, p2, colors[int(cls.item() % len(colors))], 2) text = "#" + str(id) + "-"+ label_map[int(cls.item())] + " %4.2f"%(conf.item()) cv2.putText(frame, text, (p1[0], p1[1] - 10), font, fontScale = 1, color = colors[int(cls.item() % len(colors))], thickness = 2) index += 1 cv2.imshow("yolov8", frame) if (cv2.waitKey(1) == 27): break if __name__ == "__main__": main()
<sample_track2.py>
If you run the proggram, you might see this screen shot.
You can see that it outputs the same result as the previous example.
Vehicles Counting
The number of vehicles can also be checked through the class ID and the number of tracking ids. However, in many cases, it is necessary to separately calculate the number of vehicles going up and down vehicles based on the road. Also, in places such as intersections, the number of vehicles going up and down in several places must be counted. It is only necessary to determine whether the center coordinates of the vehicle cross the line with respect to the reference line.
For reference, I tested several methods, including Roboflow's LineZone and LineZoneAnnotator.
Among several tests, the one that showed the most accurate results was that of yas-sim (Yasunori Shimura). His github is https://github.com/yas-sim/object-tracking-line-crossing-area-intrusion/tree/master.
The source code from now on will inform you in advance that many parts of his code are quoted. Thank you Shimura.
''' https://github.com/yas-sim/object-tracking-line-crossing-area-intrusion/tree/master pip install opencv-python numpy scipy munkres ''' import cv2 from ultralytics import YOLO import numpy as np from line_boundary_check import * import argparse class boundaryLine: def __init__(self, line=(0,0,0,0)): self.p0 = (line[0], line[1]) self.p1 = (line[2], line[3]) self.color = (0,255,255) self.lineThinkness = 2 self.textColor = (0,255,255) self.textSize = 2 self.textThinkness = 2 self.count1 = [0,0] #person, vehicles self.count2 = [0,0] # Draw single boundary line def drawBoundaryLine(img, line): x1, y1 = line.p0 x2, y2 = line.p1 cv2.line(img, (x1, y1), (x2, y2), line.color, line.lineThinkness) cv2.putText(img, "person:" + str(line.count1[0]), (x1 + 10, y1 + 30), cv2.FONT_HERSHEY_PLAIN, line.textSize, line.textColor, line.textThinkness) cv2.putText(img, "vehicles:" + str(line.count1[1]), (x1 + 10, y1 + 50), cv2.FONT_HERSHEY_PLAIN, line.textSize, line.textColor, line.textThinkness) cv2.putText(img, "person:" + str(line.count2[0]), (x2 - 100, y2 + 30), cv2.FONT_HERSHEY_PLAIN, line.textSize, line.textColor, line.textThinkness) cv2.putText(img, "vehicles:" + str(line.count2[1]), (x2 - 100, y2 + 50), cv2.FONT_HERSHEY_PLAIN, line.textSize, line.textColor, line.textThinkness) cv2.drawMarker(img, (x1, y1),line.color, cv2.MARKER_TRIANGLE_UP, 16, 4) cv2.drawMarker(img, (x2, y2),line.color, cv2.MARKER_TILTED_CROSS, 16, 4) # Draw multiple boundary lines def drawBoundaryLines(img, boundaryLines): for line in boundaryLines: drawBoundaryLine(img, line) # in: boundary_line = boundaryLine class object # trajectory = (x1, y1, x2, y2) def checkLineCross(boundary_line, cls, trajectory_line): traj_p0 = trajectory_line[0] # Trajectory of an object traj_p1 = trajectory_line[1] bLine_p0 = (boundary_line.p0[0], boundary_line.p0[1]) # Boundary line bLine_p1 = (boundary_line.p1[0], boundary_line.p1[1]) intersect = checkIntersect(traj_p0, traj_p1, bLine_p0, bLine_p1) # Check if intersect or not if intersect == True: angle = calcVectorAngle(traj_p0, traj_p1, bLine_p0, bLine_p1) # Calculate angle between trajectory and boundary line if angle<180: if(cls == 1): boundary_line.count1[0] += 1 else: boundary_line.count1[1] += 1 else: if(cls == 1): boundary_line.count2[0] += 1 else: boundary_line.count2[1] += 1 def update_vehicles(data): global active_vehicles for k, v in active_vehicles.items(): active_vehicles[k][0] = False for box in data: cx = int((box[0].item() + box[2].item()) / 2) cy = int((box[1].item() + box[3].item()) / 2) id = int(box[4].item()) if id in active_vehicles: active_vehicles[id][0] = True active_vehicles[id][1] = active_vehicles[id][2] active_vehicles[id][2] = [cx, cy] else: active_vehicles[id] = [True, [cx, cy], [cx, cy]] #remove invalid vehicles del_list = [] for k, v in active_vehicles.items(): if active_vehicles[k][0] == False: del_list.append(k) for k in del_list: del active_vehicles[k] # boundary lines boundaryLines = [ boundaryLine([ 0, 220, 950, 300 ]) ] label_map = None active_vehicles = {} def main(): global label_map, active_vehicles parser = argparse.ArgumentParser() parser.add_argument('--track', type=str, default="bytetrack.yaml" ) #botsort.yaml or bytetrack.yaml args = parser.parse_args() colors = [(255,255 , 0), (0,255,0), (0,0,255)] font = cv2.FONT_HERSHEY_SIMPLEX model = YOLO("yolov8m.pt") model.to('cuda') label_map = model.names for result in model.track(source="./highway_traffic.mp4", show=False, stream=True, agnostic_nms=False, conf= 0.1, tracker=args.track): update_vehicles(result.boxes.data) frame = result.orig_img for box, conf, cls in zip(result.boxes.data, result.boxes.conf, result.boxes.cls): p1 = (int(box[0].item()), int(box[1].item())) p2 = (int(box[2].item()), int(box[3].item())) id = int(box[4].item()) cls = int(cls.item()) cv2.rectangle(frame, p1, p2, colors[int(cls % len(colors))], 2) text = "#" + str(id) + "-"+ label_map[cls] + " %4.2f"%(conf.item()) cv2.putText(frame, text, (p1[0], p1[1] - 10), font, fontScale = 1, color = colors[int(cls % len(colors))], thickness = 2) for line in boundaryLines: checkLineCross(line, cls, (active_vehicles[id][1], active_vehicles[id][2])) drawBoundaryLines(frame, boundaryLines) cv2.imshow("yolov8", frame) if (cv2.waitKey(1) == 27): break if __name__ == "__main__": main()
<sample_track3.py>
If you run the proggram, you might see this screen shot. You can see that the number increases each time the center coordinates of the car cross the line.
You can increase the number of lines by slightly modifying the example. You can also change the type of object. There is one thing to note.
Cautions : The line should not be horizontal or vertical. I hope you give it a slight tilt. This is because it is difficult to use mathematical expressions such as tangent for vertical and horizontal lines.
Wrapping up
Both BoT-SORT and ByteTrack provided by YOLOV8 provide decent performance. If accuracy is important, you can use Bot-SORT, and if speed is important, you can use ByteTrack.
It works equally well for COCO models provided by YOLO as well as custom trained models.
The source code can be downloaded from my GitHub.
댓글 없음:
댓글 쓰기