精简仓库结构并增强任务链路:归档双目可选后端、新增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
+207
View File
@@ -0,0 +1,207 @@
"""demo_track_control.py —— 视觉跟踪控制链路集成演示(胶水层)。
对应 C++ 参考实现 reference-c++/visual_track_control/examples/track_control_demo.cpp。
串联三个组件(target_tracker 与 visual_track_control 互不 import,
本脚本是唯一的转换点):
模拟检测序列 → TargetTracker(8 维恒速卡尔曼多目标追踪)
→ TrackResult 转 TrackState(解耦边界)
→ TrackingControlLaw(三通道控制律 + 限幅 + 状态机)
→ CommandSender(mock 记录 / MAVSDK offboard 实发)
场景时间线(20 Hz,--speedup 可加速):
0~ 6s 正常跟踪:目标匀速横移 + 双目距离 30→10 m 前向逼近
6~10s 目标消失:追踪器 Coast 预测(60 帧 = 3 s 后轨道判丢,
控制律自动切换零速刹车)
10~13s 持续判丢:维持零速刹车
用法:
python utils/demo_track_control.py # mock 传输
python utils/demo_track_control.py --transport mavsdk # MAVSDK(需飞控/SITL)
python utils/demo_track_control.py --speedup 4 # 4 倍速
"""
import argparse
import asyncio
import logging
from pathlib import Path
from target_tracker.target_tracker import Detection, TargetTracker, TrackerConfig
from visual_track_control.config import load_from_json
from visual_track_control.mavsdk_gateway import MavsdkCommandGateway
from visual_track_control.tracking_control_law import TrackingControlLaw
from visual_track_control.types import (ControlMode, ControlSnapshot,
ControlOutput, TrackState)
logger = logging.getLogger("demo")
_DEMO_DIR = Path(__file__).resolve().parent
_DEFAULT_CONFIG = _DEMO_DIR / "config" / "track_control.json"
# 模拟场景参数
_NORMAL_SECS = 6.0 # 正常跟踪阶段
_COAST_SECS = 4.0 # 目标消失 → Coast(含轨道判亡)
_LOST_SECS = 3.0 # 判丢后持续刹车阶段
_DEMO_MAX_LOST_FRAMES = 60 # 追踪器 Coast 窗口(20 Hz 下 = 3 s)
_STATUS_EVERY_TICKS = 10 # 每 0.5 s 打印一行状态
def track_result_to_state(result) -> TrackState:
"""两包之间的唯一转换点:TrackResult(box 为对象)→ TrackState(plain tuple)。"""
box = result.box
raw = result.raw
return TrackState(
tracked=result.tracked,
primary_id=result.primary_id,
center=result.center,
box=(box.x1, box.y1, box.x2, box.y2) if box is not None else None,
confidence=result.confidence,
is_predicted=result.is_predicted,
lost_frames=result.lost_frames,
n_active=result.n_active,
raw=(raw.x1, raw.y1, raw.x2, raw.y2) if raw is not None else None,
)
def simulate_detections(tick: int, normal_ticks: int, period_s: float):
"""场景检测源:正常阶段目标匀速横移,之后消失。"""
if tick >= normal_ticks:
return []
cx = 960.0 + 3.0 * tick # 3 px/帧 匀速右移
cy = 540.0
w, h = 120.0, 160.0
return [Detection(cx - w / 2.0, cy - h / 2.0, cx + w / 2.0, cy + h / 2.0, 0.9)]
def simulate_distance(tick: int, normal_ticks: int, period_s: float):
"""场景双目距离源:正常阶段有效、距离 30→10 m;之后无效。"""
if tick >= normal_ticks:
return 0.0, False
distance = max(10.0, 30.0 - tick * period_s * 3.0)
return distance, True
class MockCommandSender:
"""CommandSender 的 mock 实现:记录最近一条指令描述(无实机验证能力)。"""
def __init__(self) -> None:
self.last_desc = "-"
async def start(self) -> bool:
logger.info("[MockTransport] offboard 启动(模拟)")
return True
async def send_velocity_heading(self, output: ControlOutput,
time_boot_ms=None) -> bool:
self.last_desc = (f"vx={output.vx_mps:+.2f} vy={output.vy_mps:+.2f} "
f"vz={output.vz_mps:+.2f} "
f"yaw_rate={output.yaw_rate_dps:+.2f}°/s "
f"yaw={output.yaw_deg:+.2f}°")
return True
async def send_brake_hover(self, time_boot_ms=None) -> bool:
self.last_desc = "零速刹车(全零机体速度设定点)"
return True
async def stop(self) -> None:
logger.info("[MockTransport] offboard 停止(模拟)")
async def make_sender(args) -> object:
if args.transport == "mock":
return MockCommandSender()
from mavsdk import System
drone = System()
logger.info("连接飞控 %s ...", args.conn)
await drone.connect(system_address=args.conn)
async for state in drone.core.connection_state():
if state.is_connected:
logger.info("飞控已连接")
break
return MavsdkCommandGateway(drone)
async def dispatch(sender, out: ControlOutput, now_ms: int):
"""按模式分发指令;DEGRADED_HOLD 不发送(禁止正常控制)。"""
if out.mode is ControlMode.VELOCITY_HEADING and out.valid:
return await sender.send_velocity_heading(out, now_ms)
if out.mode is ControlMode.BRAKE_HOVER and out.valid:
return await sender.send_brake_hover(now_ms)
return None
async def run_demo(args) -> None:
cfg = load_from_json(args.config)
period_s = 1.0 / cfg.control.frequency_hz
period_ms = int(round(period_s * 1000.0))
tracker = TargetTracker(TrackerConfig(max_lost_frames=_DEMO_MAX_LOST_FRAMES))
law = TrackingControlLaw(cfg)
sender = await make_sender(args)
normal_ticks = int(round(_NORMAL_SECS / period_s))
coast_ticks = int(round(_COAST_SECS / period_s))
lost_ticks = int(round(_LOST_SECS / period_s))
total_ticks = normal_ticks + coast_ticks + lost_ticks
if not await sender.start():
logger.error("offboard 启动失败,退出")
return
logger.info("开始 %d 拍(%.1f s,%.1f Hz):正常 %.1fs → Coast %.1fs → 判丢 %.1fs",
total_ticks, total_ticks * period_s, cfg.control.frequency_hz,
_NORMAL_SECS, _COAST_SECS, _LOST_SECS)
try:
for tick in range(total_ticks):
now_ms = 1000 + tick * period_ms
detections = simulate_detections(tick, normal_ticks, period_s)
distance_m, distance_valid = simulate_distance(tick, normal_ticks, period_s)
result = tracker.update(detections)
snap = ControlSnapshot(
track=track_result_to_state(result),
track_time_ms=now_ms, # 每拍取样即打戳(视觉始终新鲜)
target_distance_m=distance_m,
distance_valid=distance_valid,
distance_time_ms=now_ms,
attitude_present=True,
attitude_time_ms=now_ms, # 模拟姿态源持续可用
yaw_rad=0.0, # 机头朝北(NED 旋转恒等)
)
out = law.update(snap, now_ms)
await dispatch(sender, out, now_ms)
if tick % _STATUS_EVERY_TICKS == 0:
logger.info(
"t=%5.2fs 检测=%d 追踪[trk=%d pred=%d lost=%d] | "
"[%s] vx=%+.2f vy=%+.2f vz=%+.2f yaw_rate=%+.2f coast=%d | 发送: %s",
(tick * period_s), len(detections), result.tracked,
result.is_predicted, result.lost_frames, out.mode.name,
out.vx_mps, out.vy_mps, out.vz_mps, out.yaw_rate_dps,
out.coast_active, sender.last_desc)
await asyncio.sleep(period_s / args.speedup)
finally:
await sender.stop()
logger.info("演示结束")
def main() -> None:
parser = argparse.ArgumentParser(description="视觉跟踪控制链路集成演示")
parser.add_argument("--transport", choices=["mock", "mavsdk"], default="mock",
help="指令发送方式:mock=本地记录,mavsdk=offboard 实发")
parser.add_argument("--conn", default="udp://:14540",
help="MAVSDK 连接串(仅 --transport mavsdk 生效)")
parser.add_argument("--speedup", type=float, default=1.0,
help="时间倍速(>1 加速演示)")
parser.add_argument("--config", default=str(_DEFAULT_CONFIG),
help="控制律配置文件路径")
args = parser.parse_args()
logging.basicConfig(level=logging.INFO,
format="%(asctime)s.%(msecs)03d %(levelname)s %(message)s",
datefmt="%H:%M:%S")
asyncio.run(run_demo(args))
if __name__ == "__main__":
main()