mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-10 02:29:19 +03:00
Skip motion threshold configuration (#22255)
Some checks are pending
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
Some checks are pending
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
* backend * frontend * i18n * docs * add test * clean up * clean up motion detection docs * formatting * make optional
This commit is contained in:
parent
2babfd2ec9
commit
34cc1208a6
@ -38,7 +38,6 @@ Remember that motion detection is just used to determine when object detection s
|
|||||||
The threshold value dictates how much of a change in a pixels luminance is required to be considered motion.
|
The threshold value dictates how much of a change in a pixels luminance is required to be considered motion.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# default threshold value
|
|
||||||
motion:
|
motion:
|
||||||
# Optional: The threshold passed to cv2.threshold to determine if a pixel is different enough to be counted as motion. (default: shown below)
|
# Optional: The threshold passed to cv2.threshold to determine if a pixel is different enough to be counted as motion. (default: shown below)
|
||||||
# Increasing this value will make motion detection less sensitive and decreasing it will make motion detection more sensitive.
|
# Increasing this value will make motion detection less sensitive and decreasing it will make motion detection more sensitive.
|
||||||
@ -53,7 +52,6 @@ Watching the motion boxes in the debug view, increase the threshold until you on
|
|||||||
### Contour Area
|
### Contour Area
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# default contour_area value
|
|
||||||
motion:
|
motion:
|
||||||
# Optional: Minimum size in pixels in the resized motion image that counts as motion (default: shown below)
|
# Optional: Minimum size in pixels in the resized motion image that counts as motion (default: shown below)
|
||||||
# Increasing this value will prevent smaller areas of motion from being detected. Decreasing will
|
# Increasing this value will prevent smaller areas of motion from being detected. Decreasing will
|
||||||
@ -81,27 +79,49 @@ However, if the preferred day settings do not work well at night it is recommend
|
|||||||
|
|
||||||
## Tuning For Large Changes In Motion
|
## Tuning For Large Changes In Motion
|
||||||
|
|
||||||
|
### Lightning Threshold
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# default lightning_threshold:
|
|
||||||
motion:
|
motion:
|
||||||
# Optional: The percentage of the image used to detect lightning or other substantial changes where motion detection
|
# Optional: The percentage of the image used to detect lightning or
|
||||||
# needs to recalibrate. (default: shown below)
|
# other substantial changes where motion detection needs to
|
||||||
# Increasing this value will make motion detection more likely to consider lightning or ir mode changes as valid motion.
|
# recalibrate. (default: shown below)
|
||||||
# Decreasing this value will make motion detection more likely to ignore large amounts of motion such as a person approaching
|
# Increasing this value will make motion detection more likely
|
||||||
# a doorbell camera.
|
# to consider lightning or IR mode changes as valid motion.
|
||||||
|
# Decreasing this value will make motion detection more likely
|
||||||
|
# to ignore large amounts of motion such as a person
|
||||||
|
# approaching a doorbell camera.
|
||||||
lightning_threshold: 0.8
|
lightning_threshold: 0.8
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Large changes in motion like PTZ moves and camera switches between Color and IR mode should result in a pause in object detection. `lightning_threshold` defines the percentage of the image used to detect these substantial changes. Increasing this value makes motion detection more likely to treat large changes (like IR mode switches) as valid motion. Decreasing it makes motion detection more likely to ignore large amounts of motion, such as a person approaching a doorbell camera.
|
||||||
|
|
||||||
|
Note that `lightning_threshold` does **not** stop motion-based recordings from being saved — it only prevents additional motion analysis after the threshold is exceeded, reducing false positive object detections during high-motion periods (e.g. storms or PTZ sweeps) without interfering with recordings.
|
||||||
|
|
||||||
:::warning
|
:::warning
|
||||||
|
|
||||||
Some cameras like doorbell cameras may have missed detections when someone walks directly in front of the camera and the lightning_threshold causes motion detection to be re-calibrated. In this case, it may be desirable to increase the `lightning_threshold` to ensure these objects are not missed.
|
Some cameras, like doorbell cameras, may have missed detections when someone walks directly in front of the camera and the `lightning_threshold` causes motion detection to recalibrate. In this case, it may be desirable to increase the `lightning_threshold` to ensure these objects are not missed.
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
:::note
|
### Skip Motion On Large Scene Changes
|
||||||
|
|
||||||
Lightning threshold does not stop motion based recordings from being saved.
|
```yaml
|
||||||
|
motion:
|
||||||
|
# Optional: Fraction of the frame that must change in a single update
|
||||||
|
# before Frigate will completely ignore any motion in that frame.
|
||||||
|
# Values range between 0.0 and 1.0, leave unset (null) to disable.
|
||||||
|
# Setting this to 0.7 would cause Frigate to **skip** reporting
|
||||||
|
# motion boxes when more than 70% of the image appears to change
|
||||||
|
# (e.g. during lightning storms, IR/color mode switches, or other
|
||||||
|
# sudden lighting events).
|
||||||
|
skip_motion_threshold: 0.7
|
||||||
|
```
|
||||||
|
|
||||||
|
This option is handy when you want to prevent large transient changes from triggering recordings or object detection. It differs from `lightning_threshold` because it completely suppresses motion instead of just forcing a recalibration.
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
|
||||||
|
When the skip threshold is exceeded, **no motion is reported** for that frame, meaning **nothing is recorded** for that frame. That means you can miss something important, like a PTZ camera auto-tracking an object or activity while the camera is moving. If you prefer to guarantee that every frame is saved, leave this unset and accept occasional recordings containing scene noise — they typically only take up a few megabytes and are quick to scan in the timeline UI.
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
Large changes in motion like PTZ moves and camera switches between Color and IR mode should result in a pause in object detection. This is done via the `lightning_threshold` configuration. It is defined as the percentage of the image used to detect lightning or other substantial changes where motion detection needs to recalibrate. Increasing this value will make motion detection more likely to consider lightning or IR mode changes as valid motion. Decreasing this value will make motion detection more likely to ignore large amounts of motion such as a person approaching a doorbell camera.
|
|
||||||
|
|||||||
@ -480,12 +480,16 @@ motion:
|
|||||||
# Increasing this value will make motion detection less sensitive and decreasing it will make motion detection more sensitive.
|
# Increasing this value will make motion detection less sensitive and decreasing it will make motion detection more sensitive.
|
||||||
# The value should be between 1 and 255.
|
# The value should be between 1 and 255.
|
||||||
threshold: 30
|
threshold: 30
|
||||||
# Optional: The percentage of the image used to detect lightning or other substantial changes where motion detection
|
# Optional: The percentage of the image used to detect lightning or other substantial changes where motion detection needs
|
||||||
# needs to recalibrate. (default: shown below)
|
# to recalibrate and motion checks stop for that frame. Recordings are unaffected. (default: shown below)
|
||||||
# Increasing this value will make motion detection more likely to consider lightning or ir mode changes as valid motion.
|
# Increasing this value will make motion detection more likely to consider lightning or ir mode changes as valid motion.
|
||||||
# Decreasing this value will make motion detection more likely to ignore large amounts of motion such as a person approaching
|
# Decreasing this value will make motion detection more likely to ignore large amounts of motion such as a person approaching a doorbell camera.
|
||||||
# a doorbell camera.
|
|
||||||
lightning_threshold: 0.8
|
lightning_threshold: 0.8
|
||||||
|
# Optional: Fraction of the frame that must change in a single update before motion boxes are completely
|
||||||
|
# ignored. Values range between 0.0 and 1.0. When exceeded, no motion boxes are reported and **no motion
|
||||||
|
# recording** is created for that frame. Leave unset (null) to disable this feature. Use with care on PTZ
|
||||||
|
# cameras or other situations where you require guaranteed frame capture.
|
||||||
|
skip_motion_threshold: None
|
||||||
# Optional: Minimum size in pixels in the resized motion image that counts as motion (default: shown below)
|
# Optional: Minimum size in pixels in the resized motion image that counts as motion (default: shown below)
|
||||||
# Increasing this value will prevent smaller areas of motion from being detected. Decreasing will
|
# Increasing this value will prevent smaller areas of motion from being detected. Decreasing will
|
||||||
# make motion detection more sensitive to smaller moving objects.
|
# make motion detection more sensitive to smaller moving objects.
|
||||||
|
|||||||
@ -24,10 +24,17 @@ class MotionConfig(FrigateBaseModel):
|
|||||||
lightning_threshold: float = Field(
|
lightning_threshold: float = Field(
|
||||||
default=0.8,
|
default=0.8,
|
||||||
title="Lightning threshold",
|
title="Lightning threshold",
|
||||||
description="Threshold to detect and ignore brief lighting spikes (lower is more sensitive, values between 0.3 and 1.0).",
|
description="Threshold to detect and ignore brief lighting spikes (lower is more sensitive, values between 0.3 and 1.0). This does not prevent motion detection entirely; it merely causes the detector to stop analyzing additional frames once the threshold is exceeded. Motion-based recordings are still created during these events.",
|
||||||
ge=0.3,
|
ge=0.3,
|
||||||
le=1.0,
|
le=1.0,
|
||||||
)
|
)
|
||||||
|
skip_motion_threshold: Optional[float] = Field(
|
||||||
|
default=None,
|
||||||
|
title="Skip motion threshold",
|
||||||
|
description="If set to a value between 0.0 and 1.0, and more than this fraction of the image changes in a single frame, the detector will return no motion boxes and immediately recalibrate. This can save CPU and reduce false positives during lightning, storms, etc., but may miss real events such as a PTZ camera auto‑tracking an object. The trade‑off is between dropping a few megabytes of recordings versus reviewing a couple short clips. Leave unset (None) to disable this feature.",
|
||||||
|
ge=0.0,
|
||||||
|
le=1.0,
|
||||||
|
)
|
||||||
improve_contrast: bool = Field(
|
improve_contrast: bool = Field(
|
||||||
default=True,
|
default=True,
|
||||||
title="Improve contrast",
|
title="Improve contrast",
|
||||||
|
|||||||
@ -176,11 +176,32 @@ class ImprovedMotionDetector(MotionDetector):
|
|||||||
motion_boxes = []
|
motion_boxes = []
|
||||||
pct_motion = 0
|
pct_motion = 0
|
||||||
|
|
||||||
|
# skip motion entirely if the scene change percentage exceeds configured
|
||||||
|
# threshold. this is useful to ignore lighting storms, IR mode switches,
|
||||||
|
# etc. rather than registering them as brief motion and then recalibrating.
|
||||||
|
# note: skipping means the frame is dropped and **no recording will be
|
||||||
|
# created**, which could hide a legitimate object if the camera is actively
|
||||||
|
# auto‑tracking. the alternative is to allow motion and accept a small
|
||||||
|
# recording that can be reviewed in the timeline. disabled by default (None).
|
||||||
|
if (
|
||||||
|
self.config.skip_motion_threshold is not None
|
||||||
|
and pct_motion > self.config.skip_motion_threshold
|
||||||
|
):
|
||||||
|
# force a recalibration so we transition to the new background
|
||||||
|
self.calibrating = True
|
||||||
|
return []
|
||||||
|
|
||||||
# once the motion is less than 5% and the number of contours is < 4, assume its calibrated
|
# once the motion is less than 5% and the number of contours is < 4, assume its calibrated
|
||||||
if pct_motion < 0.05 and len(motion_boxes) <= 4:
|
if pct_motion < 0.05 and len(motion_boxes) <= 4:
|
||||||
self.calibrating = False
|
self.calibrating = False
|
||||||
|
|
||||||
# if calibrating or the motion contours are > 80% of the image area (lightning, ir, ptz) recalibrate
|
# if calibrating or the motion contours are > 80% of the image area
|
||||||
|
# (lightning, ir, ptz) recalibrate. the lightning threshold does **not**
|
||||||
|
# stop motion detection entirely; it simply halts additional processing for
|
||||||
|
# the current frame once the percentage crosses the threshold. this helps
|
||||||
|
# reduce false positive object detections and CPU usage during high‑motion
|
||||||
|
# events. recordings continue to be generated because users expect data
|
||||||
|
# while a PTZ camera is moving.
|
||||||
if self.calibrating or pct_motion > self.config.lightning_threshold:
|
if self.calibrating or pct_motion > self.config.lightning_threshold:
|
||||||
self.calibrating = True
|
self.calibrating = True
|
||||||
|
|
||||||
|
|||||||
91
frigate/test/test_motion_detector.py
Normal file
91
frigate/test/test_motion_detector.py
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from frigate.config.camera.motion import MotionConfig
|
||||||
|
from frigate.motion.improved_motion import ImprovedMotionDetector
|
||||||
|
|
||||||
|
|
||||||
|
class TestImprovedMotionDetector(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
# small frame for testing; actual frames are grayscale
|
||||||
|
self.frame_shape = (100, 100) # height, width
|
||||||
|
self.config = MotionConfig()
|
||||||
|
# motion detector assumes a rasterized_mask attribute exists on config
|
||||||
|
# when update_mask() is called; add one manually by bypassing pydantic.
|
||||||
|
object.__setattr__(
|
||||||
|
self.config,
|
||||||
|
"rasterized_mask",
|
||||||
|
np.ones((self.frame_shape[0], self.frame_shape[1]), dtype=np.uint8),
|
||||||
|
)
|
||||||
|
|
||||||
|
# create minimal PTZ metrics stub to satisfy detector checks
|
||||||
|
class _Stub:
|
||||||
|
def __init__(self, value=False):
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def is_set(self):
|
||||||
|
return bool(self.value)
|
||||||
|
|
||||||
|
class DummyPTZ:
|
||||||
|
def __init__(self):
|
||||||
|
self.autotracker_enabled = _Stub(False)
|
||||||
|
self.motor_stopped = _Stub(False)
|
||||||
|
self.stop_time = _Stub(0)
|
||||||
|
|
||||||
|
self.detector = ImprovedMotionDetector(
|
||||||
|
self.frame_shape, self.config, fps=30, ptz_metrics=DummyPTZ()
|
||||||
|
)
|
||||||
|
|
||||||
|
# establish a baseline frame (all zeros)
|
||||||
|
base_frame = np.zeros(
|
||||||
|
(self.frame_shape[0], self.frame_shape[1]), dtype=np.uint8
|
||||||
|
)
|
||||||
|
self.detector.detect(base_frame)
|
||||||
|
|
||||||
|
def _half_change_frame(self) -> np.ndarray:
|
||||||
|
"""Produce a frame where roughly half of the pixels are different."""
|
||||||
|
frame = np.zeros((self.frame_shape[0], self.frame_shape[1]), dtype=np.uint8)
|
||||||
|
# flip the top half to white
|
||||||
|
frame[: self.frame_shape[0] // 2, :] = 255
|
||||||
|
return frame
|
||||||
|
|
||||||
|
def test_skip_motion_threshold_default(self):
|
||||||
|
"""With the default (None) setting, motion should always be reported."""
|
||||||
|
frame = self._half_change_frame()
|
||||||
|
boxes = self.detector.detect(frame)
|
||||||
|
self.assertTrue(
|
||||||
|
boxes, "Expected motion boxes when skip threshold is unset (disabled)"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_skip_motion_threshold_applied(self):
|
||||||
|
"""Setting a low skip threshold should prevent any boxes from being returned."""
|
||||||
|
# change the config and update the detector reference
|
||||||
|
self.config.skip_motion_threshold = 0.4
|
||||||
|
self.detector.config = self.config
|
||||||
|
self.detector.update_mask()
|
||||||
|
|
||||||
|
frame = self._half_change_frame()
|
||||||
|
boxes = self.detector.detect(frame)
|
||||||
|
self.assertEqual(
|
||||||
|
boxes,
|
||||||
|
[],
|
||||||
|
"Motion boxes should be empty when scene change exceeds skip threshold",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_skip_motion_threshold_does_not_affect_calibration(self):
|
||||||
|
"""Even when skipping, the detector should go into calibrating state."""
|
||||||
|
self.config.skip_motion_threshold = 0.4
|
||||||
|
self.detector.config = self.config
|
||||||
|
self.detector.update_mask()
|
||||||
|
|
||||||
|
frame = self._half_change_frame()
|
||||||
|
_ = self.detector.detect(frame)
|
||||||
|
self.assertTrue(
|
||||||
|
self.detector.calibrating,
|
||||||
|
"Detector should be in calibrating state after skip event",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@ -264,7 +264,11 @@
|
|||||||
},
|
},
|
||||||
"lightning_threshold": {
|
"lightning_threshold": {
|
||||||
"label": "Lightning threshold",
|
"label": "Lightning threshold",
|
||||||
"description": "Threshold to detect and ignore brief lighting spikes (lower is more sensitive, values between 0.3 and 1.0)."
|
"description": "Threshold to detect and ignore brief lighting spikes (lower is more sensitive, values between 0.3 and 1.0). This does not prevent motion detection entirely; it merely causes the detector to stop analyzing additional frames once the threshold is exceeded. Motion-based recordings are still created during these events."
|
||||||
|
},
|
||||||
|
"skip_motion_threshold": {
|
||||||
|
"label": "Skip motion threshold",
|
||||||
|
"description": "If more than this fraction of the image changes in a single frame, the detector will return no motion boxes and immediately recalibrate. This can save CPU and reduce false positives during lightning, storms, etc., but may miss real events such as a PTZ camera auto‑tracking an object. The trade‑off is between dropping a few megabytes of recordings versus reviewing a couple short clips. Range 0.0 to 1.0."
|
||||||
},
|
},
|
||||||
"improve_contrast": {
|
"improve_contrast": {
|
||||||
"label": "Improve contrast",
|
"label": "Improve contrast",
|
||||||
@ -864,7 +868,8 @@
|
|||||||
"description": "A user-friendly name for the zone, displayed in the Frigate UI. If not set, a formatted version of the zone name will be used."
|
"description": "A user-friendly name for the zone, displayed in the Frigate UI. If not set, a formatted version of the zone name will be used."
|
||||||
},
|
},
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"label": "Whether this zone is active. Disabled zones are ignored at runtime."
|
"label": "Enabled",
|
||||||
|
"description": "Enable or disable this zone. Disabled zones are ignored at runtime."
|
||||||
},
|
},
|
||||||
"enabled_in_config": {
|
"enabled_in_config": {
|
||||||
"label": "Keep track of original state of zone."
|
"label": "Keep track of original state of zone."
|
||||||
|
|||||||
@ -1391,7 +1391,11 @@
|
|||||||
},
|
},
|
||||||
"lightning_threshold": {
|
"lightning_threshold": {
|
||||||
"label": "Lightning threshold",
|
"label": "Lightning threshold",
|
||||||
"description": "Threshold to detect and ignore brief lighting spikes (lower is more sensitive, values between 0.3 and 1.0)."
|
"description": "Threshold to detect and ignore brief lighting spikes (lower is more sensitive, values between 0.3 and 1.0). This does not prevent motion detection entirely; it merely causes the detector to stop analyzing additional frames once the threshold is exceeded. Motion-based recordings are still created during these events."
|
||||||
|
},
|
||||||
|
"skip_motion_threshold": {
|
||||||
|
"label": "Skip motion threshold",
|
||||||
|
"description": "If more than this fraction of the image changes in a single frame, the detector will return no motion boxes and immediately recalibrate. This can save CPU and reduce false positives during lightning, storms, etc., but may miss real events such as a PTZ camera auto‑tracking an object. The trade‑off is between dropping a few megabytes of recordings versus reviewing a couple short clips. Range 0.0 to 1.0."
|
||||||
},
|
},
|
||||||
"improve_contrast": {
|
"improve_contrast": {
|
||||||
"label": "Improve contrast",
|
"label": "Improve contrast",
|
||||||
|
|||||||
@ -8,6 +8,7 @@ const motion: SectionConfigOverrides = {
|
|||||||
"enabled",
|
"enabled",
|
||||||
"threshold",
|
"threshold",
|
||||||
"lightning_threshold",
|
"lightning_threshold",
|
||||||
|
"skip_motion_threshold",
|
||||||
"improve_contrast",
|
"improve_contrast",
|
||||||
"contour_area",
|
"contour_area",
|
||||||
"delta_alpha",
|
"delta_alpha",
|
||||||
@ -22,6 +23,7 @@ const motion: SectionConfigOverrides = {
|
|||||||
hiddenFields: ["enabled_in_config", "mask", "raw_mask"],
|
hiddenFields: ["enabled_in_config", "mask", "raw_mask"],
|
||||||
advancedFields: [
|
advancedFields: [
|
||||||
"lightning_threshold",
|
"lightning_threshold",
|
||||||
|
"skip_motion_threshold",
|
||||||
"delta_alpha",
|
"delta_alpha",
|
||||||
"frame_alpha",
|
"frame_alpha",
|
||||||
"frame_height",
|
"frame_height",
|
||||||
@ -33,6 +35,7 @@ const motion: SectionConfigOverrides = {
|
|||||||
"enabled",
|
"enabled",
|
||||||
"threshold",
|
"threshold",
|
||||||
"lightning_threshold",
|
"lightning_threshold",
|
||||||
|
"skip_motion_threshold",
|
||||||
"improve_contrast",
|
"improve_contrast",
|
||||||
"contour_area",
|
"contour_area",
|
||||||
"delta_alpha",
|
"delta_alpha",
|
||||||
|
|||||||
@ -106,6 +106,7 @@ export interface CameraConfig {
|
|||||||
frame_height: number;
|
frame_height: number;
|
||||||
improve_contrast: boolean;
|
improve_contrast: boolean;
|
||||||
lightning_threshold: number;
|
lightning_threshold: number;
|
||||||
|
skip_motion_threshold: number | null;
|
||||||
mask: {
|
mask: {
|
||||||
[maskId: string]: {
|
[maskId: string]: {
|
||||||
friendly_name?: string;
|
friendly_name?: string;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user