use calibration to determine zoom levels

This commit is contained in:
Josh Hawkins 2023-10-19 13:41:18 -05:00
parent 7ed399cf9e
commit 8a12ef78db
6 changed files with 134 additions and 36 deletions

View File

@ -187,6 +187,12 @@ class FrigateApp:
"ptz_zoom_level": mp.Value("d", 0.0), # type: ignore[typeddict-item] "ptz_zoom_level": 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
# from mypy 0.981 onwards # from mypy 0.981 onwards
"ptz_max_zoom": mp.Value("d", 0.0), # type: ignore[typeddict-item]
# issue https://github.com/python/typeshed/issues/8799
# from mypy 0.981 onwards
"ptz_min_zoom": mp.Value("d", 0.0), # type: ignore[typeddict-item]
# issue https://github.com/python/typeshed/issues/8799
# from mypy 0.981 onwards
} }
self.ptz_metrics[camera_name]["ptz_stopped"].set() self.ptz_metrics[camera_name]["ptz_stopped"].set()
self.feature_metrics[camera_name] = { self.feature_metrics[camera_name] = {

View File

@ -188,8 +188,8 @@ class PtzAutotrackConfig(FrigateBaseModel):
else: else:
raise ValueError("Invalid type for movement_weights") raise ValueError("Invalid type for movement_weights")
if len(weights) != 3: if len(weights) != 5:
raise ValueError("movement_weights must have exactly 3 floats") raise ValueError("movement_weights must have exactly 5 floats")
return weights return weights

View File

@ -58,6 +58,6 @@ AUTOTRACKING_MAX_AREA_RATIO = 0.5
AUTOTRACKING_MOTION_MIN_DISTANCE = 20 AUTOTRACKING_MOTION_MIN_DISTANCE = 20
AUTOTRACKING_MOTION_MAX_POINTS = 500 AUTOTRACKING_MOTION_MAX_POINTS = 500
AUTOTRACKING_MAX_MOVE_METRICS = 500 AUTOTRACKING_MAX_MOVE_METRICS = 500
AUTOTRACKING_ZOOM_OUT_HYSTERESIS = 2.0 AUTOTRACKING_ZOOM_OUT_HYSTERESIS = 1.5
AUTOTRACKING_ZOOM_IN_HYSTERESIS = 0.8 AUTOTRACKING_ZOOM_IN_HYSTERESIS = 0.8
AUTOTRACKING_ZOOM_EDGE_THRESHOLD = 0.05 AUTOTRACKING_ZOOM_EDGE_THRESHOLD = 0.05

View File

@ -267,12 +267,25 @@ class PtzAutoTracker:
self.move_threads[camera].start() self.move_threads[camera].start()
if camera_config.onvif.autotracking.movement_weights: if camera_config.onvif.autotracking.movement_weights:
if len(camera_config.onvif.autotracking.movement_weights) == 5:
self.ptz_metrics[camera][
"ptz_min_zoom"
].value = camera_config.onvif.autotracking.movement_weights[0]
self.ptz_metrics[camera][
"ptz_max_zoom"
].value = camera_config.onvif.autotracking.movement_weights[1]
self.intercept[ self.intercept[
camera camera
] = camera_config.onvif.autotracking.movement_weights[0] ] = camera_config.onvif.autotracking.movement_weights[2]
self.move_coefficients[ self.move_coefficients[
camera camera
] = camera_config.onvif.autotracking.movement_weights[1:] ] = camera_config.onvif.autotracking.movement_weights[3:]
else:
camera_config.onvif.autotracking.enabled = False
self.ptz_metrics[camera]["ptz_autotracker_enabled"].value = False
logger.warning(
f"Autotracker recalibration is required for {camera}. Disabling autotracking."
)
if camera_config.onvif.autotracking.calibrate_on_startup: if camera_config.onvif.autotracking.calibrate_on_startup:
self._calibrate_camera(camera) self._calibrate_camera(camera)
@ -299,11 +312,83 @@ class PtzAutoTracker:
# TODO: take zooming into account too # TODO: take zooming into account too
num_steps = 30 num_steps = 30
step_sizes = np.linspace(0, 1, num_steps) step_sizes = np.linspace(0, 1, num_steps)
zoom_in_values = []
zoom_out_values = []
self.calibrating[camera] = True self.calibrating[camera] = True
logger.info(f"Camera calibration for {camera} in progress") logger.info(f"Camera calibration for {camera} in progress")
# zoom levels test
if (
self.config.cameras[camera].onvif.autotracking.zooming
!= ZoomingModeEnum.disabled
):
logger.info(f"Calibration for {camera} in progress: 0% complete")
for i in range(2):
# absolute move to 0 - fully zoomed out
self.onvif._zoom_absolute(
camera,
self.onvif.cams[camera]["absolute_zoom_range"]["XRange"]["Min"],
1,
)
while not self.ptz_metrics[camera]["ptz_stopped"].is_set():
self.onvif.get_camera_status(camera)
zoom_out_values.append(self.ptz_metrics[camera]["ptz_zoom_level"].value)
self.onvif._zoom_absolute(
camera,
self.onvif.cams[camera]["absolute_zoom_range"]["XRange"]["Max"],
1,
)
while not self.ptz_metrics[camera]["ptz_stopped"].is_set():
self.onvif.get_camera_status(camera)
zoom_in_values.append(self.ptz_metrics[camera]["ptz_zoom_level"].value)
# relative move to -0.01
self.onvif._move_relative(
camera,
0,
0,
-1e-2,
1,
)
while not self.ptz_metrics[camera]["ptz_stopped"].is_set():
self.onvif.get_camera_status(camera)
zoom_out_values.append(self.ptz_metrics[camera]["ptz_zoom_level"].value)
# relative move to 0.01
self.onvif._move_relative(
camera,
0,
0,
1e-2,
1,
)
while not self.ptz_metrics[camera]["ptz_stopped"].is_set():
self.onvif.get_camera_status(camera)
zoom_in_values.append(self.ptz_metrics[camera]["ptz_zoom_level"].value)
self.ptz_metrics[camera]["ptz_max_zoom"].value = max(zoom_in_values)
self.ptz_metrics[camera]["ptz_min_zoom"].value = min(zoom_out_values)
logger.debug(
f'{camera}: Calibration values: max zoom: {self.ptz_metrics[camera]["ptz_max_zoom"].value}, min zoom: {self.ptz_metrics[camera]["ptz_min_zoom"].value}'
)
else:
self.ptz_metrics[camera]["ptz_max_zoom"].value = 1
self.ptz_metrics[camera]["ptz_min_zoom"].value = 0
self.onvif._move_to_preset( self.onvif._move_to_preset(
camera, camera,
self.config.cameras[camera].onvif.autotracking.return_preset.lower(), self.config.cameras[camera].onvif.autotracking.return_preset.lower(),
@ -385,12 +470,16 @@ class PtzAutoTracker:
if calibration: if calibration:
self.intercept[camera] = y[0] self.intercept[camera] = y[0]
# write the intercept and coefficients back to the config file as a comma separated string # write the min zoom, max zoom, intercept, and coefficients
movement_weights = np.concatenate( # back to the config file as a comma separated string
([self.intercept[camera]], self.move_coefficients[camera])
)
self.config.cameras[camera].onvif.autotracking.movement_weights = ", ".join( self.config.cameras[camera].onvif.autotracking.movement_weights = ", ".join(
map(str, movement_weights) str(v)
for v in [
self.ptz_metrics[camera]["ptz_min_zoom"].value,
self.ptz_metrics[camera]["ptz_max_zoom"].value,
self.intercept[camera],
*self.move_coefficients[camera],
]
) )
logger.debug( logger.debug(
@ -439,10 +528,6 @@ class PtzAutoTracker:
weighted_area / (camera_width * camera_height) weighted_area / (camera_width * camera_height)
) ** self.zoom_factor[camera] ) ** self.zoom_factor[camera]
# self.tracked_object_metrics[camera]["target_box"] = statistics.median(
# [obj["area"] for obj in self.tracked_object_history[camera]]
# ) / (camera_width * camera_height)
if "original_target_box" not in self.tracked_object_metrics[camera]: if "original_target_box" not in self.tracked_object_metrics[camera]:
self.tracked_object_metrics[camera][ self.tracked_object_metrics[camera][
"original_target_box" "original_target_box"
@ -473,20 +558,9 @@ class PtzAutoTracker:
continue continue
else: else:
if zoom != 0:
self.tracked_object_metrics[camera][
"last_zoom_time"
] = frame_time
if pan != 0 or tilt != 0:
self.tracked_object_metrics[camera][
"last_move_time"
] = frame_time
if ( if (
self.config.cameras[camera].onvif.autotracking.zooming self.config.cameras[camera].onvif.autotracking.zooming
== ZoomingModeEnum.relative == ZoomingModeEnum.relative
# this enables us to absolutely zoom if we lost an object
and self.tracked_object[camera] is not None
): ):
self.onvif._move_relative(camera, pan, tilt, zoom, 1) self.onvif._move_relative(camera, pan, tilt, zoom, 1)
@ -762,8 +836,14 @@ class PtzAutoTracker:
* AUTOTRACKING_ZOOM_IN_HYSTERESIS * AUTOTRACKING_ZOOM_IN_HYSTERESIS
) )
at_max_zoom = self.ptz_metrics[camera]["ptz_zoom_level"].value == 1 at_max_zoom = (
at_min_zoom = self.ptz_metrics[camera]["ptz_zoom_level"].value == 0 self.ptz_metrics[camera]["ptz_zoom_level"].value
== self.ptz_metrics[camera]["ptz_max_zoom"].value
)
at_min_zoom = (
self.ptz_metrics[camera]["ptz_zoom_level"].value
== self.ptz_metrics[camera]["ptz_min_zoom"].value
)
# debug zooming # debug zooming
if debug_zooming: if debug_zooming:
@ -844,7 +924,6 @@ class PtzAutoTracker:
predicted_movement_time = self._predict_movement_time(camera, pan, tilt) predicted_movement_time = self._predict_movement_time(camera, pan, tilt)
average_velocity, distance = self._get_valid_velocity(camera, obj) average_velocity, distance = self._get_valid_velocity(camera, obj)
# don't move ptz for estimates that are way too high either
if distance != -1: if distance != -1:
# this box could exceed the frame boundaries if velocity is high # this box could exceed the frame boundaries if velocity is high
@ -878,7 +957,7 @@ class PtzAutoTracker:
def _autotrack_move_zoom_only(self, camera, obj): def _autotrack_move_zoom_only(self, camera, obj):
camera_config = self.config.cameras[camera] camera_config = self.config.cameras[camera]
if camera_config.onvif.autotracking.zooming == ZoomingModeEnum.absolute: if camera_config.onvif.autotracking.zooming != ZoomingModeEnum.disabled:
zoom = self._get_zoom_amount(camera, obj, obj.obj_data["box"]) zoom = self._get_zoom_amount(camera, obj, obj.obj_data["box"])
if zoom != 0: if zoom != 0:
@ -917,7 +996,7 @@ class PtzAutoTracker:
# this is our initial zoom in on a new object # this is our initial zoom in on a new object
if "target_box" not in self.tracked_object_metrics[camera]: if "target_box" not in self.tracked_object_metrics[camera]:
zoom = target_box ** self.zoom_factor[camera] zoom = target_box ** self.zoom_factor[camera]
if target_box > self.tracked_object_metrics[camera]["max_target_box"]: if zoom > self.tracked_object_metrics[camera]["max_target_box"]:
zoom = -(1 - zoom) zoom = -(1 - zoom)
logger.debug( logger.debug(
f"{camera}: target box: {target_box}, max: {self.tracked_object_metrics[camera]['max_target_box']}, calc zoom: {zoom}" f"{camera}: target box: {target_box}, max: {self.tracked_object_metrics[camera]['max_target_box']}, calc zoom: {zoom}"
@ -953,8 +1032,7 @@ class PtzAutoTracker:
) )
logger.debug(f"{camera}: Zoom calculation: {zoom}") logger.debug(f"{camera}: Zoom calculation: {zoom}")
if not result: if not result:
# zoom out zoom = -(1 - abs(zoom)) if zoom > 0 else -zoom
zoom = -(1 - abs(zoom)) if zoom > 0 else -(zoom + 1)
logger.debug(f"{camera}: Zooming: {result} Zoom amount: {zoom}") logger.debug(f"{camera}: Zooming: {result} Zoom amount: {zoom}")

View File

@ -77,6 +77,7 @@ class OnvifController:
request = ptz.create_type("GetConfigurations") request = ptz.create_type("GetConfigurations")
configs = ptz.GetConfigurations(request)[0] configs = ptz.GetConfigurations(request)[0]
logger.debug(f"Onvif configs for {camera_name}: {configs}")
request = ptz.create_type("GetConfigurationOptions") request = ptz.create_type("GetConfigurationOptions")
request.ConfigurationToken = profile.PTZConfiguration.token request.ConfigurationToken = profile.PTZConfiguration.token
@ -194,6 +195,17 @@ class OnvifController:
if ptz_config.Spaces and ptz_config.Spaces.RelativeZoomTranslationSpace: if ptz_config.Spaces and ptz_config.Spaces.RelativeZoomTranslationSpace:
supported_features.append("zoom-r") supported_features.append("zoom-r")
try:
# get camera's zoom limits from onvif config
self.cams[camera_name][
"relative_zoom_range"
] = ptz_config.Spaces.RelativeZoomTranslationSpace[0]
except Exception:
if self.config.cameras[camera_name].onvif.autotracking.zooming:
self.config.cameras[camera_name].onvif.autotracking.zooming = False
logger.warning(
f"Disabling autotracking zooming for {camera_name}: Relative zoom not supported"
)
if ptz_config.Spaces and ptz_config.Spaces.AbsoluteZoomPositionSpace: if ptz_config.Spaces and ptz_config.Spaces.AbsoluteZoomPositionSpace:
supported_features.append("zoom-a") supported_features.append("zoom-a")

View File

@ -35,6 +35,8 @@ class PTZMetricsTypes(TypedDict):
ptz_stop_time: Synchronized ptz_stop_time: Synchronized
ptz_frame_time: Synchronized ptz_frame_time: Synchronized
ptz_zoom_level: Synchronized ptz_zoom_level: Synchronized
ptz_max_zoom: Synchronized
ptz_min_zoom: Synchronized
class FeatureMetricsTypes(TypedDict): class FeatureMetricsTypes(TypedDict):