Files
code-test-drone/utils/stereo_ranger/config.py
T

83 lines
3.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""StereoConfig 与 JSON 配置加载。
规格 docs/双目测距方案.md:全部数值参数均为配置项(§2),默认值取文档
示例值。配置校验在构造期一次性完成,热路径不读 JSON。
"""
import json
from dataclasses import dataclass, fields
from pathlib import Path
from typing import Any, Dict, Tuple
@dataclass
class StereoConfig:
# ---- 相机参数(校正后、对应原生分辨率,§3.1 / §4.3)----
fx_px: float = 960.0
fy_px: float = 960.0
cx_px: float = 960.0
cy_px: float = 540.0
baseline_m: float = 0.06 # 基线 B
# ---- 目标距离窗口:关联硬约束(§6),派生视差区间 [FB/z_max, FB/z_min] ----
z_min_m: float = 1.0
z_max_m: float = 50.0
# ---- 关联阈值(§6)----
vertical_tau_px: float = 2.0 # 垂直残差 tau_y
height_ratio_tau: float = 0.25 # 相对高度差 tau_h
# ---- 视差取点与有效性(§5)----
disparity_min_px: float = 3.0 # D_min:D 低于此值直接无效
range_tol_m: float = 2.0 # Z_tol:δZ <= Z_tol 才算有效
disp_noise_px: float = 1.0 # δD(误差模型中的边缘定位噪声)
use_center_fallback: bool = True # 左边缘视差越窗时回退中心(§5.1)
# ---- 可选时间滤波(§7,一阶低通 + 跳变重置)----
filter_enabled: bool = False
filter_alpha: float = 0.3
filter_jump_ratio: float = 0.2 # 相对跳变超此门限则重置滤波器
# ---- 诊断日志节流 ----
warn_interval_s: float = 5.0
def disparity_bounds_px(self) -> Tuple[float, float]:
"""关联用的视差区间 (lo, hi):由 z_min/z_max 派生(§6)。"""
fb = self.fx_px * self.baseline_m
return fb / self.z_max_m, fb / self.z_min_m
def __post_init__(self) -> None:
_require(self.fx_px > 0.0, "fx_px", "必须为正")
_require(self.fy_px > 0.0, "fy_px", "必须为正")
_require(self.cx_px >= 0.0, "cx_px", "必须非负")
_require(self.cy_px >= 0.0, "cy_px", "必须非负")
_require(self.baseline_m > 0.0, "baseline_m", "必须为正")
_require(0.0 < self.z_min_m < self.z_max_m, "z_min_m/z_max_m", "须满足 0 < z_min_m < z_max_m")
_require(self.vertical_tau_px > 0.0, "vertical_tau_px", "必须为正")
_require(self.height_ratio_tau > 0.0, "height_ratio_tau", "必须为正")
_require(self.disparity_min_px > 0.0, "disparity_min_px", "必须为正")
_require(self.range_tol_m > 0.0, "range_tol_m", "必须为正")
_require(self.disp_noise_px > 0.0, "disp_noise_px", "必须为正")
_require(0.0 < self.filter_alpha <= 1.0, "filter_alpha", "须落在 (0, 1]")
_require(self.filter_jump_ratio >= 0.0, "filter_jump_ratio", "必须非负")
_require(self.warn_interval_s >= 0.0, "warn_interval_s", "必须非负")
def load_from_json(path: str) -> StereoConfig:
"""从 JSON 文件构建配置。字段缺失或非法取值抛 ValueError(携带字段名)。"""
with open(path, encoding="utf-8") as f:
data: Dict[str, Any] = json.load(f)
known = {field.name for field in fields(StereoConfig)}
unknown = set(data) - known
if unknown:
raise ValueError(f"未知配置字段: {sorted(unknown)}")
missing = known - set(data)
if missing:
raise ValueError(f"缺少配置字段: {sorted(missing)}")
return StereoConfig(**data)
def _require(ok: bool, field_name: str, message: str) -> None:
if not ok:
raise ValueError(f"配置非法: {field_name} {message}")