精简仓库结构并增强任务链路:归档双目可选后端、新增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。
This commit is contained in:
keita
2026-10-21 14:48:41 +08:00
parent 53fdd3b13f
commit df91b1110e
159 changed files with 6121 additions and 1617 deletions
+38 -12
View File
@@ -1,8 +1,11 @@
# L1 航向伺服落地方案(无距离,树莓派 / Python)
# L1 航向伺服落地方案(无距离,树莓派 / Python)
> 目标:**先把"只修正飞行方向、不需要距离"的部分落地**,速度-距离闭环(L2)滞后。
> 基于结论:本吊舱无激光 → 表4 永不出,且不允许二次开发(部署文档 §10.0 / §10)。
> 日期:2026-10-09 状态:**S0 已实现(路线 A1 + L1 胶水层),全量 396 passed;符号待台架 S1 标定**
> 日期:2026-10-09 起,**2026-10-19 更新** 状态:
> **S0/S1/S2 + SITL + 真吊舱在环已验证**(全量 621 passed)。
> 后续变更:第二判据 `require_vision` → **LOS 运动学一致性**(§2.3/§6);
> 距离源由「双目」改为规划中的「激光雷达」(§7)。
---
@@ -114,7 +117,20 @@ L1 的 `tracked` 判据按阶段递进:
| 阶段 | 判据 |
|---|---|
| 台架(S1~S2) | bit2 锁定 **且** 遥测未超龄(现有 `_pod_locked()`)+ **人工在旁观察**,随时按键 `u` 解锁 |
| 接入视觉后(S3+) | bit2 **且** 我方 `stereo_yolo`/`target_tracker` 有目标(**第二判据**,必做) |
| ~~接入视觉后(S3+)~~ | ~~bit2 **且** 我方 `stereo_yolo`/`target_tracker` 有目标~~ |
| **现行(2026-10-19 起)** | bit2 **且** 遥测未超龄 **且 LOS 运动学一致性**(`pod_bridge/los_consistency.py`) |
> ⚠ **2026-10-19 架构变更**:后续决定**吊舱取代双目 + YOLO 作为唯一目标源**,
> 因此原「我方视觉第二判据」(`require_vision` / `vision_target_fn`) 已**删除** ——
> 它要么恒 False(链路永远进不了 TRACK),要么指向吊舱自身(自证,零信息量)。
>
> 替代判据:本吊舱无电机、相机刚性固连机体,机头转 Δψ 时真实目标的脱靶量必变
> ≈−Δψ。故用 `residual = Δβ + Δψ`(3s 窗口,激励门槛 20°、残差上限 8°)判断
> 「角度是活的还是冻住的」。超限 → `tracked=False` 且 `pod_locked()` 也返回 False
> (状态机会退出 TRACK 重新搜索,而不是只让控制律单方面刹车)。
>
> **局限**:它只能证明「角度是活的」,不能证明「是那个目标」。若吊舱锁错了物体
> 但角度正常变化,本判据无法识别 —— 那属于识别问题,需另接独立传感器。
---
@@ -149,7 +165,12 @@ L1 的 `tracked` 判据按阶段递进:
"alpha_from_frame": false,
"alpha_from_off_target": true,
"vertical_enabled": true, // 决策3:无人机 L1 开垂直(vz 由 tracking.vertical 压低)
"require_vision": false // true = 需我方视觉第二判据(S3 起开,§2.3)
// 第二判据:LOS 运动学一致性(2026-10-19 取代 require_vision,见 §2.3)
"los_consistency_enabled": true, // false → 退回"只信 bit2"
"los_window_ms": 3000, // 比较窗口
"los_min_yaw_change_deg": 20.0, // 窗口内机头至少转这么多才判定(激励门槛)
"los_max_residual_deg": 8.0 // |Δβ+Δψ| 上限,超过判不一致
}
```
@@ -187,10 +208,10 @@ L1 的 `tracked` 判据按阶段递进:
| 风险 | 判据 / 对策 |
|---|---|
| **符号反了** → 反向转 | S1 标定 + S2 干跑必验;配置化而非硬编码 |
| bit2 粘滞 → 丢失后仍转 | 第二判据(我方视觉);台架期人工值守 |
| bit2 粘滞 → 丢失后仍转 | **LOS 运动学一致性**(`pod_bridge/los_consistency.py`,2026-10-19 取代旧「我方视觉」方案):机头转 Δψ 而脱靶量不动 → 判丢;台架期仍人工值守 |
| 框架角定义不符(绝对而非相对机体) | S1 实测确认,不符则改用脱靶量积分等替代 |
| 云台归中/搜索态框架角乱转 | 仅锁定态启用;非锁定 → `tracked=False` → BRAKE_HOVER |
| 垂直通道撞地 | L1 强制 `vz_gain=0` |
| 垂直通道撞地 | L1 强制 `vz_gain=0`(本机实际按决策3 开垂直但压低限速) |
| 测距噪声(L2 才有) | L2 滞后,且**不用连续 PID**,改分段限速 |
---
@@ -200,7 +221,8 @@ L1 的 `tracked` 判据按阶段递进:
L1 落地时就留好口子,将来 L2 只是"换个输入源",不动结构:
- `ControlSnapshot.distance_valid` / `target_distance_m` / `distance_time_ms` 字段
**现在就按 `False` 填全**,L2 时换成 `stereo_ranger` 的 `forward_distance_m`;
**现在就按 `False` 填全**;将来接**激光雷达**距离源后填入(原计划的
`stereo_ranger.forward_distance_m` 已随"吊舱取代双目"一并搁置);
- `bridge.step()` 按配置在 `TrackingControlLaw`(L1)与 `GuidanceLaw`(L2)间切换;
- `guidance.py` 的输入接口保持不变,等有标定过的距离源再启用。
@@ -224,18 +246,22 @@ L1 落地时就留好口子,将来 L2 只是"换个输入源",不动结构
| 2 | `visual_track_control/tracking_control_law.py` | ✅ 第④步角度直入优先,否则回退像素 |
| 3 | `visual_track_control/config.py` | ✅ 拆出 `load_from_dict()` 供 bridge 内嵌复用 |
| 4 | `pod_bridge/config.py` | ✅ 加 `HeadingServoConfig` + 内嵌 `TrackingConfig`,严格校验符号(±1) |
| 5 | `pod_bridge/bridge.py` | ✅ `_build_snapshot()`;`step()` 按 enabled 切 L1/L2;`_dispatch` 对 DEGRADED_HOLD 补发刹车保心跳;`vision_target_fn` 注入点(§2.3 第二判据) |
| 5 | `pod_bridge/bridge.py` | ✅ `_build_snapshot()`;`step()` 按 enabled 切 L1/L2;`_dispatch` 对 DEGRADED_HOLD 补发刹车保心跳;~~`vision_target_fn` 注入点~~ → **已删除,改为 LOS 一致性判据**(`los_consistency.py`,§2.3) |
| 6 | `config/pod_bridge.json` | ✅ 落 L1 首飞值(slow_approach/0.2、垂直压低、无电机 → 仅脱靶量) |
| 7 | `tests/` | ✅ 新增 24 条(vtc 5 + config 9 + bridge L1 10);全量 **396 passed** |
| 8 | `pod_bridge/run_bridge.py` | ✅ 闭环 CLI 入口:`--dry-run`(S2 只接吊舱/不发 offboard/打印 β→yaw_rate) 与实飞(S3+,`PodBridge.connect`+信号优雅停机);配套 `run_bridge_test.py` 5 条,全量 **401 passed** |
| 9 | `pod_bridge/bridge.py:run()` | ✅ 循环周期改按生效律取频率(L1=tracking.control,L2=guidance),修 L1 live 频率错配 |
| 10 | `pod_bridge/virtual_pod.py` | ✅ 模拟吊舱(SITL/台架工具):按无电机语义合成表2-2(框架角恒0、无激光、不可见→bit2=0+脱靶量归0),符号与 S1 标定一致 |
| 11 | `pod_sitl_test.py`(根目录) | ✅ L1 SITL 全流程(test.py 吊舱版):解锁→起飞→锚点→offboard→L1 闭环(VirtualPod→PodBridge→控制律)→丢目标→刹车→RTL→落地→上锁,轨迹落 CSV;飞行编排不塞进 run_bridge(保持其纯 offboard 闭环入口) |
| 11 | `l1_sitl_test.py`(根目录,原 `pod_sitl_test.py`) | ✅ L1 SITL 全流程:解锁→起飞→锚点→offboard→L1 闭环(VirtualPod→PodBridge→控制律)→丢目标→刹车→RTL→落地→上锁,轨迹落 CSV;飞行编排不塞进 run_bridge(保持其纯 offboard 闭环入口) |
| 12 | `tests/pod_bridge/virtual_pod*_test.py` | ✅ 15 条:S1 符号回归 + VirtualPod×PodBridge×控制律集成(右转/下降/慢速前近/丢目标刹车);全量 **417 passed** |
| 13 | `pod_bridge/los_consistency.py`(2026-10-19) | ✅ 目标有效性第二判据:LOS 运动学一致性(Δβ+Δψ,3s 窗口/20° 激励/8° 残差);`pod_locked()` 叠加该判据;配套 `los_consistency_test.py` 12 条 + 桥接层 3 条 |
**待办(非代码,台架)**:~~S1 标定~~ **已完成(2026-10-09)**:目标在左下时 `off_target_x/y`
均为负 → 吊舱 X 轴右正(与 β 同向)`beta_sign=+1`;Y 轴上正(与 α 下正相反)
`alpha_sign=-1`(已回填 `pod_bridge.json`,并加标定回归测试守住)。厂商确认本版
**电机无用** → `beta_from_frame/alpha_from_frame` 恒 false。剩 **S2 干跑**
`run_bridge.py --dry-run` 复核方向与量级(目标偏左→yaw_rate 负、居中→趋 0),并标定 `heading.gain`;
之后 **SITL 全流程** `python pod_sitl_test.py`(需 PX4 SITL,本机尚未实跑)→ S3 不装桨接飞控。
**电机无用** → `beta_from_frame/alpha_from_frame` 恒 false。
**已完成的后续验证(2026-10-19)**:S2 干跑(`run_bridge.py --dry-run`,真吊舱 COM5,20Hz
闭环无异常)、SITL 全流程(`l1_sitl_test.py` 与 `task_runner_sitl.py` 均实跑)、
真吊舱在环全流程(`task_runner_real.py --port COM5`,无目标时走"搜索超时→返航"安全路径)。
剩:**`heading.gain` 标定**、**吊舱真实目标识别(bit2 0→1)**、S3 不装桨接飞控。
Binary file not shown.
-170
View File
@@ -1,170 +0,0 @@
# 双目 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 约束——标定完成前内参等数值均为占位值,仅限台架/回放验证,不得接入实飞控制回路。
-754
View File
@@ -1,754 +0,0 @@
# 双目测距技术路线
> 本文档把双目测距(立体目标测距)抽象为**与编程语言、硬件平台无关**的技术路线:
> 只讲几何模型、数据结构、算法流程与质量语义,不依赖任何语言特性、加速器
> 型号或操作系统。可作为任意语言(C++/Rust/Python 仿真/嵌入式 C)在任意
> 算力平台上重新实现或移植的规格说明。
>
> **文档分工**(各司其职,不重复抄录):
>
> | 文档 | 定位 | 与本文的分工 |
> |------|------|--------------|
> | `docs/双目深度估计思路.md` | 平台工程路线 + 原稿对照 | **平台与工程细节权威**:采集/解码链路、加速器预算、线程与文件落点、原稿缺陷对照;本文一律不涉及 |
> | 本文 | **算法路线** | **算法与语义权威**:几何、取点、匹配、有效性、滤波、方位源、误差模型、职责与缺点 |
> | `docs/视觉跟踪控制律技术路线.md` | 距离/方位的消费者 | 其距离通道与方位源如何消费本文输出,见其 §1.2/§2/§6.4 |
> | `docs/双目测距方案讨论纪要.md` | 决策索引 | D2/D4/D5/S1~S3 等拍板结论的唯一索引,本文只落实、不重新讨论 |
>
> 三份技术路线若出现表述分歧:拍板结论以《双目测距方案讨论纪要》为准,
> 算法语义以本文为准,平台工程以《双目深度估计思路》为准。
>
> **参考实现**:`utils/stereo_ranger/`(Python,构件划分与本文逐条对应)。
> 实现时冻结的补充决策与发现的原文矛盾已回填本文(§4/§6.1/§6.2/§6.4/
> §6.7/§9/§11),两文档以本文为准。
>
> ⚠️ **全部数值均为占位值**:焦距/标定参数待厂商数据(P1/P2),各阈值未整定。
> 标定与地面真值验证完成前,本模块输出**仅限静态标靶/回放/台架验证,
> 不得作为飞行控制决策依据**(见 §12.3 红线)。
---
## 目录
1. [概述与系统定位](#1-概述与系统定位)
2. [输入输出接口定义](#2-输入输出接口定义)
3. [坐标系与符号约定](#3-坐标系与符号约定)
4. [核心构件与状态清单](#4-核心构件与状态清单)
5. [单周期处理流程(主流程伪代码)](#5-单周期处理流程主流程伪代码)
6. [各环节算法细节](#6-各环节算法细节)
7. [误差模型与有效距离域](#7-误差模型与有效距离域)
8. [职责、边界与已知缺点](#8-职责边界与已知缺点)
9. [配置参数全量表](#9-配置参数全量表)
10. [设计取舍记录(为什么不是别的做法)](#10-设计取舍记录为什么不是别的做法)
11. [实现与重写指引](#11-实现与重写指引)
12. [验证路线与待定项](#12-验证路线与待定项)
---
## 1. 概述与系统定位
### 1.1 模块职责
本模块是**目标距离与方位的机载唯一来源**(决策 D2:双目替代激光雷达,
与提交 b96111e 一致)。它对**同一时刻**左右两幅已立体校正图像上的目标
检测框做立体几何解算,输出**少量目标的**距离与方位(不是稠密深度图)。
职责四件事:
1. **匹配**:把左右两目的检测框按"同一物理目标"配对(硬约束筛选 +
贪心代价排序);
2. **测距**:同侧边缘视差 D → `Z = F·B / D` 前向距离(斜距 R 仅参考输出);
3. **方位源**:输出**虚拟双目中心**像素 `c_mid`(两目框中心等权平均,
消除单目近场视差偏差,§6.6);
4. **质量语义**:输出有效性标志与置信度(有效域内 + 匹配成功 + 一致性
正常),对距离做轻量时间滤波;域外/失配/异常时**明确输出无效**,
不得强行估计。
### 1.2 在系统中的位置
```text
平台采集/解码/立体校正(平台相关,见《双目深度估计思路》第 3/8 章)
→ 校正后左/右图像(同帧)
→ 左/右目标检测(外部模块,输出原图坐标系的检测框)
→ 本模块:匹配 + 测距 + 方位源 + 有效性 + 滤波 → 目标距离记录
├─ 距离 Z(valid)──▶ 控制律距离通道(Δd → PID → 前向速度,域内闭环)
├─ 方位 c_mid ──────▶ 接入层填入控制律目标中心(虚拟中心相机像素)
└─ valid / 置信度 ──▶ 接入层降级语义:
域外/失配 → "无距离"策略(方位闭环 + 定速接近,D4)
持续无距离 → exit 档降级(S1 门限,消费侧实现)
```
**上游**:同帧校正图像对与左右检测框(检测、采集、解码、校正均为外部
模块);**下游**:跟踪控制律与接入层。"域外定速接近"的远场补偿策略
属消费侧行为,本文只负责提供诚实的有效性标志与持续的方位。
### 1.3 设计特点
| 特点 | 含义 | 带来的约束 |
|------|------|-----------|
| **稀疏输出** | 只算检测到的目标,不做逐像素匹配 | 无检测即无输出;距离精度受框质量制约(§8) |
| **纯值输入/输出** | 每帧传入同帧框对 + 参数,输出纯值记录列表 | 无隐藏输入、可离线回放 |
| **纯函数构件** | 取点、测距、域判定均为无状态纯函数 | 可独立单元测试(合成框对即可,无需真实图像) |
| **有状态部分极小** | 仅滤波器、一致性监控与目标隔离状态,且按目标隔离 | 重写时状态清单见 §11.1,漏一个就变行为 |
| **参数全外置** | 几何、阈值、滤波参数全部来自配置 | 代码内不得写死任何相机参数或阈值 |
| **诚实降级** | 域外/失配输出 `valid = false` 而非勉强给值 | 消费侧的远场策略建立在"不撒谎"的有效性上 |
### 1.4 明确不做的事(边界)
- **不做采集/解码/立体校正**:消费校正后的图像与框;校正与自标定属
平台工程(v1.1 备选),不在本文范围;
- **不做稠密匹配**:不做 SGBM 类逐像素立体匹配,不输出深度图;
- **不做环境感知/避障**:环境深度由避障侧深度模型负责,与本模块并存
互不替代;
- **不做目标跟踪**:不维护轨道与运动滤波;上游 track_id 只用作目标
状态隔离键(§4),不参与匹配打分与连续性先验(后者留 v1.1);
- **不做运动/IMU 补偿**:帧间运动引起的虚假视差靠帧同步契约与一致性
监控兜底,不做解算级补偿;
- **不做多传感器融合**:双目是目标距离机载唯一来源(D2),无交叉校验
基准;数据质量靠内部一致性监控自检(§6.7);
- **不在域外强行估计**:超出有效距离域一律输出 `valid = false`。
---
## 2. 输入输出接口定义
### 2.1 输入(每帧一次,由平台层供给)
| 分组 | 项 | 语义与约束 |
|------|----|-----------|
| 帧对 | 左帧、右帧 | 各含**帧标识**;两帧标识必须一致(同帧契约,见下) |
| 检测 | 检测框数组 ×2 | 每框含:类别、置信度、框(左上/右下角像素);**已映射回校正图像坐标系** |
| 几何 | 基线 `B`、焦距 `F`、主点 `cx, cy` | 校正后内参(`fx = fy = F`);来自标定配置,禁止写死 |
| 参数 | 匹配/有效性/滤波阈值 | 配置外置(§9),启动期加载并校验 |
| 时钟 | 当前单调时刻 | 用于输出打戳 |
**契约要点**:
- **同帧契约**:左右帧必须来自同一曝光时刻(同步与配对由平台层保证,
形态差异见《双目深度估计思路》§3.2)。本文把"左右帧标识一致"作为
输入违约检查:不一致直接拒收本帧、输出空、告警节流——**不同帧的
框配对解算没有物理意义**;
- **校正契约**:检测框位于**已立体校正**的图像上("两目 y 对齐"是校正
的结果而非假设,§3);未校正图像上的框不得进入本模块;
- **坐标基准一致**:检测若在缩放/裁剪图上运行,框必须先映射回校正图像
基准,且几何参数 `F/cx/cy` 使用同一基准的内参——否则视差与几何失配,
距离系统性错误;
- **时间戳纪律**:输出取样时刻以**平台接收时刻的单调时钟**为准,
不伪造采集时刻。
### 2.2 输出:目标距离记录(每帧每目标一条,纯值)
| 字段 | 单位/类型 | 语义 |
|------|-----------|------|
| 帧标识 | — | 继承自输入帧对 |
| 类别 | 枚举 | 目标类别 |
| 轨道关联 | 整数或"无" | 检测侧 track 关联(如有;v1.0 可无) |
| `Z` | m | 前向距离(沿光轴分量,**距离通道唯一消费量**) |
| `R` | m | 斜距(参考值,不进控制回路;与已发布的 `Z` **同基准**——按滤波后 Z 换算,保证 `R ≥ Z`) |
| `D` | px | 本次视差(调试与交叉校验用) |
| `c_mid` | 像素 (x, y) | **虚拟双目中心**框中心:方位控制源(§6.6) |
| 参考目框 | 像素框 | 左目(参考目)检测框,供交叉校验与调试 |
| `confidence` | [0,1] | 匹配质量与一致性综合置信度 |
| `valid` | 布尔 | 是否落在有效距离域且匹配成功且一致性正常(§6.4) |
| 退化标记 | 枚举 | `完整`(距离+方位)/ `仅方位`(单目检出,§5 第 ⑤ 步) |
| 取样时刻 | 单调时钟 | 平台接收时刻打戳(§2.1 时间戳纪律) |
**记录两态**:
- **完整记录**(匹配成功):距离 + 方位 + 有效性;
- **仅方位记录**(仅一目检出,v1.0 允许):发布该目框中心作方位,
`valid = false`,无距离;单目方位存在近场视差偏差(§6.6,v1.0 接受)。
### 2.3 输出语义与消费侧衔接
- `valid == true` 的 `Z` 才可作为控制律距离通道输入;消费侧另有自己的
"有效性 + 正值 + 未超龄"三重门(见《视觉跟踪控制律技术路线》§6.4);
- `valid == false`(域外/失配/一致性异常)→ 消费侧进入"无距离"策略
(方位闭环 + 定速接近,默认档),持续无距离达门限才退出近距操作
(消费侧 exit 门限,S1);
- **`c_mid` 始终随检测发布**(域外仍有方位)——这是远场"方位闭环 +
定速接近"策略得以成立的输入基础;
- 消费侧以 `track_id` 一致性校验本记录与追踪主目标对应(调用方约定
S3,库内不实现)。
---
## 3. 坐标系与符号约定
### 3.1 符号表
| 符号 | 含义 | 单位 |
|------|------|------|
| `B` | 基线长度(左右光心间距) | m |
| `F` | 像素焦距(校正后 `fx = fy`) | px |
| `cx, cy` | x/y 方向主点 | px |
| `xL, yL` | 目标点在**左目(参考目)**图像中的像素坐标 | px |
| `xR` | 同一目标点在右目图像中的像素坐标 | px |
| `D` | 视差 = `xL − xR` | px |
| `Z` | 前向距离(沿光轴分量) | m |
| `X, Y` | 相机(左目)坐标系横向/纵向偏移 | m |
| `X_C` | 目标在**模组中心系**的横向偏移 | m |
| `R` | 斜距 | m |
| `cL, cR` | 同一目标在左/右目中的**检测框中心** | px |
| `c_mid` | 虚拟双目中心框中心(方位源) | px |
**统一约定**:校正后**以左目为参考系**,视差 `D = xL − xR > 0`
(正视差是物理前提;`D ≤ 0` 视为违例,§6.3)。
### 3.2 公式(校正后坐标系)
```text
D = xL − xR (同一物理特征点的 x 坐标差)
Z = F × B / D
X = (xL − cx) × Z / F (左目系)
Y = (yL − cy) × Z / F
R = sqrt(X² + Y² + Z²)
```
- **距离通道使用 Z(前向分量)而非 R**:与机头前向的期望射程语义对齐,
也与地面真值仪(手持激光测距仪)的测量方向一致(决策 D8);
小角度下 `Z ≈ R`,大偏航角目标时二者差异增大,地面验证解读数据须注意;
- **X/Y 以左目系表达**;模组中心系下 `X_C = X − B/2`(左目光心在模组中线
−B/2 侧,符号与 §6.6 一致;原文 `+B/2` 为笔误),`B/2` 对 `Z`、`R` 的
影响可忽略(本机型 3cm)。
---
## 4. 核心构件与状态清单
| 构件 | 职责 | 运行期持久状态 | 备注 |
|------|------|---------------|------|
| 流程编排 | 单周期五步流程(§5) | 无 | 每帧一次 |
| 视差取点器 | 框 → 取点 x(同侧边缘,回退中心) | 无(纯函数) | §6.1 |
| 匹配器 | 左右框配对 | 无(纯函数) | **接口注入**,默认贪心;可替换为匈牙利等而不动调用方 |
| 测距计算 | `(D, 框)` → `(Z, X, Y, R, c_mid)` | 无(纯函数) | §3.2/§6.6 |
| 有效性判定 | 域内 + 误差预算判定 | 无(纯函数) | §6.4 |
| 一致性监控 | 时序层面质量自检 | **每目标**:上次视差 + 连续异常计数 | §6.7 |
| 时间滤波器 | 距离低通 + 跳变重置 | **每目标**:上次滤波值、初始化标志、连续失效计数 | §6.5 |
| 目标状态容器 | 隔离键解析 + 帧末老化清理 | **每目标**:最近框中心、最近出现帧 | 隔离键见下;连续 `filter_reset_frames` 帧未出现则整体清理 |
| 告警节流器 | 异常日志限频 | 每类异常:首见标记 + 累计计数 | 第 1 次 + 每满 100 次 |
**目标隔离键(v1.0 冻结)**:`track_id` 优先——左右目的 track_id 一致且
为正时,用 `(轨道, 类别, id)` 作键(v1.1 语义提前启用,上游追踪库已在
链路中);否则回退**框位置最近邻**(同类别、门限 `isolation_gate_px` 内、
本帧尚未被占用的最近状态,一一归属)。
重写/移植时最容易漏的是**每目标隔离**的一致性计数与滤波状态——按
"全局单份"实现会在多目标下互相污染。
---
## 5. 单周期处理流程(主流程伪代码)
```text
函数 单周期处理(左帧, 右帧, 参数, now_ms) -> 目标距离记录列表:
# ---- ① 输入违约检查 ----
若 左帧.帧标识 ≠ 右帧.帧标识:
告警节流("左右帧非同帧,拒收")
返回 空列表
# ---- ② 取点(每目每框一次,纯函数) ----
左点表 ← 对 左帧.框列表 逐框取点(同侧边缘 x) # §6.1
右点表 ← 对 右帧.框列表 逐框取点(同侧边缘 x)
丢弃 坐标非有限值 的框(不参与匹配,也不发布记录;告警节流)
# ---- ③ 左右匹配(接口注入,默认贪心) ----
配对表 ← 匹配器.匹配(左点表, 右点表, 参数.匹配) # §6.2
# ---- ④ 配对成功的目标:测距 + 方位源 + 有效性 + 滤波 ----
对 配对表 中每个 (左框, 右框, 代价):
D ← 视差选择(左框, 右框) # 同侧边缘;越窗且中心在域内→回退中心(§6.1)
(Z, R) ← 测距(D, 参数.几何) # §3.2
c_mid ← (左框.中心 + 右框.中心) / 2 # §6.6
域内 ← 有效性判定(D, Z, 代价, 参数.有效性) # §6.4 门①②
一致性 ← 一致性监控(目标键).推进(D) # §6.7
置信度 ← 置信度映射(代价, 一致性) # §6.4 映射
有效 ← 域内 且 一致性 正常
若 有效:
Z_out ← 滤波器(目标键).推进(Z, 参数.滤波) # §6.5
否则:
滤波器(目标键).记失效() # §6.5 失效策略
Z_out ← 无
发布 记录(帧标识, 类别, Z_out, R, D, c_mid, 左框,
置信度, 有效, 完整, 取样时刻 = now_ms)
# ---- ⑤ 单目退化(仅一目检出) ----
对 未匹配的左框:
发布 记录(仅方位 = 左框.中心, valid = false, 退化 = 单目)
对 未匹配的右框:
发布 记录(仅方位 = 右框.中心, valid = false, 退化 = 单目)
# ---- ⑥ 空输入 ----
若 两目框列表均为空:
发布 无目标记录(或空列表);不告警(正常空帧)
```
**失败路径语义**:
| 情形 | 行为 | 日志 |
|------|------|------|
| 左右帧标识不一致 | 拒收本帧,输出空 | WARN 节流 |
| 单目检测为空 | 另一目发布仅方位记录 | 不告警(正常退化) |
| 匹配全部失败 | 全部按仅方位发布 | 不告警 |
| 视差违例(`D ≤ 0` 或超界) | 该目标 `valid = false`,仍配对并发布 `D`/`c_mid` | WARN 节流 |
| 边缘视差越窗、中心回退 | 该目标用中心视差解算(框宽差可能混入,§6.1) | WARN 节流 |
| 框坐标非法(非有限值) | 丢弃该框,不参与匹配也不发布 | WARN 节流 |
| 两目均无检测 | 无目标记录 | 不告警 |
高频热路径(每帧执行的数学)不打日志;所有异常日志按节流规范
(第 1 次 + 每满 100 次,带累计计数)。
---
## 6. 各环节算法细节
### 6.1 视差取点:同侧边缘(v1.0 主方案)
同一物理目标在两目中的检测框宽并不严格相等(检测抖动、外观差异),
**框中心**视差会混入框宽差的一半,**同一侧边缘**视差可抵消框宽项:
| 取点方式 | 误差来源 | 结论 |
|----------|----------|------|
| 框中心 | 两目框宽差直接进入视差 | 作对照/回退 |
| **同侧边缘(v1.0)** | 边缘定位噪声 | **v1.0 主方案** |
| 多边缘 + 亚像素精化(灰度重心/抛物线拟合) | 实现复杂度 | v1.1 |
```text
取法: D = 左框.左上角x − 右框.左上角x (同取左侧边缘)
回退: 边缘视差越窗 且 中心视差在域内 → 回退框中心并告警节流
两者均越窗 → 保留边缘值(供诊断与违例告警),由 §6.4 门 ① 判无效
```
> **不做"左右两边缘各算一次取中值"(v1.0 冻结)**:两边缘视差的中值恒
> 等于二者均值,即 `(cL.x − cR.x)`——**框中心视差**,与本表"同侧边缘
> 抵消框宽差"的理由自相矛盾(早期原文笔误),故不实现;亚像素精化留
> v1.1(§8.3)。
### 6.2 左右匹配:硬约束 + 贪心代价
**硬约束**(任一不满足直接拒绝配对,不进代价排序):
- 类别一致;
- `|yL − yR| ≤ τ_y`(校正后 y 应对齐;这是校正质量的运行期体现);
- 框高相近:`|hL − hR| / max(hL, hR) ≤ τ_h`。
**视差域不是配对硬约束(v1.0 冻结)**:越界目标仍应配对、仍发布 `D` 与
`c_mid`(远场"方位闭环 + 定速接近"的输入基础),仅由 §6.4 门 ① 判
`valid = false`。若把域并入硬约束,越界目标会退化为无方位的失配记录,
与 §2.3/§5 失败路径表的语义冲突。
**代价与贪心**:
```text
cost = |Δy| / τ_y + |Δh| / (τ_h × max(hL, hR))
按 cost 升序逐一配对,cost < 1 才接受,一一配对(一个左框至多配一个右框)
```
- 本场景目标数极少(1~2 个),O(n²) 贪心足够(§10.3);
- 失败处理:任一目未匹配 → 该目标本帧无距离(仅方位发布),
不阻塞其他目标。
### 6.3 测距计算与违例处理
```text
Z = F × B / D (D ≤ 0:物理违例 → valid = false,告警节流)
R = sqrt(X² + Y² + Z²) (参考输出)
```
- `D ≤ 0` 在校正正确的正视差几何下不应出现;出现说明匹配错误或校正
残差过大,宁可无效不可给值;
- `D ≤ 0` 时**不出解算结果**(无 `Z`、无 `R`);超界(`0 < D < D_min`
或 `> D_max`)仍解算 `Z`/`R` 供诊断与告警,同样 `valid = false`;
- 边缘取点为像素级整数(取整/插值规则由实现自定,但**左右目必须一致**);
- 所有除法分母(`D`)在使用前校验为正。
### 6.4 有效性判定:三道门(缺一不可)
| 门 | 判据 | 拦截什么 |
|----|------|---------|
| ① 域内 | `D ∈ [D_min, D_max]`(含边界);距离域由 `Z = F·B/D` 派生 | 远场小视差病态 + 近场大视差盲区 |
| ② 误差预算 | `δZ(δD=1px) = Z²/(F·B) ≤ δZ_max` | 有效域边界处的精度下限 |
| ③ 一致性正常 | §6.7 监控未判异常 | 持续视差跳变(标定漂移/同步故障/匹配错误) |
```text
valid = 域内 且 误差预算内 且 匹配成功 且 一致性正常
```
匹配成功由第 ③ 步保证(只有配对目标才进入本节判定)。
**门 ① 以视差域为唯一真源(v1.0 冻结)**:配置只存
`disparity_min_px / disparity_max_px`,`Z_min / Z_max` 由 `Z = F·B/D`
派生为只读量——`D` 是直接观测量,`Z` 是导出量,用 `D` 判域可避免
"距离阈值反推视差阈值"的舍入不一致;两处冗余存储正是该不一致的根源
(原文 §9.2 占位值即不自洽:`D_min = 3px` ⇔ 19.2m ≠ 20m),故不再
"互推并校验自洽"。
**门 ② 的 δD 基准固定为 1px(⚠占位,v1.0 冻结)**:δD 的真实构成
(边缘定位噪声 + 校正残差 + 帧不同步 + 压缩伪影,§7.1)留待地面真值
拟合(§12.2),当前不做估计、不随目标状态变化。
**置信度映射(⚠占位,v1.0 冻结)**:基值 = `clamp(1 − 匹配代价, 0, 1)`;
连续跳变 ≥ N 帧(§6.7)时乘降级系数(⚠0.5);判 invalid(≥ M 帧)时置 0。
### 6.5 时间滤波与跳变重置(每目标独立)
```text
推进(Z_new):
若 未初始化: 状态 ← Z_new; 返回 Z_new
若 |Z_new − 上次| > 跳变门限:
状态 ← Z_new # 直接重置而非滤波,防止拖尾
否则:
状态 ← 上次 + α × (Z_new − 上次) # 一阶低通,α ∈ (0,1]
返回 状态
记失效(): # 无效帧与"目标本帧缺席"均记失效(§6.5 失效策略)
连续失效计数 +1
若 连续失效 ≥ filter_reset_frames: 清空滤波状态(下次有效帧重新初始化)
```
- **单帧毛刺由跳变重置处理,持续性异常由 §6.7 一致性监控处理**,
两者职责不同不得合并;
- `α = 1` 等效不滤波;滤波只在**有效帧**之间进行,无效帧不参与平滑;
- 清理门限 `filter_reset_frames` **独立配置**(不复用 §6.7 的 M,⚠占位
5 帧),防"陈旧状态平滑新测量";
- 参考斜距 `R` 与**滤波后**的 `Z_out` 同基准(`X/Y/Z ∝ Z`,按
`Z_out/Z_raw` 精确换基,保证 `R ≥ Z`,§2.2);
- 滤波引入的群延迟约 `1/α` 帧(⚠占位 α=0.3 → 约 3 帧 ≈ 0.1s),
接近阶段的距离滞后由消费侧 PID 与限幅吸收。
### 6.6 方位源:虚拟双目中心 `c_mid`
**问题**:双目模组两枚相机分列模组中线两侧(各偏 ±B/2),都不在机身
中轴上。校正后严格成立的只有 **y 对齐**(垂直方向不受两目差异影响);
水平方向上直接以某一目(如左目)的像素做方位控制,收敛后对准的是
**该相机的光轴**,相对模组中线存在恒定视差偏差:
```text
β_bias ≈ atan( (B/2) / Z )
```
| 距离 Z | 角度偏差 | 像素等效(F≈960px,⚠占位) |
|-------:|-------:|-------:|
| 3 m | 0.57° | ≈10 px |
| 10 m | 0.17° | ≈2.9 px |
远距离可忽略,**近场不可忽略**;偏差方向恒定、随距离减小。
**精确修正(零额外成本)**:校正后几何下,位于基线中点的**虚拟相机**
对任一物理点 P 的投影恰为该点在两目投影的**等权平均**:
```text
xL = cx + F·(X_C + B/2)/Z (左目光心在模组中线 −B/2 侧)
xR = cx + F·(X_C − B/2)/Z (右目光心在模组中线 +B/2 侧)
(xL + xR) / 2 = cx + F·X_C/Z (= 虚拟相机投影,逐点精确,无近似)
```
检测框的每条边缘都是物理点、逐点满足上式,因此:
```text
虚拟框 = 两目框逐边平均 ⇒ 虚拟框中心 c_mid = (cL + cR) / 2
c_mid.y = (cL.y + cR.y) / 2 (校正后两目 y 天然相等,取平均兼得降噪)
```
即**等权平均等价于把相机搬到模组中线**,方位控制对标的是虚拟中心相机,
与机体中轴的安装偏移(如有)属安装外参,按安装标定处理。
**方位源与视差取点是两个不同的取点,不得混用**:
| 用途 | 取点 | 原因 |
|------|------|------|
| 视差 D(测距) | **同侧边缘** | 抵消两目框宽差(§6.1) |
| 方位 c_mid(控制) | **框中心等权平均** | 瞄点语义 = 虚拟框中心;误用边缘点平均会把瞄点偏到目标边缘(约半个框宽) |
**单目退化**(仅一目检出,v1.0):退化为该目框中心,接受上表偏差;
v1.1 可用最近一次有效 Z 做定值修正 `Δx = F·(B/2)/Z` px。
### 6.7 内部一致性监控(替代原激光交叉校验)
机载激光移除后无外部距离基准,飞行中只能靠自身时序一致性监控数据
质量(**只能降级、不能纠偏**,绝对精度靠地面静态验证,§12.1):
```text
逐目标推进(D):
视差跳变 = |D − 上次 D|(或等效 Z 跳变量;门限 consistency_jump_px)
连续 N 帧跳变超门限 → confidence 降级(⚠×0.5)
连续 M 帧异常 → valid 置 false 并 WARN(节流)
任一正常帧 → 清零连续计数
```
- 匹配层面质量(|Δy| 残差、框高一致性、代价门限)已由 §6.2 硬约束
承担;本节只管**时序层面**;
- 跳变门限 `consistency_jump_px`(⚠占位 3px)、N / M(⚠ 3 / 5 帧)均
为占位,实测后整定(§9.3);
- **推进时机(v1.0 冻结)**:只在配对成功帧推进——仅方位帧没有 `D`
观测量,不推进 D 序列、也不清零;目标缺席/无效帧的**滤波失效**由
§6.5 记,两条状态机职责不混;
- 监控判 invalid 是"质量语义"而非"错误":输出仍正常发布(仅方位),
由消费侧按无距离策略接管。
---
## 7. 误差模型与有效距离域
### 7.1 误差模型
由 `D = F·B/Z` 微分得:
```text
δZ ≈ Z² · δD / (F·B) (绝对误差,随距离平方增长)
δZ / Z ≈ Z · δD / (F·B) (相对误差,随距离线性增长)
```
`δD` 的构成:检测框边缘定位噪声 + 校正残差(|Δy| 失配混入)+ 帧不同步
引入的运动虚假视差 + 有损压缩伪影(平台相关,抽象计入边缘噪声)。
基线与焦距误差(δB/B、δF/F)线性传递、量级小,出厂标定后可忽略。
**这是"双目只能作近场距离源"的定量依据**,也是第 8 章多数缺点的根源。
### 7.2 有效距离域判据
```text
valid 域 ⇔ D ∈ [D_min, D_max](⚠占位 3 ~ 57.6px)
且 δZ(δD = 1px) ≤ δZ_max(⚠占位 2m)
```
- 按占位焦距计算:宽角档(HFOV≈90°)有效域约 **≤15m**、窄角档
(HFOV≈60°)约 **≤20m**(**⚠占位,待厂商实焦距回填后重算**;
数值回填的权威位置在《双目深度估计思路》§5.3 误差表);
- 近界:`D_max`(或 `Z_min`)限定最近可测距离——目标过近时视差超上限、
框占满画面、边缘取点失效(§8 第 3 条);
- 敏感性:误差 ∝ 1/B。更换大基线机型误差按比例缩小、有效域等比扩大
——**换机型(P6)是扩大远场能力的唯一杠杆**。
### 7.3 远场空洞(如实声明)
双目有效域之外(预计 >15~20m,待焦距回填),系统**不存在任何机载
目标距离源**。补偿方式是消费侧的远场策略(方位闭环 + 定速接近,D4),
而非强行使用不可靠的远场估计。本模块的职责边界是:把"测不了"诚实
地表达为 `valid = false` + 持续发布方位。
---
## 8. 职责、边界与已知缺点
### 8.1 职责(一句话版)
> 对同帧左右检测框做立体几何解算:**有效域内给出目标前向距离,
> 全程给出虚拟中心方位,测不了时诚实输出无效**——供控制律做
> 近场距离闭环与远场方位闭环 + 定速接近。
边界(不做什么)见 §1.4;与避障深度模型的分工见《双目深度估计思路》§1.3。
### 8.2 已知缺点与局限(如实声明,不得因文档目的删减)
| # | 缺点 | 根因 | 影响 | 现行对策 / 接受理由 |
|---|------|------|------|---------------------|
| 1 | **误差随距离平方增长** | `δZ ∝ Z²`(§7.1) | 远距离距离值不可用 | 有效域判据硬性拦截(§6.4);域外交给定速接近 |
| 2 | **远场空洞** | 有效域 >15~20m 外无任何机载距离源 | 远场无法距离闭环 | 消费侧 D4 策略(方位闭环 + 定速接近);扩域唯一杠杆是换机型(P6) |
| 3 | **近场盲区** | 视差超 `D_max`、框占满画面、边缘取点失效 | 反制逼近的最后阶段恰是最需要距离时失效 | 消费侧判丢刹车/定速接近兜底;`Z_min` 如实设界 |
| 4 | **检测框是单点依赖** | 全部几何量从框导出 | 无检测即无距离也无方位;框抖动直接进入 δD | 检测置信度门槛在检测侧;追踪库 Coast 补帧兜底 |
| 5 | **框级精度上限** | 取点在框边缘/中心,非特征级匹配 | 精度天花板高于特征级方案 | 接受(几何换算换实时性,§10.1);v1.1 亚像素精化 |
| 6 | **小视差病态** | 远距 D → 1px,量化误差主导 | 远场 δD=1px 假设过于乐观 | 同上由有效域拦截 |
| 7 | **标定与校正依赖** | y 对齐、无畸变是解算前提 | 未标定则系统性偏差,甚至全盘不成立 | P2 出厂标定为 v1.0 首要前置;不达标则 v1.1 自标定提前 |
| 8 | **帧不同步敏感** | 非同瞬曝光 → 运动目标虚假视差 | 快速横移目标距离失真 | 同帧契约(§2.1)+ 13 章快速横移验证;同步形态待 P3 |
| 9 | **固定基线/焦距** | 单一几何配置 | 近盲远废的区间固定 | 参数化配置;换机型只改配置(红线 2) |
| 10 | **贪心匹配局限** | O(n²) 局部最优 | 目标多/邻近/遮挡时错配风险 | 目标数极少(1~2)可接受;匹配器接口注入保留替换空间 |
| 11 | **无飞行中绝对基准** | 激光移除后无外部真值(D2/D8) | 一致性监控只能降级、不能纠偏 | 绝对精度靠地面手持激光测距仪静态验证(§12.1) |
| 12 | **单目退化有瞄偏** | 单目光轴偏离模组中线(§6.6) | 仅方位记录时方位存在 0.5° 量级近场偏差 | v1.0 接受并如实标注退化;v1.1 定值修正 |
**阅读提示**:1/2/3/6/9 是几何固有,只能拦截或换硬件,不能修好;
4/5/8/10/12 是工程折中,有 v1.1/v2.0 改进路径;7/11 是流程前提,
靠标定与地面验证闭环。
### 8.3 缺点对策索引
| 路径 | 覆盖的缺点 |
|------|-----------|
| v1.1(算法) | 亚像素边缘(5)、真值拟合误差修正项(1/6)、track_id 辅助匹配(10)、单目定值修正(12)、自标定(7,视 P2 结论提前) |
| v2.0(远期) | 实例分割替代框(4/5)、端到端立体评估(5)、更大基线/长焦机型(1/2/3/6/9,P6) |
| 消费侧兜底 | 定速接近 + 判丢刹车 + exit 门限(2/3 的安全网) |
| 地面验证 | 7/8/11 的唯一闭环手段 |
---
## 9. 配置参数全量表
全部参数**配置外置、启动期一次加载并校验**:缺项、类型错误、非法取值
一律拒绝进入运行期(fail-fast,语义要求,不绑定具体配置格式)。
所有数值均为占位(⚠),整定后回填。
### 9.1 几何组(来自标定,P1/P2)
| 键 | 单位 | 占位值 | 作用 |
|----|------|-------|------|
| `baseline_m` | m | 0.06 | 基线 B;更换机型只改此项 |
| `fx_px` / `fy_px` | px | 960.0(⚠占位,待 P1) | 像素焦距(校正后 `fx = fy = F`) |
| `cx_px` / `cy_px` | px | 960.0 / 540.0(⚠占位,待 P2) | 主点 |
### 9.2 匹配组
| 键 | 单位 | 占位值 | 作用 |
|----|------|-------|------|
| `tau_y_px` | px | 2.0 | \|Δy\| 硬约束(同时是校正质量的运行期体现) |
| `tau_h_ratio` | — | 0.25 | 框高相对差硬约束 |
| `disparity_min_px` | px | 3.0 | 最小可测视差(远界);**域唯一真源**(§6.4) |
| `disparity_max_px` | px | 57.6 | 最大可测视差(近界);⚠占位:按 F=960/B=0.06 反推 ⇔ 1.0 m |
| `distance_min_m` / `distance_max_m` | m | **不再配置** | v1.0 冻结:距离域由 `Z = F·B/D` 从视差域派生为只读量(§6.4) |
### 9.3 有效性组
| 键 | 单位 | 占位值 | 作用 |
|----|------|-------|------|
| `max_delta_z_m` | m | 2.0 | 误差预算 `δZ(δD=1px)` 上限(δD 基准固定 1px,§6.4) |
| `consistency_N_frames` | 帧 | 3 | 连续跳变降置信度的帧数 |
| `consistency_M_frames` | 帧 | 5 | 连续异常判 invalid 的帧数 |
| `consistency_jump_px` | px | 3.0 | 单帧视差跳变门限(§6.7);**v1.0 补充项**(原文缺项) |
### 9.4 滤波组
| 键 | 单位 | 占位值 | 作用 |
|----|------|-------|------|
| `smooth_alpha` | — | 0.3 | 一阶低通系数(§6.5 定义,1 = 不滤波) |
| `jump_reset_m` | m | 5.0 | 单帧跳变直接重置的门限 |
| `filter_reset_frames` | 帧 | 5(⚠独立配置,不复用 M) | 连续失效多久后清空滤波状态(§6.5) |
### 9.5 目标隔离组
| 键 | 单位 | 占位值 | 作用 |
|----|------|-------|------|
| `isolation_gate_px` | px | 50.0 | 最近邻兜底的匹配门限(§4);**v1.0 补充项**(原文缺项) |
---
## 10. 设计取舍记录(为什么不是别的做法)
### 10.1 两次目标检测代替稠密像素匹配
双目深度最难的是逐像素匹配:稠密立体匹配(SGBM 类)在该分辨率与
嵌入式 CPU 预算下不可行,而系统需求只是**少量目标的距离**。用左右
各一次轻量检测 + 框级几何换算绕开逐像素匹配,以"检测框天然给出 ROI"
换"精度天花板"(§8 第 5 条)。检测算力实测单核 20~30ms,NPU 预算
成立(平台侧见《双目深度估计思路》第 9 章)。
### 10.2 不先上端到端深度网络
需采集标注数据集、可解释性差、误差行为不可解析;先以几何法建立
**可验证的真值基线**,端到端立体/深度网络留 v2.0 评估。
### 10.3 贪心匹配而非匈牙利
目标数 1~2 个,贪心与全局最优几乎必然同解;O(n²) 足够。匹配器做
**接口注入**,保留替换为匈牙利/带连续性先验算法的空间而不动调用方。
### 10.4 同侧边缘而非框中心取视差
框宽差直接污染中心视差;同侧边缘可抵消框宽项(§6.1,原稿缺陷 2 的修正)。
### 10.5 距离通道用 Z 不用 R
与机头前向期望射程语义对齐,也与地面真值仪测量方向一致(决策 D8);
R 仅作参考输出。
### 10.6 方位源取两目框中心等权平均(虚拟中心像素)
两目均偏于模组中线 ±B/2,单目存在近场视差偏差(3m 处约 0.57°);
等权平均是投影几何**精确解**(§6.6,原稿"offset 加权"构想的正确形式,
决策 D5)。注意与视差取点(同侧边缘)分工不同、不可混用。
### 10.7 双目为机载唯一距离源、域外定速接近
替代激光(b96111e / D2);远场误差平方发散,强行估计不可靠——以
"诚实 invalid + 持续方位"支撑消费侧的方位闭环 + 定速接近(D4),
而不是在算法侧掩盖远场空洞。
---
## 11. 实现与重写指引
### 11.1 必须持久化的状态(漏一个就变行为)
| 状态 | 所属 | 初值 | 何时改变 |
|------|------|------|---------|
| 上次视差 + 连续异常计数 ×目标 | 一致性监控 | 无 / 0 | 配对帧写入上次视差;异常 +1 / 正常清零 |
| 上次滤波值 ×目标 | 时间滤波器 | 无(未初始化) | 每有效帧写入 |
| 滤波初始化标志 ×目标 | 时间滤波器 | false | 首个有效帧置 true;清空时回 false |
| 连续失效计数 ×目标 | 时间滤波器 | 0 | 每无效/缺席帧 +1 / 有效帧清零 |
| 最近框中心、最近出现帧 ×目标 | 目标状态容器 | 首帧写入 | 每次解析隔离键时刷新;连续 `filter_reset_frames` 帧未出现则整体清理 |
| 帧序号 | 流程编排 | 0 | 每帧 +1(被拒收的违约帧不加) |
| 告警节流状态 ×异常类 | 节流器 | 首见未打印 + 计数 0 | 每次异常更新 |
构造期预计算量:针孔系数与常量(如 `F·B`)一次算好,热路径只做乘除。
### 11.2 数值与精度注意
| 事项 | 处理方式 | 理由 |
|------|---------|------|
| 除零 | `D ≤ 0` 先判违例再除 | 正视差是物理前提(§6.3) |
| 视差取整 | 左右目规则必须一致 | 不一致会引入系统性 δD |
| 域判定基准 | 用 `D` 判域而非 `Z` | 直接观测量,避免往返舍入(§6.4) |
| 域配置单一来源 | 只配视差域,距离域派生 | 两处冗余存储是互推舍入不一致的根源(§6.4/§9.2) |
| 误差预算基准 | δD 固定 1px(⚠占位),不做在线估计 | 真实 δD 留待地面真值拟合(§12.2) |
| 浮点比较 | 阈值比较用 `≥/≤` 含边界语义 | 边界帧的归类必须确定 |
### 11.3 并发与生命周期
- 本模块是**纯计算单元**:驱动方式(独立线程 / 订阅回调 / 同步调用)
由平台层决定,本文只约定数据契约;
- 同一实例**不得并发调用**单周期处理(含滤波/一致性状态的无锁前提);
- 输出记录为**值拷贝**语义:多消费者各自持有快照,不得共享可变状态;
- 单周期处理应当无阻塞、无 IO(日志走节流通道,不落在数学热路径上)。
### 11.4 可测试性设计(构件边界即测试边界)
| 构件 | 是否纯函数 | 测试要点 |
|------|-----------|---------|
| 取点器 | 是 | 同侧边缘取值;框宽差不进入视差(构造两目等框不同宽断言 D 相同);越窗回退中心;边界含等号 |
| 匹配器 | 是 | 硬约束拦截各一例;贪心排序;一一配对;cost ≥ 1 拒绝 |
| 测距计算 | 是 | 已知 Z 反推 D 构造框对,断言 `Z = F·B/D` 回收;`D ≤ 0` 违例;`c_mid` 等权平均精确性(§6.6 恒等式);`R` 与发布 Z 同基准(`R ≥ Z`) |
| 有效性判定 | 是 | 三道门各一例;边界帧(恰在域界)归类确定;预算门通过/拦截两侧 |
| 时间滤波器 | 否 | 低通收敛;跳变重置不拖尾;失效清理 |
| 一致性监控 | 否 | N 帧降级、M 帧判 invalid、正常帧清零 |
| 目标隔离 | 否 | track_id 一致跨大位移保持状态;最近邻越门限新建;老化清理 |
| 全流程 | 否 | 合成帧对端到端:单目标/多目标/单目退化/非同帧拒收/记录顺序/非有限框/reset |
**无需真实图像**:全部测试用合成框对驱动,几何是解析可断言的。参考实现
的构件级用例已全部通过(§12.1)。
---
## 12. 验证路线与待定项
### 12.1 验证项
| 项 | 方法 | 通过标准 |
|----|------|----------|
| 构件级 | §11.4 合成框对单测 | 全部通过(无硬件依赖);Python 参考实现 `utils/stereo_ranger/` 用例已跑通 |
| 校正质量 | 静态场景统计匹配框 \|Δy\| 分布 | P95 ≤ 1px(⚠占位);不达标触发自标定提前 |
| 测距精度 | 5/10/15/20m 静态标靶,以**地面手持激光测距仪**为真值(非机载,D8),记录误差曲线 | 趋势与 §7 一致;有效域内 δZ ≤ 2m(⚠占位) |
| 帧同步 | 目标横向快速移动,观察视差时序抖动 | 无系统性虚假视差 |
| 控制联动 | 回放/SITL:距离闭环 ↔ 定速接近切换、invalid 持续降级路径 | 《视觉跟踪控制律技术路线》第 7 章场景全覆盖 |
| 日志纪律 | 检查异常路径日志 | 节流规范(第 1 次 + 每 100 次,带累计计数) |
### 12.2 待定项
开放问题(P1 焦距规格、P2 出厂标定、P3 帧同步形态、P6 换机型、
P7 性能实测、P8 避障激光去留)以《双目深度估计思路》第 15 章为权威
清单,本文不另立编号;其中:
- **P1/P2 直接决定本文 §7 结论与 §9 几何组能否从占位变实值**;
- **v1.0 实现补充的占位量**:`consistency_jump_px`(3px)、
`isolation_gate_px`(50px)、置信度降级系数(0.5)、误差预算基准 δD
(固定 1px)——随 P1/P2 回填与实测一并整定(§9.3/§9.5);
- **P6 是扩大有效域的唯一杠杆**(§7.2);
- P8(避障单点激光去留)不影响本链路——目标测距职责已由本模块承担。
### 12.3 红线
1. **占位数值不得用于飞行决策**:§7/§9 中所有 ⚠ 数值未经厂商数据与
整定确认前,本模块输出仅限静态标靶/回放/台架验证;
2. **相机参数与阈值一律配置外置**,禁止写死(后续可能更换机型);
3. **域外/失配必须输出 `valid = false`**,不得强行估计距离(远场策略
的正确性建立在有效性不撒谎之上);
4. **标定与地面真值验证完成前不得接入实飞控制回路**(与消费侧控制律
红线一致,见《视觉跟踪控制律技术路线》§12.3)。
+17 -13
View File
@@ -1,4 +1,4 @@
# pod_bridge 移交树莓派 4B 部署注意事项
# pod_bridge 移交树莓派 4B 部署注意事项
> 目标平台:树莓派 4B + **Raspberry Pi OS 官方镜像(64 位,Bookworm / Trixie)**。
> (2026-09-23 由 Ubuntu 24.04 换回官方镜像:Ubuntu 镜像上串口配置与
@@ -48,7 +48,7 @@ pytest # 仅上机自测需要,部署运行可省
- **mavsdk 4.x 是全新原生 API 重写,与经典 gRPC API 完全不兼容**(2026-09-24
树莓派实测):没有 `mavsdk.offboard` / `System` / `telemetry`,本仓库全部
飞控代码(Px4Gateway / MavsdkCommandGateway / test.py)都是经典 API,
飞控代码(Px4Gateway / MavsdkCommandGateway / 各入口脚本)都是经典 API,
装 4.x 直接 `LegacyModuleError`。**必须锁 `mavsdk<4`**(最新 3.17.4,
aarch64 官方 wheel `manylinux2014_aarch64`,PyPI 已核对);
32 位 armv7l 支持不稳定——这是必须 64 位镜像的主因。
@@ -66,7 +66,7 @@ pytest # 仅上机自测需要,部署运行可省
- 连接串写法(`pod_bridge.json` 的 `px4.connection_string`):
- Pi 串口直连飞控 TELEM:`serial:///dev/ttyUSB0:57600`(波特率按飞控侧设置);
- 经数传/仿真:`udp://:14540`。
- 经数传/仿真:`udpin://0.0.0.0:14540`(本机 SITL 实测可用;`udp://:14540` 亦可,语义为监听该端口)。
- PX4 侧需确认 TELEM 口已配 MAVLink 且 offboard 心跳未被占用。
## 4. 串口硬件(吊舱侧,最容易翻车)
@@ -251,26 +251,28 @@ python -m pod_bridge.sim_fc /dev/ttyAMA0 --detect person --auto-lock \
| 受影响项 | 后果 |
|---|---|
| `guidance.py` 地理位置引导律 | **永久刹车**。桥接层 `bridge.py:169` 的 `target_valid` 依赖表4;表4 恒缺 → `_latest_target` 恒 None → `GuidanceLaw.update()` 每周期走 `_brake("载机位置或目标不可用/超龄")`。安全方向正确(降级刹车、不会乱飞),但**吊舱表4 这条引导路径在本设备上等于不存在** |
| 目标距离/坐标来源 | 必须改由**我方自有链路**提供:`stereo_yolo` 检测 → `stereo_ranger` 双目视距。吊舱只当作"稳定指向 + 表2-2 脱靶量"的伺服/传感器用 |
| `guidance.py` 地理位置引导律 | **表4 路径在本设备上恒不可用**(`target_valid` 恒假)。**现行做法:坐标走地面站 UDP 注入**(`ground_link` → `ground_target_fn`),`guidance` 吃注入坐标;表4 仅作回退 |
| 目标距离/坐标来源 | **坐标 = 地面站注入**(已落地)。**距离尚未接入**:规划由激光雷达提供(低算力);~~`stereo_yolo` 检测 → `stereo_ranger` 双目视距~~ 因 Pi 4 跑不动实时 YOLO,**暂不接入** |
| `--auto-unlock`(§10.1) | **必然误触发,已实测禁用**:`last_tgt_s` 恒 None 被当成超时。见 §10.1 |
| 后续选型 | 若要保留吊舱侧目标定位,只能换**带激光测距的吊舱型号**(且需确认激光能接入吊舱解算链路) |
#### 架构该怎么办(分叉已选边)
#### 架构该怎么办(2026-10-19 已定稿)
既然距离无法回灌、表4 不会有,目标地理坐标就**必须由我方自己解算**:
既然距离无法回灌、表4 不会有,目标地理坐标就**必须来自吊舱之外**。当时的设想是
"双目自算坐标";实际实现改成了更简单的分工:
```
stereo_yolo 检测 → stereo_ranger 双目视距(斜距/前向距离)
→ 载机 GPS + 姿态(表3 我方已有) + 吊舱框架角(表2-2)
→ **我方自己算出目标经纬度**,再喂给 guidance.py
目标**坐标** ← 地面站 UDP 注入(ground_link) ← 已落地
目标**方位** ← 吊舱表2-2 脱靶量(角度直入 L1) ← 已落地
目标**距离** ← 待接激光雷达(串口,低算力) ← 未接
```
吊舱在本设备上退化为:**伺服稳定指向 + 表2-2 脱靶量/框架角传感器**。
它仍然很有用(稳像、跟得住、给得出视场角),只是不再提供坐标。
> 这意味着 `guidance.py` 的输入要从"吊舱表4"换成"我方自算的目标经纬度",
> `bridge.py:169` 的 `target_valid` 判据也要跟着改。**动手前先对齐方案再实现。**
> ~~下文"stereo_yolo → stereo_ranger 自算经纬度再喂 guidance"的方案~~ 已被上述
> 分工取代:`guidance.py` 的输入改为**注入坐标**(已实现),不需要我方自算经纬度。
> 距离接入后,只影响控制律的 `distance_valid` 分支,不影响坐标来源。
> 复测命令(`--json` 后是**输出产物路径**,非预置输入文件,建议给绝对路径):
> ```
@@ -298,7 +300,9 @@ SEC 秒重复一次(与"假锁定"形成持续震荡:解锁 → 自动锁定 →
2. 表4 超时(需先确认表4 存在);
3. 锁定持续超过 T 秒且**从未**收到表4 → 判为假锁定,解锁一次并告警(不循环);
4. 脱靶量恒为 0 持续 T 秒 —— 居中跟踪时也会为 0,**误杀风险高**,不推荐;
5. 由我方视觉链路(stereo_yolo / target_tracker)交叉验证 —— 最可靠,待集成。
5. ~~由我方视觉链路(stereo_yolo / target_tracker)交叉验证~~ → **已实行为 LOS 运动学一致性**
(`pod_bridge/los_consistency.py`,2026-10-19):无电机、刚性固连 → 机头转 Δψ 时脱靶量
必变 ≈−Δψ,不动即判丢。无需额外传感器;局限是只证"角度是活的",不证"是那个目标"。
飞行期的正确行为是**刹车保护**(现有 `target_stale_ms` 已覆盖),与"解锁以便重新
搜索"是两件事,不要混为一谈。