mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-10-18 07:29:45 +00:00
fix: keep only the anchor-free PPU verdict, re-read anchor-based each frame
This commit is contained in:
parent
82dcdca8c0
commit
dec97f674f
@ -684,8 +684,8 @@ class DeepxDetector(DetectionApi):
|
||||
self.session = InferenceEngine(str(model_path), options)
|
||||
self.output = self.inspect_model(config)
|
||||
self.logged_layout = False
|
||||
# settled on the first frame that carries a record, then kept
|
||||
self.ppu_anchor_based: bool | None = None
|
||||
# set once a frame proves the PPU head anchor-free, then kept
|
||||
self.ppu_anchor_free = False
|
||||
self.ppu_anchor_based_reported = False
|
||||
self.damoyolo_outputs_reported = False
|
||||
|
||||
@ -757,12 +757,16 @@ class DeepxDetector(DetectionApi):
|
||||
return post_process_yolo(outputs, self.width, self.height)
|
||||
|
||||
def decode_ppu(self, outputs: list[np.ndarray]) -> np.ndarray:
|
||||
"""Decode PPU records, settling the head on the first conclusive
|
||||
frame."""
|
||||
if self.ppu_anchor_based is None:
|
||||
self.ppu_anchor_based = ppu_layout_is_anchor_based(outputs)
|
||||
"""Decode PPU records. A box value above 1 proves the head is
|
||||
anchor-free and that is kept; the anchor-based verdict is re-read on
|
||||
every frame, so a sub-pixel box cannot lock the model out."""
|
||||
if self.ppu_anchor_free:
|
||||
anchor_based = False
|
||||
else:
|
||||
anchor_based = ppu_layout_is_anchor_based(outputs)
|
||||
self.ppu_anchor_free = anchor_based is False
|
||||
|
||||
if self.ppu_anchor_based:
|
||||
if anchor_based:
|
||||
if not self.ppu_anchor_based_reported:
|
||||
self.ppu_anchor_based_reported = True
|
||||
logger.error(
|
||||
@ -773,15 +777,15 @@ class DeepxDetector(DetectionApi):
|
||||
|
||||
return np.zeros((20, 6), np.float32)
|
||||
|
||||
# None here means no frame has carried a record yet, which leaves the
|
||||
# heuristic to run again on the next one
|
||||
# None here means nothing conclusive in this frame, and no pixel
|
||||
# box to draw either
|
||||
return decode_ppu(
|
||||
outputs,
|
||||
self.width,
|
||||
self.height,
|
||||
SCORE_THRESHOLD,
|
||||
NMS_THRESHOLD,
|
||||
anchor_based=self.ppu_anchor_based,
|
||||
anchor_based=anchor_based,
|
||||
)
|
||||
|
||||
def decode_damoyolo(self, outputs: list[np.ndarray]) -> np.ndarray:
|
||||
@ -846,4 +850,4 @@ class DeepxDetector(DetectionApi):
|
||||
array.dtype,
|
||||
array.min() if array.size else "n/a",
|
||||
array.max() if array.size else "n/a",
|
||||
)
|
||||
)
|
||||
@ -707,9 +707,8 @@ class TestDeepxDetectorLayout(unittest.TestCase):
|
||||
self.assertTrue(np.all(first == 0) and np.all(second == 0))
|
||||
self.assertEqual(len([r for r in logs.records if r.levelname == "ERROR"]), 1)
|
||||
|
||||
def test_the_ppu_head_is_settled_once_and_kept(self):
|
||||
"""Which head a PPU model was compiled from is a property of the
|
||||
model. A later frame whose boxes all happen to fall under 1.0 must not
|
||||
def test_anchor_free_is_kept_once_proven(self):
|
||||
"""A later frame whose boxes all happen to fall within 1.0 must not
|
||||
re-open the question and start dropping detections."""
|
||||
detector, _ = self._detector(
|
||||
ModelTypeEnum.yologeneric, [{"shape": [8400]}], ppu=True
|
||||
@ -720,7 +719,7 @@ class TestDeepxDetectorLayout(unittest.TestCase):
|
||||
]
|
||||
|
||||
first = detector.detect_raw(np.zeros((1, 640, 640, 3), np.uint8))
|
||||
self.assertFalse(detector.ppu_anchor_based)
|
||||
self.assertTrue(detector.ppu_anchor_free)
|
||||
|
||||
detector.session.run.return_value = [build_ppu_record((0.6, 0.4, 0.3, 0.7))]
|
||||
second = detector.detect_raw(np.zeros((1, 640, 640, 3), np.uint8))
|
||||
@ -730,11 +729,33 @@ class TestDeepxDetectorLayout(unittest.TestCase):
|
||||
# decoded as pixels, which is a sub-pixel box in the corner
|
||||
self.assertAlmostEqual(second[0][1], 0.9, places=5)
|
||||
|
||||
def test_an_empty_or_all_zero_ppu_frame_leaves_the_head_undecided(self):
|
||||
def test_an_anchor_based_verdict_is_not_kept(self):
|
||||
"""An anchor-free model whose first record is a sub-pixel box in the
|
||||
corner looks anchor-based for that frame only; the next normal box
|
||||
decodes and settles the head."""
|
||||
detector, _ = self._detector(
|
||||
ModelTypeEnum.yologeneric, [{"shape": [8400]}], ppu=True
|
||||
)
|
||||
detector.width, detector.height = 640, 640
|
||||
|
||||
detector.session.run.return_value = [build_ppu_record((0.6, 0.4, 0.3, 0.7))]
|
||||
with self.assertLogs("frigate.detectors.plugins.deepx", level="ERROR"):
|
||||
first = detector.detect_raw(np.zeros((1, 640, 640, 3), np.uint8))
|
||||
self.assertTrue(np.all(first == 0))
|
||||
self.assertFalse(detector.ppu_anchor_free)
|
||||
|
||||
detector.session.run.return_value = [
|
||||
build_ppu_record((320.0, 160.0, 64.0, 32.0), label=3)
|
||||
]
|
||||
second = detector.detect_raw(np.zeros((1, 640, 640, 3), np.uint8))
|
||||
|
||||
self.assertEqual(second[0][0], 3)
|
||||
self.assertTrue(detector.ppu_anchor_free)
|
||||
|
||||
def test_an_empty_or_all_zero_ppu_frame_proves_nothing(self):
|
||||
"""Only a box value above 1 (anchor-free) or a frame of values
|
||||
strictly inside 0..1 (anchor-based) is evidence; an empty frame or a
|
||||
degenerate all-zero record is neither and must not freeze the model
|
||||
as anchor-based."""
|
||||
degenerate all-zero record is neither."""
|
||||
detector, _ = self._detector(
|
||||
ModelTypeEnum.yologeneric, [{"shape": [8400]}], ppu=True
|
||||
)
|
||||
@ -748,15 +769,8 @@ class TestDeepxDetectorLayout(unittest.TestCase):
|
||||
detections = detector.detect_raw(np.zeros((1, 640, 640, 3), np.uint8))
|
||||
|
||||
self.assertTrue(np.all(detections == 0))
|
||||
self.assertIsNone(detector.ppu_anchor_based)
|
||||
|
||||
detector.session.run.return_value = [
|
||||
build_ppu_record((320.0, 160.0, 64.0, 32.0), label=3)
|
||||
]
|
||||
detections = detector.detect_raw(np.zeros((1, 640, 640, 3), np.uint8))
|
||||
|
||||
self.assertFalse(detector.ppu_anchor_based)
|
||||
self.assertEqual(detections[0][0], 3)
|
||||
self.assertFalse(detector.ppu_anchor_free)
|
||||
self.assertFalse(detector.ppu_anchor_based_reported)
|
||||
|
||||
def test_damoyolo_is_checked_against_the_model_at_load(self):
|
||||
"""A YOLO model or a wrong label map under damo-yolo fails at load
|
||||
@ -878,4 +892,4 @@ class TestDeepxDetectorLayout(unittest.TestCase):
|
||||
|
||||
self.assertEqual(detections[0][0], 5)
|
||||
self.assertAlmostEqual(detections[0][1], 0.8, places=5)
|
||||
self.assertAlmostEqual(detections[0][4], 300 / 640, places=5)
|
||||
self.assertAlmostEqual(detections[0][4], 300 / 640, places=5)
|
||||
Loading…
x
Reference in New Issue
Block a user