mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-14 07:05:24 +03:00
Initial implementation of active object counters. Need to clean up a bit more and examine reuse of stationary/active logic in neighboring modules.
This commit is contained in:
parent
d96f76c27f
commit
782ab40098
@ -132,6 +132,7 @@ class TrackedObject:
|
|||||||
self.last_updated = 0
|
self.last_updated = 0
|
||||||
self.last_published = 0
|
self.last_published = 0
|
||||||
self.frame = None
|
self.frame = None
|
||||||
|
self.active = True
|
||||||
self.previous = self.to_dict()
|
self.previous = self.to_dict()
|
||||||
|
|
||||||
def _is_false_positive(self):
|
def _is_false_positive(self):
|
||||||
@ -165,6 +166,7 @@ class TrackedObject:
|
|||||||
if self.computed_score > self.top_score:
|
if self.computed_score > self.top_score:
|
||||||
self.top_score = self.computed_score
|
self.top_score = self.computed_score
|
||||||
self.false_positive = self._is_false_positive()
|
self.false_positive = self._is_false_positive()
|
||||||
|
self.active = self.is_active()
|
||||||
|
|
||||||
if not self.false_positive:
|
if not self.false_positive:
|
||||||
# determine if this frame is a better thumbnail
|
# determine if this frame is a better thumbnail
|
||||||
@ -256,6 +258,10 @@ class TrackedObject:
|
|||||||
):
|
):
|
||||||
significant_change = True
|
significant_change = True
|
||||||
|
|
||||||
|
# or if the motionless_count resets
|
||||||
|
if self.obj_data["motionless_count"] == 0:
|
||||||
|
significant_change = True
|
||||||
|
|
||||||
# update at least once per minute
|
# update at least once per minute
|
||||||
if self.obj_data["frame_time"] - self.previous["frame_time"] > 60:
|
if self.obj_data["frame_time"] - self.previous["frame_time"] > 60:
|
||||||
significant_change = True
|
significant_change = True
|
||||||
@ -285,8 +291,7 @@ class TrackedObject:
|
|||||||
"area": self.obj_data["area"],
|
"area": self.obj_data["area"],
|
||||||
"ratio": self.obj_data["ratio"],
|
"ratio": self.obj_data["ratio"],
|
||||||
"region": self.obj_data["region"],
|
"region": self.obj_data["region"],
|
||||||
"stationary": self.obj_data["motionless_count"]
|
"stationary": not self.active,
|
||||||
> self.camera_config.detect.stationary.threshold,
|
|
||||||
"motionless_count": self.obj_data["motionless_count"],
|
"motionless_count": self.obj_data["motionless_count"],
|
||||||
"position_changes": self.obj_data["position_changes"],
|
"position_changes": self.obj_data["position_changes"],
|
||||||
"current_zones": self.current_zones.copy(),
|
"current_zones": self.current_zones.copy(),
|
||||||
@ -302,6 +307,12 @@ class TrackedObject:
|
|||||||
|
|
||||||
return event
|
return event
|
||||||
|
|
||||||
|
def is_active(self):
|
||||||
|
return not self.is_stationary()
|
||||||
|
|
||||||
|
def is_stationary(self):
|
||||||
|
return self.obj_data["motionless_count"] >= self.camera_config.detect.stationary.threshold
|
||||||
|
|
||||||
def get_thumbnail(self):
|
def get_thumbnail(self):
|
||||||
if (
|
if (
|
||||||
self.thumbnail_data is None
|
self.thumbnail_data is None
|
||||||
@ -476,6 +487,7 @@ class CameraState:
|
|||||||
self.frame_manager = frame_manager
|
self.frame_manager = frame_manager
|
||||||
self.best_objects: dict[str, TrackedObject] = {}
|
self.best_objects: dict[str, TrackedObject] = {}
|
||||||
self.object_counts = defaultdict(int)
|
self.object_counts = defaultdict(int)
|
||||||
|
self.active_object_counts = defaultdict(int)
|
||||||
self.tracked_objects: dict[str, TrackedObject] = {}
|
self.tracked_objects: dict[str, TrackedObject] = {}
|
||||||
self.frame_cache = {}
|
self.frame_cache = {}
|
||||||
self.zone_objects = defaultdict(list)
|
self.zone_objects = defaultdict(list)
|
||||||
@ -796,8 +808,15 @@ class CameraState:
|
|||||||
if not obj.false_positive
|
if not obj.false_positive
|
||||||
)
|
)
|
||||||
|
|
||||||
|
active_obj_counter = Counter(
|
||||||
|
obj.obj_data["label"]
|
||||||
|
for obj in tracked_objects.values()
|
||||||
|
if not obj.false_positive and obj.active
|
||||||
|
)
|
||||||
|
|
||||||
# keep track of all labels detected for this camera
|
# keep track of all labels detected for this camera
|
||||||
total_label_count = 0
|
total_label_count = 0
|
||||||
|
total_active_label_count = 0
|
||||||
|
|
||||||
# report on detected objects
|
# report on detected objects
|
||||||
for obj_name, count in obj_counter.items():
|
for obj_name, count in obj_counter.items():
|
||||||
@ -808,12 +827,26 @@ class CameraState:
|
|||||||
for c in self.callbacks["object_status"]:
|
for c in self.callbacks["object_status"]:
|
||||||
c(self.name, obj_name, count)
|
c(self.name, obj_name, count)
|
||||||
|
|
||||||
|
for obj_name, count in active_obj_counter.items():
|
||||||
|
total_active_label_count += count
|
||||||
|
|
||||||
|
if count != self.active_object_counts[obj_name]:
|
||||||
|
self.active_object_counts[obj_name] = count
|
||||||
|
for c in self.callbacks["active_object_status"]:
|
||||||
|
c(self.name, obj_name, count)
|
||||||
|
|
||||||
# publish for all labels detected for this camera
|
# publish for all labels detected for this camera
|
||||||
if total_label_count != self.object_counts.get("all"):
|
if total_label_count != self.object_counts.get("all"):
|
||||||
self.object_counts["all"] = total_label_count
|
self.object_counts["all"] = total_label_count
|
||||||
for c in self.callbacks["object_status"]:
|
for c in self.callbacks["object_status"]:
|
||||||
c(self.name, "all", total_label_count)
|
c(self.name, "all", total_label_count)
|
||||||
|
|
||||||
|
# publish activel label counts for this camera
|
||||||
|
if total_active_label_count != self.active_object_counts.get("all"):
|
||||||
|
self.active_object_counts["all"] = total_active_label_count
|
||||||
|
for c in self.callbacks["active_object_status"]:
|
||||||
|
c(self.name, "all", total_active_label_count)
|
||||||
|
|
||||||
# expire any objects that are >0 and no longer detected
|
# expire any objects that are >0 and no longer detected
|
||||||
expired_objects = [
|
expired_objects = [
|
||||||
obj_name
|
obj_name
|
||||||
@ -886,6 +919,18 @@ class TrackedObjectProcessor(threading.Thread):
|
|||||||
|
|
||||||
self.camera_activity: dict[str, dict[str, any]] = {}
|
self.camera_activity: dict[str, dict[str, any]] = {}
|
||||||
|
|
||||||
|
# {
|
||||||
|
# 'zone_name': {
|
||||||
|
# 'person': {
|
||||||
|
# 'camera_1': 2,
|
||||||
|
# 'camera_2': 1
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
self.zone_data = defaultdict(lambda: defaultdict(dict))
|
||||||
|
self.active_zone_data = defaultdict(lambda: defaultdict(dict))
|
||||||
|
|
||||||
|
|
||||||
def start(camera, obj: TrackedObject, current_frame_time):
|
def start(camera, obj: TrackedObject, current_frame_time):
|
||||||
self.event_sender.publish(
|
self.event_sender.publish(
|
||||||
(
|
(
|
||||||
@ -1003,6 +1048,9 @@ class TrackedObjectProcessor(threading.Thread):
|
|||||||
def object_status(camera, object_name, status):
|
def object_status(camera, object_name, status):
|
||||||
self.dispatcher.publish(f"{camera}/{object_name}", status, retain=False)
|
self.dispatcher.publish(f"{camera}/{object_name}", status, retain=False)
|
||||||
|
|
||||||
|
def active_object_status(camera, object_name, status):
|
||||||
|
self.dispatcher.publish(f"{camera}/{object_name}/active", status, retain=False)
|
||||||
|
|
||||||
def camera_activity(camera, activity):
|
def camera_activity(camera, activity):
|
||||||
last_activity = self.camera_activity.get(camera)
|
last_activity = self.camera_activity.get(camera)
|
||||||
|
|
||||||
@ -1020,19 +1068,10 @@ class TrackedObjectProcessor(threading.Thread):
|
|||||||
camera_state.on("end", end)
|
camera_state.on("end", end)
|
||||||
camera_state.on("snapshot", snapshot)
|
camera_state.on("snapshot", snapshot)
|
||||||
camera_state.on("object_status", object_status)
|
camera_state.on("object_status", object_status)
|
||||||
|
camera_state.on("active_object_status", active_object_status)
|
||||||
camera_state.on("camera_activity", camera_activity)
|
camera_state.on("camera_activity", camera_activity)
|
||||||
self.camera_states[camera] = camera_state
|
self.camera_states[camera] = camera_state
|
||||||
|
|
||||||
# {
|
|
||||||
# 'zone_name': {
|
|
||||||
# 'person': {
|
|
||||||
# 'camera_1': 2,
|
|
||||||
# 'camera_2': 1
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
self.zone_data = defaultdict(lambda: defaultdict(dict))
|
|
||||||
|
|
||||||
def should_save_snapshot(self, camera, obj: TrackedObject):
|
def should_save_snapshot(self, camera, obj: TrackedObject):
|
||||||
if obj.false_positive:
|
if obj.false_positive:
|
||||||
return False
|
return False
|
||||||
@ -1206,7 +1245,13 @@ class TrackedObjectProcessor(threading.Thread):
|
|||||||
for obj in camera_state.tracked_objects.values()
|
for obj in camera_state.tracked_objects.values()
|
||||||
if zone in obj.current_zones and not obj.false_positive
|
if zone in obj.current_zones and not obj.false_positive
|
||||||
)
|
)
|
||||||
|
active_obj_counter = Counter(
|
||||||
|
obj.obj_data["label"]
|
||||||
|
for obj in camera_state.tracked_objects.values()
|
||||||
|
if zone in obj.current_zones and not obj.false_positive and obj.active
|
||||||
|
)
|
||||||
total_label_count = 0
|
total_label_count = 0
|
||||||
|
total_active_label_count = 0
|
||||||
|
|
||||||
# update counts and publish status
|
# update counts and publish status
|
||||||
for label in set(self.zone_data[zone].keys()) | set(obj_counter.keys()):
|
for label in set(self.zone_data[zone].keys()) | set(obj_counter.keys()):
|
||||||
@ -1216,41 +1261,66 @@ class TrackedObjectProcessor(threading.Thread):
|
|||||||
|
|
||||||
# if we have previously published a count for this zone/label
|
# if we have previously published a count for this zone/label
|
||||||
zone_label = self.zone_data[zone][label]
|
zone_label = self.zone_data[zone][label]
|
||||||
|
active_zone_label = self.active_zone_data[zone][label]
|
||||||
if camera in zone_label:
|
if camera in zone_label:
|
||||||
current_count = sum(zone_label.values())
|
current_count = sum(zone_label.values())
|
||||||
|
current_active_count = sum(active_zone_label.values())
|
||||||
zone_label[camera] = (
|
zone_label[camera] = (
|
||||||
obj_counter[label] if label in obj_counter else 0
|
obj_counter[label] if label in obj_counter else 0
|
||||||
)
|
)
|
||||||
|
active_zone_label[camera] = (
|
||||||
|
active_obj_counter[label] if label in active_obj_counter else 0
|
||||||
|
)
|
||||||
new_count = sum(zone_label.values())
|
new_count = sum(zone_label.values())
|
||||||
|
new_active_count = sum(active_zone_label.values())
|
||||||
if new_count != current_count:
|
if new_count != current_count:
|
||||||
self.dispatcher.publish(
|
self.dispatcher.publish(
|
||||||
f"{zone}/{label}",
|
f"{zone}/{label}",
|
||||||
new_count,
|
new_count,
|
||||||
retain=False,
|
retain=False,
|
||||||
)
|
)
|
||||||
|
if new_active_count != current_active_count:
|
||||||
|
self.dispatcher.publish(
|
||||||
|
f"{zone}/{label}/active",
|
||||||
|
new_active_count,
|
||||||
|
retain=False,
|
||||||
|
)
|
||||||
|
|
||||||
# Set the count for the /zone/all topic.
|
# Set the count for the /zone/all topic.
|
||||||
total_label_count += new_count
|
total_label_count += new_count
|
||||||
|
total_active_label_count += new_active_count
|
||||||
|
|
||||||
# if this is a new zone/label combo for this camera
|
# if this is a new zone/label combo for this camera
|
||||||
else:
|
else:
|
||||||
if label in obj_counter:
|
if label in obj_counter:
|
||||||
zone_label[camera] = obj_counter[label]
|
zone_label[camera] = obj_counter[label]
|
||||||
|
# Since this is a new combo, it is by definition active and will be present
|
||||||
|
active_zone_label[camera] = active_obj_counter[label]
|
||||||
self.dispatcher.publish(
|
self.dispatcher.publish(
|
||||||
f"{zone}/{label}",
|
f"{zone}/{label}",
|
||||||
obj_counter[label],
|
obj_counter[label],
|
||||||
retain=False,
|
retain=False,
|
||||||
)
|
)
|
||||||
|
self.dispatcher.publish(
|
||||||
|
f"{zone}/{label}/active",
|
||||||
|
active_obj_counter[label],
|
||||||
|
retain=False,
|
||||||
|
)
|
||||||
|
|
||||||
# Set the count for the /zone/all topic.
|
# Set the count for the /zone/all topic.
|
||||||
total_label_count += obj_counter[label]
|
total_label_count += obj_counter[label]
|
||||||
|
total_active_label_count += active_obj_counter[label]
|
||||||
|
|
||||||
# if we have previously published a count for this zone all labels
|
# if we have previously published a count for this zone all labels
|
||||||
zone_label = self.zone_data[zone]["all"]
|
zone_label = self.zone_data[zone]["all"]
|
||||||
|
active_zone_label = self.active_zone_data[zone]["all"]
|
||||||
if camera in zone_label:
|
if camera in zone_label:
|
||||||
current_count = sum(zone_label.values())
|
current_count = sum(zone_label.values())
|
||||||
|
current_active_count = sum(active_zone_label.values())
|
||||||
zone_label[camera] = total_label_count
|
zone_label[camera] = total_label_count
|
||||||
|
active_zone_label[camera] = total_active_label_count
|
||||||
new_count = sum(zone_label.values())
|
new_count = sum(zone_label.values())
|
||||||
|
new_active_count = sum(active_zone_label.values())
|
||||||
|
|
||||||
if new_count != current_count:
|
if new_count != current_count:
|
||||||
self.dispatcher.publish(
|
self.dispatcher.publish(
|
||||||
@ -1258,14 +1328,26 @@ class TrackedObjectProcessor(threading.Thread):
|
|||||||
new_count,
|
new_count,
|
||||||
retain=False,
|
retain=False,
|
||||||
)
|
)
|
||||||
|
if new_active_count != current_active_count:
|
||||||
|
self.dispatcher.publish(
|
||||||
|
f"{zone}/all/active",
|
||||||
|
new_active_count,
|
||||||
|
retain=False,
|
||||||
|
)
|
||||||
# if this is a new zone all label for this camera
|
# if this is a new zone all label for this camera
|
||||||
else:
|
else:
|
||||||
zone_label[camera] = total_label_count
|
zone_label[camera] = total_label_count
|
||||||
|
active_zone_label[camera] = total_active_label_count
|
||||||
self.dispatcher.publish(
|
self.dispatcher.publish(
|
||||||
f"{zone}/all",
|
f"{zone}/all",
|
||||||
total_label_count,
|
total_label_count,
|
||||||
retain=False,
|
retain=False,
|
||||||
)
|
)
|
||||||
|
self.dispatcher.publish(
|
||||||
|
f"{zone}/all/active",
|
||||||
|
total_active_label_count,
|
||||||
|
retain=False,
|
||||||
|
)
|
||||||
|
|
||||||
# cleanup event finished queue
|
# cleanup event finished queue
|
||||||
while not self.stop_event.is_set():
|
while not self.stop_event.is_set():
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user