mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-07 11:45:24 +03:00
Use object grid to reduce score for objects with unexpected sizes
This commit is contained in:
parent
16dc9f4bf7
commit
21c99cc955
@ -27,6 +27,8 @@ from frigate.util.image import (
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
GRID_SIZE = 8
|
GRID_SIZE = 8
|
||||||
|
MIN_OBJECT_DATA_POINTS = 5
|
||||||
|
MIN_STD_ACCURACY = 0.5
|
||||||
|
|
||||||
|
|
||||||
def get_camera_regions_grid(
|
def get_camera_regions_grid(
|
||||||
@ -36,16 +38,24 @@ def get_camera_regions_grid(
|
|||||||
# get grid from db if available
|
# get grid from db if available
|
||||||
try:
|
try:
|
||||||
regions: Regions = Regions.select().where(Regions.camera == name).get()
|
regions: Regions = Regions.select().where(Regions.camera == name).get()
|
||||||
grid = regions.grid
|
|
||||||
last_update = regions.last_update
|
# check if old style grid is used and rebuild
|
||||||
|
if regions.grid[0][0].get("sizes") is not None:
|
||||||
|
grid = []
|
||||||
|
last_update = 0
|
||||||
|
else:
|
||||||
|
grid = regions.grid
|
||||||
|
last_update = regions.last_update
|
||||||
except DoesNotExist:
|
except DoesNotExist:
|
||||||
grid = []
|
grid = []
|
||||||
|
last_update = 0
|
||||||
|
|
||||||
|
if last_update == 0:
|
||||||
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({"region": {"sizes": []}})
|
||||||
grid.append(row)
|
grid.append(row)
|
||||||
last_update = 0
|
|
||||||
|
|
||||||
# get events for timeline entries
|
# get events for timeline entries
|
||||||
events = (
|
events = (
|
||||||
@ -86,6 +96,9 @@ def get_camera_regions_grid(
|
|||||||
|
|
||||||
box = t["data"]["box"]
|
box = t["data"]["box"]
|
||||||
|
|
||||||
|
###
|
||||||
|
# save region to grid
|
||||||
|
###
|
||||||
# calculate centroid position
|
# calculate centroid position
|
||||||
x = box[0] + (box[2] / 2)
|
x = box[0] + (box[2] / 2)
|
||||||
y = box[1] + (box[3] / 2)
|
y = box[1] + (box[3] / 2)
|
||||||
@ -103,24 +116,40 @@ def get_camera_regions_grid(
|
|||||||
1.35,
|
1.35,
|
||||||
)
|
)
|
||||||
# save width of region to grid as relative
|
# save width of region to grid as relative
|
||||||
grid[x_pos][y_pos]["sizes"].append(
|
grid[x_pos][y_pos]["region"]["sizes"].append(
|
||||||
(calculated_region[2] - calculated_region[0]) / width
|
(calculated_region[2] - calculated_region[0]) / width
|
||||||
)
|
)
|
||||||
|
|
||||||
|
###
|
||||||
|
# save object size to grid
|
||||||
|
###
|
||||||
|
label = t["data"]["label"]
|
||||||
|
x = box[0] + (box[2] / 2)
|
||||||
|
y = box[1] + box[3]
|
||||||
|
|
||||||
|
x_pos = int(x * GRID_SIZE)
|
||||||
|
y_pos = int(y * GRID_SIZE)
|
||||||
|
|
||||||
|
if grid[x_pos][y_pos].get(label):
|
||||||
|
grid[x_pos][y_pos][label]["sizes"].append(box[2] * box[3])
|
||||||
|
else:
|
||||||
|
grid[x_pos][y_pos][label] = {"sizes": [box[2] * box[3]]}
|
||||||
|
|
||||||
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]
|
||||||
|
|
||||||
if len(cell["sizes"]) == 0:
|
|
||||||
continue
|
|
||||||
|
|
||||||
std_dev = np.std(cell["sizes"])
|
|
||||||
mean = np.mean(cell["sizes"])
|
|
||||||
logger.debug(f"std dev: {std_dev} mean: {mean}")
|
|
||||||
cell["x"] = x
|
cell["x"] = x
|
||||||
cell["y"] = y
|
cell["y"] = y
|
||||||
cell["std_dev"] = std_dev
|
|
||||||
cell["mean"] = mean
|
for key in cell.keys():
|
||||||
|
if key in ["x", "y"] or len(cell[key]["sizes"]) == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
std_dev = np.std(cell[key]["sizes"])
|
||||||
|
mean = np.mean(cell[key]["sizes"])
|
||||||
|
logger.debug(f"{x},{y} :: {key} -> std dev: {std_dev} mean: {mean}")
|
||||||
|
cell[key]["std_dev"] = std_dev
|
||||||
|
cell[key]["mean"] = mean
|
||||||
|
|
||||||
# update db with new grid
|
# update db with new grid
|
||||||
region = {
|
region = {
|
||||||
@ -172,7 +201,7 @@ def get_region_from_grid(
|
|||||||
grid_x = int(centroid[0] / frame_shape[1] * GRID_SIZE)
|
grid_x = int(centroid[0] / frame_shape[1] * GRID_SIZE)
|
||||||
grid_y = int(centroid[1] / frame_shape[0] * GRID_SIZE)
|
grid_y = int(centroid[1] / frame_shape[0] * GRID_SIZE)
|
||||||
|
|
||||||
cell = region_grid[grid_x][grid_y]
|
cell = region_grid[grid_x][grid_y]["region"]
|
||||||
|
|
||||||
# if there is no known data, get standard region for motion box
|
# if there is no known data, get standard region for motion box
|
||||||
if not cell or not cell["sizes"]:
|
if not cell or not cell["sizes"]:
|
||||||
@ -424,17 +453,19 @@ def get_startup_regions(
|
|||||||
"""Get a list of regions to run on startup."""
|
"""Get a list of regions to run on startup."""
|
||||||
# return 8 most popular regions for the camera
|
# return 8 most popular regions for the camera
|
||||||
all_cells = np.concatenate(region_grid).flat
|
all_cells = np.concatenate(region_grid).flat
|
||||||
startup_cells = sorted(all_cells, key=lambda c: len(c["sizes"]), reverse=True)[0:8]
|
startup_cells = sorted(
|
||||||
|
all_cells, key=lambda c: len(c["region"]["sizes"]), reverse=True
|
||||||
|
)[0:8]
|
||||||
regions = []
|
regions = []
|
||||||
|
|
||||||
for cell in startup_cells:
|
for cell in startup_cells:
|
||||||
# rest of the cells are empty
|
# rest of the cells are empty
|
||||||
if not cell["sizes"]:
|
if not cell["region"]["sizes"]:
|
||||||
break
|
break
|
||||||
|
|
||||||
x = frame_shape[1] / GRID_SIZE * (0.5 + cell["x"])
|
x = frame_shape[1] / GRID_SIZE * (0.5 + cell["x"])
|
||||||
y = frame_shape[0] / GRID_SIZE * (0.5 + cell["y"])
|
y = frame_shape[0] / GRID_SIZE * (0.5 + cell["y"])
|
||||||
size = cell["mean"] * frame_shape[1]
|
size = cell["region"]["mean"] * frame_shape[1]
|
||||||
regions.append(
|
regions.append(
|
||||||
calculate_region(
|
calculate_region(
|
||||||
frame_shape,
|
frame_shape,
|
||||||
@ -544,3 +575,52 @@ def reduce_detections(
|
|||||||
return get_consolidated_object_detections(
|
return get_consolidated_object_detections(
|
||||||
reduce_overlapping_detections(all_detections)
|
reduce_overlapping_detections(all_detections)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_object_size_with_grid(
|
||||||
|
detect_config: DetectConfig,
|
||||||
|
label: str,
|
||||||
|
score: float,
|
||||||
|
box: tuple[int],
|
||||||
|
region_grid: list[list[dict[str, any]]],
|
||||||
|
) -> tuple[bool, float]:
|
||||||
|
"""Validate if the object is within expected size and return new size if not."""
|
||||||
|
# get object position in grid with bottom center
|
||||||
|
x = box[0] + ((box[2] - box[0]) / 2)
|
||||||
|
y = box[3]
|
||||||
|
x_pos = int(x / detect_config.width * GRID_SIZE)
|
||||||
|
y_pos = int(y / detect_config.height * GRID_SIZE)
|
||||||
|
cell = region_grid[x_pos][y_pos]
|
||||||
|
|
||||||
|
# get data specific for this object type
|
||||||
|
object_data = cell.get(label)
|
||||||
|
|
||||||
|
# check if enough data in grid to process
|
||||||
|
if not object_data or len(object_data["sizes"]) < MIN_OBJECT_DATA_POINTS:
|
||||||
|
return (False, 0.0)
|
||||||
|
|
||||||
|
# check if data is accurate enough to process
|
||||||
|
std_dev = object_data["std_dev"]
|
||||||
|
mean = object_data["mean"]
|
||||||
|
|
||||||
|
if std_dev / mean > MIN_STD_ACCURACY:
|
||||||
|
return (False, 0.0)
|
||||||
|
|
||||||
|
relative_area = ((box[2] - box[0]) / detect_config.width) * (
|
||||||
|
(box[3] - box[1]) / detect_config.height
|
||||||
|
)
|
||||||
|
|
||||||
|
diff = abs(mean - relative_area)
|
||||||
|
|
||||||
|
# if object area is within 1 std deviation, no changes should be made
|
||||||
|
if diff < std_dev:
|
||||||
|
return (False, 0.0)
|
||||||
|
|
||||||
|
logger.debug(f"Comparing for {x_pos},{y_pos} {label} {relative_area} vs {mean} +- {std_dev}")
|
||||||
|
|
||||||
|
# calculate how many std deviations away the area is
|
||||||
|
factor = relative_area / std_dev
|
||||||
|
|
||||||
|
# reduce object score by 5% for every factor
|
||||||
|
new_score = score - factor * 0.05
|
||||||
|
return (True, new_score)
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import time
|
|||||||
import cv2
|
import cv2
|
||||||
from setproctitle import setproctitle
|
from setproctitle import setproctitle
|
||||||
|
|
||||||
from frigate.config import CameraConfig, DetectConfig, ModelConfig
|
from frigate.config import CameraConfig, DetectConfig, FilterConfig, ModelConfig
|
||||||
from frigate.const import (
|
from frigate.const import (
|
||||||
ALL_ATTRIBUTE_LABELS,
|
ALL_ATTRIBUTE_LABELS,
|
||||||
ATTRIBUTE_LABEL_MAP,
|
ATTRIBUTE_LABEL_MAP,
|
||||||
@ -44,6 +44,7 @@ from frigate.util.object import (
|
|||||||
intersects_any,
|
intersects_any,
|
||||||
is_object_filtered,
|
is_object_filtered,
|
||||||
reduce_detections,
|
reduce_detections,
|
||||||
|
validate_object_size_with_grid,
|
||||||
)
|
)
|
||||||
from frigate.util.services import listen
|
from frigate.util.services import listen
|
||||||
|
|
||||||
@ -459,10 +460,11 @@ def detect(
|
|||||||
detect_config: DetectConfig,
|
detect_config: DetectConfig,
|
||||||
object_detector,
|
object_detector,
|
||||||
frame,
|
frame,
|
||||||
model_config,
|
model_config: ModelConfig,
|
||||||
region,
|
region,
|
||||||
objects_to_track,
|
objects_to_track,
|
||||||
object_filters,
|
object_filters: dict[str, FilterConfig],
|
||||||
|
region_grid: list[list[dict[str, any]]],
|
||||||
):
|
):
|
||||||
tensor_input = create_tensor_input(frame, model_config, region)
|
tensor_input = create_tensor_input(frame, model_config, region)
|
||||||
|
|
||||||
@ -484,18 +486,35 @@ def detect(
|
|||||||
height = y_max - y_min
|
height = y_max - y_min
|
||||||
area = width * height
|
area = width * height
|
||||||
ratio = width / max(1, height)
|
ratio = width / max(1, height)
|
||||||
|
label = d[0]
|
||||||
|
score = d[1]
|
||||||
|
box = (x_min, y_min, x_max, y_max)
|
||||||
|
|
||||||
|
# check if object is outside range of expected sizes
|
||||||
|
# only check objects with scores that are > min_score
|
||||||
|
if score >= object_filters[label].min_score:
|
||||||
|
outside_expected_size, new_score = validate_object_size_with_grid(
|
||||||
|
detect_config, d[0], score, box, region_grid
|
||||||
|
)
|
||||||
|
if outside_expected_size:
|
||||||
|
logger.debug(f"{label} {box} has a new score {score} -> {new_score}")
|
||||||
|
score = new_score
|
||||||
|
|
||||||
det = (
|
det = (
|
||||||
d[0],
|
label,
|
||||||
d[1],
|
score,
|
||||||
(x_min, y_min, x_max, y_max),
|
box,
|
||||||
area,
|
area,
|
||||||
ratio,
|
ratio,
|
||||||
region,
|
region,
|
||||||
)
|
)
|
||||||
|
|
||||||
# apply object filters
|
# apply object filters
|
||||||
if is_object_filtered(det, objects_to_track, object_filters):
|
if is_object_filtered(det, objects_to_track, object_filters):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
detections.append(det)
|
detections.append(det)
|
||||||
|
|
||||||
return detections
|
return detections
|
||||||
|
|
||||||
|
|
||||||
@ -690,6 +709,7 @@ def process_frames(
|
|||||||
region,
|
region,
|
||||||
objects_to_track,
|
objects_to_track,
|
||||||
object_filters,
|
object_filters,
|
||||||
|
region_grid,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user