Use a rolling average of iou to determine if an object is no longer stationary (#9381)

* Use a rolling average of iou to determine if an object is no longer stationary

* Use different box variation to designate when an object is stationary on debug

* In progress

* Use average of boxes instead of average of iou

* Update frigate/track/norfair_tracker.py

Co-authored-by: Blake Blackshear <blake@frigate.video>

---------

Co-authored-by: Blake Blackshear <blake@frigate.video>
This commit is contained in:
Nicolas Mowen 2024-02-26 06:37:56 -07:00 committed by GitHub
parent 0a15ef022b
commit 3f1bd891e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 41 additions and 11 deletions

View File

@ -489,8 +489,12 @@ class CameraState:
# draw the bounding boxes on the frame # draw the bounding boxes on the frame
for obj in tracked_objects.values(): for obj in tracked_objects.values():
if obj["frame_time"] == frame_time: if obj["frame_time"] == frame_time:
thickness = 2 if obj["stationary"]:
color = self.config.model.colormap[obj["label"]] color = (220, 220, 220)
thickness = 1
else:
thickness = 2
color = self.config.model.colormap[obj["label"]]
else: else:
thickness = 1 thickness = 1
color = (255, 0, 0) color = (255, 0, 0)

View File

@ -17,10 +17,15 @@ from frigate.ptz.autotrack import PtzMotionEstimator
from frigate.track import ObjectTracker from frigate.track import ObjectTracker
from frigate.types import PTZMetricsTypes from frigate.types import PTZMetricsTypes
from frigate.util.image import intersection_over_union from frigate.util.image import intersection_over_union
from frigate.util.object import average_boxes
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
THRESHOLD_STATIONARY_IOU_AVERAGE = 0.6
MAX_STATIONARY_HISTORY = 10
# Normalizes distance from estimate relative to object size # Normalizes distance from estimate relative to object size
# Other ideas: # Other ideas:
# - if estimates are inaccurate for first N detections, compare with last_detection (may be fine) # - if estimates are inaccurate for first N detections, compare with last_detection (may be fine)
@ -74,6 +79,7 @@ class NorfairTracker(ObjectTracker):
self.untracked_object_boxes: list[list[int]] = [] self.untracked_object_boxes: list[list[int]] = []
self.disappeared = {} self.disappeared = {}
self.positions = {} self.positions = {}
self.stationary_box_history: dict[str, list[list[int, int, int, int]]] = {}
self.camera_config = config self.camera_config = config
self.detect_config = config.detect self.detect_config = config.detect
self.ptz_metrics = ptz_metrics self.ptz_metrics = ptz_metrics
@ -127,6 +133,7 @@ class NorfairTracker(ObjectTracker):
"xmax": self.detect_config.width, "xmax": self.detect_config.width,
"ymax": self.detect_config.height, "ymax": self.detect_config.height,
} }
self.stationary_box_history[id] = []
def deregister(self, id, track_id): def deregister(self, id, track_id):
del self.tracked_objects[id] del self.tracked_objects[id]
@ -138,22 +145,24 @@ class NorfairTracker(ObjectTracker):
# tracks the current position of the object based on the last N bounding boxes # tracks the current position of the object based on the last N bounding boxes
# returns False if the object has moved outside its previous position # returns False if the object has moved outside its previous position
def update_position(self, id, box): def update_position(self, id: str, box: list[int, int, int, int]):
position = self.positions[id] position = self.positions[id]
position_box = ( self.stationary_box_history[id].append(box)
position["xmin"],
position["ymin"], if len(self.stationary_box_history[id]) > MAX_STATIONARY_HISTORY:
position["xmax"], self.stationary_box_history[id] = self.stationary_box_history[id][
position["ymax"], -MAX_STATIONARY_HISTORY:
]
avg_iou = intersection_over_union(
box, average_boxes(self.stationary_box_history[id])
) )
xmin, ymin, xmax, ymax = box xmin, ymin, xmax, ymax = box
iou = intersection_over_union(position_box, box)
# if the iou drops below the threshold # if the iou drops below the threshold
# assume the object has moved to a new position and reset the computed box # assume the object has moved to a new position and reset the computed box
if iou < 0.6: if avg_iou < THRESHOLD_STATIONARY_IOU_AVERAGE:
self.positions[id] = { self.positions[id] = {
"xmins": [xmin], "xmins": [xmin],
"ymins": [ymin], "ymins": [ymin],
@ -220,6 +229,7 @@ class NorfairTracker(ObjectTracker):
): ):
self.tracked_objects[id]["position_changes"] += 1 self.tracked_objects[id]["position_changes"] += 1
self.tracked_objects[id]["motionless_count"] = 0 self.tracked_objects[id]["motionless_count"] = 0
self.stationary_box_history[id] = []
self.tracked_objects[id].update(obj) self.tracked_objects[id].update(obj)

View File

@ -323,6 +323,22 @@ def reduce_boxes(boxes, iou_threshold=0.0):
return [tuple(c) for c in clusters] return [tuple(c) for c in clusters]
def average_boxes(boxes: list[list[int, int, int, int]]) -> list[int, int, int, int]:
"""Return a box that is the average of a list of boxes."""
x_mins = []
y_mins = []
x_max = []
y_max = []
for box in boxes:
x_mins.append(box[0])
y_mins.append(box[1])
x_max.append(box[2])
y_max.append(box[3])
return [np.mean(x_mins), np.mean(y_mins), np.mean(x_max), np.mean(y_max)]
def intersects_any(box_a, boxes): def intersects_any(box_a, boxes):
for box in boxes: for box in boxes:
if box_overlaps(box_a, box): if box_overlaps(box_a, box):