objects.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. import copy
  2. import datetime
  3. import itertools
  4. import multiprocessing as mp
  5. import random
  6. import string
  7. import threading
  8. import time
  9. from collections import defaultdict
  10. import cv2
  11. import numpy as np
  12. from scipy.spatial import distance as dist
  13. from frigate.util import calculate_region, draw_box_with_label
  14. class ObjectTracker():
  15. def __init__(self, max_disappeared):
  16. self.tracked_objects = {}
  17. self.disappeared = {}
  18. self.max_disappeared = max_disappeared
  19. def register(self, index, obj):
  20. rand_id = ''.join(random.choices(string.ascii_lowercase + string.digits, k=6))
  21. id = f"{obj['frame_time']}-{rand_id}"
  22. obj['id'] = id
  23. obj['start_time'] = obj['frame_time']
  24. self.tracked_objects[id] = obj
  25. self.disappeared[id] = 0
  26. def deregister(self, id):
  27. del self.tracked_objects[id]
  28. del self.disappeared[id]
  29. def update(self, id, new_obj):
  30. self.disappeared[id] = 0
  31. self.tracked_objects[id].update(new_obj)
  32. def match_and_update(self, frame_time, new_objects):
  33. # group by name
  34. new_object_groups = defaultdict(lambda: [])
  35. for obj in new_objects:
  36. new_object_groups[obj[0]].append({
  37. 'label': obj[0],
  38. 'score': obj[1],
  39. 'box': obj[2],
  40. 'area': obj[3],
  41. 'region': obj[4],
  42. 'frame_time': frame_time
  43. })
  44. # update any tracked objects with labels that are not
  45. # seen in the current objects and deregister if needed
  46. for obj in list(self.tracked_objects.values()):
  47. if not obj['label'] in new_object_groups:
  48. if self.disappeared[obj['id']] >= self.max_disappeared:
  49. self.deregister(obj['id'])
  50. else:
  51. self.disappeared[obj['id']] += 1
  52. if len(new_objects) == 0:
  53. return
  54. # track objects for each label type
  55. for label, group in new_object_groups.items():
  56. current_objects = [o for o in self.tracked_objects.values() if o['label'] == label]
  57. current_ids = [o['id'] for o in current_objects]
  58. current_centroids = np.array([o['centroid'] for o in current_objects])
  59. # compute centroids of new objects
  60. for obj in group:
  61. centroid_x = int((obj['box'][0]+obj['box'][2]) / 2.0)
  62. centroid_y = int((obj['box'][1]+obj['box'][3]) / 2.0)
  63. obj['centroid'] = (centroid_x, centroid_y)
  64. if len(current_objects) == 0:
  65. for index, obj in enumerate(group):
  66. self.register(index, obj)
  67. return
  68. new_centroids = np.array([o['centroid'] for o in group])
  69. # compute the distance between each pair of tracked
  70. # centroids and new centroids, respectively -- our
  71. # goal will be to match each new centroid to an existing
  72. # object centroid
  73. D = dist.cdist(current_centroids, new_centroids)
  74. # in order to perform this matching we must (1) find the
  75. # smallest value in each row and then (2) sort the row
  76. # indexes based on their minimum values so that the row
  77. # with the smallest value is at the *front* of the index
  78. # list
  79. rows = D.min(axis=1).argsort()
  80. # next, we perform a similar process on the columns by
  81. # finding the smallest value in each column and then
  82. # sorting using the previously computed row index list
  83. cols = D.argmin(axis=1)[rows]
  84. # in order to determine if we need to update, register,
  85. # or deregister an object we need to keep track of which
  86. # of the rows and column indexes we have already examined
  87. usedRows = set()
  88. usedCols = set()
  89. # loop over the combination of the (row, column) index
  90. # tuples
  91. for (row, col) in zip(rows, cols):
  92. # if we have already examined either the row or
  93. # column value before, ignore it
  94. if row in usedRows or col in usedCols:
  95. continue
  96. # otherwise, grab the object ID for the current row,
  97. # set its new centroid, and reset the disappeared
  98. # counter
  99. objectID = current_ids[row]
  100. self.update(objectID, group[col])
  101. # indicate that we have examined each of the row and
  102. # column indexes, respectively
  103. usedRows.add(row)
  104. usedCols.add(col)
  105. # compute the column index we have NOT yet examined
  106. unusedRows = set(range(0, D.shape[0])).difference(usedRows)
  107. unusedCols = set(range(0, D.shape[1])).difference(usedCols)
  108. # in the event that the number of object centroids is
  109. # equal or greater than the number of input centroids
  110. # we need to check and see if some of these objects have
  111. # potentially disappeared
  112. if D.shape[0] >= D.shape[1]:
  113. for row in unusedRows:
  114. id = current_ids[row]
  115. if self.disappeared[id] >= self.max_disappeared:
  116. self.deregister(id)
  117. else:
  118. self.disappeared[id] += 1
  119. # if the number of input centroids is greater
  120. # than the number of existing object centroids we need to
  121. # register each new input centroid as a trackable object
  122. else:
  123. for col in unusedCols:
  124. self.register(col, group[col])