abewley / sort

Simple, online, and realtime tracking of multiple objects in a video sequence.
GNU General Public License v3.0
4k stars 1.1k forks source link

Why the tracker ID keeps increase even I recreate the tracker object. #106

Open Uoops opened 4 years ago

Uoops commented 4 years ago

Hi, Thank you for sharing this helpful tracking algorithm. I have a question about the tracker IDs.

I have a sequence of object's bounding boxes and I create a while loop to do the tracking over and over again. At the beginning of each loop I always initialize the tracker by recreating the tracker object (my_tracker = Sort()). And at the end of each loop I also set my_tracker = None. However, the tracker IDs are different each time. For example, the tracker IDs start from 1&2... for the first iteration, then they start from 11&12... for the second iteration, and so on.

I am wondering is there a way to clean the old tracker? Thank you in advance.

ErlingLie commented 4 years ago

You should re initialize the static counter in the KalmanBoxTracker class. Although you reinitialize the sort object, the counter remains unchanged.

Mechazo11 commented 3 years ago

@Uoops Thank you for the great question. I am also facing a similar issue. @ErlingLie would you kindly suggest how to reset the KalmanBoxTracker class

ErlingLie commented 3 years ago

@Mechazo11 You need to reset the count on this line

If you for instance initialize multiple trackers in a for-loop, it can be done like this:

for i in range(n):
    KalmanBoxTracker.count = 0
    myTracker = Sort()
    # Do tracking
Mechazo11 commented 3 years ago

@ErlingLie thank you very much for the code example. I did not know we can directly access a class variable from the main script in the manner you showed above.

With best @Mechazo11

jingtongli commented 3 years ago

@ErlingLie Thanks for your code example.

I have a slightly different situation. I have actually two trackers tracker_1 = Sort() and tracker_2 = Sort(). They are applied to two different datasets, but get updated in the same loop, like this

for i in range(n):
    # Get detection for frame i
    tracker_1.update(det_1_i)
    tracker_2.update(det_2_i)

Is it possible to run two trackers in parallel while the track_id is not affected by other trackers, as if they were run sequentially with KalmanBoxTracker.count reset to 0 ?

ErlingLie commented 3 years ago

@jingtongli I would modify the KalmanBoxTracker in the Sort code to take in id as argument instead of using a static variable. For this to work I made some small changes to KalmanBoxTracker and Sort. I did not test this solution, but I hope you get the idea. I tried to make some very visible comments in the places where I made changes. Hope this helps :)

class KalmanBoxTracker(object):
  """
  This class represents the internal state of individual tracked objects observed as bbox.
  """

  #########Comment out the old count###########
  #count = 0
  ###########Take in id as argument instead#############
  def __init__(self,bbox, id):
    """
    Initialises a tracker using initial bounding box.
    """
    #define constant velocity model
    self.kf = KalmanFilter(dim_x=7, dim_z=4) 
    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]])
    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]])

    self.kf.R[2:,2:] *= 10.
    self.kf.P[4:,4:] *= 1000. #give high uncertainty to the unobservable initial velocities
    self.kf.P *= 10.
    self.kf.Q[-1,-1] *= 0.01
    self.kf.Q[4:,4:] *= 0.01

    self.kf.x[:4] = convert_bbox_to_z(bbox)
    self.time_since_update = 0
    ############Set Id to argument id instead of static variable count##############
    self.id = id
    #self.id = KalmanBoxTracker.count
    #KalmanBoxTracker.count += 1
    self.history = []
    self.hits = 0
    self.hit_streak = 0
    self.age = 0

class Sort(object):
  def __init__(self, max_age=1, min_hits=3, iou_threshold=0.3):
    """
    Sets key parameters for SORT
    """
    self.max_age = max_age
    self.min_hits = min_hits
    self.iou_threshold = iou_threshold
    self.trackers = []
    self.frame_count = 0
    ############ Initialize id ##############
    self.id = 0

  def update(self, dets=np.empty((0, 5))):
    """
    Params:
      dets - a numpy array of detections in the format [[x1,y1,x2,y2,score],[x1,y1,x2,y2,score],...]
    Requires: this method must be called once for each frame even with empty detections (use np.empty((0, 5)) for frames without detections).
    Returns the a similar array, where the last column is the object ID.
    NOTE: The number of objects returned may differ from the number of detections provided.
    """
    self.frame_count += 1
    # get predicted locations from existing trackers.
    trks = np.zeros((len(self.trackers), 5))
    to_del = []
    ret = []
    for t, trk in enumerate(trks):
      pos = self.trackers[t].predict()[0]
      trk[:] = [pos[0], pos[1], pos[2], pos[3], 0]
      if np.any(np.isnan(pos)):
        to_del.append(t)
    trks = np.ma.compress_rows(np.ma.masked_invalid(trks))
    for t in reversed(to_del):
      self.trackers.pop(t)
    matched, unmatched_dets, unmatched_trks = associate_detections_to_trackers(dets,trks, self.iou_threshold)

    # update matched trackers with assigned detections
    for m in matched:
      self.trackers[m[1]].update(dets[m[0], :])

    # create and initialise new trackers for unmatched detections
    for i in unmatched_dets:
    ########Take in id to KalmanBoxTracker constructor############
        trk = KalmanBoxTracker(dets[i,:], self.id)
        self.id += 1
        self.trackers.append(trk)
    i = len(self.trackers)
    for trk in reversed(self.trackers):
        d = trk.get_state()[0]
        if (trk.time_since_update < 1) and (trk.hit_streak >= self.min_hits or self.frame_count <= self.min_hits):
          ret.append(np.concatenate((d,[trk.id+1])).reshape(1,-1)) # +1 as MOT benchmark requires positive
        i -= 1
        # remove dead tracklet
        if(trk.time_since_update > self.max_age):
          self.trackers.pop(i)
    if(len(ret)>0):
      return np.concatenate(ret)
    return np.empty((0,5))
