objects.py 6.3 KB

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