mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-10-16 19:52:49 +03:00
Compare mask coordinates numerically (#24666)
CI / AMD64 Build (push) Waiting to run
CI / AMD64 Smoke Test (push) Blocked by required conditions
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
CI / AMD64 Build (push) Waiting to run
CI / AMD64 Smoke Test (push) Blocked by required conditions
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
* Compare mask coordinates numerically * Fix zone coordinate comparisons and normalize mask examples * Fix validation errors for malformed zone coordinates
This commit is contained in:
@@ -556,7 +556,7 @@ motion:
|
|||||||
# Optional: Whether this mask is active (default: true)
|
# Optional: Whether this mask is active (default: true)
|
||||||
enabled: true
|
enabled: true
|
||||||
# Required: Coordinates polygon for the mask
|
# Required: Coordinates polygon for the mask
|
||||||
coordinates: "0.000,0.469,1.000,0.469,1.000,1.000,0.000,1.000"
|
coordinates: "0.000,0.469,1.0,0.469,1.0,1.0,0.000,1.0"
|
||||||
# Optional: improve contrast (default: shown below)
|
# Optional: improve contrast (default: shown below)
|
||||||
# Enables dynamic contrast improvement. This should help improve night detections at the cost of making motion detection more sensitive
|
# Enables dynamic contrast improvement. This should help improve night detections at the cost of making motion detection more sensitive
|
||||||
# for daytime.
|
# for daytime.
|
||||||
|
|||||||
@@ -82,13 +82,13 @@ objects:
|
|||||||
person_filter1:
|
person_filter1:
|
||||||
friendly_name: "Roof area"
|
friendly_name: "Roof area"
|
||||||
enabled: true
|
enabled: true
|
||||||
coordinates: "0.000,0.000,1.000,0.000,1.000,0.400,0.000,0.400"
|
coordinates: "0.000,0.000,1.0,0.000,1.0,0.400,0.000,0.400"
|
||||||
car:
|
car:
|
||||||
mask:
|
mask:
|
||||||
car_filter1:
|
car_filter1:
|
||||||
friendly_name: "Sidewalk area"
|
friendly_name: "Sidewalk area"
|
||||||
enabled: true
|
enabled: true
|
||||||
coordinates: "0.000,0.700,1.000,0.700,1.000,1.000,0.000,1.000"
|
coordinates: "0.000,0.700,1.0,0.700,1.0,1.0,0.000,1.0"
|
||||||
```
|
```
|
||||||
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|||||||
@@ -128,8 +128,8 @@ class ZoneConfig(BaseModel):
|
|||||||
# we know if any points are > 1 then it is using the
|
# we know if any points are > 1 then it is using the
|
||||||
# old native resolution coordinates
|
# old native resolution coordinates
|
||||||
if isinstance(coordinates, list):
|
if isinstance(coordinates, list):
|
||||||
explicit = any(p.split(",")[0] > "1.0" for p in coordinates)
|
|
||||||
try:
|
try:
|
||||||
|
explicit = any(float(p.split(",")[0]) > 1.0 for p in coordinates)
|
||||||
self._contour = np.array(
|
self._contour = np.array(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
@@ -157,8 +157,8 @@ class ZoneConfig(BaseModel):
|
|||||||
)
|
)
|
||||||
elif isinstance(coordinates, str):
|
elif isinstance(coordinates, str):
|
||||||
points = coordinates.split(",")
|
points = coordinates.split(",")
|
||||||
explicit = any(p > "1.0" for p in points)
|
|
||||||
try:
|
try:
|
||||||
|
explicit = any(float(p) > 1.0 for p in points)
|
||||||
self._contour = np.array(
|
self._contour = np.array(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
"""Tests for mask coordinate normalization and rasterization."""
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
from itertools import product
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from frigate.config.config import RuntimeFilterConfig, RuntimeMotionConfig
|
||||||
|
|
||||||
|
|
||||||
|
class TestMaskCoordinates(unittest.TestCase):
|
||||||
|
def test_relative_coordinate_precision(self):
|
||||||
|
cases = (
|
||||||
|
("0.000,0.000,{edge},0.000,{edge},0.400,0.000,0.400", slice(0, 41)),
|
||||||
|
("0.000,0.700,{edge},0.700,{edge},{edge},0.000,{edge}", slice(70, 100)),
|
||||||
|
)
|
||||||
|
for config_type, (template, rows), edge, as_list in product(
|
||||||
|
(RuntimeMotionConfig, RuntimeFilterConfig),
|
||||||
|
cases,
|
||||||
|
("1", "1.0", "1.00", "1.000", "1.000000"),
|
||||||
|
(False, True),
|
||||||
|
):
|
||||||
|
coordinates = template.format(edge=edge)
|
||||||
|
if as_list:
|
||||||
|
coordinates = [coordinates]
|
||||||
|
with self.subTest(
|
||||||
|
config_type=config_type.__name__, coordinates=coordinates
|
||||||
|
):
|
||||||
|
config = config_type(
|
||||||
|
frame_shape=(100, 200),
|
||||||
|
mask={"test_mask": {"coordinates": coordinates}},
|
||||||
|
)
|
||||||
|
expected = np.full((100, 200), 255, dtype=np.uint8)
|
||||||
|
expected[rows] = 0
|
||||||
|
|
||||||
|
np.testing.assert_array_equal(config.rasterized_mask, expected)
|
||||||
|
self.assertEqual(config.mask["test_mask"].coordinates, coordinates)
|
||||||
|
|
||||||
|
def test_pixel_coordinates(self):
|
||||||
|
pixel_mask = "0,0,200,0,200,40,0,40"
|
||||||
|
relative_mask = "0.000,0.700,1.000,0.700,1.000,1.000,0.000,1.000"
|
||||||
|
for config_type, coordinates in product(
|
||||||
|
(RuntimeMotionConfig, RuntimeFilterConfig),
|
||||||
|
(pixel_mask, [pixel_mask], [pixel_mask, relative_mask]),
|
||||||
|
):
|
||||||
|
with self.subTest(
|
||||||
|
config_type=config_type.__name__, coordinates=coordinates
|
||||||
|
):
|
||||||
|
config = config_type(
|
||||||
|
frame_shape=(100, 200),
|
||||||
|
mask={"test_mask": {"coordinates": coordinates}},
|
||||||
|
)
|
||||||
|
expected = np.full((100, 200), 255, dtype=np.uint8)
|
||||||
|
expected[:41] = 0
|
||||||
|
if isinstance(coordinates, list) and len(coordinates) == 2:
|
||||||
|
expected[70:] = 0
|
||||||
|
|
||||||
|
np.testing.assert_array_equal(config.rasterized_mask, expected)
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
"""Tests for relative and legacy zone coordinates."""
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
from itertools import product
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from frigate.config.camera.zone import ZoneConfig
|
||||||
|
|
||||||
|
|
||||||
|
class TestZoneCoordinates(unittest.TestCase):
|
||||||
|
def test_relative_coordinate_precision(self):
|
||||||
|
expected = np.array([[0, 0], [200, 0], [200, 40], [0, 40]])
|
||||||
|
|
||||||
|
for edge, as_list in product(
|
||||||
|
("1", "1.0", "1.00", "1.000", "1.000000"), (False, True)
|
||||||
|
):
|
||||||
|
points = ["0,0", f"{edge},0", f"{edge},0.4", "0,0.4"]
|
||||||
|
coordinates = points if as_list else ",".join(points)
|
||||||
|
|
||||||
|
with self.subTest(coordinates=coordinates):
|
||||||
|
zone = ZoneConfig(coordinates=coordinates)
|
||||||
|
zone.generate_contour((100, 200))
|
||||||
|
|
||||||
|
np.testing.assert_array_equal(zone.contour, expected)
|
||||||
|
self.assertEqual(zone.coordinates, coordinates)
|
||||||
|
|
||||||
|
def test_invalid_coordinates(self):
|
||||||
|
for points in (
|
||||||
|
["invalid,0", "1,0", "1,0.4", "0,0.4"],
|
||||||
|
["0,invalid", "1,0", "1,0.4", "0,0.4"],
|
||||||
|
):
|
||||||
|
for coordinates in (",".join(points), points):
|
||||||
|
with self.subTest(coordinates=coordinates):
|
||||||
|
zone = ZoneConfig(coordinates=coordinates)
|
||||||
|
with self.assertRaisesRegex(
|
||||||
|
ValueError, "Invalid coordinates found in configuration file"
|
||||||
|
):
|
||||||
|
zone.generate_contour((100, 200))
|
||||||
|
|
||||||
|
def test_pixel_coordinates(self):
|
||||||
|
points = ["0,0", "200,0", "200,40", "0,40"]
|
||||||
|
expected = np.array([[0, 0], [200, 0], [200, 40], [0, 40]])
|
||||||
|
|
||||||
|
for coordinates in (",".join(points), points):
|
||||||
|
with self.subTest(coordinates=coordinates):
|
||||||
|
zone = ZoneConfig(coordinates=coordinates)
|
||||||
|
zone.generate_contour((100, 200))
|
||||||
|
|
||||||
|
np.testing.assert_array_equal(zone.contour, expected)
|
||||||
|
self.assertEqual(zone.coordinates, "0.0,0.0,1.0,0.0,1.0,0.4,0.0,0.4")
|
||||||
@@ -1000,12 +1000,12 @@ def get_relative_coordinates(
|
|||||||
# old native resolution coordinates
|
# old native resolution coordinates
|
||||||
where = f" for camera {camera_name}" if camera_name else ""
|
where = f" for camera {camera_name}" if camera_name else ""
|
||||||
if mask:
|
if mask:
|
||||||
if isinstance(mask, list) and any(x > "1.0" for x in mask[0].split(",")):
|
if isinstance(mask, list) and any(float(x) > 1.0 for x in mask[0].split(",")):
|
||||||
relative_masks = []
|
relative_masks = []
|
||||||
for m in mask:
|
for m in mask:
|
||||||
points = m.split(",")
|
points = m.split(",")
|
||||||
|
|
||||||
if any(x > "1.0" for x in points):
|
if any(float(x) > 1.0 for x in points):
|
||||||
rel_points = []
|
rel_points = []
|
||||||
for i in range(0, len(points), 2):
|
for i in range(0, len(points), 2):
|
||||||
x = int(points[i])
|
x = int(points[i])
|
||||||
@@ -1026,7 +1026,7 @@ def get_relative_coordinates(
|
|||||||
relative_masks.append(m)
|
relative_masks.append(m)
|
||||||
|
|
||||||
mask = relative_masks
|
mask = relative_masks
|
||||||
elif isinstance(mask, str) and any(x > "1.0" for x in mask.split(",")):
|
elif isinstance(mask, str) and any(float(x) > 1.0 for x in mask.split(",")):
|
||||||
points = mask.split(",")
|
points = mask.split(",")
|
||||||
rel_points = []
|
rel_points = []
|
||||||
|
|
||||||
|
|||||||
@@ -1225,7 +1225,7 @@ def add_mask(mask: str, mask_img: np.ndarray):
|
|||||||
# masks and zones are saved as relative coordinates
|
# masks and zones are saved as relative coordinates
|
||||||
# we know if any points are > 1 then it is using the
|
# we know if any points are > 1 then it is using the
|
||||||
# old native resolution coordinates
|
# old native resolution coordinates
|
||||||
if any(x > "1.0" for x in points):
|
if any(float(x) > 1.0 for x in points):
|
||||||
raise Exception("add mask expects relative coordinates only")
|
raise Exception("add mask expects relative coordinates only")
|
||||||
|
|
||||||
contour = np.array(
|
contour = np.array(
|
||||||
|
|||||||
Reference in New Issue
Block a user