jingtongli commented 3 years ago

@ErlingLie Thanks a lot for solving this quickly and effectively!!

It works exactly as I expected without modifying my own loops. I tested it also on the provided MOT datasets. It turns out that the track_id starts at 1 for each individual dataset.

Thanks again. I think this functionality should be somehow included in the master branch.

ErlingLie commented 3 years ago

@jingtongli Glad to help!

varungupta31 commented 2 years ago

Hey @ErlingLie, Kindly help me out as well please.

I'm trying to run the tracker on multiple files using the terminal command python sort.py --phase <path to my custom DET files> --max_age 10 --min_hits 5 and the track files are created. If I clear the contents of output and replace my custom data folder content with another data (DET file) , and re-run the tracker using the same command, the tracker files is indeed generated but the track ID continues (and sometimes misses some number too) . For e.x., If there are 3 files and all files have 2 tracks, than rather than giving file1 [1,2], file2[1,2], file3[1,2], it gives file1[1,2],file2[3,4], file3[6,7].

Can I do something to reset the track ID before each and ensure that no numbers are skipped in track sequence when running tracker via the terminal command? Also, any information on why the tracker is skipping certain track IDs randomly? e.g., it would assign ID 1,2,3 and then skip 4,5 and start with 7.

ErlingLie commented 2 years ago

Hey, I think making the changes as I outlined in my long code snippet above will fix this issue. As to why some numbers are skipped I believe this is because the algorithm attempts to create tracks that are then discarded. The next actual track will then skip that ID.

varungupta31 commented 2 years ago

@ErlingLie Thank you for the prompt reply. Another thing I noticed was that if I re-run on the same file and overwrite the output files, then the tracked ID's do not change. For e.g., I have file1 and file2 with 2 tracks each. If I run the tracker on file1, i get IDs 1 and 2. Now if I run the tracker on file2, i get IDs 3 and 4. Now, if I re-run on file2, i get ID 3 and 4 again, and NOT 5 and 6. Any reason why this may be happening, and will the edits suggested by you handle this issue as well? (Though I'll still make the suggested edits and write back)

varungupta31 commented 2 years ago

@ErlingLie Hi, I tried the suggested edit, and it is still resulting in the same problem.

ErlingLie commented 2 years ago

Hm, then I am not sure if I understand your problem correctly. The general issue is that the tracker keeps one static ID-counter for all SORT objects created during the runtime of the program. When you run it from the command-line one SORT object is created to track each file's detections. Since the static counter does not reset when you create a new SORT object, the next file's ids will continue where the previous file left off. Changing out the SORT code with my suggestion above should however fix this.

An alternative fix could be to add the following change to the original source code here https://github.com/abewley/sort/blob/bce9f0d1fc8fb5f45bf7084130248561a3d42f31/sort.py#L290 (revert the change I suggested above):

for seq_dets_fn in glob.glob(pattern):
    KalmanBoxTracker.count = 0
    mot_tracker = Sort(max_age=args.max_age, 
                       min_hits=args.min_hits,
                       iou_threshold=args.iou_threshold) #create instance of the SORT tracker
    seq_dets = np.loadtxt(seq_dets_fn, delimiter=',')
    seq = seq_dets_fn[pattern.find('*'):].split(os.path.sep)[0]
varungupta31 commented 2 years ago

My apologies for the late reply. I was wondering the same as well. But I noticed that for some videos the count was indeed starting from 1. So as you mentioned earlier, this seems like a 'missing issue', i.e. tracker skips certain track. I think I should have explored my problem a bit more. However, for my application (CVAT) having the tracks numbers is sufficient enough and I don't need them to start from a certain number, as the tool does that for me (I realised this later)

Thank you for your taking out the time to help :)