Update region grid on startup

This commit is contained in:
Nick Mowen 2023-10-17 06:50:25 -06:00
parent de8232f722
commit 01da588fdf
3 changed files with 70 additions and 28 deletions

View File

@ -36,7 +36,7 @@ from frigate.events.external import ExternalEventProcessor
from frigate.events.maintainer import EventProcessor from frigate.events.maintainer import EventProcessor
from frigate.http import create_app from frigate.http import create_app
from frigate.log import log_process, root_configurer from frigate.log import log_process, root_configurer
from frigate.models import Event, Recordings, RecordingsToDelete, Timeline from frigate.models import Event, Recordings, RecordingsToDelete, Regions, Timeline
from frigate.object_detection import ObjectDetectProcess from frigate.object_detection import ObjectDetectProcess
from frigate.object_processing import TrackedObjectProcessor from frigate.object_processing import TrackedObjectProcessor
from frigate.output import output_frames from frigate.output import output_frames
@ -329,7 +329,7 @@ class FrigateApp:
60, 10 * len([c for c in self.config.cameras.values() if c.enabled]) 60, 10 * len([c for c in self.config.cameras.values() if c.enabled])
), ),
) )
models = [Event, Recordings, RecordingsToDelete, Timeline] models = [Event, Recordings, RecordingsToDelete, Regions, Timeline]
self.db.bind(models) self.db.bind(models)
def init_stats(self) -> None: def init_stats(self) -> None:

View File

