新增双目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
+170
View File
@@ -0,0 +1,170 @@
# 双目 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 约束——标定完成前内参等数值均为占位值,仅限台架/回放验证,不得接入实飞控制回路。