mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-10-18 07:29:45 +00:00
fix: assume the largest PPU anchor table when the .dxnn has no layout
This commit is contained in:
parent
1388305439
commit
238073f943
@ -77,9 +77,6 @@ PPU_ANCHORS_BY_SCALES = {
|
||||
}
|
||||
|
||||
PPU_MAX_STRIDE = 32
|
||||
# the largest scale count we have a table for; a count below this with no
|
||||
# table of its own is under-evidenced rather than disproven, so it still
|
||||
# gets the biggest table as a best guess
|
||||
PPU_MAX_KNOWN_SCALES = max(PPU_ANCHORS_BY_SCALES)
|
||||
|
||||
# .dxnn: "DXNN", a 4-byte LE version, then a JSON index padded to
|
||||
@ -627,15 +624,18 @@ def ppu_scale_count_lower_bound(records: np.ndarray, width: int, height: int) ->
|
||||
def resolve_scale_count(
|
||||
records: np.ndarray, scale_count: int | None, width: int, height: int
|
||||
) -> int:
|
||||
"""`scale_count` if given, else the fewest scales this frame alone
|
||||
proves, defaulting to 3 when the frame has no records to read."""
|
||||
"""`scale_count` if given, else PPU_MAX_KNOWN_SCALES unless this frame
|
||||
proves more; a frame showing only the finer layers near the origin is
|
||||
what a bigger head looks like too, so it never argues for fewer."""
|
||||
if scale_count is not None:
|
||||
return scale_count
|
||||
|
||||
if len(records) == 0:
|
||||
return 3
|
||||
return PPU_MAX_KNOWN_SCALES
|
||||
|
||||
return ppu_scale_count_lower_bound(records, width, height)
|
||||
return max(
|
||||
PPU_MAX_KNOWN_SCALES, ppu_scale_count_lower_bound(records, width, height)
|
||||
)
|
||||
|
||||
|
||||
def ppu_grid_regression_geometry(
|
||||
@ -1047,10 +1047,11 @@ class DeepxDetector(DetectionApi):
|
||||
|
||||
if layout is None:
|
||||
logger.warning(
|
||||
"Could not read the PPU head layout from %s; inferring its "
|
||||
"scale count from the detections instead, which can misplace "
|
||||
"boxes until a record proves every scale",
|
||||
"Could not read the PPU head layout from %s; assuming a "
|
||||
"%d-scale head, so a head with fewer scales will decode to "
|
||||
"misplaced boxes",
|
||||
model_path,
|
||||
PPU_MAX_KNOWN_SCALES,
|
||||
)
|
||||
return None
|
||||
|
||||
@ -1182,7 +1183,9 @@ class DeepxDetector(DetectionApi):
|
||||
def decode_ppu(self, outputs: list[np.ndarray]) -> np.ndarray:
|
||||
"""Decode PPU records. The head layout read from the model fixes
|
||||
the kind, scale count and grid decode from the first frame; without
|
||||
it, each is settled from the records and kept once proven."""
|
||||
it, the kind and grid decode are settled from the records and kept
|
||||
once proven, and the scale count is PPU_MAX_KNOWN_SCALES unless a
|
||||
record proves more (a partial frame never argues for fewer)."""
|
||||
records = ppu_records(outputs)
|
||||
boxes = None
|
||||
if records is not None and len(records):
|
||||
@ -1201,6 +1204,7 @@ class DeepxDetector(DetectionApi):
|
||||
if boxes is not None:
|
||||
if self.ppu_layout is None:
|
||||
self.ppu_scale_count = max(
|
||||
PPU_MAX_KNOWN_SCALES,
|
||||
self.ppu_scale_count,
|
||||
ppu_scale_count_lower_bound(records, self.width, self.height),
|
||||
)
|
||||
|
||||
@ -362,17 +362,16 @@ class TestDeepxPpuDecode(unittest.TestCase):
|
||||
self.assertAlmostEqual(detections[0][4], 0.137188, places=5)
|
||||
self.assertAlmostEqual(detections[0][5], 0.125750, places=5)
|
||||
|
||||
def test_a_grid_cell_past_the_coarser_layout_proves_a_third_scale(self):
|
||||
"""A layer-0 cell at column 40 of a 640 input needs stride 8, so the
|
||||
head has three scales even though no layer-2 record has shown up;
|
||||
the layer-1 record in the same frame must then read against the
|
||||
three-scale table, not the two-scale one its layer index alone
|
||||
would suggest."""
|
||||
def test_a_frame_of_only_the_finer_layers_still_reads_as_three_scales(self):
|
||||
"""Layers 0 and 1 near the origin look the same from a two-scale
|
||||
head and from the finer two thirds of a three-scale one, so with no
|
||||
scale_count given the frame reads against the three-scale table,
|
||||
never the two-scale one its highest layer index alone suggests."""
|
||||
detections = decode_ppu(
|
||||
[
|
||||
build_ppu_records(
|
||||
build_ppu_record((0.5, 0.5, 0.4, 0.4), grid=(3, 4, 0, 1)),
|
||||
build_ppu_record((0.6, 0.4, 0.3, 0.7), grid=(7, 40, 1, 0)),
|
||||
build_ppu_record((0.6, 0.4, 0.3, 0.7), grid=(7, 9, 1, 0)),
|
||||
)
|
||||
],
|
||||
640,
|
||||
@ -388,6 +387,10 @@ class TestDeepxPpuDecode(unittest.TestCase):
|
||||
self.assertAlmostEqual(layer_one[0][2], 0.057, places=5)
|
||||
self.assertAlmostEqual(layer_one[0][4], 0.118, places=5)
|
||||
self.assertAlmostEqual(layer_one[0][5], 0.1275, places=5)
|
||||
# layer 0 of 3 is stride 8 and box 1 is the 16x30 anchor
|
||||
layer_zero = detections[np.isclose(detections[:, 3], 0.116750)]
|
||||
self.assertEqual(len(layer_zero), 1)
|
||||
self.assertAlmostEqual(layer_zero[0][2], 0.045313, places=5)
|
||||
|
||||
def test_scale_count_lower_bound_reads_layer_and_grid_position(self):
|
||||
"""Every record proves at least one scale past its layer, and a
|
||||
@ -1221,12 +1224,11 @@ class TestDeepxDetectorLayout(unittest.TestCase):
|
||||
self.assertEqual(second[0][0], 3)
|
||||
self.assertTrue(detector.ppu_anchor_free)
|
||||
|
||||
def test_ppu_scale_count_is_kept_once_a_coarser_layer_is_seen(self):
|
||||
"""A frame that only shows layer 0 cannot tell a two-scale head from
|
||||
a three-scale one and falls back to three; once a frame proves
|
||||
layer 1 exists, two scales is the best-evidenced count and every
|
||||
later layer-0-only frame must use the two-scale table, until some
|
||||
record proves a third scale."""
|
||||
def test_ppu_scale_count_without_a_layout_never_drops_below_three(self):
|
||||
"""With no layout to read, a three-scale head whose first frames
|
||||
only hold layers 0 and 1 near the origin must not be read as a
|
||||
two-scale head: those records are exactly what a three-scale head
|
||||
emits too, so every frame keeps the three-scale table."""
|
||||
detector, _ = self._detector(
|
||||
ModelTypeEnum.yologeneric, [{"shape": [8400]}], ppu=True
|
||||
)
|
||||
@ -1236,31 +1238,29 @@ class TestDeepxDetectorLayout(unittest.TestCase):
|
||||
build_ppu_record((0.6, 0.4, 0.3, 0.7), grid=(7, 9, 1, 0))
|
||||
]
|
||||
first = detector.detect_raw(np.zeros((1, 640, 640, 3), np.uint8))
|
||||
# layer 0 read against the three-scale default: stride 8, anchor 16x30
|
||||
# layer 0 of 3: stride 8, anchor 16x30
|
||||
self.assertAlmostEqual(first[0][3], 0.116750, places=5)
|
||||
self.assertEqual(detector.ppu_scale_count, 3)
|
||||
|
||||
detector.session.run.return_value = [
|
||||
build_ppu_record((0.5, 0.5, 0.4, 0.4), grid=(3, 4, 0, 1))
|
||||
]
|
||||
second = detector.detect_raw(np.zeros((1, 640, 640, 3), np.uint8))
|
||||
# layer 1 of 2 is stride 32 and box 0 is the 81x82 anchor
|
||||
self.assertAlmostEqual(second[0][3], 0.1845, places=5)
|
||||
self.assertEqual(detector.ppu_scale_count, 2)
|
||||
# layer 1 of 3: stride 16, anchor 30x61, not layer 1 of 2
|
||||
self.assertAlmostEqual(second[0][3], 0.0975, places=5)
|
||||
self.assertEqual(detector.ppu_scale_count, 3)
|
||||
|
||||
detector.session.run.return_value = [
|
||||
build_ppu_record((0.6, 0.4, 0.3, 0.7), grid=(7, 9, 1, 0))
|
||||
]
|
||||
third = detector.detect_raw(np.zeros((1, 640, 640, 3), np.uint8))
|
||||
# the same layer-0 record as the first frame, now read against the
|
||||
# two-scale table proven by the second frame: stride 16, anchor 23x27
|
||||
self.assertAlmostEqual(third[0][3], 0.236031, places=5)
|
||||
self.assertAlmostEqual(third[0][3], 0.116750, places=5)
|
||||
|
||||
def test_ppu_scale_count_grows_when_a_grid_cell_proves_a_third_scale(self):
|
||||
"""A three-scale head whose early frames only ever hold layers 0
|
||||
and 1 near the origin reads as two scales; a later layer-0 record
|
||||
at column 40 of 640 needs stride 8, which proves the third scale
|
||||
without a layer-2 record ever appearing, and every frame after
|
||||
that must read against the three-scale table."""
|
||||
def test_ppu_scale_count_grows_when_a_grid_cell_proves_a_fourth_scale(self):
|
||||
"""A layer-0 record at column 80 of 640 needs stride 4, which proves
|
||||
a fourth scale with no layer-3 record ever appearing; there is no
|
||||
table for that, so it is reported once and every frame after that
|
||||
stays unplaced rather than reading against the three-scale table."""
|
||||
detector, _ = self._detector(
|
||||
ModelTypeEnum.yologeneric, [{"shape": [8400]}], ppu=True
|
||||
)
|
||||
@ -1269,20 +1269,19 @@ class TestDeepxDetectorLayout(unittest.TestCase):
|
||||
layer_one = build_ppu_record((0.5, 0.5, 0.4, 0.4), grid=(3, 4, 0, 1))
|
||||
detector.session.run.return_value = [layer_one]
|
||||
first = detector.detect_raw(np.zeros((1, 640, 640, 3), np.uint8))
|
||||
# read as layer 1 of 2: stride 32, anchor 81x82
|
||||
self.assertAlmostEqual(first[0][3], 0.1845, places=5)
|
||||
self.assertEqual(detector.ppu_scale_count, 2)
|
||||
self.assertAlmostEqual(first[0][3], 0.0975, places=5)
|
||||
|
||||
detector.session.run.return_value = [
|
||||
build_ppu_record((0.6, 0.4, 0.3, 0.7), grid=(7, 40, 1, 0))
|
||||
build_ppu_record((0.6, 0.4, 0.3, 0.7), grid=(7, 80, 1, 0))
|
||||
]
|
||||
detector.detect_raw(np.zeros((1, 640, 640, 3), np.uint8))
|
||||
self.assertEqual(detector.ppu_scale_count, 3)
|
||||
with self.assertLogs("frigate.detectors.plugins.deepx", level="ERROR") as logs:
|
||||
second = detector.detect_raw(np.zeros((1, 640, 640, 3), np.uint8))
|
||||
detector.session.run.return_value = [layer_one]
|
||||
third = detector.detect_raw(np.zeros((1, 640, 640, 3), np.uint8))
|
||||
|
||||
detector.session.run.return_value = [layer_one]
|
||||
third = detector.detect_raw(np.zeros((1, 640, 640, 3), np.uint8))
|
||||
# the same record, now layer 1 of 3: stride 16, anchor 30x61
|
||||
self.assertAlmostEqual(third[0][3], 0.0975, places=5)
|
||||
self.assertEqual(detector.ppu_scale_count, 4)
|
||||
self.assertTrue(np.all(second == 0) and np.all(third == 0))
|
||||
self.assertEqual(len([r for r in logs.records if r.levelname == "ERROR"]), 1)
|
||||
|
||||
def _ppu_detector_for(self, ppu, layers):
|
||||
"""A PPU detector loaded from a model file that describes its head."""
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user