relative zooming option for dahua/amcrest cams

This commit is contained in:
Josh Hawkins 2023-09-04 12:59:44 -05:00
parent 6ae9faf5a8
commit 0eb90f6e6d
5 changed files with 95 additions and 37 deletions

View File

@ -52,6 +52,8 @@ cameras:
enabled: False
# Optional: enable/disable camera zooming in/out on objects during autotracking. (default: shown below)
zooming: False
# Optional: enable/disable relative zooming for the camera (default: shown below)
zoom_relative: False
# Optional: list of objects to track from labelmap.txt (default: shown below)
track:
- person
@ -76,6 +78,8 @@ The autotracker will add PTZ motion requests to a queue while the motor is movin
Zooming is an experimental feature and may use significantly more CPU when tracking objects than panning/tilting only. It may be helpful to tweak your camera's autofocus settings if you are noticing focus problems when using zooming.
Relative zooming makes a zoom movement concurrently with any pan/tilt movements and was tested to work with some Dahua and Amcrest PTZs. If zooming behavior is erratic or relative zooming is unsupported, the autotracker will fall back to absolute zooming where any zoom movements are separate from pan/tilt movements.
## Usage applications
In security and surveillance, it's common to use "spotter" cameras in combination with your PTZ. When your fixed spotter camera detects an object, you could use an automation platform like Home Assistant to move the PTZ to a specific preset so that Frigate can begin automatically tracking the object. For example: a residence may have fixed cameras on the east and west side of the property, capturing views up and down a street. When the spotter camera on the west side detects a person, a Home Assistant automation could move the PTZ to a camera preset aimed toward the west. When the object enters the specified zone, Frigate's autotracker could then continue to track the person as it moves out of view of any of the fixed cameras.

View File

@ -575,6 +575,8 @@ cameras:
enabled: False
# Optional: enable/disable camera zooming in/out on objects during autotracking. (default: shown below)
zooming: False
# Optional: enable/disable relative zooming for the camera (default: shown below)
zoom_relative: False
# Optional: list of objects to track from labelmap.txt (default: shown below)
track:
- person

View File

@ -140,6 +140,9 @@ class MqttConfig(FrigateBaseModel):
class PtzAutotrackConfig(FrigateBaseModel):
enabled: bool = Field(default=False, title="Enable PTZ object autotracking.")
zooming: bool = Field(default=False, title="Enable zooming on autotracked object.")
zoom_relative: bool = Field(
default=False, title="Use relative zooming instead of absolute."
)
track: List[str] = Field(default=DEFAULT_TRACKED_OBJECTS, title="Objects to track.")
required_zones: List[str] = Field(
default_factory=list,

View File

@ -231,6 +231,12 @@ class PtzAutoTracker:
)
continue
else:
if (
self.config.cameras[camera].onvif.autotracking.zooming
and self.config.cameras[camera].onvif.autotracking.zoom_relative
):
self.onvif._move_relative(camera, pan, tilt, zoom, 1)
else:
if zoom > 0:
self.onvif._zoom_absolute(camera, zoom, 1)
@ -238,7 +244,7 @@ class PtzAutoTracker:
# on some cameras with cheaper motors it seems like small values can cause jerky movement
# TODO: double check, might not need this
if abs(pan) > 0.02 or abs(tilt) > 0.02:
self.onvif._move_relative(camera, pan, tilt, 1)
self.onvif._move_relative(camera, pan, tilt, 0, 1)
else:
logger.debug(
f"Not moving, pan and tilt too small: {pan}, {tilt}"
@ -263,6 +269,30 @@ class PtzAutoTracker:
)
self.move_queues[camera].put(move_data)
def _should_zoom_in(self, obj, camera):
camera_config = self.config.cameras[camera]
camera_width = camera_config.frame_shape[1]
camera_height = camera_config.frame_shape[0]
camera_area = camera_width * camera_height
bb_left, bb_top, bb_right, bb_bottom = obj.obj_data["box"]
# If bounding box is not within 5% of an edge
# If object area is less than 70% of frame
# Then zoom in, otherwise try zooming out
# should we make these configurable?
edge_threshold = 0.05
area_threshold = 0.7
# returns True to zoom in, False to zoom out
return (
bb_left > edge_threshold * camera_width
and bb_right < (1 - edge_threshold) * camera_width
and bb_top > edge_threshold * camera_height
and bb_bottom < (1 - edge_threshold) * camera_height
and obj.obj_data["area"] < area_threshold * camera_area
)
def _autotrack_move_ptz(self, camera, obj):
camera_config = self.config.cameras[camera]
@ -275,35 +305,38 @@ class PtzAutoTracker:
tilt = (0.5 - (obj.obj_data["centroid"][1] / camera_height)) * 2
# ideas: check object velocity for camera speed?
if (
camera_config.onvif.autotracking.zooming
and camera_config.onvif.autotracking.zoom_relative
):
zoom = obj.obj_data["area"] / (camera_width * camera_height)
# test if we need to zoom out
if not self._should_zoom_in(obj, camera):
zoom = -(1 - zoom)
self._enqueue_move(
camera,
obj.obj_data["frame_time"],
pan,
tilt,
zoom,
)
else:
self._enqueue_move(camera, obj.obj_data["frame_time"], pan, tilt, 0)
def _autotrack_zoom_ptz(self, camera, obj):
camera_config = self.config.cameras[camera]
if camera_config.onvif.autotracking.zooming:
camera_width = camera_config.frame_shape[1]
camera_height = camera_config.frame_shape[0]
camera_area = camera_width * camera_height
bb_left, bb_top, bb_right, bb_bottom = obj.obj_data["box"]
if (
camera_config.onvif.autotracking.zooming
and not camera_config.onvif.autotracking.zoom_relative
):
# absolute zooming
zoom_level = self.ptz_metrics[camera]["ptz_zoom_level"].value
# ensure zooming level is in range
# if so, check if bounding box is 10% of an edge
# if so, try zooming in, otherwise try zooming out
# should we make these configurable?
edge_threshold = 0.1
area_threshold = 0.6
if 0 < zoom_level <= 1:
if (
bb_left > edge_threshold * camera_width
and bb_right < (1 - edge_threshold) * camera_width
and bb_top > edge_threshold * camera_height
and bb_bottom < (1 - edge_threshold) * camera_height
and obj.obj_data["area"] < area_threshold * camera_area
):
if self._should_zoom_in(obj, camera):
zoom = min(1.0, zoom_level + 0.1)
else:
zoom = max(0.0, zoom_level - 0.1)

