Search code examples
pythonyolov8

YOLO v8n - keep original IDs of objects, once other objects are not detected


I am using YOLOv8 with track in python. I can get the bounding boxes and classes of each object, but the IDs change every time one of the objects is no longer visible. If current object IDs are 1,2,3,4 and number 2 is not detected, YOLO simply decrements 3 and 4, so new IDs are 1,2,3, forcing me to manually detect which one left, which is a pain. I know YOLO can do this for me, as the show=True method for track plots the correct IDs even after objects are missed or leave the frame. I have tried looking in documentation but have not been able to find anything. The object returned by invoking track has methods sort, and index, which might be of help, but I don't know how to use them.

Small example:

from ultralytics import YOLO
model = YOLO("yolov8n-seg.pt")
results = model.track(img, conf=0.20, show = True, persist = True, tracker="botsort.yaml", verbose=False )
print(results[0].boxes.data[:,4])
----------------------------------
1,2,3,4
# when 2 is not detected:
1,2,3

Solution

  • check official documentation about persisting tracks loop.

    You've got to use model.track frame by frame in loop with persist=True parameter. Furthermore be shure that you don't use stream=True parameter, it will breake your track's id.

    so here is code from documentation with my correction about "finished tracks" (the objects that is no longer visible)

    from collections import defaultdict
    
    import cv2
    import numpy as np
    
    from ultralytics import YOLO
    
    # Load the YOLOv8 model
    model = YOLO('yolov8n.pt')
    
    # Open the video file
    video_path = "path/to/video.mp4"
    cap = cv2.VideoCapture(video_path)
    
    # Store the track history
    track_history = defaultdict(lambda: [])
    
    # Loop through the video frames
    while cap.isOpened():
        # Read a frame from the video
        success, frame = cap.read()
    
        if success:
            # Run YOLOv8 tracking on the frame, persisting tracks between frames
            results = model.track(frame, persist=True)
    
            # Get the boxes and track IDs
            boxes = results[0].boxes.xywh.cpu()
            track_ids = results[0].boxes.id.int().cpu().tolist()
    
            # Visualize the results on the frame
            annotated_frame = results[0].plot()
    
            # Plot the tracks
            for box, track_id in zip(boxes, track_ids):
                x, y, w, h = box
                track = track_history[track_id]
                track.append((float(x), float(y)))  # x, y center point
                if len(track) > 30:  # retain 90 tracks for 90 frames
                    track.pop(0)
    
                # Draw the tracking lines
                points = np.hstack(track).astype(np.int32).reshape((-1, 1, 2))
                cv2.polylines(annotated_frame, [points], isClosed=False, color=(230, 230, 230), thickness=10)
    
            # get finished tracks and do some logic with them
            finished_tracks = track_history.keys() - track_ids
            for ft_id in finished_tracks:
                ft = track_history.pop(ft_id)
                # do some logic with ft here.........
    
            # Display the annotated frame
            cv2.imshow("YOLOv8 Tracking", annotated_frame)
    
            # Break the loop if 'q' is pressed
            if cv2.waitKey(1) & 0xFF == ord("q"):
                break
        else:
            # Break the loop if the end of the video is reached
            break
    
    # Release the video capture object and close the display window
    cap.release()
    cv2.destroyAllWindows()