mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-12-06 13:34:13 +03:00
Improve stationary classification (#20303)
* Improve stationary classification * Cleanup for mypy
This commit is contained in:
parent
28e3aa39f0
commit
8f0be18422
@ -17,7 +17,11 @@ from frigate.camera import PTZMetrics
|
|||||||
from frigate.config import CameraConfig
|
from frigate.config import CameraConfig
|
||||||
from frigate.ptz.autotrack import PtzMotionEstimator
|
from frigate.ptz.autotrack import PtzMotionEstimator
|
||||||
from frigate.track import ObjectTracker
|
from frigate.track import ObjectTracker
|
||||||
from frigate.track.stationary_classifier import StationaryMotionClassifier
|
from frigate.track.stationary_classifier import (
|
||||||
|
StationaryMotionClassifier,
|
||||||
|
StationaryThresholds,
|
||||||
|
get_stationary_threshold,
|
||||||
|
)
|
||||||
from frigate.util.image import (
|
from frigate.util.image import (
|
||||||
SharedMemoryFrameManager,
|
SharedMemoryFrameManager,
|
||||||
get_histogram,
|
get_histogram,
|
||||||
@ -28,12 +32,6 @@ from frigate.util.object import average_boxes, median_of_boxes
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
THRESHOLD_KNOWN_ACTIVE_IOU = 0.2
|
|
||||||
THRESHOLD_STATIONARY_CHECK_IOU = 0.6
|
|
||||||
THRESHOLD_ACTIVE_CHECK_IOU = 0.9
|
|
||||||
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)
|
||||||
@ -328,6 +326,7 @@ class NorfairTracker(ObjectTracker):
|
|||||||
id: str,
|
id: str,
|
||||||
box: list[int],
|
box: list[int],
|
||||||
stationary: bool,
|
stationary: bool,
|
||||||
|
thresholds: StationaryThresholds,
|
||||||
yuv_frame: np.ndarray | None,
|
yuv_frame: np.ndarray | None,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
def reset_position(xmin: int, ymin: int, xmax: int, ymax: int) -> None:
|
def reset_position(xmin: int, ymin: int, xmax: int, ymax: int) -> None:
|
||||||
@ -346,9 +345,9 @@ class NorfairTracker(ObjectTracker):
|
|||||||
position = self.positions[id]
|
position = self.positions[id]
|
||||||
self.stationary_box_history[id].append(box)
|
self.stationary_box_history[id].append(box)
|
||||||
|
|
||||||
if len(self.stationary_box_history[id]) > MAX_STATIONARY_HISTORY:
|
if len(self.stationary_box_history[id]) > thresholds.max_stationary_history:
|
||||||
self.stationary_box_history[id] = self.stationary_box_history[id][
|
self.stationary_box_history[id] = self.stationary_box_history[id][
|
||||||
-MAX_STATIONARY_HISTORY:
|
-thresholds.max_stationary_history :
|
||||||
]
|
]
|
||||||
|
|
||||||
avg_box = average_boxes(self.stationary_box_history[id])
|
avg_box = average_boxes(self.stationary_box_history[id])
|
||||||
@ -367,7 +366,7 @@ class NorfairTracker(ObjectTracker):
|
|||||||
|
|
||||||
# object has minimal or zero iou
|
# object has minimal or zero iou
|
||||||
# assume object is active
|
# assume object is active
|
||||||
if avg_iou < THRESHOLD_KNOWN_ACTIVE_IOU:
|
if avg_iou < thresholds.known_active_iou:
|
||||||
if stationary and yuv_frame is not None:
|
if stationary and yuv_frame is not None:
|
||||||
if not self.stationary_classifier.evaluate(
|
if not self.stationary_classifier.evaluate(
|
||||||
id, yuv_frame, cast(tuple[int, int, int, int], tuple(box))
|
id, yuv_frame, cast(tuple[int, int, int, int], tuple(box))
|
||||||
@ -379,7 +378,9 @@ class NorfairTracker(ObjectTracker):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
threshold = (
|
threshold = (
|
||||||
THRESHOLD_STATIONARY_CHECK_IOU if stationary else THRESHOLD_ACTIVE_CHECK_IOU
|
thresholds.stationary_check_iou
|
||||||
|
if stationary
|
||||||
|
else thresholds.active_check_iou
|
||||||
)
|
)
|
||||||
|
|
||||||
# object has iou below threshold, check median and optionally crop similarity
|
# object has iou below threshold, check median and optionally crop similarity
|
||||||
@ -447,6 +448,7 @@ class NorfairTracker(ObjectTracker):
|
|||||||
self,
|
self,
|
||||||
track_id: str,
|
track_id: str,
|
||||||
obj: dict[str, Any],
|
obj: dict[str, Any],
|
||||||
|
thresholds: StationaryThresholds,
|
||||||
yuv_frame: np.ndarray | None,
|
yuv_frame: np.ndarray | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
id = self.track_id_map[track_id]
|
id = self.track_id_map[track_id]
|
||||||
@ -456,7 +458,7 @@ class NorfairTracker(ObjectTracker):
|
|||||||
>= self.detect_config.stationary.threshold
|
>= self.detect_config.stationary.threshold
|
||||||
)
|
)
|
||||||
# update the motionless count if the object has not moved to a new position
|
# update the motionless count if the object has not moved to a new position
|
||||||
if self.update_position(id, obj["box"], stationary, yuv_frame):
|
if self.update_position(id, obj["box"], stationary, thresholds, yuv_frame):
|
||||||
self.tracked_objects[id]["motionless_count"] += 1
|
self.tracked_objects[id]["motionless_count"] += 1
|
||||||
if self.is_expired(id):
|
if self.is_expired(id):
|
||||||
self.deregister(id, track_id)
|
self.deregister(id, track_id)
|
||||||
@ -502,9 +504,9 @@ class NorfairTracker(ObjectTracker):
|
|||||||
detections_by_type: dict[str, list[Detection]] = {}
|
detections_by_type: dict[str, list[Detection]] = {}
|
||||||
yuv_frame: np.ndarray | None = None
|
yuv_frame: np.ndarray | None = None
|
||||||
|
|
||||||
if self.ptz_metrics.autotracker_enabled.value or (
|
if (
|
||||||
self.detect_config.stationary.classifier
|
self.ptz_metrics.autotracker_enabled.value
|
||||||
and any(obj[0] == "car" for obj in detections)
|
or self.detect_config.stationary.classifier
|
||||||
):
|
):
|
||||||
yuv_frame = self.frame_manager.get(
|
yuv_frame = self.frame_manager.get(
|
||||||
frame_name, self.camera_config.frame_shape_yuv
|
frame_name, self.camera_config.frame_shape_yuv
|
||||||
@ -614,10 +616,12 @@ class NorfairTracker(ObjectTracker):
|
|||||||
self.tracked_objects[id]["estimate"] = new_obj["estimate"]
|
self.tracked_objects[id]["estimate"] = new_obj["estimate"]
|
||||||
# else update it
|
# else update it
|
||||||
else:
|
else:
|
||||||
|
thresholds = get_stationary_threshold(new_obj["label"])
|
||||||
self.update(
|
self.update(
|
||||||
str(t.global_id),
|
str(t.global_id),
|
||||||
new_obj,
|
new_obj,
|
||||||
yuv_frame if new_obj["label"] == "car" else None,
|
thresholds,
|
||||||
|
yuv_frame if thresholds.motion_classifier_enabled else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
# clear expired tracks
|
# clear expired tracks
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
"""Tools for determining if an object is stationary."""
|
"""Tools for determining if an object is stationary."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from dataclasses import dataclass
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
import cv2
|
import cv2
|
||||||
@ -10,10 +11,60 @@ from scipy.ndimage import gaussian_filter
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
THRESHOLD_KNOWN_ACTIVE_IOU = 0.2
|
@dataclass
|
||||||
THRESHOLD_STATIONARY_CHECK_IOU = 0.6
|
class StationaryThresholds:
|
||||||
THRESHOLD_ACTIVE_CHECK_IOU = 0.9
|
"""IOU thresholds and history parameters for stationary object classification.
|
||||||
MAX_STATIONARY_HISTORY = 10
|
|
||||||
|
This allows different sensitivity settings for different object types.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Objects to apply these thresholds to
|
||||||
|
# If None, apply to all objects
|
||||||
|
objects: list[str] = []
|
||||||
|
|
||||||
|
# Threshold of IoU that causes the object to immediately be considered active
|
||||||
|
# Below this threshold, assume object is active
|
||||||
|
known_active_iou: float = 0.2
|
||||||
|
|
||||||
|
# IOU threshold for checking if stationary object has moved
|
||||||
|
# If mean and median IOU drops below this, assume object is no longer stationary
|
||||||
|
stationary_check_iou: float = 0.6
|
||||||
|
|
||||||
|
# IOU threshold for checking if active object has changed position
|
||||||
|
# Higher threshold makes it more difficult for the object to be considered stationary
|
||||||
|
active_check_iou: float = 0.9
|
||||||
|
|
||||||
|
# Maximum number of bounding boxes to keep in stationary history
|
||||||
|
max_stationary_history: int = 10
|
||||||
|
|
||||||
|
# Whether to use the motion classifier
|
||||||
|
motion_classifier_enabled: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
# Thresholds for objects that are expected to be stationary
|
||||||
|
STATIONARY_OBJECT_THRESHOLDS = StationaryThresholds(
|
||||||
|
objects=["bbq_grill", "package", "waste_bin"],
|
||||||
|
known_active_iou=0.0,
|
||||||
|
motion_classifier_enabled=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Thresholds for objects that are active but can be stationary for longer periods of time
|
||||||
|
DYNAMIC_OBJECT_THRESHOLDS = StationaryThresholds(
|
||||||
|
objects=["bicycle", "boat", "car", "motorcycle", "tractor", "truck"],
|
||||||
|
motion_classifier_enabled=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_stationary_threshold(label: str) -> StationaryThresholds:
|
||||||
|
"""Get the stationary thresholds for a given object label."""
|
||||||
|
|
||||||
|
if label in STATIONARY_OBJECT_THRESHOLDS.objects:
|
||||||
|
return STATIONARY_OBJECT_THRESHOLDS
|
||||||
|
|
||||||
|
if label in DYNAMIC_OBJECT_THRESHOLDS.objects:
|
||||||
|
return DYNAMIC_OBJECT_THRESHOLDS
|
||||||
|
|
||||||
|
return StationaryThresholds()
|
||||||
|
|
||||||
|
|
||||||
class StationaryMotionClassifier:
|
class StationaryMotionClassifier:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user