@ -1,13 +1,15 @@
"""Utils for reading and writing object detection data.""" """Utils for reading and writing object detection data."""
import datetime
import logging import logging
import cv2 import cv2
import numpy as np import numpy as np
from peewee import DoesNotExist
from frigate.config import CameraConfig, ModelConfig from frigate.config import CameraConfig, ModelConfig
from frigate.detectors.detector_config import PixelFormatEnum from frigate.detectors.detector_config import PixelFormatEnum
from frigate.models import Timeline from frigate.models import Event, Regions, Timeline
from frigate.util.image import ( from frigate.util.image import (
calculate_region, calculate_region,
yuv_region_2_bgr, yuv_region_2_bgr,
@ -17,19 +19,40 @@ from frigate.util.image import (
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
GRID_SIZE = 8
def get_camera_regions_grid(
camera: CameraConfig, grid_size: int = 8 def get_camera_regions_grid(camera: CameraConfig) -> list[list[dict[str, any]]]:
) -> list[list[dict[str, any]]]:
"""Build a grid of expected region sizes for a camera.""" """Build a grid of expected region sizes for a camera."""
# create a grid # get grid from db if available
try:
regions: Regions = Regions.select().where(Regions.camera == camera.name).get()
logger.error(f"The existing grid for is {regions.grid}")
grid = regions.grid
last_update = regions.last_update
except DoesNotExist:
grid = [] grid = []
for x in range(grid_size): for x in range(GRID_SIZE):
row = [] row = []
for y in range(grid_size): for y in range(GRID_SIZE):
row.append({"sizes": []}) row.append({"sizes": []})
grid.append(row) grid.append(row)
last_update = 0
# get events for timeline entries
events = (
Event.select(Event.id)
.where(Event.camera == camera.name)
.where(Event.false_positive != True)
.where(Event.start_time > last_update)
)
valid_event_ids = [e["id"] for e in events.dicts()]
# no new events, return as is
if not valid_event_ids:
return grid
new_update = datetime.datetime.now().timestamp()
timeline = ( timeline = (
Timeline.select( Timeline.select(
*[ *[
@ -38,20 +61,15 @@ def get_camera_regions_grid(
Timeline.data, Timeline.data,
] ]
) )
.where(Timeline.camera == camera.name) .where(Timeline.source_id << valid_event_ids)
.limit(10000) .limit(10000)
.dicts() .dicts()
) )
if not timeline:
return grid
logger.debug(f"There are {len(timeline)} entries for {camera.name}")
width = camera.detect.width width = camera.detect.width
height = camera.detect.height 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
grid_coef = 1.0 / grid_size
for t in timeline: for t in timeline:
if t.get("source") != "tracked_object": if t.get("source") != "tracked_object":
@ -63,8 +81,8 @@ def get_camera_regions_grid(
x = box[0] + (box[2] / 2) x = box[0] + (box[2] / 2)
y = box[1] + (box[3] / 2) y = box[1] + (box[3] / 2)
x_pos = int(x * grid_size) x_pos = int(x * GRID_SIZE)
y_pos = int(y * grid_size) y_pos = int(y * GRID_SIZE)
calculated_region = calculate_region( calculated_region = calculate_region(
(height, width), (height, width),
@ -80,8 +98,8 @@ def get_camera_regions_grid(
(calculated_region[2] - calculated_region[0]) / width (calculated_region[2] - calculated_region[0]) / width
) )
for x in range(grid_size): for x in range(GRID_SIZE):
for y in range(grid_size): for y in range(GRID_SIZE):
cell = grid[x][y] cell = grid[x][y]
logger.debug( 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" 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"
@ -96,6 +114,21 @@ def get_camera_regions_grid(
cell["std_dev"] = std_dev cell["std_dev"] = std_dev
cell["mean"] = mean cell["mean"] = mean
# update db with new grid
region = {
Regions.camera: camera.name,
Regions.grid: grid,
Regions.last_update: new_update,
}
(
Regions.insert(region)
.on_conflict(
conflict_target=[Regions.camera],
update=region,
)
.execute()
)
return grid return grid
@ -121,8 +154,13 @@ def get_region_from_grid(
region_grid: list[list[dict[str, any]]], region_grid: list[list[dict[str, any]]],
) -> list[int]: ) -> list[int]:
"""Get a region for a box based on the region grid.""" """Get a region for a box based on the region grid."""
box = calculate_region(frame_shape, cluster[0], cluster[1], cluster[2], cluster[3], min_region) box = calculate_region(
centroid = (box[0] + (min(frame_shape[1], box[2]) - box[0]) / 2, box[1] + (min(frame_shape[0], box[3]) - box[1]) / 2) frame_shape, cluster[0], cluster[1], cluster[2], cluster[3], min_region
)
centroid = (
box[0] + (min(frame_shape[1], box[2]) - box[0]) / 2,
box[1] + (min(frame_shape[0], box[3]) - box[1]) / 2,
)
grid_x = int(centroid[0] / frame_shape[1] * len(region_grid)) grid_x = int(centroid[0] / frame_shape[1] * len(region_grid))
grid_y = int(centroid[1] / frame_shape[0] * len(region_grid)) grid_y = int(centroid[1] / frame_shape[0] * len(region_grid))
@ -135,7 +173,11 @@ def get_region_from_grid(
calc_size = (box[2] - box[0]) / frame_shape[1] calc_size = (box[2] - box[0]) / frame_shape[1]
# if region is within expected size, don't resize # if region is within expected size, don't resize
if (cell["mean"] - cell["std_dev"]) <= calc_size <= (cell["mean"] + cell["std_dev"]): if (
(cell["mean"] - cell["std_dev"])
<= calc_size
<= (cell["mean"] + cell["std_dev"])
):
return box return box
# TODO not sure how to handle case where cluster is larger than expected region # TODO not sure how to handle case where cluster is larger than expected region
elif calc_size > (cell["mean"] + cell["std_dev"]): elif calc_size > (cell["mean"] + cell["std_dev"]):

View File

@ -27,7 +27,7 @@ SQL = pw.SQL
def migrate(migrator, database, fake=False, **kwargs): def migrate(migrator, database, fake=False, **kwargs):
migrator.sql( migrator.sql(
'CREATE TABLE IF NOT EXISTS "regions" ("camera" VARCHAR(20) NOT NULL PRIMARY KEY, "last_updated" DATETIME NOT NULL, "grid" JSON)' 'CREATE TABLE IF NOT EXISTS "regions" ("camera" VARCHAR(20) NOT NULL PRIMARY KEY, "last_update" DATETIME NOT NULL, "grid" JSON)'
) )