mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-07 11:45:24 +03:00
revamp zooming to use area moving average
This commit is contained in:
parent
2b20e7e972
commit
18b9540e4f
@ -141,7 +141,9 @@ In security and surveillance, it's common to use "spotter" cameras in combinatio
|
|||||||
|
|
||||||
### The autotracker loses track of my object. Why?
|
### The autotracker loses track of my object. Why?
|
||||||
|
|
||||||
There are many reasons this could be the case. If you are using experimental zooming, your `zoom_factor` value might be too high, the object might be traveling too quickly, the scene might be too dark, there are not enough details in the scene (for example, a PTZ looking down on a driveway or other monotone background without any hard lines), or the scene is otherwise less than optimal for Frigate to maintain tracking.
|
There are many reasons this could be the case. If you are using experimental zooming, your `zoom_factor` value might be too high, the object might be traveling too quickly, the scene might be too dark, there are not enough details in the scene (for example, a PTZ looking down on a driveway or other monotone background without a sufficient number of hard edges or corners), or the scene is otherwise less than optimal for Frigate to maintain tracking.
|
||||||
|
|
||||||
|
Your camera's shutter speed may also be set too low so that blurring occurs with motion. Check your camera's firmware to see if you can increase the shutter speed.
|
||||||
|
|
||||||
Watching Frigate's debug view can help to determine a possible cause. The autotracked object will have a thicker colored box around it.
|
Watching Frigate's debug view can help to determine a possible cause. The autotracked object will have a thicker colored box around it.
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import os
|
|||||||
import queue
|
import queue
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
from collections import deque
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from multiprocessing.synchronize import Event as MpEvent
|
from multiprocessing.synchronize import Event as MpEvent
|
||||||
|
|
||||||
@ -162,7 +163,7 @@ class PtzAutoTrackerThread(threading.Thread):
|
|||||||
# disabled dynamically by mqtt
|
# disabled dynamically by mqtt
|
||||||
if self.ptz_autotracker.tracked_object.get(camera):
|
if self.ptz_autotracker.tracked_object.get(camera):
|
||||||
self.ptz_autotracker.tracked_object[camera] = None
|
self.ptz_autotracker.tracked_object[camera] = None
|
||||||
self.ptz_autotracker.tracked_object_previous[camera] = None
|
self.ptz_autotracker.tracked_object_history[camera].clear()
|
||||||
|
|
||||||
logger.info("Exiting autotracker...")
|
logger.info("Exiting autotracker...")
|
||||||
|
|
||||||
@ -178,8 +179,8 @@ class PtzAutoTracker:
|
|||||||
self.onvif = onvif
|
self.onvif = onvif
|
||||||
self.ptz_metrics = ptz_metrics
|
self.ptz_metrics = ptz_metrics
|
||||||
self.tracked_object: dict[str, object] = {}
|
self.tracked_object: dict[str, object] = {}
|
||||||
self.tracked_object_previous: dict[str, object] = {}
|
self.tracked_object_history: dict[str, object] = {}
|
||||||
self.previous_frame_time: dict[str, object] = {}
|
self.tracked_object_metrics: dict[str, object] = {}
|
||||||
self.object_types: dict[str, object] = {}
|
self.object_types: dict[str, object] = {}
|
||||||
self.required_zones: dict[str, object] = {}
|
self.required_zones: dict[str, object] = {}
|
||||||
self.move_queues: dict[str, object] = {}
|
self.move_queues: dict[str, object] = {}
|
||||||
@ -191,8 +192,6 @@ class PtzAutoTracker:
|
|||||||
self.intercept: dict[str, object] = {}
|
self.intercept: dict[str, object] = {}
|
||||||
self.move_coefficients: dict[str, object] = {}
|
self.move_coefficients: dict[str, object] = {}
|
||||||
self.zoom_factor: dict[str, object] = {}
|
self.zoom_factor: dict[str, object] = {}
|
||||||
self.original_target_box: dict[str, object] = {}
|
|
||||||
self.previous_target_box: dict[str, object] = {}
|
|
||||||
|
|
||||||
# if cam is set to autotrack, onvif should be set up
|
# if cam is set to autotrack, onvif should be set up
|
||||||
for camera, camera_config in self.config.cameras.items():
|
for camera, camera_config in self.config.cameras.items():
|
||||||
@ -211,13 +210,15 @@ class PtzAutoTracker:
|
|||||||
self.zoom_factor[camera] = camera_config.onvif.autotracking.zoom_factor
|
self.zoom_factor[camera] = camera_config.onvif.autotracking.zoom_factor
|
||||||
|
|
||||||
self.tracked_object[camera] = None
|
self.tracked_object[camera] = None
|
||||||
self.tracked_object_previous[camera] = None
|
self.tracked_object_history[camera] = deque(
|
||||||
|
maxlen=round(camera_config.detect.fps * 1.5)
|
||||||
|
)
|
||||||
|
self.tracked_object_metrics[camera] = {}
|
||||||
|
|
||||||
self.calibrating[camera] = False
|
self.calibrating[camera] = False
|
||||||
self.move_metrics[camera] = []
|
self.move_metrics[camera] = []
|
||||||
self.intercept[camera] = None
|
self.intercept[camera] = None
|
||||||
self.move_coefficients[camera] = []
|
self.move_coefficients[camera] = []
|
||||||
self.previous_target_box[camera] = 1
|
|
||||||
|
|
||||||
self.move_queues[camera] = queue.Queue()
|
self.move_queues[camera] = queue.Queue()
|
||||||
self.move_queue_locks[camera] = threading.Lock()
|
self.move_queue_locks[camera] = threading.Lock()
|
||||||
@ -400,11 +401,43 @@ class PtzAutoTracker:
|
|||||||
|
|
||||||
return np.dot(self.move_coefficients[camera], input_data)
|
return np.dot(self.move_coefficients[camera], input_data)
|
||||||
|
|
||||||
def _process_move_queue(self, camera):
|
def _calculate_tracked_object_metrics(self, camera):
|
||||||
|
def remove_outliers(data):
|
||||||
|
Q1 = np.percentile(data, 25)
|
||||||
|
Q3 = np.percentile(data, 75)
|
||||||
|
IQR = Q3 - Q1
|
||||||
|
lower_bound = Q1 - 1.5 * IQR
|
||||||
|
upper_bound = Q3 + 1.5 * IQR
|
||||||
|
return [x for x in data if lower_bound <= x <= upper_bound]
|
||||||
|
|
||||||
camera_config = self.config.cameras[camera]
|
camera_config = self.config.cameras[camera]
|
||||||
camera_width = camera_config.frame_shape[1]
|
camera_width = camera_config.frame_shape[1]
|
||||||
camera_height = camera_config.frame_shape[0]
|
camera_height = camera_config.frame_shape[0]
|
||||||
|
|
||||||
|
# Extract areas and calculate weighted average
|
||||||
|
areas = [obj["area"] for obj in self.tracked_object_history[camera]]
|
||||||
|
filtered_areas = remove_outliers(areas)
|
||||||
|
weights = np.arange(1, len(filtered_areas) + 1)
|
||||||
|
weighted_area = np.average(filtered_areas, weights=weights)
|
||||||
|
|
||||||
|
self.tracked_object_metrics[camera]["target_box"] = weighted_area / (
|
||||||
|
camera_width * camera_height
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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]:
|
||||||
|
self.tracked_object_metrics[camera][
|
||||||
|
"original_target_box"
|
||||||
|
] = self.tracked_object_metrics[camera]["target_box"]
|
||||||
|
|
||||||
|
def _process_move_queue(self, camera):
|
||||||
|
camera_config = self.config.cameras[camera]
|
||||||
|
camera_config.frame_shape[1]
|
||||||
|
camera_config.frame_shape[0]
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
move_data = self.move_queues[camera].get()
|
move_data = self.move_queues[camera].get()
|
||||||
|
|
||||||
@ -426,9 +459,13 @@ class PtzAutoTracker:
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
if zoom != 0:
|
if zoom != 0:
|
||||||
self.previous_target_box[camera] = self.tracked_object[
|
self.tracked_object_metrics[camera][
|
||||||
camera
|
"last_zoom_time"
|
||||||
].obj_data["area"] / (camera_width * camera_height)
|
] = 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
|
||||||
@ -531,6 +568,28 @@ class PtzAutoTracker:
|
|||||||
camera_height = camera_config.frame_shape[0]
|
camera_height = camera_config.frame_shape[0]
|
||||||
camera_fps = camera_config.detect.fps
|
camera_fps = camera_config.detect.fps
|
||||||
|
|
||||||
|
def remove_outliers_iqr(arrays):
|
||||||
|
def is_valid(array):
|
||||||
|
Q1 = np.percentile(array, 25)
|
||||||
|
Q3 = np.percentile(array, 75)
|
||||||
|
IQR = Q3 - Q1
|
||||||
|
lower_bound = Q1 - 1.5 * IQR
|
||||||
|
upper_bound = Q3 + 1.5 * IQR
|
||||||
|
return (lower_bound <= array) & (array <= upper_bound)
|
||||||
|
|
||||||
|
valid_arrays = [array for array in arrays if is_valid(array[1] - array[0])]
|
||||||
|
return valid_arrays
|
||||||
|
|
||||||
|
bbox_start = obj.previous["box"]
|
||||||
|
bbox_end = obj.obj_data["box"]
|
||||||
|
start_time = obj.previous["frame_time"]
|
||||||
|
end_time = obj.obj_data["frame_time"]
|
||||||
|
velocity = [
|
||||||
|
round((end - start) / ((end_time - start_time) * camera_fps))
|
||||||
|
for start, end in zip(bbox_start, bbox_end)
|
||||||
|
]
|
||||||
|
logger.debug(f"{camera}: Calculated velocity: {velocity}")
|
||||||
|
|
||||||
velocities = obj.obj_data["estimate_velocity"]
|
velocities = obj.obj_data["estimate_velocity"]
|
||||||
diff = np.abs(velocities[1] - velocities[0])
|
diff = np.abs(velocities[1] - velocities[0])
|
||||||
|
|
||||||
@ -583,7 +642,7 @@ class PtzAutoTracker:
|
|||||||
|
|
||||||
# larger objects should lower the threshold, smaller objects should raise it
|
# larger objects should lower the threshold, smaller objects should raise it
|
||||||
scaling_factor = 1 - np.log(max_obj / max_frame)
|
scaling_factor = 1 - np.log(max_obj / max_frame)
|
||||||
distance_threshold = 0.1 * max_frame * (scaling_factor / 2)
|
distance_threshold = 0.1 * max_frame * scaling_factor
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"{camera}: Distance: {centroid_distance}, threshold: {distance_threshold}"
|
f"{camera}: Distance: {centroid_distance}, threshold: {distance_threshold}"
|
||||||
@ -598,15 +657,6 @@ class PtzAutoTracker:
|
|||||||
camera_height = camera_config.frame_shape[0]
|
camera_height = camera_config.frame_shape[0]
|
||||||
camera_fps = camera_config.detect.fps
|
camera_fps = camera_config.detect.fps
|
||||||
|
|
||||||
# save target box area of initial zoom to be a limiter for the session
|
|
||||||
if (
|
|
||||||
self.original_target_box[camera] == 1
|
|
||||||
and self.previous_target_box[camera] != 1
|
|
||||||
):
|
|
||||||
self.original_target_box[camera] = (
|
|
||||||
self.previous_target_box[camera] ** self.zoom_factor[camera]
|
|
||||||
)
|
|
||||||
|
|
||||||
average_velocity, distance = self._get_valid_velocity(camera, obj)
|
average_velocity, distance = self._get_valid_velocity(camera, obj)
|
||||||
|
|
||||||
bb_left, bb_top, bb_right, bb_bottom = box
|
bb_left, bb_top, bb_right, bb_bottom = box
|
||||||
@ -624,20 +674,19 @@ class PtzAutoTracker:
|
|||||||
velocity_threshold_x = camera_width * 0.02
|
velocity_threshold_x = camera_width * 0.02
|
||||||
velocity_threshold_y = camera_height * 0.02
|
velocity_threshold_y = camera_height * 0.02
|
||||||
|
|
||||||
away_from_edge = (
|
touching_frame_edges = int(
|
||||||
bb_left > edge_threshold * camera_width
|
(bb_left < edge_threshold * camera_width)
|
||||||
and bb_right < (1 - edge_threshold) * camera_width
|
+ (bb_right > (1 - edge_threshold) * camera_width)
|
||||||
and bb_top > edge_threshold * camera_height
|
+ (bb_top < edge_threshold * camera_height)
|
||||||
and bb_bottom < (1 - edge_threshold) * camera_height
|
+ (bb_bottom > (1 - edge_threshold) * camera_height)
|
||||||
)
|
)
|
||||||
|
|
||||||
# make sure object is centered in the frame
|
# make sure object is centered in the frame
|
||||||
below_distance_threshold = self._below_distance_threshold(camera, obj)
|
below_distance_threshold = self._below_distance_threshold(camera, obj)
|
||||||
|
|
||||||
# ensure object isn't taking up too much of the frame
|
below_dimension_threshold = (bb_right - bb_left) <= camera_width * 0.8 and (
|
||||||
below_dimension_threshold = (bb_right - bb_left) / camera_width < 0.8 and (
|
|
||||||
bb_bottom - bb_top
|
bb_bottom - bb_top
|
||||||
) / camera_height < 0.8
|
) <= camera_height * 0.8
|
||||||
|
|
||||||
# ensure object is not moving quickly
|
# ensure object is not moving quickly
|
||||||
below_velocity_threshold = (
|
below_velocity_threshold = (
|
||||||
@ -648,16 +697,25 @@ class PtzAutoTracker:
|
|||||||
or distance == -1
|
or distance == -1
|
||||||
)
|
)
|
||||||
|
|
||||||
target_box = obj.obj_data["area"] / (camera_width * camera_height)
|
below_area_threshold = (
|
||||||
|
self.tracked_object_metrics[camera]["target_box"]
|
||||||
|
< self.tracked_object_metrics[camera]["original_target_box"]
|
||||||
|
)
|
||||||
|
|
||||||
below_area_threshold = target_box < self.original_target_box[camera]
|
below_zoom_factored_threshold = (
|
||||||
|
self.tracked_object_metrics[camera]["original_target_box"]
|
||||||
|
** self.zoom_factor[camera]
|
||||||
|
< 0.75
|
||||||
|
)
|
||||||
|
|
||||||
# introduce some hysteresis to prevent a yo-yo zooming effect
|
# introduce some hysteresis to prevent a yo-yo zooming effect
|
||||||
zoom_out_hysteresis = target_box > (
|
zoom_out_hysteresis = self.tracked_object_metrics[camera]["target_box"] > (
|
||||||
self.previous_target_box[camera] * AUTOTRACKING_ZOOM_OUT_HYSTERESIS
|
self.tracked_object_metrics[camera]["original_target_box"]
|
||||||
|
* AUTOTRACKING_ZOOM_OUT_HYSTERESIS
|
||||||
)
|
)
|
||||||
zoom_in_hysteresis = target_box < (
|
zoom_in_hysteresis = self.tracked_object_metrics[camera]["target_box"] < (
|
||||||
self.previous_target_box[camera] * AUTOTRACKING_ZOOM_IN_HYSTERESIS
|
self.tracked_object_metrics[camera]["original_target_box"]
|
||||||
|
* AUTOTRACKING_ZOOM_IN_HYSTERESIS
|
||||||
)
|
)
|
||||||
|
|
||||||
at_max_zoom = self.ptz_metrics[camera]["ptz_zoom_level"].value == 1
|
at_max_zoom = self.ptz_metrics[camera]["ptz_zoom_level"].value == 1
|
||||||
@ -666,25 +724,19 @@ class PtzAutoTracker:
|
|||||||
# debug zooming
|
# debug zooming
|
||||||
if debug_zooming:
|
if debug_zooming:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"{camera}: Zoom test: far from left edge: {bb_left > edge_threshold * camera_width}"
|
f"{camera}: Zoom test: far from edges: count: {touching_frame_edges} left: {bb_left < edge_threshold * camera_width}, right: {bb_right > (1 - edge_threshold) * camera_width}, top: {bb_top < edge_threshold * camera_height}, bottom: {bb_bottom > (1 - edge_threshold) * camera_height}"
|
||||||
)
|
|
||||||
logger.debug(
|
|
||||||
f"{camera}: Zoom test: far from right edge: {bb_right < (1 - edge_threshold) * camera_width}"
|
|
||||||
)
|
|
||||||
logger.debug(
|
|
||||||
f"{camera}: Zoom test: far from top edge: {bb_top > edge_threshold * camera_height}"
|
|
||||||
)
|
|
||||||
logger.debug(
|
|
||||||
f"{camera}: Zoom test: far from bottom edge: {bb_bottom < (1 - edge_threshold) * camera_height}"
|
|
||||||
)
|
)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"{camera}: Zoom test: below distance threshold: {(below_distance_threshold)}"
|
f"{camera}: Zoom test: below distance threshold: {(below_distance_threshold)}"
|
||||||
)
|
)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"{camera}: Zoom test: below area threshold: {(below_area_threshold)} original: {self.original_target_box[camera]} target box: {target_box}"
|
f"{camera}: Zoom test: below area threshold: {(below_area_threshold)} original: {self.tracked_object_metrics[camera]['original_target_box']} target box: {self.tracked_object_metrics[camera]['target_box']}"
|
||||||
)
|
)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"{camera}: Zoom test: below dimension threshold: {below_dimension_threshold} width: {(bb_right - bb_left) / camera_width}, height: { (bb_bottom - bb_top) / camera_height}"
|
f"{camera}: Zoom test: below zoom factored threshold: {(below_zoom_factored_threshold)} target box: {self.tracked_object_metrics[camera]['target_box'] ** self.zoom_factor[camera]}"
|
||||||
|
)
|
||||||
|
logger.debug(
|
||||||
|
f"{camera}: Zoom test: below dimension threshold: {below_dimension_threshold} width: {bb_right - bb_left} height: {bb_bottom - bb_top}"
|
||||||
)
|
)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"{camera}: Zoom test: below velocity threshold: {below_velocity_threshold} velocity x: {abs(average_velocity[0])}, x threshold: {velocity_threshold_x}, velocity y: {abs(average_velocity[1])}, y threshold: {velocity_threshold_y}"
|
f"{camera}: Zoom test: below velocity threshold: {below_velocity_threshold} velocity x: {abs(average_velocity[0])}, x threshold: {velocity_threshold_x}, velocity y: {abs(average_velocity[1])}, y threshold: {velocity_threshold_y}"
|
||||||
@ -692,22 +744,16 @@ class PtzAutoTracker:
|
|||||||
logger.debug(f"{camera}: Zoom test: at max zoom: {at_max_zoom}")
|
logger.debug(f"{camera}: Zoom test: at max zoom: {at_max_zoom}")
|
||||||
logger.debug(f"{camera}: Zoom test: at min zoom: {at_min_zoom}")
|
logger.debug(f"{camera}: Zoom test: at min zoom: {at_min_zoom}")
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"{camera}: Zoom test: zoom in hysteresis limit: {zoom_in_hysteresis} previous: {self.previous_target_box[camera]} target: {target_box}"
|
f'{camera}: Zoom test: zoom in hysteresis limit: {zoom_in_hysteresis} value: {AUTOTRACKING_ZOOM_IN_HYSTERESIS} original: {self.tracked_object_metrics[camera]["original_target_box"]} target: {self.tracked_object_metrics[camera]["target_box"]}'
|
||||||
)
|
)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"{camera}: Zoom test: zoom out hysteresis limit: {zoom_out_hysteresis} previous: {self.previous_target_box[camera]} target: {target_box}"
|
f'{camera}: Zoom test: zoom out hysteresis limit: {zoom_out_hysteresis} value: {AUTOTRACKING_ZOOM_OUT_HYSTERESIS} original: {self.tracked_object_metrics[camera]["original_target_box"]} target: {self.tracked_object_metrics[camera]["target_box"]}'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Zoom in conditions (and)
|
# Zoom in conditions (and)
|
||||||
# object area could be larger
|
|
||||||
# object is away from the edge of the frame
|
|
||||||
# object is not moving too quickly
|
|
||||||
# object is small in the frame
|
|
||||||
# object area is below initial area
|
|
||||||
# camera is not fully zoomed in
|
|
||||||
if (
|
if (
|
||||||
zoom_in_hysteresis
|
zoom_in_hysteresis
|
||||||
and away_from_edge
|
and touching_frame_edges == 0
|
||||||
and below_velocity_threshold
|
and below_velocity_threshold
|
||||||
and below_dimension_threshold
|
and below_dimension_threshold
|
||||||
and below_area_threshold
|
and below_area_threshold
|
||||||
@ -716,15 +762,21 @@ class PtzAutoTracker:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
# Zoom out conditions (or)
|
# Zoom out conditions (or)
|
||||||
# object area got too large
|
|
||||||
# object area has at least one dimension that is too large
|
|
||||||
# object is touching an edge and either centered
|
|
||||||
# object is moving too quickly
|
|
||||||
if (
|
if (
|
||||||
zoom_out_hysteresis
|
(
|
||||||
and not below_area_threshold
|
zoom_out_hysteresis
|
||||||
or not below_dimension_threshold
|
and not at_max_zoom
|
||||||
or (not away_from_edge and below_distance_threshold)
|
# and not below_zoom_factored_threshold
|
||||||
|
and (not below_area_threshold or not below_dimension_threshold)
|
||||||
|
)
|
||||||
|
or (
|
||||||
|
zoom_out_hysteresis
|
||||||
|
and not below_area_threshold
|
||||||
|
and at_max_zoom
|
||||||
|
# and not below_zoom_factored_threshold
|
||||||
|
)
|
||||||
|
or (touching_frame_edges == 1 and below_distance_threshold)
|
||||||
|
or touching_frame_edges > 1
|
||||||
or not below_velocity_threshold
|
or not below_velocity_threshold
|
||||||
) and not at_min_zoom:
|
) and not at_min_zoom:
|
||||||
return False
|
return False
|
||||||
@ -786,12 +838,15 @@ class PtzAutoTracker:
|
|||||||
self._enqueue_move(camera, obj.obj_data["frame_time"], pan, tilt, zoom)
|
self._enqueue_move(camera, obj.obj_data["frame_time"], pan, tilt, zoom)
|
||||||
|
|
||||||
def _autotrack_move_zoom_only(self, camera, obj):
|
def _autotrack_move_zoom_only(self, camera, obj):
|
||||||
zoom = self._get_zoom_amount(camera, obj, obj.obj_data["box"])
|
camera_config = self.config.cameras[camera]
|
||||||
|
|
||||||
if zoom != 0:
|
if camera_config.onvif.autotracking.zooming == ZoomingModeEnum.absolute:
|
||||||
self._enqueue_move(camera, obj.obj_data["frame_time"], 0, 0, zoom)
|
zoom = self._get_zoom_amount(camera, obj, obj.obj_data["box"])
|
||||||
|
|
||||||
def _get_zoom_amount(self, camera, obj, predicted_box, debug_zoom=False):
|
if zoom != 0:
|
||||||
|
self._enqueue_move(camera, obj.obj_data["frame_time"], 0, 0, zoom)
|
||||||
|
|
||||||
|
def _get_zoom_amount(self, camera, obj, predicted_box, debug_zoom=True):
|
||||||
camera_config = self.config.cameras[camera]
|
camera_config = self.config.cameras[camera]
|
||||||
|
|
||||||
# frame width and height
|
# frame width and height
|
||||||
@ -802,13 +857,11 @@ class PtzAutoTracker:
|
|||||||
result = None
|
result = None
|
||||||
current_zoom_level = self.ptz_metrics[camera]["ptz_zoom_level"].value
|
current_zoom_level = self.ptz_metrics[camera]["ptz_zoom_level"].value
|
||||||
target_box = obj.obj_data["area"] / (camera_width * camera_height)
|
target_box = obj.obj_data["area"] / (camera_width * camera_height)
|
||||||
if self.tracked_object_previous[camera] is None:
|
|
||||||
self.original_target_box[camera] = 1
|
|
||||||
|
|
||||||
# absolute zooming separately from pan/tilt
|
# absolute zooming separately from pan/tilt
|
||||||
if camera_config.onvif.autotracking.zooming == ZoomingModeEnum.absolute:
|
if camera_config.onvif.autotracking.zooming == ZoomingModeEnum.absolute:
|
||||||
# don't zoom on initial move
|
# don't zoom on initial move
|
||||||
if self.tracked_object_previous[camera] is None:
|
if not self.tracked_object_history[camera]:
|
||||||
zoom = current_zoom_level
|
zoom = current_zoom_level
|
||||||
else:
|
else:
|
||||||
if (
|
if (
|
||||||
@ -823,7 +876,8 @@ class PtzAutoTracker:
|
|||||||
|
|
||||||
# relative zooming concurrently with pan/tilt
|
# relative zooming concurrently with pan/tilt
|
||||||
if camera_config.onvif.autotracking.zooming == ZoomingModeEnum.relative:
|
if camera_config.onvif.autotracking.zooming == ZoomingModeEnum.relative:
|
||||||
if self.tracked_object_previous[camera] is None:
|
# this is our initial zoom in on a new object
|
||||||
|
if not self.tracked_object_metrics[camera]:
|
||||||
zoom = target_box ** self.zoom_factor[camera]
|
zoom = target_box ** self.zoom_factor[camera]
|
||||||
else:
|
else:
|
||||||
if (
|
if (
|
||||||
@ -840,8 +894,13 @@ class PtzAutoTracker:
|
|||||||
zoom = (
|
zoom = (
|
||||||
2
|
2
|
||||||
* (
|
* (
|
||||||
self.previous_target_box[camera]
|
self.tracked_object_metrics[camera]["original_target_box"]
|
||||||
/ (target_box + self.previous_target_box[camera])
|
/ (
|
||||||
|
self.tracked_object_metrics[camera]["target_box"]
|
||||||
|
+ self.tracked_object_metrics[camera][
|
||||||
|
"original_target_box"
|
||||||
|
]
|
||||||
|
)
|
||||||
)
|
)
|
||||||
- 1
|
- 1
|
||||||
)
|
)
|
||||||
@ -875,31 +934,35 @@ class PtzAutoTracker:
|
|||||||
and set(obj.entered_zones) & set(self.required_zones[camera])
|
and set(obj.entered_zones) & set(self.required_zones[camera])
|
||||||
and not obj.previous["false_positive"]
|
and not obj.previous["false_positive"]
|
||||||
and not obj.false_positive
|
and not obj.false_positive
|
||||||
and self.tracked_object_previous[camera] is None
|
and not self.tracked_object_history[camera]
|
||||||
and obj.obj_data["motionless_count"] == 0
|
and obj.obj_data["motionless_count"] == 0
|
||||||
):
|
):
|
||||||
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.tracked_object[camera] = obj
|
self.tracked_object[camera] = obj
|
||||||
|
|
||||||
|
self.tracked_object_history[camera].append(copy.deepcopy(obj.obj_data))
|
||||||
self._autotrack_move_ptz(camera, obj)
|
self._autotrack_move_ptz(camera, obj)
|
||||||
self.tracked_object_previous[camera] = copy.deepcopy(obj)
|
|
||||||
self.previous_frame_time[camera] = obj.obj_data["frame_time"]
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if (
|
if (
|
||||||
# already tracking an object
|
# already tracking an object
|
||||||
self.tracked_object[camera] is not None
|
self.tracked_object[camera] is not None
|
||||||
and self.tracked_object_previous[camera] is not None
|
and self.tracked_object_history[camera]
|
||||||
and obj.obj_data["id"] == self.tracked_object[camera].obj_data["id"]
|
and obj.obj_data["id"] == self.tracked_object[camera].obj_data["id"]
|
||||||
and obj.obj_data["frame_time"] != self.previous_frame_time
|
and obj.obj_data["frame_time"]
|
||||||
|
!= self.tracked_object_history[camera][-1]["frame_time"]
|
||||||
and not ptz_moving_at_frame_time(
|
and not ptz_moving_at_frame_time(
|
||||||
obj.obj_data["frame_time"],
|
obj.obj_data["frame_time"],
|
||||||
self.ptz_metrics[camera]["ptz_start_time"].value,
|
self.ptz_metrics[camera]["ptz_start_time"].value,
|
||||||
self.ptz_metrics[camera]["ptz_stop_time"].value,
|
self.ptz_metrics[camera]["ptz_stop_time"].value,
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
|
self.tracked_object_history[camera].append(copy.deepcopy(obj.obj_data))
|
||||||
|
self._calculate_tracked_object_metrics(camera)
|
||||||
|
|
||||||
if self._below_distance_threshold(camera, obj):
|
if self._below_distance_threshold(camera, obj):
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"{camera}: Existing object (do NOT move ptz): {obj.obj_data['id']} {obj.obj_data['box']} {obj.obj_data['frame_time']}"
|
f"{camera}: Existing object (do NOT move ptz): {obj.obj_data['id']} {obj.obj_data['box']} {obj.obj_data['frame_time']}"
|
||||||
@ -914,9 +977,6 @@ class PtzAutoTracker:
|
|||||||
|
|
||||||
self._autotrack_move_ptz(camera, obj)
|
self._autotrack_move_ptz(camera, obj)
|
||||||
|
|
||||||
self.tracked_object_previous[camera] = copy.deepcopy(obj)
|
|
||||||
self.previous_frame_time[camera] = obj.obj_data["frame_time"]
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -928,11 +988,11 @@ class PtzAutoTracker:
|
|||||||
and obj.obj_data["label"] in self.object_types[camera]
|
and obj.obj_data["label"] in self.object_types[camera]
|
||||||
and not obj.previous["false_positive"]
|
and not obj.previous["false_positive"]
|
||||||
and not obj.false_positive
|
and not obj.false_positive
|
||||||
and self.tracked_object_previous[camera] is not None
|
and self.tracked_object_history[camera]
|
||||||
):
|
):
|
||||||
if (
|
if (
|
||||||
intersection_over_union(
|
intersection_over_union(
|
||||||
self.tracked_object_previous[camera].obj_data["region"],
|
self.tracked_object_history[camera][-1]["region"],
|
||||||
obj.obj_data["box"],
|
obj.obj_data["box"],
|
||||||
)
|
)
|
||||||
< 0.2
|
< 0.2
|
||||||
@ -941,9 +1001,13 @@ class PtzAutoTracker:
|
|||||||
f"{camera}: Reacquired object: {obj.obj_data['id']} {obj.obj_data['box']} {obj.obj_data['frame_time']}"
|
f"{camera}: Reacquired object: {obj.obj_data['id']} {obj.obj_data['box']} {obj.obj_data['frame_time']}"
|
||||||
)
|
)
|
||||||
self.tracked_object[camera] = obj
|
self.tracked_object[camera] = obj
|
||||||
|
|
||||||
|
self.tracked_object_history[camera].clear()
|
||||||
|
self.tracked_object_history[camera].append(
|
||||||
|
copy.deepcopy(obj.obj_data)
|
||||||
|
)
|
||||||
|
self._calculate_tracked_object_metrics(camera)
|
||||||
self._autotrack_move_ptz(camera, obj)
|
self._autotrack_move_ptz(camera, obj)
|
||||||
self.tracked_object_previous[camera] = copy.deepcopy(obj)
|
|
||||||
self.previous_frame_time[camera] = obj.obj_data["frame_time"]
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -957,6 +1021,7 @@ class PtzAutoTracker:
|
|||||||
f"{camera}: End object: {obj.obj_data['id']} {obj.obj_data['box']}"
|
f"{camera}: End object: {obj.obj_data['id']} {obj.obj_data['box']}"
|
||||||
)
|
)
|
||||||
self.tracked_object[camera] = None
|
self.tracked_object[camera] = None
|
||||||
|
self.tracked_object_metrics[camera] = {}
|
||||||
|
|
||||||
def camera_maintenance(self, camera):
|
def camera_maintenance(self, camera):
|
||||||
# bail and don't check anything if we're calibrating or tracking an object
|
# bail and don't check anything if we're calibrating or tracking an object
|
||||||
@ -980,19 +1045,18 @@ class PtzAutoTracker:
|
|||||||
# return to preset if tracking is over
|
# return to preset if tracking is over
|
||||||
if (
|
if (
|
||||||
self.tracked_object[camera] is None
|
self.tracked_object[camera] is None
|
||||||
and self.tracked_object_previous[camera] is not None
|
and self.tracked_object_history[camera]
|
||||||
and (
|
and (
|
||||||
# might want to use a different timestamp here?
|
# might want to use a different timestamp here?
|
||||||
self.ptz_metrics[camera]["ptz_frame_time"].value
|
self.ptz_metrics[camera]["ptz_frame_time"].value
|
||||||
- self.tracked_object_previous[camera].obj_data["frame_time"]
|
- self.tracked_object_history[camera][-1]["frame_time"]
|
||||||
>= autotracker_config.timeout
|
>= autotracker_config.timeout
|
||||||
)
|
)
|
||||||
and autotracker_config.return_preset
|
and autotracker_config.return_preset
|
||||||
):
|
):
|
||||||
# clear tracked object and reset zoom level
|
# clear tracked object and reset zoom level
|
||||||
self.tracked_object[camera] = None
|
self.tracked_object[camera] = None
|
||||||
self.tracked_object_previous[camera] = None
|
self.tracked_object_history[camera].clear()
|
||||||
self.previous_target_box[camera] = 1
|
|
||||||
|
|
||||||
# empty move queue
|
# empty move queue
|
||||||
while not self.move_queues[camera].empty():
|
while not self.move_queues[camera].empty():
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user