Files
code-test-drone/legacy/docs/双目YOLO目标识别需求说明.md
keita df91b1110e 精简仓库结构并增强任务链路:归档双目可选后端、新增LOS判据与自定义返航
归档 legacy/:stereo_yolo/stereo_ranger/target_tracker 三包 + 175 条测试 + demo/配置/权重/三份规格;运行链路不再依赖,默认 pytest 446 条,归档测试按需运行。

目标有效性第二判据改为 LOS 运动学一致性(pod_bridge/los_consistency.py),删除 require_vision;pod_locked() 叠加该判据,状态机随之后退重搜。

自定义返航(task_runner/return_home.py):保持转场高度回起飞点 → 恢复初始航向 → 垂直降落,不再爬 RTL_RETURN_ALT;未起飞/无起飞点/超时/异常仍退回 PX4 原生 RTL。

任务链安全兜底:主循环异常→紧急返航、stop 幂等、landing_stop 保留遥测、in_air 流复用+超时、解锁/起飞与吊舱识别仅执行一次、rtl_timeout 上限。

入口共享件拆到 utils/sitl_support.py;pod_sitl_test.py→l1_sitl_test.py;删除旧架构 test.py;相机脚本移入 tools/。

新增 .gitignore 并剔除历史误跟踪的 59 个 .pyc;requirements 按实际依赖重写;文档全面对齐,新增 legacy/README.md 与 tools/README.md。
2026-10-21 14:48:41 +08:00

