双目测距方案重构与其他小修改

This commit is contained in:
keita
2026-09-10 16:16:05 +08:00
parent 3325465359
commit 538ae4c6c7
39 changed files with 2285 additions and 1444 deletions
+89 -42
View File
@@ -1,65 +1,112 @@
# stereo_ranger(Python)
# stereo_ranger(Python 版)
双目稀疏目标测距(规格:`docs/双目测距方案.md`)。输入左右目各自、**校正后原生坐标系**下的检测框,输出目标的 \(Z, X, Y, R\) 与方位。不做目标识别(YOLO)、不做图像采集与立体校正——对应规格 §10 中可独立替换的检测前端与采集预处理模块。
双目稀疏目标测距:对**同一时刻**左右两目已立体校正图像上的目标检测框做立体
几何解算,输出少量目标的**前向距离 Z + 斜距 R(参考)+ 虚拟双目中心方位
c_mid + 有效性/置信度**。不做目标识别、图像采集/立体校正、稠密深度。
仅依赖标准库(`math`/`logging`),不 import 本仓库任何其他模块。
规格:`docs/双目测距技术路线.md`(算法与语义权威)。
⚠ **全部参数为占位值**(焦距/标定待厂商数据):标定与地面真值验证完成前,
本模块输出**仅限静态标靶/回放/台架验证,不得作为飞行控制决策依据**(§12.3)。
## 接口清单
| 名称 | 说明 |
| --- | --- |
| `Detection(x1, y1, x2, y2, conf=1.0, cls_id=0, track_id=0)` | 输入检测框(px,左上/右下角) |
| `StereoConfig(...)` | 配置(默认值 = 规格示例值,见下) |
| `load_from_json(path)` | 从 JSON 构建配置(缺字段/未知字段/非法值抛 `ValueError`) |
| `StereoRanger(config=None)` | 测距器主类 |
| `.update(left_dets, right_dets, frame_sequence=0, timestamp_s=None) -> list[RangeReport]` | 单帧主流程(返回记录数与左目检测数一致、顺序一致) |
| `.reset()` / `.config` | 清空滤波状态 / 当前配置 |
| `RangeReport` | 输出记录(字段见 `types.py`) |
| `focal_from_hfov(w_px, hfov_deg)` / `range_error_abs(z, fx, B, δD=1)` / `range_error_rel(...)` | 工具函数(§5.2/§5.3) |
| `StereoRanger(config=None, matcher=None)` | 主入口;`matcher` 可注入自定义匹配器 |
| `.update(left, right, now_ms=0) -> List[RangeRecord]` | 单周期处理(§5 五步),纯计算、无 IO |
| `.reset()` | 清空全部运行期状态(滤波/一致性/隔离/节流计数) |
| `.config` / `.matcher` | 只读访问(配置与匹配器) |
| `StereoConfig` / `load_from_json(path)` | 17 项配置;缺项/未知字段/类型错误/非法取值一律 `ValueError`(fail-fast) |
| `.focal_baseline_px_m` / `.z_min_m` / `.z_max_m` / `.z_error_budget_max_m` | 派生只读量(F·B 常量、视差域互推的距离域、误差预算远界) |
| `Detection(x1,y1,x2,y2, confidence, class_id, track_id)` | 单框(校正后坐标系;含 `cx/cy/width/height/bbox` 属性) |
| `DetectionFrame(frame_id, detections)` | 单目单帧 |
| `RangeRecord` | 输出记录:`frame_id / class_id / degradation / valid / track_id / forward_distance_m(Z) / slant_range_m(R) / disparity_px(D) / center_mid_px(c_mid) / reference_box / reference_eye / confidence / timestamp_ms` |
| `Degradation` / `Eye` / `DisparitySource` | 枚举:完整/仅方位;左/右目;同侧边缘/中心回退 |
| `sample_point` / `SamplePoint` / `select_disparity` / `Disparity` | 取点器(§6.1,纯函数) |
| `Matcher`(Protocol)/ `GreedyMatcher` / `MatchPair` | 匹配器(§6.2,接口注入,可换匈牙利等) |
| `solve_range` / `RangeSolution` / `virtual_center_px` / `forward_distance_m` | 测距与方位源(§3.2/§6.6,纯函数) |
| `evaluate_validity` / `ValidityDecision` / `range_error_abs_m` / `range_error_rel` / `confidence_from_cost` / `DELTA_D_PX` | 有效性三道门与误差模型(§6.4/§7.1,纯函数) |
| `ConsistencyMonitor` / `ConsistencyStatus` | 一致性监控(§6.7,每目标状态) |
| `DistanceFilter` | 距离低通 + 跳变重置(§6.5,每目标状态) |
| `TargetStore` | 目标隔离与老化清理(§4,track_id 优先 + 最近邻兜底) |
| `WarnThrottle` | 告警节流(第 1 次 + 每满 100 次,带累计计数) |
## 与检测器的约定(规格 §4.3)
`logger` 名称为 `stereo_ranger`,走标准 `logging`。
1. 左右图像必须使用**完全一致**的预处理几何(同 letter-box、同缩放),由检测前端保证;
2. 传入本模块的框必须已映射回**原生分辨率**(视差在原生坐标计算),内参 `fx/fy/cx/cy` 亦对应原生分辨率;
3. 图像对须已完成立体校正(同一目标在两目 y 坐标一致);校正残留由 `vertical_tau_px` 容错;
4. YOLO 推理及左右目并行执行由调用方负责,本模块只收检测结果。
## 单帧流程(`update` 五步)
## 单帧流程(对照规格)
1. **同帧契约**:`left.frame_id != right.frame_id` → 拒收整帧、返回空列表、WARN 节流(§2.1);
2. **取点**:逐框取同侧(左)边缘 x 与框中心;坐标非有限值的框丢弃并 WARN(不参与解算也不发布);
3. **匹配**:硬约束(类别一致、`|Δy| ≤ tau_y_px`、框高相对差 ≤ `tau_h_ratio`)+ 代价升序贪心一一配对,`cost < 1` 才接受;
4. **配对目标**:`D → Z = F·B/D`(`D ≤ 0` 判违例)→ `R` 参考 → `c_mid = (cL + cR)/2` → 门① 域内 / 门② 误差预算 / 门③ 一致性 → 有效帧过 `DistanceFilter`,记 `Z_out`;
5. **未配对框**:发布仅方位记录(`BEARING_ONLY`,`valid = false`,右目未匹配同样发布);帧末对未出现目标记失效、连续 `filter_reset_frames` 帧未出现整体清理。
硬约束过滤(类别一致、`|Δy| ≤ τy`、高度比 `≤ τh`、视差落在 `FB/z_max ~ FB/z_min` 区间)→ 代价 `|Δy|/τy + |Δh|/(τh·max(hL,hR))` 升序贪心一一配对(严格 `cost < 1`;同 `track_id` 优先)→ 主取**同侧左边缘**视差,越出关联窗口时按配置回退中心视差(节流警告)→ `Z=FB/D`、`X=(xL−cx)Z/F`、`Y=(yL−cy)Z/F`、`R=√(X²+Y²+Z²)` → 有效性 `D ≥ D_min 且 δZ ≤ Z_tol` → 可选一阶低通滤波(按 (cls, track) 分键,跳变超门限重置;`track_id=0` 不过滤)。
记录顺序:先按左目框顺序(配对→完整 / 未配对→仅方位),再给未配对右目框的仅方位记录。
## 输出语义
## 实现决策(v1.0 冻结,已回填规格)
- `matched=True, valid=True`:有效测距;
- `matched=True, valid=False`:已配对但超出有效工作域(§5.4),距离/坐标仍发布供诊断,方位可用;
- `matched=False, valid=False`:左目检测本帧未配对(§6 失败处理),仅单目方位回退。
规格 `docs/双目测距技术路线.md` 已按下列决策修订(§4/§6.1/§6.2/§6.4/§6.7/
§9/§11 均已更新),本节保留决策动机与实现落点,供重写/移植核对。
方位源 `centre_pixel_x_mid` 为基线中点虚拟相机投影(§11.3);注意 `X` 属**左目坐标系**(§4.2),二者相差 `B/2`,勿混用。
1. **越界 = 有效性门,不是配对硬约束**:规格 §6.2 原把"视差在域内"列入
硬约束,与 §5/§6.3/§6.4/§2.3 的表述冲突;按 §5 主流程实现并回填——
越界目标仍配对、仍发布 `D` 与 `c_mid`(远场方位闭环的输入基础),仅
`valid = false`。
2. **视差域权威**:`disparity_min_px / disparity_max_px` 为真源,`z_min_m / z_max_m`
为派生只读属性;原 §9.2 占位值不自洽(`D_min = 3 px ⇔ 19.2 m ≠ 20 m`),
距离域配置项已从规格删除(§6.4/§9.2)。
3. **不实现 §6.1 的"左右边缘各算一次取中值"精化**:两支的 median 恒等于均值,
即框中心视差,与"同侧边缘"的理由矛盾,规格已删除该项(早期笔误),留
v1.1 亚像素精化。
4. **补充配置项**(原文 §9 未列、算法需要,规格 §9.3/§9.5 已回填):
`consistency_jump_px`(§6.7 视差跳变门限)、`isolation_gate_px`(§4 最近邻
兜底的门限)。
5. **δD 固定 1 px**:`DELTA_D_PX = 1.0`(§7.1 ⚠占位);标定回填后可参数化。
6. **R 与已发布 Z 同基准**:`R = R_raw · z_out / z_raw`(X/Y/Z 均 ∝ Z,精确换基,
保证 `R ≥ Z`);无效帧无 `Z_out` 时 `R` 取本帧原始值(诊断参考)。规格
§2.2/§6.5 已注明。
7. **置信度映射**(§6.4 ⚠占位):`clamp(1 − cost, 0, 1)`;连续跳变 ≥ N 帧乘 0.5
(`_CONFIDENCE_DEGRADE_FACTOR`);判无效置 0。
8. **一致性只在配对成功帧推进**:仅方位帧没有 D 观测量,不推进 D 序列;但目标
未出现的帧会记一次滤波失效(§6.5 失效清理),两条状态机职责不混。
9. **取点回退规则**:边缘视差越窗且中心视差在域内 → 回退中心
(`CENTER_FALLBACK` + WARN);两者均越窗 → 保留边缘值供诊断与违例告警。
10. **track_id 优先路径提前启用**(v1.1 语义,规格 §4 已按此冻结):左右
`track_id` 一致且 > 0 时用 `(track, 类别, id)` 作隔离键;最近邻只在无
track_id 的状态间匹配,一一归属。
11. **§3.2 勘误**:原文 `X_C = X + B/2` 与 §6.6 的符号相反(应为 `X_C = X − B/2`),
规格已修正;代码不输出 `X_C`,以 §6.6 恒等式为准。
## StereoConfig 关键项
## 运行期状态清单(§11.1,漏一个就变行为)
| 字段 | 默认 | 说明 |
| --- | --- | --- |
| `fx_px/fy_px/cx_px/cy_px` | 960/960/960/540 | 校正后内参(原生分辨率) |
| `baseline_m` | 0.06 | 基线 B |
| `z_min_m / z_max_m` | 1.0 / 50.0 | 距离窗口(派生视差关联区间) |
| `vertical_tau_px` / `height_ratio_tau` | 2.0 / 0.25 | 关联阈值 τy / τh |
| `disparity_min_px` / `range_tol_m` | 3.0 / 2.0 | 有效性判据 D_min / Z_tol |
| `disp_noise_px` | 1.0 | 误差模型 δD(用于 δZ 估计与有效性) |
| `use_center_fallback` | true | 边缘视差越窗时回退中心 |
| `filter_enabled` / `filter_alpha` / `filter_jump_ratio` | false / 0.3 / 0.2 | 时间滤波 |
| `warn_interval_s` | 5.0 | 诊断日志节流间隔 |
| 状态 | 所属 | 初值 | 改变时机 |
| --- | --- | --- | --- |
| 连续异常计数 ×目标 | `ConsistencyMonitor` | 0 | 每帧异常 +1 / 正常帧清零 |
| 上次滤波值、初始化标志、连续失效计数 ×目标 | `DistanceFilter` | 无 / false / 0 | 有效帧写入 / 首有效帧置位 / 无效帧 +1 |
| 最近位置、最近出现帧 ×目标 | `TargetStore` | 首帧写入 | 每次 `resolve` |
| 帧序号 | `StereoRanger` | 0 | 每帧 +1(拒收帧不加) |
| 告警计数 ×异常类 | `WarnThrottle` | 0 | 每次异常 |
## 运行
## 测试与演示
```bash
cd utils
"D:/program file/conda/envs/pytorch/python.exe" -m pytest -q # 全部测试(149 条)
"D:/program file/conda/envs/pytorch/python.exe" demo_stereo_ranger.py # 合成场景 demo
"D:/program file/conda/envs/pytorch/python.exe" -m pytest tests/stereo_ranger -q # 111 条
"D:/program file/conda/envs/pytorch/python.exe" -m pytest -q # 全量 220 条
python utils/demo_stereo_ranger.py # 六场景合成框对演示
```
## 未覆盖边界(属其他模块/后续版本)
构件级测试逐条对应 §11.4(取点器/匹配器/测距/有效性/滤波器/一致性/全流程),
**全部用合成框对驱动,无需真实图像**(几何是解析可断言的):
- 图像采集、立体校正 remap、帧同步、side-by-side 裁切(规格 §3);
- YOLO/检测器及其预处理(由调用方按上文约定接入);
- 亚像素边缘精化、跨帧跟踪(`track_id` 由外部追踪器提供后,时间滤波即可用)、与主测距传感器融合(§5.5/§11)。
- 取点:同侧边缘取值、框宽差不进入视差、中心对照、越窗回退、边界含等号、负视差;
- 匹配:硬约束各一例、`cost ≥ 1` 拒绝、贪心排序、一一配对;
- 测距:`Z = F·B/D` 回收、`D ≤ 0` 违例、`c_mid` 等权平均恒等式、误差平方增长;
- 有效性:三道门与边界归类、预算门通过/拦截两侧;
- 滤波/一致性:低通收敛、跳变重置不拖尾、失效清理、N 帧降级 / M 帧判无效 / 正常帧清零;
- 全流程:单目标、滤波、远场越界仍配对、单目退化、记录顺序、非同帧拒收与节流、
空输入、非有限框、track_id 跨大位移保持状态、最近邻越门限新建状态、匹配器注入、`reset`。
demo 覆盖:① 逼近(三道门依次生效 + 滤波)② 远场域外(方位照常发布)
③ 单目退化 ④ 非同帧拒收与节流 ⑤ 一致性降级/判无效 ⑥ 多目标贪心配对。