diff --git a/docs/docs/configuration/index.md b/docs/docs/configuration/index.md index a929d8363..e334d88ee 100644 --- a/docs/docs/configuration/index.md +++ b/docs/docs/configuration/index.md @@ -159,11 +159,23 @@ detect: enabled: True # Optional: Number of frames without a detection before frigate considers an object to be gone. (default: 5x the frame rate) max_disappeared: 25 - # Optional: Frequency for running detection on stationary objects (default: shown below) - # When set to 0, object detection will never be run on stationary objects. If set to 10, it will be run on every 10th frame. - stationary_interval: 0 - # Optional: Number of frames without a position change for an object to be considered stationary (default: 10x the frame rate or 10s) - stationary_threshold: 50 + # Optional: Configuration for stationary object tracking + stationary: + # Optional: Frequency for running detection on stationary objects (default: shown below) + # When set to 0, object detection will never be run on stationary objects. If set to 10, it will be run on every 10th frame. + interval: 0 + # Optional: Number of frames without a position change for an object to be considered stationary (default: 10x the frame rate or 10s) + threshold: 50 + # Optional: Define a maximum number of frames for tracking a stationary object (default: not set, track forever) + # This can help with false positives for objects that should only be stationary for a limited amount of time. + # It can also be used to disable stationary object tracking. For example, you may want to set a value for person, but leave + # car at the default. + max_frames: + # Optional: Default for all object types (default: not set, track forever) + default: 3000 + # Optional: Object specific values + objects: + person: 1000 # Optional: Object configuration # NOTE: Can be overridden at the camera level diff --git a/frigate/config.py b/frigate/config.py index 3bfda8c55..c210713a2 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -162,6 +162,29 @@ class RuntimeMotionConfig(MotionConfig): extra = Extra.ignore +class StationaryMaxFramesConfig(FrigateBaseModel): + default: Optional[int] = Field(title="Default max frames.", ge=1) + objects: Dict[str, int] = Field( + default_factory=dict, title="Object specific max frames." + ) + + +class StationaryConfig(FrigateBaseModel): + interval: Optional[int] = Field( + default=0, + title="Frame interval for checking stationary objects.", + ge=0, + ) + threshold: Optional[int] = Field( + title="Number of frames without a position change for an object to be considered stationary", + ge=1, + ) + max_frames: StationaryMaxFramesConfig = Field( + default_factory=StationaryMaxFramesConfig, + title="Max frames for stationary objects.", + ) + + class DetectConfig(FrigateBaseModel): height: int = Field(default=720, title="Height of the stream for the detect role.") width: int = Field(default=1280, title="Width of the stream for the detect role.") @@ -172,14 +195,9 @@ class DetectConfig(FrigateBaseModel): max_disappeared: Optional[int] = Field( title="Maximum number of frames the object can dissapear before detection ends." ) - stationary_interval: Optional[int] = Field( - default=0, - title="Frame interval for checking stationary objects.", - ge=0, - ) - stationary_threshold: Optional[int] = Field( - title="Number of frames without a position change for an object to be considered stationary", - ge=1, + stationary: StationaryConfig = Field( + default_factory=StationaryConfig, + title="Stationary objects config.", ) @@ -772,8 +790,8 @@ class FrigateConfig(FrigateBaseModel): # Default stationary_threshold configuration stationary_threshold = camera_config.detect.fps * 10 - if camera_config.detect.stationary_threshold is None: - camera_config.detect.stationary_threshold = stationary_threshold + if camera_config.detect.stationary.threshold is None: + camera_config.detect.stationary.threshold = stationary_threshold # FFMPEG input substitution for input in camera_config.ffmpeg.inputs: diff --git a/frigate/object_processing.py b/frigate/object_processing.py index 527e080b3..37d45e127 100644 --- a/frigate/object_processing.py +++ b/frigate/object_processing.py @@ -161,7 +161,7 @@ class TrackedObject: # if the motionless_count reaches the stationary threshold if ( self.obj_data["motionless_count"] - == self.camera_config.detect.stationary_threshold + == self.camera_config.detect.stationary.threshold ): significant_change = True @@ -194,7 +194,7 @@ class TrackedObject: "area": self.obj_data["area"], "region": self.obj_data["region"], "stationary": self.obj_data["motionless_count"] - > self.camera_config.detect.stationary_threshold, + > self.camera_config.detect.stationary.threshold, "motionless_count": self.obj_data["motionless_count"], "position_changes": self.obj_data["position_changes"], "current_zones": self.current_zones.copy(), diff --git a/frigate/objects.py b/frigate/objects.py index 7adf421d9..7b66536c2 100644 --- a/frigate/objects.py +++ b/frigate/objects.py @@ -93,18 +93,40 @@ class ObjectTracker: return True + def is_expired(self, id): + obj = self.tracked_objects[id] + # get the max frames for this label type or the default + max_frames = self.detect_config.stationary.max_frames.objects.get( + obj["label"], self.detect_config.stationary.max_frames.default + ) + + # if there is no max_frames for this label type, continue + if max_frames is None: + return False + + # if the object has exceeded the max_frames setting, deregister + if ( + obj["motionless_count"] - self.detect_config.stationary.threshold + > max_frames + ): + print(f"expired: {obj['motionless_count']}") + return True + def update(self, id, new_obj): self.disappeared[id] = 0 # update the motionless count if the object has not moved to a new position if self.update_position(id, new_obj["box"]): self.tracked_objects[id]["motionless_count"] += 1 + if self.is_expired(id): + self.deregister(id) + return else: # register the first position change and then only increment if # the object was previously stationary if ( self.tracked_objects[id]["position_changes"] == 0 or self.tracked_objects[id]["motionless_count"] - >= self.detect_config.stationary_threshold + >= self.detect_config.stationary.threshold ): self.tracked_objects[id]["position_changes"] += 1 self.tracked_objects[id]["motionless_count"] = 0 @@ -112,9 +134,11 @@ class ObjectTracker: self.tracked_objects[id].update(new_obj) def update_frame_times(self, frame_time): - for id in self.tracked_objects.keys(): + for id in list(self.tracked_objects.keys()): self.tracked_objects[id]["frame_time"] = frame_time self.tracked_objects[id]["motionless_count"] += 1 + if self.is_expired(id): + self.deregister(id) def match_and_update(self, frame_time, new_objects): # group by name diff --git a/frigate/video.py b/frigate/video.py index cb201fd22..26b99176f 100755 --- a/frigate/video.py +++ b/frigate/video.py @@ -511,8 +511,8 @@ def process_frames( if obj["motionless_count"] >= 10 # and it isn't due for a periodic check and ( - detect_config.stationary_interval == 0 - or obj["motionless_count"] % detect_config.stationary_interval != 0 + detect_config.stationary.interval == 0 + or obj["motionless_count"] % detect_config.stationary.interval != 0 ) # and it hasn't disappeared and object_tracker.disappeared[obj["id"]] == 0