objects.py 5.7 KB

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