objects.py 5.8 KB

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