新增双目yolo实现python代码

This commit is contained in:
keita
2026-09-14 15:05:03 +08:00
parent 538ae4c6c7
commit 3a8e2c7f4b
65 changed files with 2467 additions and 10 deletions
+155
View File
@@ -0,0 +1,155 @@
"""StereoYoloFrontend 出口契约测试(需求说明 §2.2/§2.3/§2.5/§5)。
全部用注入的假检测器驱动:不需要模型权重、不需要图像素材、不需要硬件;
真并行用双人屏障断言(串行实现会屏障超时)。
"""
import logging
import threading
import numpy as np
import pytest
from stereo_ranger.types import Detection
from stereo_yolo import StereoYoloConfig, StereoYoloFrontend
class ScriptedDetector:
"""按调用顺序返回脚本项:list[Detection] 正常返回;Exception 抛出。"""
def __init__(self, script):
self._script = list(script)
self.calls = 0
def detect(self, image):
self.calls += 1
if not self._script:
raise AssertionError("假检测器脚本已耗尽(调用次数多于预期)")
item = self._script.pop(0)
if isinstance(item, Exception):
raise item
return list(item)
class BarrierDetector:
"""两次调用必须同时在途(屏障超时即证明串行或单线程执行)。"""
def __init__(self, barrier: threading.Barrier, detections):
self._barrier = barrier
self._detections = list(detections)
def detect(self, image):
self._barrier.wait(timeout=2.0)
return list(self._detections)
def _img(fill=0, shape=(8, 8, 3)) -> np.ndarray:
return np.full(shape, fill, dtype=np.uint8)
def _det(x1=1.0, y1=2.0, x2=11.0, y2=12.0, class_id=0, confidence=0.9, track_id=None) -> Detection:
return Detection(x1, y1, x2, y2, confidence=confidence, class_id=class_id, track_id=track_id)
def _frontend(script, **cfg_kwargs) -> StereoYoloFrontend:
kwargs = {"concurrency": "sequential"}
kwargs.update(cfg_kwargs)
return StereoYoloFrontend(StereoYoloConfig(**kwargs), detector=ScriptedDetector(script))
def test_frames_share_frame_id_and_keep_boxes():
left_box, right_box = _det(1.0, 2.0, 11.0, 12.0, confidence=0.77), _det(3.0, 4.0, 13.0, 14.0)
frontend = _frontend([[left_box], [right_box]])
left_frame, right_frame = frontend.detect(_img(), _img(), frame_id=7)
assert left_frame.frame_id == right_frame.frame_id == 7
assert left_frame.detections == (left_box,)
assert right_frame.detections == (right_box,)
assert left_frame.detections[0].confidence == pytest.approx(0.77)
assert left_frame.detections[0].track_id is None # 无追踪器时留空(§2.1)
assert frontend._detector.calls == 2
def test_whitelist_filters_by_class():
frontend = _frontend([[_det(class_id=3)], [_det(class_id=0)]], class_whitelist=(0,))
left_frame, right_frame = frontend.detect(_img(), _img(), frame_id=1)
assert left_frame.detections == ()
assert len(right_frame.detections) == 1 and right_frame.detections[0].class_id == 0
def test_no_whitelist_keeps_all_classes():
frontend = _frontend([[_det(class_id=3)], [_det(class_id=5)]])
left_frame, right_frame = frontend.detect(_img(), _img(), frame_id=1)
assert len(left_frame.detections) == 1 and len(right_frame.detections) == 1
def test_degenerate_boxes_dropped(caplog):
nan_box = _det(x1=float("nan"))
zero_width = _det(x1=5.0, x2=5.0)
inverted = _det(x1=20.0, x2=10.0)
good = _det()
frontend = _frontend([[nan_box, zero_width, inverted, good], []])
with caplog.at_level(logging.WARNING, logger="stereo_yolo"):
left_frame, right_frame = frontend.detect(_img(), _img(), frame_id=2)
assert left_frame.detections == (good,)
assert right_frame.detections == ()
assert "丢弃退化检测框" in caplog.text
assert "累计 1 次" in caplog.text
def test_boundary_touching_box_kept():
hairline = _det(x1=10.0, x2=10.0 + 1e-9)
frontend = _frontend([[hairline], []])
left_frame, _ = frontend.detect(_img(), _img(), frame_id=3)
assert left_frame.detections == (hairline,)
def test_one_eye_failure_isolated(caplog):
good = _det()
frontend = _frontend([[good], RuntimeError("boom")])
with caplog.at_level(logging.WARNING, logger="stereo_yolo"):
left_frame, right_frame = frontend.detect(_img(), _img(), frame_id=4)
assert left_frame.detections == (good,)
assert right_frame.detections == ()
assert left_frame.frame_id == right_frame.frame_id == 4
assert "右目推理失败" in caplog.text
def test_shape_mismatch_rejected(caplog):
frontend = _frontend([]) # 脚本为空:若检测器被调用会抛断言
with caplog.at_level(logging.WARNING, logger="stereo_yolo"):
left_frame, right_frame = frontend.detect(_img(shape=(8, 8, 3)), _img(shape=(8, 6, 3)), frame_id=5)
assert left_frame.detections == () and right_frame.detections == ()
assert left_frame.frame_id == right_frame.frame_id == 5
assert frontend._detector.calls == 0
assert "左右图尺寸不一致" in caplog.text
def test_parallel_eyes_run_concurrently():
barrier = threading.Barrier(2)
box = _det()
frontend = StereoYoloFrontend(
StereoYoloConfig(concurrency="parallel"),
detector=BarrierDetector(barrier, [box]),
)
try:
left_frame, right_frame = frontend.detect(_img(), _img(), frame_id=6)
finally:
frontend.close()
assert left_frame.detections == (box,)
assert right_frame.detections == (box,)
def test_close_is_idempotent():
frontend = StereoYoloFrontend(StereoYoloConfig(concurrency="parallel"), detector=ScriptedDetector([]))
frontend.close()
frontend.close()