mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-07-30 13:48:07 +02:00
Add MQTT topic for active autotracking (#8419)
* prevent estimate clipping when autotracking * use unclipped estimate in distance function only * remove autotracking velocity changes * publish on init
This commit is contained in:
parent
8b6b83bd62
commit
6eff08eb2d
@ -221,6 +221,10 @@ Topic to turn the PTZ autotracker for a camera on and off. Expected values are `
|
|||||||
|
|
||||||
Topic with current state of the PTZ autotracker for a camera. Published values are `ON` and `OFF`.
|
Topic with current state of the PTZ autotracker for a camera. Published values are `ON` and `OFF`.
|
||||||
|
|
||||||
|
### `frigate/<camera_name>/ptz_autotracker/active`
|
||||||
|
|
||||||
|
Topic to determine if PTZ autotracker is actively tracking an object. Published values are `ON` and `OFF`.
|
||||||
|
|
||||||
### `frigate/<camera_name>/birdseye/set`
|
### `frigate/<camera_name>/birdseye/set`
|
||||||
|
|
||||||
Topic to turn Birdseye for a camera on and off. Expected values are `ON` and `OFF`. Birdseye mode
|
Topic to turn Birdseye for a camera on and off. Expected values are `ON` and `OFF`. Birdseye mode
|
||||||
|
@ -191,7 +191,8 @@ class FrigateApp:
|
|||||||
"i",
|
"i",
|
||||||
self.config.cameras[camera_name].onvif.autotracking.enabled,
|
self.config.cameras[camera_name].onvif.autotracking.enabled,
|
||||||
),
|
),
|
||||||
"ptz_stopped": mp.Event(),
|
"ptz_tracking_active": mp.Event(),
|
||||||
|
"ptz_motor_stopped": mp.Event(),
|
||||||
"ptz_reset": mp.Event(),
|
"ptz_reset": mp.Event(),
|
||||||
"ptz_start_time": mp.Value("d", 0.0), # type: ignore[typeddict-item]
|
"ptz_start_time": mp.Value("d", 0.0), # type: ignore[typeddict-item]
|
||||||
# issue https://github.com/python/typeshed/issues/8799
|
# issue https://github.com/python/typeshed/issues/8799
|
||||||
@ -212,7 +213,7 @@ class FrigateApp:
|
|||||||
# issue https://github.com/python/typeshed/issues/8799
|
# issue https://github.com/python/typeshed/issues/8799
|
||||||
# from mypy 0.981 onwards
|
# from mypy 0.981 onwards
|
||||||
}
|
}
|
||||||
self.ptz_metrics[camera_name]["ptz_stopped"].set()
|
self.ptz_metrics[camera_name]["ptz_motor_stopped"].set()
|
||||||
self.feature_metrics[camera_name] = {
|
self.feature_metrics[camera_name] = {
|
||||||
"audio_enabled": mp.Value( # type: ignore[typeddict-item]
|
"audio_enabled": mp.Value( # type: ignore[typeddict-item]
|
||||||
# issue https://github.com/python/typeshed/issues/8799
|
# issue https://github.com/python/typeshed/issues/8799
|
||||||
@ -444,6 +445,7 @@ class FrigateApp:
|
|||||||
self.config,
|
self.config,
|
||||||
self.onvif_controller,
|
self.onvif_controller,
|
||||||
self.ptz_metrics,
|
self.ptz_metrics,
|
||||||
|
self.dispatcher,
|
||||||
self.stop_event,
|
self.stop_event,
|
||||||
)
|
)
|
||||||
self.ptz_autotracker_thread.start()
|
self.ptz_autotracker_thread.start()
|
||||||
|
@ -18,6 +18,7 @@ from norfair.camera_motion import (
|
|||||||
TranslationTransformationGetter,
|
TranslationTransformationGetter,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from frigate.comms.dispatcher import Dispatcher
|
||||||
from frigate.config import CameraConfig, FrigateConfig, ZoomingModeEnum
|
from frigate.config import CameraConfig, FrigateConfig, ZoomingModeEnum
|
||||||
from frigate.const import (
|
from frigate.const import (
|
||||||
AUTOTRACKING_MAX_AREA_RATIO,
|
AUTOTRACKING_MAX_AREA_RATIO,
|
||||||
@ -144,11 +145,12 @@ class PtzAutoTrackerThread(threading.Thread):
|
|||||||
config: FrigateConfig,
|
config: FrigateConfig,
|
||||||
onvif: OnvifController,
|
onvif: OnvifController,
|
||||||
ptz_metrics: dict[str, PTZMetricsTypes],
|
ptz_metrics: dict[str, PTZMetricsTypes],
|
||||||
|
dispatcher: Dispatcher,
|
||||||
stop_event: MpEvent,
|
stop_event: MpEvent,
|
||||||
) -> None:
|
) -> None:
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
self.name = "ptz_autotracker"
|
self.name = "ptz_autotracker"
|
||||||
self.ptz_autotracker = PtzAutoTracker(config, onvif, ptz_metrics)
|
self.ptz_autotracker = PtzAutoTracker(config, onvif, ptz_metrics, dispatcher)
|
||||||
self.stop_event = stop_event
|
self.stop_event = stop_event
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
@ -175,10 +177,12 @@ class PtzAutoTracker:
|
|||||||
config: FrigateConfig,
|
config: FrigateConfig,
|
||||||
onvif: OnvifController,
|
onvif: OnvifController,
|
||||||
ptz_metrics: PTZMetricsTypes,
|
ptz_metrics: PTZMetricsTypes,
|
||||||
|
dispatcher: Dispatcher,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.config = config
|
self.config = config
|
||||||
self.onvif = onvif
|
self.onvif = onvif
|
||||||
self.ptz_metrics = ptz_metrics
|
self.ptz_metrics = ptz_metrics
|
||||||
|
self.dispatcher = dispatcher
|
||||||
self.tracked_object: dict[str, object] = {}
|
self.tracked_object: dict[str, object] = {}
|
||||||
self.tracked_object_history: dict[str, object] = {}
|
self.tracked_object_history: dict[str, object] = {}
|
||||||
self.tracked_object_metrics: dict[str, object] = {}
|
self.tracked_object_metrics: dict[str, object] = {}
|
||||||
@ -294,6 +298,8 @@ class PtzAutoTracker:
|
|||||||
if camera_config.onvif.autotracking.calibrate_on_startup:
|
if camera_config.onvif.autotracking.calibrate_on_startup:
|
||||||
self._calibrate_camera(camera)
|
self._calibrate_camera(camera)
|
||||||
|
|
||||||
|
self.ptz_metrics[camera]["ptz_tracking_active"].clear()
|
||||||
|
self.dispatcher.publish(f"{camera}/ptz_autotracker/active", "OFF", retain=False)
|
||||||
self.autotracker_init[camera] = True
|
self.autotracker_init[camera] = True
|
||||||
|
|
||||||
def _write_config(self, camera):
|
def _write_config(self, camera):
|
||||||
@ -338,7 +344,7 @@ class PtzAutoTracker:
|
|||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
|
|
||||||
while not self.ptz_metrics[camera]["ptz_stopped"].is_set():
|
while not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set():
|
||||||
self.onvif.get_camera_status(camera)
|
self.onvif.get_camera_status(camera)
|
||||||
|
|
||||||
zoom_out_values.append(self.ptz_metrics[camera]["ptz_zoom_level"].value)
|
zoom_out_values.append(self.ptz_metrics[camera]["ptz_zoom_level"].value)
|
||||||
@ -349,7 +355,7 @@ class PtzAutoTracker:
|
|||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
|
|
||||||
while not self.ptz_metrics[camera]["ptz_stopped"].is_set():
|
while not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set():
|
||||||
self.onvif.get_camera_status(camera)
|
self.onvif.get_camera_status(camera)
|
||||||
|
|
||||||
zoom_in_values.append(self.ptz_metrics[camera]["ptz_zoom_level"].value)
|
zoom_in_values.append(self.ptz_metrics[camera]["ptz_zoom_level"].value)
|
||||||
@ -367,7 +373,7 @@ class PtzAutoTracker:
|
|||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
|
|
||||||
while not self.ptz_metrics[camera]["ptz_stopped"].is_set():
|
while not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set():
|
||||||
self.onvif.get_camera_status(camera)
|
self.onvif.get_camera_status(camera)
|
||||||
|
|
||||||
zoom_out_values.append(
|
zoom_out_values.append(
|
||||||
@ -383,7 +389,7 @@ class PtzAutoTracker:
|
|||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
|
|
||||||
while not self.ptz_metrics[camera]["ptz_stopped"].is_set():
|
while not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set():
|
||||||
self.onvif.get_camera_status(camera)
|
self.onvif.get_camera_status(camera)
|
||||||
|
|
||||||
zoom_in_values.append(
|
zoom_in_values.append(
|
||||||
@ -406,10 +412,10 @@ class PtzAutoTracker:
|
|||||||
self.config.cameras[camera].onvif.autotracking.return_preset.lower(),
|
self.config.cameras[camera].onvif.autotracking.return_preset.lower(),
|
||||||
)
|
)
|
||||||
self.ptz_metrics[camera]["ptz_reset"].set()
|
self.ptz_metrics[camera]["ptz_reset"].set()
|
||||||
self.ptz_metrics[camera]["ptz_stopped"].clear()
|
self.ptz_metrics[camera]["ptz_motor_stopped"].clear()
|
||||||
|
|
||||||
# Wait until the camera finishes moving
|
# Wait until the camera finishes moving
|
||||||
while not self.ptz_metrics[camera]["ptz_stopped"].is_set():
|
while not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set():
|
||||||
self.onvif.get_camera_status(camera)
|
self.onvif.get_camera_status(camera)
|
||||||
|
|
||||||
for step in range(num_steps):
|
for step in range(num_steps):
|
||||||
@ -420,7 +426,7 @@ class PtzAutoTracker:
|
|||||||
self.onvif._move_relative(camera, pan, tilt, 0, 1)
|
self.onvif._move_relative(camera, pan, tilt, 0, 1)
|
||||||
|
|
||||||
# Wait until the camera finishes moving
|
# Wait until the camera finishes moving
|
||||||
while not self.ptz_metrics[camera]["ptz_stopped"].is_set():
|
while not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set():
|
||||||
self.onvif.get_camera_status(camera)
|
self.onvif.get_camera_status(camera)
|
||||||
stop_time = time.time()
|
stop_time = time.time()
|
||||||
|
|
||||||
@ -438,10 +444,10 @@ class PtzAutoTracker:
|
|||||||
self.config.cameras[camera].onvif.autotracking.return_preset.lower(),
|
self.config.cameras[camera].onvif.autotracking.return_preset.lower(),
|
||||||
)
|
)
|
||||||
self.ptz_metrics[camera]["ptz_reset"].set()
|
self.ptz_metrics[camera]["ptz_reset"].set()
|
||||||
self.ptz_metrics[camera]["ptz_stopped"].clear()
|
self.ptz_metrics[camera]["ptz_motor_stopped"].clear()
|
||||||
|
|
||||||
# Wait until the camera finishes moving
|
# Wait until the camera finishes moving
|
||||||
while not self.ptz_metrics[camera]["ptz_stopped"].is_set():
|
while not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set():
|
||||||
self.onvif.get_camera_status(camera)
|
self.onvif.get_camera_status(camera)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
@ -606,7 +612,9 @@ class PtzAutoTracker:
|
|||||||
self.onvif._move_relative(camera, pan, tilt, 0, 1)
|
self.onvif._move_relative(camera, pan, tilt, 0, 1)
|
||||||
|
|
||||||
# Wait until the camera finishes moving
|
# Wait until the camera finishes moving
|
||||||
while not self.ptz_metrics[camera]["ptz_stopped"].is_set():
|
while not self.ptz_metrics[camera][
|
||||||
|
"ptz_motor_stopped"
|
||||||
|
].is_set():
|
||||||
self.onvif.get_camera_status(camera)
|
self.onvif.get_camera_status(camera)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -616,7 +624,7 @@ class PtzAutoTracker:
|
|||||||
self.onvif._zoom_absolute(camera, zoom, 1)
|
self.onvif._zoom_absolute(camera, zoom, 1)
|
||||||
|
|
||||||
# Wait until the camera finishes moving
|
# Wait until the camera finishes moving
|
||||||
while not self.ptz_metrics[camera]["ptz_stopped"].is_set():
|
while not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set():
|
||||||
self.onvif.get_camera_status(camera)
|
self.onvif.get_camera_status(camera)
|
||||||
|
|
||||||
if self.config.cameras[camera].onvif.autotracking.movement_weights:
|
if self.config.cameras[camera].onvif.autotracking.movement_weights:
|
||||||
@ -1118,6 +1126,10 @@ class PtzAutoTracker:
|
|||||||
logger.debug(
|
logger.debug(
|
||||||
f"{camera}: New object: {obj.obj_data['id']} {obj.obj_data['box']} {obj.obj_data['frame_time']}"
|
f"{camera}: New object: {obj.obj_data['id']} {obj.obj_data['box']} {obj.obj_data['frame_time']}"
|
||||||
)
|
)
|
||||||
|
self.ptz_metrics[camera]["ptz_tracking_active"].set()
|
||||||
|
self.dispatcher.publish(
|
||||||
|
f"{camera}/ptz_autotracker/active", "ON", retain=False
|
||||||
|
)
|
||||||
self.tracked_object[camera] = obj
|
self.tracked_object[camera] = obj
|
||||||
|
|
||||||
self.tracked_object_history[camera].append(copy.deepcopy(obj.obj_data))
|
self.tracked_object_history[camera].append(copy.deepcopy(obj.obj_data))
|
||||||
@ -1220,7 +1232,7 @@ class PtzAutoTracker:
|
|||||||
if not self.autotracker_init[camera]:
|
if not self.autotracker_init[camera]:
|
||||||
self._autotracker_setup(self.config.cameras[camera], camera)
|
self._autotracker_setup(self.config.cameras[camera], camera)
|
||||||
# regularly update camera status
|
# regularly update camera status
|
||||||
if not self.ptz_metrics[camera]["ptz_stopped"].is_set():
|
if not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set():
|
||||||
self.onvif.get_camera_status(camera)
|
self.onvif.get_camera_status(camera)
|
||||||
|
|
||||||
# return to preset if tracking is over
|
# return to preset if tracking is over
|
||||||
@ -1243,7 +1255,7 @@ class PtzAutoTracker:
|
|||||||
while not self.move_queues[camera].empty():
|
while not self.move_queues[camera].empty():
|
||||||
self.move_queues[camera].get()
|
self.move_queues[camera].get()
|
||||||
|
|
||||||
self.ptz_metrics[camera]["ptz_stopped"].wait()
|
self.ptz_metrics[camera]["ptz_motor_stopped"].wait()
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"{camera}: Time is {self.ptz_metrics[camera]['ptz_frame_time'].value}, returning to preset: {autotracker_config.return_preset}"
|
f"{camera}: Time is {self.ptz_metrics[camera]['ptz_frame_time'].value}, returning to preset: {autotracker_config.return_preset}"
|
||||||
)
|
)
|
||||||
@ -1253,7 +1265,11 @@ class PtzAutoTracker:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# update stored zoom level from preset
|
# update stored zoom level from preset
|
||||||
if not self.ptz_metrics[camera]["ptz_stopped"].is_set():
|
if not self.ptz_metrics[camera]["ptz_motor_stopped"].is_set():
|
||||||
self.onvif.get_camera_status(camera)
|
self.onvif.get_camera_status(camera)
|
||||||
|
|
||||||
|
self.ptz_metrics[camera]["ptz_tracking_active"].clear()
|
||||||
|
self.dispatcher.publish(
|
||||||
|
f"{camera}/ptz_autotracker/active", "OFF", retain=False
|
||||||
|
)
|
||||||
self.ptz_metrics[camera]["ptz_reset"].set()
|
self.ptz_metrics[camera]["ptz_reset"].set()
|
||||||
|
@ -299,7 +299,7 @@ class OnvifController:
|
|||||||
return
|
return
|
||||||
|
|
||||||
self.cams[camera_name]["active"] = True
|
self.cams[camera_name]["active"] = True
|
||||||
self.ptz_metrics[camera_name]["ptz_stopped"].clear()
|
self.ptz_metrics[camera_name]["ptz_motor_stopped"].clear()
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"{camera_name}: PTZ start time: {self.ptz_metrics[camera_name]['ptz_frame_time'].value}"
|
f"{camera_name}: PTZ start time: {self.ptz_metrics[camera_name]['ptz_frame_time'].value}"
|
||||||
)
|
)
|
||||||
@ -366,7 +366,7 @@ class OnvifController:
|
|||||||
return
|
return
|
||||||
|
|
||||||
self.cams[camera_name]["active"] = True
|
self.cams[camera_name]["active"] = True
|
||||||
self.ptz_metrics[camera_name]["ptz_stopped"].clear()
|
self.ptz_metrics[camera_name]["ptz_motor_stopped"].clear()
|
||||||
self.ptz_metrics[camera_name]["ptz_start_time"].value = 0
|
self.ptz_metrics[camera_name]["ptz_start_time"].value = 0
|
||||||
self.ptz_metrics[camera_name]["ptz_stop_time"].value = 0
|
self.ptz_metrics[camera_name]["ptz_stop_time"].value = 0
|
||||||
move_request = self.cams[camera_name]["move_request"]
|
move_request = self.cams[camera_name]["move_request"]
|
||||||
@ -413,7 +413,7 @@ class OnvifController:
|
|||||||
return
|
return
|
||||||
|
|
||||||
self.cams[camera_name]["active"] = True
|
self.cams[camera_name]["active"] = True
|
||||||
self.ptz_metrics[camera_name]["ptz_stopped"].clear()
|
self.ptz_metrics[camera_name]["ptz_motor_stopped"].clear()
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"{camera_name}: PTZ start time: {self.ptz_metrics[camera_name]['ptz_frame_time'].value}"
|
f"{camera_name}: PTZ start time: {self.ptz_metrics[camera_name]['ptz_frame_time'].value}"
|
||||||
)
|
)
|
||||||
@ -543,8 +543,8 @@ class OnvifController:
|
|||||||
zoom_status is None or zoom_status.lower() == "idle"
|
zoom_status is None or zoom_status.lower() == "idle"
|
||||||
):
|
):
|
||||||
self.cams[camera_name]["active"] = False
|
self.cams[camera_name]["active"] = False
|
||||||
if not self.ptz_metrics[camera_name]["ptz_stopped"].is_set():
|
if not self.ptz_metrics[camera_name]["ptz_motor_stopped"].is_set():
|
||||||
self.ptz_metrics[camera_name]["ptz_stopped"].set()
|
self.ptz_metrics[camera_name]["ptz_motor_stopped"].set()
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"{camera_name}: PTZ stop time: {self.ptz_metrics[camera_name]['ptz_frame_time'].value}"
|
f"{camera_name}: PTZ stop time: {self.ptz_metrics[camera_name]['ptz_frame_time'].value}"
|
||||||
@ -555,8 +555,8 @@ class OnvifController:
|
|||||||
]["ptz_frame_time"].value
|
]["ptz_frame_time"].value
|
||||||
else:
|
else:
|
||||||
self.cams[camera_name]["active"] = True
|
self.cams[camera_name]["active"] = True
|
||||||
if self.ptz_metrics[camera_name]["ptz_stopped"].is_set():
|
if self.ptz_metrics[camera_name]["ptz_motor_stopped"].is_set():
|
||||||
self.ptz_metrics[camera_name]["ptz_stopped"].clear()
|
self.ptz_metrics[camera_name]["ptz_motor_stopped"].clear()
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"{camera_name}: PTZ start time: {self.ptz_metrics[camera_name]['ptz_frame_time'].value}"
|
f"{camera_name}: PTZ start time: {self.ptz_metrics[camera_name]['ptz_frame_time'].value}"
|
||||||
@ -586,7 +586,7 @@ class OnvifController:
|
|||||||
|
|
||||||
# some hikvision cams won't update MoveStatus, so warn if it hasn't changed
|
# some hikvision cams won't update MoveStatus, so warn if it hasn't changed
|
||||||
if (
|
if (
|
||||||
not self.ptz_metrics[camera_name]["ptz_stopped"].is_set()
|
not self.ptz_metrics[camera_name]["ptz_motor_stopped"].is_set()
|
||||||
and not self.ptz_metrics[camera_name]["ptz_reset"].is_set()
|
and not self.ptz_metrics[camera_name]["ptz_reset"].is_set()
|
||||||
and self.ptz_metrics[camera_name]["ptz_start_time"].value != 0
|
and self.ptz_metrics[camera_name]["ptz_start_time"].value != 0
|
||||||
and self.ptz_metrics[camera_name]["ptz_frame_time"].value
|
and self.ptz_metrics[camera_name]["ptz_frame_time"].value
|
||||||
|
@ -31,7 +31,8 @@ class CameraMetricsTypes(TypedDict):
|
|||||||
|
|
||||||
class PTZMetricsTypes(TypedDict):
|
class PTZMetricsTypes(TypedDict):
|
||||||
ptz_autotracker_enabled: Synchronized
|
ptz_autotracker_enabled: Synchronized
|
||||||
ptz_stopped: Event
|
ptz_tracking_active: Event
|
||||||
|
ptz_motor_stopped: Event
|
||||||
ptz_reset: Event
|
ptz_reset: Event
|
||||||
ptz_start_time: Synchronized
|
ptz_start_time: Synchronized
|
||||||
ptz_stop_time: Synchronized
|
ptz_stop_time: Synchronized
|
||||||
|
Loading…
Reference in New Issue
Block a user