mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-07 11:45:24 +03:00
Smarter Regions
This commit is contained in:
parent
cd35481e92
commit
6b49b81ba4
@ -49,6 +49,7 @@ from frigate.stats import StatsEmitter, stats_init
|
|||||||
from frigate.storage import StorageMaintainer
|
from frigate.storage import StorageMaintainer
|
||||||
from frigate.timeline import TimelineProcessor
|
from frigate.timeline import TimelineProcessor
|
||||||
from frigate.types import CameraMetricsTypes, FeatureMetricsTypes, PTZMetricsTypes
|
from frigate.types import CameraMetricsTypes, FeatureMetricsTypes, PTZMetricsTypes
|
||||||
|
from frigate.util.object import get_camera_regions_grid
|
||||||
from frigate.version import VERSION
|
from frigate.version import VERSION
|
||||||
from frigate.video import capture_camera, track_camera
|
from frigate.video import capture_camera, track_camera
|
||||||
from frigate.watchdog import FrigateWatchdog
|
from frigate.watchdog import FrigateWatchdog
|
||||||
@ -69,6 +70,7 @@ class FrigateApp:
|
|||||||
self.feature_metrics: dict[str, FeatureMetricsTypes] = {}
|
self.feature_metrics: dict[str, FeatureMetricsTypes] = {}
|
||||||
self.ptz_metrics: dict[str, PTZMetricsTypes] = {}
|
self.ptz_metrics: dict[str, PTZMetricsTypes] = {}
|
||||||
self.processes: dict[str, int] = {}
|
self.processes: dict[str, int] = {}
|
||||||
|
self.region_grids: dict[str, list[list[dict[str, any]]]] = {}
|
||||||
|
|
||||||
def set_environment_vars(self) -> None:
|
def set_environment_vars(self) -> None:
|
||||||
for key, value in self.config.environment_vars.items():
|
for key, value in self.config.environment_vars.items():
|
||||||
@ -452,6 +454,10 @@ class FrigateApp:
|
|||||||
output_processor.start()
|
output_processor.start()
|
||||||
logger.info(f"Output process started: {output_processor.pid}")
|
logger.info(f"Output process started: {output_processor.pid}")
|
||||||
|
|
||||||
|
def init_historical_regions(self) -> None:
|
||||||
|
for camera in self.config.cameras.values():
|
||||||
|
self.region_grids[camera.name] = get_camera_regions_grid(camera)
|
||||||
|
|
||||||
def start_camera_processors(self) -> None:
|
def start_camera_processors(self) -> None:
|
||||||
for name, config in self.config.cameras.items():
|
for name, config in self.config.cameras.items():
|
||||||
if not self.config.cameras[name].enabled:
|
if not self.config.cameras[name].enabled:
|
||||||
@ -471,6 +477,7 @@ class FrigateApp:
|
|||||||
self.detected_frames_queue,
|
self.detected_frames_queue,
|
||||||
self.camera_metrics[name],
|
self.camera_metrics[name],
|
||||||
self.ptz_metrics[name],
|
self.ptz_metrics[name],
|
||||||
|
self.region_grids[name],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
camera_process.daemon = True
|
camera_process.daemon = True
|
||||||
@ -611,6 +618,7 @@ class FrigateApp:
|
|||||||
self.start_detectors()
|
self.start_detectors()
|
||||||
self.start_video_output_processor()
|
self.start_video_output_processor()
|
||||||
self.start_ptz_autotracker()
|
self.start_ptz_autotracker()
|
||||||
|
self.init_historical_regions()
|
||||||
self.start_detected_frames_processor()
|
self.start_detected_frames_processor()
|
||||||
self.start_camera_processors()
|
self.start_camera_processors()
|
||||||
self.start_camera_capture_processes()
|
self.start_camera_capture_processes()
|
||||||
|
|||||||
145
frigate/util/object.py
Normal file
145
frigate/util/object.py
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
"""Utils for reading and writing object detection data."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import numpy
|
||||||
|
|
||||||
|
from frigate.config import CameraConfig
|
||||||
|
from frigate.models import Timeline
|
||||||
|
from frigate.util.image import calculate_region
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def get_camera_regions_grid(
|
||||||
|
camera: CameraConfig, grid_size: int = 8
|
||||||
|
) -> list[list[dict[str, any]]]:
|
||||||
|
"""Build a grid of expected region sizes for a camera."""
|
||||||
|
# create a grid
|
||||||
|
grid = []
|
||||||
|
for x in range(grid_size):
|
||||||
|
row = []
|
||||||
|
for y in range(grid_size):
|
||||||
|
row.append({"sizes": []})
|
||||||
|
grid.append(row)
|
||||||
|
|
||||||
|
timeline = (
|
||||||
|
Timeline.select(
|
||||||
|
*[
|
||||||
|
Timeline.camera,
|
||||||
|
Timeline.source,
|
||||||
|
Timeline.data,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
.where(Timeline.camera == camera.name)
|
||||||
|
.limit(10000)
|
||||||
|
.dicts()
|
||||||
|
)
|
||||||
|
|
||||||
|
if not timeline:
|
||||||
|
return grid
|
||||||
|
|
||||||
|
logger.debug(f"There are {len(timeline)} entries for {camera.name}")
|
||||||
|
width = camera.detect.width
|
||||||
|
height = camera.detect.height
|
||||||
|
|
||||||
|
logger.debug(f"The size of grid is {len(grid)} x {len(grid[grid_size - 1])}")
|
||||||
|
grid_coef = 1.0 / grid_size
|
||||||
|
|
||||||
|
for t in timeline:
|
||||||
|
if t.get("source") != "tracked_object":
|
||||||
|
continue
|
||||||
|
|
||||||
|
box = t["data"]["box"]
|
||||||
|
|
||||||
|
# calculate centroid position
|
||||||
|
x = box[0] + (box[2] / 2)
|
||||||
|
y = box[1] + (box[3] / 2)
|
||||||
|
|
||||||
|
x_pos = int(x * grid_size)
|
||||||
|
y_pos = int(y * grid_size)
|
||||||
|
|
||||||
|
calculated_region = calculate_region(
|
||||||
|
(height, width),
|
||||||
|
box[0] * width,
|
||||||
|
box[1] * height,
|
||||||
|
(box[0] + box[2]) * width,
|
||||||
|
(box[1] + box[3]) * height,
|
||||||
|
320,
|
||||||
|
1.35,
|
||||||
|
)
|
||||||
|
# save width of region to grid as relative
|
||||||
|
grid[x_pos][y_pos]["sizes"].append((calculated_region[2] - calculated_region[0]) / width)
|
||||||
|
|
||||||
|
for x in range(grid_size):
|
||||||
|
for y in range(grid_size):
|
||||||
|
cell = grid[x][y]
|
||||||
|
logger.debug(
|
||||||
|
f"stats for cell {x * grid_coef * width},{y * grid_coef * height} -> {(x + 1) * grid_coef * width},{(y + 1) * grid_coef * height} :: {len(cell['sizes'])} objects"
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(cell["sizes"]) == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
std_dev = numpy.std(cell["sizes"])
|
||||||
|
mean = numpy.mean(cell["sizes"])
|
||||||
|
logger.debug(f"std dev: {std_dev} mean: {mean}")
|
||||||
|
cell["std_dev"] = std_dev
|
||||||
|
cell["mean"] = mean
|
||||||
|
|
||||||
|
return grid
|
||||||
|
|
||||||
|
|
||||||
|
def get_cluster_region_from_grid(frame_shape, min_region, cluster, boxes, region_grid):
|
||||||
|
min_x = frame_shape[1]
|
||||||
|
min_y = frame_shape[0]
|
||||||
|
max_x = 0
|
||||||
|
max_y = 0
|
||||||
|
for b in cluster:
|
||||||
|
min_x = min(boxes[b][0], min_x)
|
||||||
|
min_y = min(boxes[b][1], min_y)
|
||||||
|
max_x = max(boxes[b][2], max_x)
|
||||||
|
max_y = max(boxes[b][3], max_y)
|
||||||
|
return get_region_from_grid(
|
||||||
|
frame_shape, [min_x, min_y, max_x, max_y], min_region, region_grid
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_region_from_grid(
|
||||||
|
frame_shape: tuple[int],
|
||||||
|
box: list[int],
|
||||||
|
min_region: int,
|
||||||
|
region_grid: list[list[dict[str, any]]],
|
||||||
|
) -> list[int]:
|
||||||
|
"""Get a region for a box based on the region grid."""
|
||||||
|
centroid = (box[0] - (box[2] - box[0]), box[1] - (box[3] - box[1]))
|
||||||
|
grid_x = int(centroid[0] / frame_shape[1] * len(region_grid))
|
||||||
|
grid_y = int(centroid[1] / frame_shape[0] * len(region_grid))
|
||||||
|
|
||||||
|
cell = region_grid[grid_x][grid_y]
|
||||||
|
|
||||||
|
# if there is no known data, get standard region for motion box
|
||||||
|
if not cell or not cell["sizes"]:
|
||||||
|
return calculate_region(frame_shape, box[0], box[1], box[2], box[3], min_region)
|
||||||
|
|
||||||
|
calc_size = (box[2] - box[0]) / frame_shape[1]
|
||||||
|
|
||||||
|
# if region is within expected size, don't resize
|
||||||
|
if (cell["mean"] - cell["std_dev"]) < calc_size < (cell["mean"] + cell["std_dev"]):
|
||||||
|
return calculate_region(frame_shape, box[0], box[1], box[2], box[3], min_region)
|
||||||
|
# TODO not sure how to handle case where cluster is larger than expected region
|
||||||
|
elif calc_size > (cell["mean"] + cell["std_dev"]):
|
||||||
|
return calculate_region(frame_shape, box[0], box[1], box[2], box[3], min_region)
|
||||||
|
|
||||||
|
size = cell["mean"] * frame_shape[1]
|
||||||
|
|
||||||
|
# get region based on grid size
|
||||||
|
new = calculate_region(
|
||||||
|
frame_shape,
|
||||||
|
max(0, centroid[0] - size / 2),
|
||||||
|
max(0, centroid[1] - size / 2),
|
||||||
|
min(frame_shape[1], centroid[0] + size / 2),
|
||||||
|
min(frame_shape[0], centroid[1] + size / 2),
|
||||||
|
min_region,
|
||||||
|
)
|
||||||
|
return new
|
||||||
@ -37,6 +37,7 @@ from frigate.util.image import (
|
|||||||
yuv_region_2_rgb,
|
yuv_region_2_rgb,
|
||||||
yuv_region_2_yuv,
|
yuv_region_2_yuv,
|
||||||
)
|
)
|
||||||
|
from frigate.util.object import get_cluster_region_from_grid
|
||||||
from frigate.util.services import listen
|
from frigate.util.services import listen
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -457,6 +458,7 @@ def track_camera(
|
|||||||
detected_objects_queue,
|
detected_objects_queue,
|
||||||
process_info,
|
process_info,
|
||||||
ptz_metrics,
|
ptz_metrics,
|
||||||
|
region_grid,
|
||||||
):
|
):
|
||||||
stop_event = mp.Event()
|
stop_event = mp.Event()
|
||||||
|
|
||||||
@ -515,6 +517,7 @@ def track_camera(
|
|||||||
motion_enabled,
|
motion_enabled,
|
||||||
stop_event,
|
stop_event,
|
||||||
ptz_metrics,
|
ptz_metrics,
|
||||||
|
region_grid,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"{name}: exiting subprocess")
|
logger.info(f"{name}: exiting subprocess")
|
||||||
@ -559,6 +562,14 @@ def intersects_any(box_a, boxes):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def inside_any(box_a, boxes):
|
||||||
|
for box in boxes:
|
||||||
|
# check if box_a is inside of box
|
||||||
|
if box_inside(box, box_a):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def detect(
|
def detect(
|
||||||
detect_config: DetectConfig,
|
detect_config: DetectConfig,
|
||||||
object_detector,
|
object_detector,
|
||||||
@ -623,7 +634,9 @@ def get_cluster_boundary(box, min_region):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_cluster_candidates(frame_shape, min_region, boxes):
|
def get_cluster_candidates(
|
||||||
|
frame_shape, min_region, boxes, region_grid: list[list[dict[str, any]]]
|
||||||
|
):
|
||||||
# and create a cluster of other boxes using it's max region size
|
# and create a cluster of other boxes using it's max region size
|
||||||
# only include boxes where the region is an appropriate(except the region could possibly be smaller?)
|
# only include boxes where the region is an appropriate(except the region could possibly be smaller?)
|
||||||
# size in the cluster. in order to be in the cluster, the furthest corner needs to be within x,y offset
|
# size in the cluster. in order to be in the cluster, the furthest corner needs to be within x,y offset
|
||||||
@ -740,6 +753,7 @@ def process_frames(
|
|||||||
motion_enabled: mp.Value,
|
motion_enabled: mp.Value,
|
||||||
stop_event,
|
stop_event,
|
||||||
ptz_metrics: PTZMetricsTypes,
|
ptz_metrics: PTZMetricsTypes,
|
||||||
|
region_grid: list[list[dict[str, any]]],
|
||||||
exit_on_empty: bool = False,
|
exit_on_empty: bool = False,
|
||||||
):
|
):
|
||||||
fps = process_info["process_fps"]
|
fps = process_info["process_fps"]
|
||||||
@ -815,22 +829,37 @@ def process_frames(
|
|||||||
if obj["id"] not in stationary_object_ids
|
if obj["id"] not in stationary_object_ids
|
||||||
]
|
]
|
||||||
|
|
||||||
combined_boxes = tracked_object_boxes
|
# get consolidated regions for tracked objects
|
||||||
# only add in the motion boxes when not calibrating
|
|
||||||
if not motion_detector.is_calibrating():
|
|
||||||
combined_boxes += motion_boxes
|
|
||||||
|
|
||||||
cluster_candidates = get_cluster_candidates(
|
|
||||||
frame_shape, region_min_size, combined_boxes
|
|
||||||
)
|
|
||||||
|
|
||||||
regions = [
|
regions = [
|
||||||
get_cluster_region(
|
get_cluster_region(
|
||||||
frame_shape, region_min_size, candidate, combined_boxes
|
frame_shape, region_min_size, candidate, tracked_object_boxes
|
||||||
|
)
|
||||||
|
for candidate in get_cluster_candidates(
|
||||||
|
frame_shape, region_min_size, tracked_object_boxes, region_grid
|
||||||
)
|
)
|
||||||
for candidate in cluster_candidates
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# only add in the motion boxes when not calibrating
|
||||||
|
if not motion_detector.is_calibrating():
|
||||||
|
# find motion boxes that are not inside tracked object regions
|
||||||
|
standalone_motion_boxes = [b for b in motion_boxes if not inside_any(b, regions)]
|
||||||
|
|
||||||
|
if standalone_motion_boxes:
|
||||||
|
motion_clusters = get_cluster_candidates(
|
||||||
|
frame_shape, region_min_size, standalone_motion_boxes, region_grid
|
||||||
|
)
|
||||||
|
motion_regions = [
|
||||||
|
get_cluster_region_from_grid(
|
||||||
|
frame_shape,
|
||||||
|
region_min_size,
|
||||||
|
candidate,
|
||||||
|
standalone_motion_boxes,
|
||||||
|
region_grid,
|
||||||
|
)
|
||||||
|
for candidate in motion_clusters
|
||||||
|
]
|
||||||
|
regions += motion_regions
|
||||||
|
|
||||||
# if starting up, get the next startup scan region
|
# if starting up, get the next startup scan region
|
||||||
if startup_scan_counter < 9:
|
if startup_scan_counter < 9:
|
||||||
ymin = int(frame_shape[0] / 3 * startup_scan_counter / 3)
|
ymin = int(frame_shape[0] / 3 * startup_scan_counter / 3)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user