View File

@ -95,6 +95,7 @@ class OnvifController:
)
# autoracking relative panning/tilting needs a relative zoom value set to 0
# if camera supports relative movement
if self.config.cameras[camera_name].onvif.autotracking.zooming:
zoom_space_id = next(
(
@ -131,7 +132,13 @@ class OnvifController:
"RelativeZoomTranslationSpace"
][0]["URI"]
except Exception:
pass
if self.config.cameras[camera_name].onvif.autotracking.zoom_relative:
self.config.cameras[
camera_name
].onvif.autotracking.zoom_relative = False
logger.warning(
f"Disabling autotracking zooming for {camera_name}: Absolute zoom not supported"
)
if move_request.Speed is None:
move_request.Speed = ptz.GetStatus({"ProfileToken": profile.token}).Position
@ -173,7 +180,6 @@ class OnvifController:
if ptz_config.Spaces and ptz_config.Spaces.RelativeZoomTranslationSpace:
supported_features.append("zoom-r")
# autotracker uses absolute zooming
if ptz_config.Spaces and ptz_config.Spaces.AbsoluteZoomPositionSpace:
supported_features.append("zoom-a")
try:
@ -245,7 +251,7 @@ class OnvifController:
onvif.get_service("ptz").ContinuousMove(move_request)
def _move_relative(self, camera_name: str, pan, tilt, speed) -> None:
def _move_relative(self, camera_name: str, pan, tilt, zoom, speed) -> None:
if "pt-r-fov" not in self.cams[camera_name]["features"]:
logger.error(f"{camera_name} does not support ONVIF RelativeMove (FOV).")
return
@ -298,7 +304,14 @@ class OnvifController:
move_request.Translation.PanTilt.y = tilt
if "zoom-r" in self.cams[camera_name]["features"]:
move_request.Translation.Zoom.x = 0
move_request.Speed = {
"PanTilt": {
"x": speed,
"y": speed,
},
"Zoom": {"x": speed},
}
move_request.Translation.Zoom.x = zoom
onvif.get_service("ptz").RelativeMove(move_request)
@ -306,6 +319,9 @@ class OnvifController:
move_request.Translation.PanTilt.x = 0
move_request.Translation.PanTilt.y = 0
if "zoom-r" in self.cams[camera_name]["features"]:
move_request.Translation.Zoom.x = 0
self.cams[camera_name]["active"] = False
def _move_to_preset(self, camera_name: str, preset: str) -> None:
@ -465,7 +481,7 @@ class OnvifController:
self.ptz_metrics[camera_name]["ptz_stop_time"].value = 0
if self.config.cameras[camera_name].onvif.autotracking.zooming:
# store zoom level as 0 to 1 interpolated from the values of the camera
# store absolute zoom level as 0 to 1 interpolated from the values of the camera
self.ptz_metrics[camera_name]["ptz_zoom_level"].value = numpy.interp(
round(status.Position.Zoom.x, 2),
[0, 1],