新增双目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
+86
View File
@@ -0,0 +1,86 @@
"""StereoYoloConfig 与 JSON 配置加载(需求说明 §4 配置项需求)。
全部配置外置、构造期一次加载并校验:缺项、未知字段、类型错误、非法取值
一律拒绝进入运行期(fail-fast)。几何参数(F/B/cx/cy/原分辨率)不在此
重复存储——输出框位于**输入图像自身的原生坐标系**,几何参数的唯一来源
是 utils/config/stereo_ranger.json(§4 配置一致性要求:同一配置源驱动,
杜绝两处漂移)。
"""
import json
from dataclasses import dataclass, fields
from typing import Any, Dict, Optional, Tuple
_CONCURRENCY_MODES = ("parallel", "sequential")
_INT_FIELDS = frozenset({"imgsz"})
_STR_FIELDS = frozenset({"model_path", "concurrency"})
@dataclass
class StereoYoloConfig:
model_path: str = "models/yolo26n-drone.pt"
imgsz: int = 640 # 左右共用(§2.2 第 2 条:同一缩放、同一填充)
conf_threshold: float = 0.25 # ⚠占位:识别端自身过滤口径(§2.4)
iou_threshold: float = 0.7 # ⚠占位:NMS IoU 门限
class_whitelist: Optional[Tuple[int, ...]] = None # None = 不过滤(§4 可选白名单)
device: Optional[str] = None # None = 自动选择(cpu / cuda:0 / …)
concurrency: str = "parallel" # parallel = 左右并发(§5 硬性要求,串行会吃掉全部预算)
def __post_init__(self) -> None:
_require(bool(self.model_path.strip()), "model_path", "不得为空")
_require(
self.imgsz >= 32 and self.imgsz % 32 == 0,
"imgsz",
"须为 32 的整数倍且 ≥ 32(YOLO 步长约束)",
)
_require(0.0 <= self.conf_threshold <= 1.0, "conf_threshold", "须落在 [0, 1]")
_require(0.0 < self.iou_threshold <= 1.0, "iou_threshold", "须落在 (0, 1]")
_require(self.concurrency in _CONCURRENCY_MODES, "concurrency", f"须为 {_CONCURRENCY_MODES} 之一")
_require(self.device is None or bool(self.device.strip()), "device", "须为非空字符串或 null")
if self.class_whitelist is not None:
ids = tuple(self.class_whitelist)
_require(
len(ids) > 0 and all(isinstance(item, int) and not isinstance(item, bool) and item >= 0 for item in ids),
"class_whitelist",
"须为非空非负整数列表或 null",
)
self.class_whitelist = ids
def load_from_json(path: str) -> StereoYoloConfig:
"""从 JSON 文件构建配置;缺项/未知字段/类型错误/非法取值一律抛 ValueError。"""
with open(path, encoding="utf-8") as handle:
data: Dict[str, Any] = json.load(handle)
if not isinstance(data, dict):
raise ValueError("配置非法: 顶层须为 JSON 对象")
known = {item.name for item in fields(StereoYoloConfig)}
unknown = sorted(set(data) - known)
if unknown:
raise ValueError(f"未知配置字段: {unknown}")
missing = sorted(known - set(data))
if missing:
raise ValueError(f"缺少配置字段: {missing}")
for name, value in data.items():
if name == "class_whitelist":
if value is not None and not (
isinstance(value, list)
and all(isinstance(item, int) and not isinstance(item, bool) for item in value)
):
raise ValueError("配置类型非法: class_whitelist 须为整数数组或 null")
elif name == "device":
if value is not None and not isinstance(value, str):
raise ValueError("配置类型非法: device 须为字符串或 null")
elif name in _STR_FIELDS:
if not isinstance(value, str):
raise ValueError(f"配置类型非法: {name} 须为字符串")
elif name in _INT_FIELDS:
if not isinstance(value, int) or isinstance(value, bool):
raise ValueError(f"配置类型非法: {name} 须为整数")
elif isinstance(value, bool) or not isinstance(value, (int, float)):
raise ValueError(f"配置类型非法: {name} 须为数值")
return StereoYoloConfig(**data)
def _require(condition: bool, field_name: str, message: str) -> None:
if not condition:
raise ValueError(f"配置非法: {field_name} {message}")