objects.py 6.1 KB

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