171 lines
14 KiB
Markdown
Raw Permalink 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.
# 双目 YOLO 目标识别模块需求说明(检测前端)
> **定位**:双目链路中的"检测前端",服务于已实现的 `utils/stereo_ranger`。本模块负责在左右目图像上独立运行 YOLO 类检测器,并输出**符合 stereo_ranger 输入契约**的检测框列表;**不承担**测距、左右目关联与立体校正。
>
> **版本**:0.2(2026-09-11;按 `utils/stereo_ranger` v1.0 实现接口对齐,文档引用随《双目测距方案》更名为《双目测距技术路线》同步更新)。
>
> **语义权威**:`docs/双目测距技术路线.md`(下称《测距路线》)——几何、取点、匹配、有效性、退化语义一律以其为准;**字段级接口以 `utils/stereo_ranger/types.py` 为准**(本文只做需求描述,不重复定义)。
---
## 1. 目的与定位
单目检测给出像素框,stereo_ranger 需要"同一逻辑帧、左右目各自"的检测结果来完成视差测距。本模块把"图像 → 原生坐标系检测框"的整段处理收敛为对测距端透明的单一输入源:
```
采集/同步 → 预处理(校正+letterbox)→ 左右目并行 YOLO 推理 → NMS
→ 框映射回原生分辨率 → 输出 DetectionFrame(左右各一)→ stereo_ranger.update(...)
```
### 1.1 明确不做的事
- 不做视差计算、左右目关联、距离/方位估计(均属 stereo_ranger);
- 不做立体校正本身(校正映射属于采集预处理,契约见《测距路线》§2.1"校正契约"与本文 §2.2 第 4 条);
- 不做跨帧跟踪(`track_id` 可由外部追踪器注入;无追踪器时留空,见 §2.1)。
---
## 2. 与 stereo_ranger 的接口契约(硬性要求)
接口对齐 `utils/stereo_ranger/types.py` 与 `utils/stereo_ranger/stereo_ranger.py` 的实现,任何偏离都会破坏测距正确性。
### 2.1 输出数据定义
输出即 stereo_ranger 的输入:**每目一个 `DetectionFrame`**。字段名与语义以
`utils/stereo_ranger/types.py` 为准(下例仅为语义说明,不重复定义):
```python
@dataclass
class Detection:
x1: float # 框左上角 x(已校正图像坐标,原生分辨率 px)
y1: float # 框左上角 y
x2: float # 框右下角 x
y2: float # 框右下角 y
confidence: float = 1.0 # 检测置信度 [0,1]
class_id: int = 0 # 类别 id(左右目同一目标必须一致,§2.2 第 5 条)
track_id: int | None = None # 检测侧轨道关联;无追踪器时留空(0/负数一律视为"无轨道")
@dataclass
class DetectionFrame:
frame_id: int # 帧标识;左右两帧必须一致(同帧契约,§2.3)
detections: Sequence[Detection] = () # 该目本帧检测框
```
- **推荐直接复用** `utils/stereo_ranger/types.py` 的 `Detection` / `DetectionFrame`
(纯值语义、无第三方依赖;只共享数据约定、不引入解算逻辑,不违反前端/测距解耦);
若因部署边界另建类型,字段名与语义必须逐字一致——两处定义漂移会静默破坏视差取点;
- `track_id` **仅供测距端做目标隔离键**(左右一致且为正时优先),不参与匹配与解算(§2.5);
- 每目一帧内多目标的排列顺序无要求,按检测器输出自然顺序即可。
### 2.2 坐标与几何硬约束
1. **原生分辨率坐标系**:输出框必须位于校正后图像的原生分辨率(如 1920×1080)下;视差在原生坐标计算,才能保留传感器全部像素精度(《测距路线》§2.1"坐标基准一致")。检测器输入尺寸(如 640×640)上的框必须经 letter-box 逆变换精确映射回原生坐标。
2. **左右目预处理几何位级一致**:同一缩放、同一填充。禁止左右图各自自适应缩放/填充——那会破坏校正后的极线对齐性质。
3. **框边语义**:`(x1, y1)` 为左上角、`x1` 即框"左边缘"(测距端主方案直接取 `x1_L − x1_R` 作视差,《测距路线》§6.1)。实现方不得把框定义改为中心点+宽高而不转换。
4. **立体校正前提**:本模块输出的是"校正后图像"上的框。若输入为原始图,采集端须先做与测距端内参一致的重映射(校准映射、内参由标定给出)。
5. **同一物理目标在左右目必然同时可见时**,其两框 `y` 坐标应接近(垂直残差验收见 §6);`x1` 有系统偏移(`≈ F·B/Z` 视差)属正常,不得在识别端"对齐"掉。**同一目标的类别在两目必须一致**——类别一致是测距端匹配的硬约束(《测距路线》§6.2),不一致会退化为两条"仅方位"记录。
6. **输出前自检**:所有框坐标必须为有限值且满足 `x2 > x1`、`y2 > y1`。非有限值框会被测距端丢弃(不参与匹配、不发布记录),畸变框会污染框高约束与匹配代价——自检应在识别端出口完成,而不是依赖下游兜底。
### 2.3 帧配对要求
- 每帧必须携带**单调递增的帧标识**(`DetectionFrame.frame_id`,int),左右两个容器共用同一值;
- `frame_id` 不一致时测距端**拒收整帧、返回空列表**(《测距路线》§2.1 同帧契约、§5 第 ① 步)——下游兜底不等于可以松懈,正确配对是采集端的责任;
- 无硬件同步的双独立流形态下,采集端按接收时间窗配对并输出帧标识,残余时序抖动应记录供误差评估(两种同步形态的取舍见《双目深度估计思路》§3.2,该文档为链路外部参考);
- 同帧拼接(side-by-side)单流形态:解码一次、裁切为左右图,天然同曝光,推荐优先支持;
- 帧标识由采集端按真实曝光顺序分配,**禁止为凑成"同帧"而重编号**(§2.5 第 5 条)。
### 2.4 与测距端的对接方式(示例)
```python
from utils.stereo_ranger import StereoRanger
from utils.stereo_ranger.types import DetectionFrame # 纯值类型,可安全复用
frontend = StereoYoloFrontend(cfg) # 本模块主类
ranger = StereoRanger(stereo_cfg) # 配置见 §4,实测值见 utils/config/stereo_ranger.json
left_frame, right_frame = frontend.detect(left_img, right_img, frame_id=n) # 各一个 DetectionFrame
records = ranger.update(left_frame, right_frame, now_ms=monotonic_ms())
```
- **第三个参数是 `now_ms`(平台单调时钟毫秒,仅用于输出打戳,默认 0),不是帧序号**——按位置把 `n` 传进去会被静默当作时间戳,污染 `timestamp_ms`;
- 返回 `list[RangeRecord]`,顺序:先按左目框顺序(配对→完整记录 / 未配对→仅方位记录),再补未配对的右目框的"仅方位"记录;左目非空时记录数与左目框数一致;
- **没有 `matched` 字段**。单目检出的记录为 `degradation = BEARING_ONLY`、`valid = False`、`forward_distance_m = None`,方位取该目框中心;
- 配对成功但域外/一致性异常时仍发布 `degradation = COMPLETE` 的记录(`disparity_px`、`center_mid_px` 可用),但 `valid = False`、`forward_distance_m = None`——**域外不撒谎,也不丢方位**(《测距路线》§7.3);
- **测距端不消费检测置信度**:完整记录的 `confidence` 是匹配质量 `clamp(1 − 匹配代价)`(连续跳变时乘降级系数,判 invalid 时置 0);仅方位记录的 `confidence` 才直接取检测置信度。故 conf 阈值/NMS 必须由识别端自行完成后再输出;
- 两目检测均为空 → 返回空列表(正常空帧,不告警)。
### 2.5 输入纯净性硬约束(v0.2 新增,飞行安全相关)
1. **只送本帧原生实测框**:`update()` 的输入必须是**本帧、在原生校正图上检测得到的框**。跨帧外推框(追踪器预测/Coast 补帧)、平滑后的框、按上一帧位移推算的"虚拟框"**一律不得送入**;
2. **外推框会伪装成合法测量**:框的 `x1` 偏差直接进入视差 `D = x1_L − x1_R`,`Z = F·B/D` 没有任何机制识别"这不是本帧观测",误差随 `Z²` 放大且不触发 `valid` 门(《测距路线》§7.1);测距端的时序一致性监控最多把它降级,不能纠偏(《测距路线》§6.7);
3. **目标丢失就该没有距离**:本帧未检出 → 不发布该目标检测框;测距端对其记失效、连续 `filter_reset_frames` 帧后清空滤波状态(《测距路线》§6.5)。距离中断(coast)属于**消费侧**策略,且必须与 `valid` 语义分开标注;
4. **`track_id` 仅作隔离键**:由外部追踪器注入时只用于目标状态隔离(《测距路线》§4);**不得用追踪框充当检测框**;左右目同一目标的 `track_id` 必须一致,否则隔离退化为最近邻兜底(§2.1);
5. **两目同源**:左右框必须来自同一次曝光(同帧契约);左右取自不同帧、仅凭"时间接近"拼成一对,即使 `frame_id` 相同也属违约——`frame_id` 由采集端保证真实(§2.3);
6. **前端不做左右匹配、不做对齐**:左右匹配、视差取点、方位解算全部在 stereo_ranger;识别端只需保证"每目各自把该目可见目标检测出来",不得为便于后端匹配而调整框位置(《测距路线》§1.4)。
---
## 3. 模块内部建议构成(实现参考)
| 子模块 | 职责 | 说明 |
|---|---|---|
| 采集/同步(可选) | 双流按帧标识+时间窗配对,或 sbs 裁切 | 无硬同步时量化残余抖动;帧标识不得重编号(§2.3) |
| 几何预处理 | 校正 remap → 左右完全一致的 letterbox | letterbox 参数(缩放 s、填充)必须左右共用一份配置 |
| 推理调度 | 左右目各一次检测 | **必须并发**:单设备双流或双实例双线程;串行使时延翻倍(《测距路线》§10.1:检测单核 20~30ms,NPU 预算成立) |
| YOLO 推理 + NMS | 输出 640×640(或配置尺寸)坐标系下的框 | 类别表、conf/NMS 阈值外置 |
| 坐标回映 | 逆 letterbox 映射到原生分辨率 | 与预处理使用同一套变换参数,禁止各写一份 |
| 输出适配 | 出口自检(有限值、`x2>x1`、`y2>y1`)→ 每目封装 `DetectionFrame(frame_id, detections)` | 字段严格按 §2.1;`frame_id` 左右共用 |
---
## 4. 配置项需求(全部外置,禁止硬编码)
| 配置 | 说明 |
|---|---|
| 模型权重 / 类别表 | 检测器选择(YOLOv5/v8/…)不设限 |
| 输入尺寸 | 如 640×640;左右共用 |
| conf / NMS IoU 阈值 | 识别端自身过滤口径(测距端不消费检测置信度,§2.4) |
| 输出类别过滤 | 可选白名单;同一目标左右目类别必须一致(§2.2 第 5 条) |
| 原分辨率与内参 | 逆映射/校验用;`fx_px`/`fy_px`/`cx_px`/`cy_px` 必须与测距端配置**同源同值**(当前占位值见 `utils/config/stereo_ranger.json`,标定回填后同步) |
| 并发策略 | 单实例多线程 or 双实例 |
| 帧同步方式 | sbs / 双流 + 时间窗 |
配置一致性要求(§5"一致性"项的落地):letterbox 缩放与填充参数、内参、原分辨率
均由**同一配置源**驱动左右两目与两个模块;任何一处单独调整都会造成视差系统性偏差
(《测距路线》§9 全部参数外置、启动期校验的语义同样适用于本模块)。
---
## 5. 非功能需求(大致)
| 项目 | 要求 |
|---|---|
| 帧率 | 满足测距流水线帧周期(如 30 fps → ≤33 ms/帧) |
| 左右推理 | 必须并发执行(《测距路线》§10.1:检测单核 20~30ms,串行会吃掉全部预算) |
| 端到端时延 | 实测并留出余量:识别之外,匹配+测距在少量目标时 <1 ms(测距端为纯计算单元、无 IO 无阻塞,《测距路线》§11.3) |
| 一致性 | 左右目预处理/回映参数由同一配置源驱动,杜绝两路漂移(§4 配置一致性要求) |
| 失败处理 | 单目推理失败等异常不阻塞整体,该目输出空帧列表(测距端退化为另一目的仅方位记录);错误日志节流(《测距路线》§5:第 1 次 + 每满 100 次,带累计计数) |
---
## 6. 验证与验收(大致)
| 项目 | 方法 | 通过标准(示意) |
|---|---|---|
| 坐标回映 | 已知缩放/填充的合成图,比对回映框与真值 | 误差 <0.5 px(像素取整前) |
| 垂直对齐 | 静态场景,左右匹配框 \|Δy\| 直方图 | P95 ≤1 px(《测距路线》§12.1 校正质量;不达标触发自标定提前) |
| 类别一致性 | 静态场景统计同一目标两目类别 | 不一致率 ≈0(不一致会被测距端匹配硬约束拦截,退化为两条仅方位记录) |
| 输出合法性 | 出口自检单测(含非有限值、退化框构造用例) | 无 `x2 ≤ x1` / `y2 ≤ y1` / 非有限值框输出(§2.2 第 6 条) |
| 输入纯净性 | 代码审查 + 单测:断言送入 `update()` 的框集合恰为本帧检测输出 | 无预测/Coast/平滑框进入(§2.5) |
| 下游对接 | 用本模块输出喂 `StereoRanger.update()` | 与 `utils/demo_stereo_ranger.py` 真值投影法结果同量级;误差随距离趋势符合 `δZ ∝ Z²`(《测距路线》§7.1) |
| 帧周期 | 双流满负荷计时 | 满足所选帧率预算 |
---
## 7. 依赖与边界
- 依赖:YOLO 推理栈(如 onnxruntime/torch)、opencv(解码/预处理/校正 remap,若选用 numpy 实现 letterbox 亦可);**不 import `stereo_ranger` 的测距逻辑**(保持前端/测距解耦),仅可复用 `stereo_ranger/types.py` 的纯值类型(可选,见 §2.1)与同一份配置数值(§4);
- 本模块不实现:跨帧跟踪(无追踪器时 `track_id` 留 `None` 可接受)、距离 coast/外推(属消费侧,§2.5 第 3 条)、自标定与立体校正本身、主测距传感器融合;
- 若部署平台算力不足(如边缘设备),允许降低输入分辨率或帧率,但左右一致性与原生回映两条约束不可妥协(§2.2 第 1、2 条);
- **红线同源**:本模块输出同样受《测距路线》§12.3 约束——标定完成前内参等数值均为占位值,仅限台架/回放验证,不得接入实飞控制回路。