mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-09-10 17:51:45 +02:00
Autotracking improvements (#17955)
* add zoom time to movement predictions * config migrator * add space to face rename regex * more debug * only calculate zoom time of relative move * fix test * make migrated movement weight a zero * check for str and bool for movestatus support
This commit is contained in:
parent
e57dde7bb0
commit
c91c6970de
@ -63,9 +63,9 @@ class PtzAutotrackConfig(FrigateBaseModel):
|
||||
else:
|
||||
raise ValueError("Invalid type for movement_weights")
|
||||
|
||||
if len(weights) != 5:
|
||||
if len(weights) != 6:
|
||||
raise ValueError(
|
||||
"movement_weights must have exactly 5 floats, remove this line from your config and run autotracking calibration"
|
||||
"movement_weights must have exactly 6 floats, remove this line from your config and run autotracking calibration"
|
||||
)
|
||||
|
||||
return weights
|
||||
|
@ -243,7 +243,7 @@ class EmbeddingsContext:
|
||||
)
|
||||
|
||||
def rename_face(self, old_name: str, new_name: str) -> None:
|
||||
valid_name_pattern = r"^[a-zA-Z0-9_-]{1,50}$"
|
||||
valid_name_pattern = r"^[a-zA-Z0-9\s_-]{1,50}$"
|
||||
|
||||
try:
|
||||
sanitized_old_name = sanitize_filename(old_name, replacement_text="_")
|
||||
|
@ -206,6 +206,7 @@ class PtzAutoTracker:
|
||||
self.calibrating: dict[str, object] = {}
|
||||
self.intercept: dict[str, object] = {}
|
||||
self.move_coefficients: dict[str, object] = {}
|
||||
self.zoom_time: dict[str, float] = {}
|
||||
self.zoom_factor: dict[str, object] = {}
|
||||
|
||||
# if cam is set to autotrack, onvif should be set up
|
||||
@ -272,7 +273,12 @@ class PtzAutoTracker:
|
||||
|
||||
move_status_supported = self.onvif.get_service_capabilities(camera)
|
||||
|
||||
if move_status_supported is None or move_status_supported.lower() != "true":
|
||||
if not (
|
||||
isinstance(move_status_supported, bool) and move_status_supported
|
||||
) and not (
|
||||
isinstance(move_status_supported, str)
|
||||
and move_status_supported.lower() == "true"
|
||||
):
|
||||
logger.warning(
|
||||
f"Disabling autotracking for {camera}: ONVIF MoveStatus not supported"
|
||||
)
|
||||
@ -292,7 +298,7 @@ class PtzAutoTracker:
|
||||
self.move_threads[camera].start()
|
||||
|
||||
if camera_config.onvif.autotracking.movement_weights:
|
||||
if len(camera_config.onvif.autotracking.movement_weights) == 5:
|
||||
if len(camera_config.onvif.autotracking.movement_weights) == 6:
|
||||
camera_config.onvif.autotracking.movement_weights = [
|
||||
float(val)
|
||||
for val in camera_config.onvif.autotracking.movement_weights
|
||||
@ -311,7 +317,10 @@ class PtzAutoTracker:
|
||||
camera_config.onvif.autotracking.movement_weights[2]
|
||||
)
|
||||
self.move_coefficients[camera] = (
|
||||
camera_config.onvif.autotracking.movement_weights[3:]
|
||||
camera_config.onvif.autotracking.movement_weights[3:5]
|
||||
)
|
||||
self.zoom_time[camera] = (
|
||||
camera_config.onvif.autotracking.movement_weights[5]
|
||||
)
|
||||
else:
|
||||
camera_config.onvif.autotracking.enabled = False
|
||||
@ -360,6 +369,7 @@ class PtzAutoTracker:
|
||||
!= ZoomingModeEnum.disabled
|
||||
):
|
||||
logger.info(f"Calibration for {camera} in progress: 0% complete")
|
||||
self.zoom_time[camera] = 0
|
||||
|
||||
for i in range(2):
|
||||
# absolute move to 0 - fully zoomed out
|
||||
@ -403,6 +413,7 @@ class PtzAutoTracker:
|
||||
|
||||
zoom_out_values.append(self.ptz_metrics[camera].zoom_level.value)
|
||||
|
||||
zoom_start_time = time.time()
|
||||
# relative move to 0.01
|
||||
self.onvif._move_relative(
|
||||
camera,
|
||||
@ -415,13 +426,45 @@ class PtzAutoTracker:
|
||||
while not self.ptz_metrics[camera].motor_stopped.is_set():
|
||||
self.onvif.get_camera_status(camera)
|
||||
|
||||
zoom_stop_time = time.time()
|
||||
|
||||
full_relative_start_time = time.time()
|
||||
|
||||
self.onvif._move_relative(
|
||||
camera,
|
||||
-1,
|
||||
-1,
|
||||
-1e-2,
|
||||
1,
|
||||
)
|
||||
|
||||
while not self.ptz_metrics[camera].motor_stopped.is_set():
|
||||
self.onvif.get_camera_status(camera)
|
||||
|
||||
full_relative_stop_time = time.time()
|
||||
|
||||
self.onvif._move_relative(
|
||||
camera,
|
||||
1,
|
||||
1,
|
||||
1e-2,
|
||||
1,
|
||||
)
|
||||
|
||||
while not self.ptz_metrics[camera].motor_stopped.is_set():
|
||||
self.onvif.get_camera_status(camera)
|
||||
|
||||
self.zoom_time[camera] = (
|
||||
full_relative_stop_time - full_relative_start_time
|
||||
) - (zoom_stop_time - zoom_start_time)
|
||||
|
||||
zoom_in_values.append(self.ptz_metrics[camera].zoom_level.value)
|
||||
|
||||
self.ptz_metrics[camera].max_zoom.value = max(zoom_in_values)
|
||||
self.ptz_metrics[camera].min_zoom.value = min(zoom_out_values)
|
||||
|
||||
logger.debug(
|
||||
f"{camera}: Calibration values: max zoom: {self.ptz_metrics[camera].max_zoom.value}, min zoom: {self.ptz_metrics[camera].min_zoom.value}"
|
||||
f"{camera}: Calibration values: max zoom: {self.ptz_metrics[camera].max_zoom.value}, min zoom: {self.ptz_metrics[camera].min_zoom.value}, zoom time: {self.zoom_time[camera]}"
|
||||
)
|
||||
|
||||
else:
|
||||
@ -537,6 +580,7 @@ class PtzAutoTracker:
|
||||
self.ptz_metrics[camera].max_zoom.value,
|
||||
self.intercept[camera],
|
||||
*self.move_coefficients[camera],
|
||||
self.zoom_time[camera],
|
||||
]
|
||||
)
|
||||
|
||||
@ -1061,6 +1105,7 @@ class PtzAutoTracker:
|
||||
|
||||
average_velocity = np.zeros((4,))
|
||||
predicted_box = obj.obj_data["box"]
|
||||
zoom_predicted_box = obj.obj_data["box"]
|
||||
|
||||
centroid_x = obj.obj_data["centroid"][0]
|
||||
centroid_y = obj.obj_data["centroid"][1]
|
||||
@ -1069,20 +1114,20 @@ class PtzAutoTracker:
|
||||
pan = ((centroid_x / camera_width) - 0.5) * 2
|
||||
tilt = (0.5 - (centroid_y / camera_height)) * 2
|
||||
|
||||
_, average_velocity = (
|
||||
self._get_valid_velocity(camera, obj)
|
||||
if "velocity" not in self.tracked_object_metrics[camera]
|
||||
else (
|
||||
self.tracked_object_metrics[camera]["valid_velocity"],
|
||||
self.tracked_object_metrics[camera]["velocity"],
|
||||
)
|
||||
)
|
||||
|
||||
if (
|
||||
camera_config.onvif.autotracking.movement_weights
|
||||
): # use estimates if we have available coefficients
|
||||
predicted_movement_time = self._predict_movement_time(camera, pan, tilt)
|
||||
|
||||
_, average_velocity = (
|
||||
self._get_valid_velocity(camera, obj)
|
||||
if "velocity" not in self.tracked_object_metrics[camera]
|
||||
else (
|
||||
self.tracked_object_metrics[camera]["valid_velocity"],
|
||||
self.tracked_object_metrics[camera]["velocity"],
|
||||
)
|
||||
)
|
||||
|
||||
if np.any(average_velocity):
|
||||
# this box could exceed the frame boundaries if velocity is high
|
||||
# but we'll handle that in _enqueue_move() as two separate moves
|
||||
@ -1111,6 +1156,34 @@ class PtzAutoTracker:
|
||||
camera, obj, predicted_box, predicted_movement_time, debug_zoom=True
|
||||
)
|
||||
|
||||
if (
|
||||
camera_config.onvif.autotracking.movement_weights
|
||||
and camera_config.onvif.autotracking.zooming == ZoomingModeEnum.relative
|
||||
and zoom != 0
|
||||
):
|
||||
zoom_predicted_movement_time = 0
|
||||
|
||||
if np.any(average_velocity):
|
||||
zoom_predicted_movement_time = abs(zoom) * self.zoom_time[camera]
|
||||
|
||||
zoom_predicted_box = (
|
||||
predicted_box
|
||||
+ camera_fps * zoom_predicted_movement_time * average_velocity
|
||||
)
|
||||
|
||||
zoom_predicted_box = np.round(zoom_predicted_box).astype(int)
|
||||
|
||||
centroid_x = round((zoom_predicted_box[0] + zoom_predicted_box[2]) / 2)
|
||||
centroid_y = round((zoom_predicted_box[1] + zoom_predicted_box[3]) / 2)
|
||||
|
||||
# recalculate pan and tilt with new centroid
|
||||
pan = ((centroid_x / camera_width) - 0.5) * 2
|
||||
tilt = (0.5 - (centroid_y / camera_height)) * 2
|
||||
|
||||
logger.debug(
|
||||
f"{camera}: Zoom amount: {zoom}, zoom predicted time: {zoom_predicted_movement_time}, zoom predicted box: {tuple(zoom_predicted_box)}"
|
||||
)
|
||||
|
||||
self._enqueue_move(camera, obj.obj_data["frame_time"], pan, tilt, zoom)
|
||||
|
||||
def _autotrack_move_zoom_only(self, camera, obj):
|
||||
@ -1242,7 +1315,7 @@ class PtzAutoTracker:
|
||||
return
|
||||
|
||||
# this is a brand new object that's on our camera, has our label, entered the zone,
|
||||
# is not a false positive, and is not initially motionless
|
||||
# is not a false positive, and is active
|
||||
if (
|
||||
# new object
|
||||
self.tracked_object[camera] is None
|
||||
@ -1252,7 +1325,7 @@ class PtzAutoTracker:
|
||||
and not obj.previous["false_positive"]
|
||||
and not obj.false_positive
|
||||
and not self.tracked_object_history[camera]
|
||||
and obj.obj_data["motionless_count"] == 0
|
||||
and obj.active
|
||||
):
|
||||
logger.debug(
|
||||
f"{camera}: New object: {obj.obj_data['id']} {obj.obj_data['box']} {obj.obj_data['frame_time']}"
|
||||
|
@ -1491,7 +1491,9 @@ class TestConfig(unittest.TestCase):
|
||||
"fps": 5,
|
||||
},
|
||||
"onvif": {
|
||||
"autotracking": {"movement_weights": "0, 1, 1.23, 2.34, 0.50"}
|
||||
"autotracking": {
|
||||
"movement_weights": "0, 1, 1.23, 2.34, 0.50, 1"
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
@ -1504,6 +1506,7 @@ class TestConfig(unittest.TestCase):
|
||||
"1.23",
|
||||
"2.34",
|
||||
"0.5",
|
||||
"1.0",
|
||||
]
|
||||
|
||||
def test_fails_invalid_movement_weights(self):
|
||||
|
@ -319,6 +319,21 @@ def migrate_016_0(config: dict[str, dict[str, any]]) -> dict[str, dict[str, any]
|
||||
|
||||
camera_config["live"] = live_config
|
||||
|
||||
# add another value to movement_weights for autotracking cams
|
||||
onvif_config = camera_config.get("onvif", {})
|
||||
if "autotracking" in onvif_config:
|
||||
movement_weights = (
|
||||
camera_config.get("onvif", {})
|
||||
.get("autotracking")
|
||||
.get("movement_weights", {})
|
||||
)
|
||||
|
||||
if movement_weights and len(movement_weights.split(",")) == 5:
|
||||
onvif_config["autotracking"]["movement_weights"] = (
|
||||
movement_weights + ", 0"
|
||||
)
|
||||
camera_config["onvif"] = onvif_config
|
||||
|
||||
new_config["cameras"][name] = camera_config
|
||||
|
||||
new_config["version"] = "0.16-0"
|
||||
|
Loading…
Reference in New Issue
Block a user