Files
code-test-drone/reference-c++/target_tracker/target_tracker.md
T
2026-09-02 15:13:13 +08:00

11 KiB
Raw Blame History

target_tracker 视觉追踪库实现文档

独立动态库(libtarget_tracker.so),实现轻量级多目标追踪过滤器: 恒定速度卡尔曼滤波平滑 + Coast 抗短暂丢失 + ID 稳定与主目标锁定。 算法行为的唯一权威规格为 docs/视觉追踪技术路线.md(下称"技术路线"), 本文档不重复抄录算法全文,仅描述实现结构与对照关系。


一、功能职责与边界

做什么:

  • 每帧接收 YOLO 检测框列表,输出一个主目标的平滑中心点/边界框/状态标志 (TrackResult),供下游速度闭环(PID)消费。
  • 平滑:8 维恒定速度卡尔曼滤波(位置/尺寸 + 速度),抑制检测框逐帧抖动。
  • Coast 预测:目标短暂消失时用滤波器预测值继续输出(is_predicted=true), 避免控制输出突变;连续丢失超过 max_lost_frames(严格大于)才判丢。
  • ID 稳定:贪婪最邻近关联(中心距离 + IoU 加权代价)维持轨道 ID; 主目标锁定后不被高置信度新目标抢占,仅在轨道死亡或 Reset() 时解除。

不做什么(边界):

  • 不做光流估计、不做激光/惯导等多源融合(属 perception 模块职责);
  • 不做 Topic 订阅/发布接线、不依赖 spdlog/Eigen/OpenCV,零第三方依赖;
  • 不做画面边界钳制与负尺寸约束(与技术路线 §4.5 注记保持一致);
  • 不要求检测置信度下限过滤(上游检测器已完成阈值过滤)。

二、接口与数据流

命名空间 drone::tracker,头文件保护 #pragma once。

头文件 内容
include/target_tracker/kalman_box_filter.h BoxCXCYWH、KalmanBoxFilter
include/target_tracker/tracklet.h BoxXYXY、ToCenterSize/ToXYXY/ComputeIoU、Tracklet
include/target_tracker/target_tracker.h Detection、FrameShape、TrackResult、TargetTracker

主要接口签名:

class KalmanBoxFilter {
    void Initiate(const BoxCXCYWH& z);              // 首次观测初始化(§4.3)
    std::optional<BoxCXCYWH> Predict();             // 未初始化返回 nullopt(§4.4)
    void Update(const BoxCXCYWH& z);                // 观测更新(§4.5)
    bool Initialized() const;
    const std::array<double, 8>& State() const;     // [cx,cy,w,h,vx,vy,vw,vh]
};

struct Tracklet {
    Tracklet(int id, const BoxCXCYWH& measurement, double conf, int cls,
             int max_lost_frames);
    bool IsDead() const;      // lost_count > max_lost(严格大于)
    bool IsCoasting() const;  // lost_count > 0
    void Predict();           // kf.predict 非空时更新 last_center/last_box、age+1
    void Update(const BoxXYXY& box, double conf, int cls);  // 匹配更新
    void MarkMiss();          // lost+1、age+1
    // 字段:track_id / kf / lost_count / max_lost / confidence / cls_id /
    //       age / hits / last_center / last_box / last_raw_box
};

class TargetTracker {
    struct Config {  // §8.1 默认值
        int    max_lost_frames      = 8;
        double max_association_dist = 200.0;
        int    min_hits             = 3;
        double dist_weight          = 0.5;
        double iou_weight           = 0.5;
    };
    TargetTracker();                               // 默认配置
    explicit TargetTracker(const Config& cfg);
    TrackResult Update(const std::vector<Detection>& detections,
                       std::optional<FrameShape> frame_shape = std::nullopt);
    TrackResult LastResult() const;                // 线程安全拷贝
    void Reset();                                  // 清全部状态且 next_id 归零
    int ActiveCount() const;
    std::unordered_map<int, Tracklet> ActiveTracks() const;  // 只读快照
};

数据流:调用方每帧将检测列表(像素坐标 [x1,y1,x2,y2,conf,cls_id])传入 Update(),得到 TrackResult(九字段:tracked / primary_id / center / box / confidence / is_predicted / lost_frames / n_active / raw)。丢失时 tracked=false、定位字段空、lost_frames=0(技术路线 §2.3)。

三、关键实现点

3.1 卡尔曼滤波(对照技术路线 §4)

  • 状态 x[8]=[cx,cy,w,h,vx,vy,vw,vh],观测 z[4]=[cx,cy,w,h],dt=1 帧。
  • 常量矩阵:F(8×8,I4 + 右上 I4)、H(4×8,[I4|0])为 constexpr; 矩阵乘法/转置用 std::array 定长手写,全程 double(§9.3)。
  • 常量:kStdPos=1/50、kStdVel=1/200、kIniPos=2.0、kIniVel=100.0、 kRScale=2.0(§4.2/§8.2)。
  • 动态噪声:Predict() 时 Q=diag(σ_pos²,σ_vel²),σ 直接用状态中的 w/h 缩放;Update() 时 R 用 abs(w)/abs(h) 缩放并乘 2——两处差异与规格 §4.2 注记严格保持一致。
  • Initiate():速度置零,P=diag(2,2,2,2,100,100,100,100)(§4.3)。
  • Update() 中 S=HPHᵀ+R 为 4×4,用高斯-约当消元 + 部分主元求逆, 消元前对角加 1e-9 正则化防奇异(§9.3);随后 K=PHᵀS⁻¹、 x←x+K(z−Hx)、P←(I−KH)P(§4.5)。

3.2 贪婪关联(对照 §5)

  • cost = dist_weight·dist/max(Dmax,1e-3) + iou_weight·(1−IoU), 距离用 std::hypot;每条轨道 best_cost 初始化为 1.0,仅 cost 严格 小于才匹配(cost=1.0 恰好不匹配,§5.1)。
  • 按轨道 confidence 降序(平局按 ID 升序保证确定性)逐一贪心匹配; 排序时复制 ID 到 std::vector 后 std::sort,不直接排哈希表(§9.2)。

3.3 主目标选举三级回退(对照 §6.3)

  1. 锁定延续优先:locked_primary_id 存活则直接输出(Coast 中照样输出 is_predicted=true),不被高置信度目标抢占;
  2. 重新选举(无锁定/锁定已死亡):measured(lost==0)中 hits≥min_hits 的已确认池取 confidence 最大;池空则退回全部 measured;
  3. 无 measured 时从 coasting 中选离画面中心最近者(欧氏距离平方, frame_shape 缺省按 640×480 中心 (320,240)),is_predicted=true; 都没有则 tracked=false。

锁定仅在轨道死亡删除处或 Reset() 时解除。

3.4 主流程六步(对照 §7)

Update() 严格按:①全体轨道 predict ②检测中心表 + 贪婪关联 ③匹配轨道 update(raw_box 由检测重建)④未匹配轨道 mark_miss、死亡即删、若为锁定 目标同时清锁 ⑤未匹配检测新建轨道(创建当帧赋 last_box/last_raw_box, 保证当帧可参与输出;next_id 从 0 递增永不复用)⑥主目标选举组装输出并 缓存 last_result。

3.5 线程模型(对照 §9.5)

单线程写 Update()(推理主循环);库内仅对 last_result 缓存加一把 std::mutex,LastResult() 返回受保护拷贝。内部轨道状态不加锁; 若未来改为多线程调用 Update() 需整体加锁。

3.6 符号导出说明

当前不加 -fvisibility=hidden、不使用导出宏,共享库默认导出全部公共符号。 日后若启用隐藏可见性(如 CXX_VISIBILITY_PRESET hidden),需补 TARGET_TRACKER_API 宏(导出/导入双态)并标注所有公共类与函数, 否则下游链接将缺失符号。

四、日志行为

本库为纯算法层,不打任何日志(不依赖 spdlog)。若未来接入主工程, 接线层(订阅/发布适配部件)按主工程日志纪律负责关键路径日志与节流, 本库内部不新增日志。

五、测试方式

构建环境:WSL2 Ubuntu(AGENTS.md 规定的开发机构建环境)。

cd /mnt/d/ProgramData/drone_test/drone_test
cmake -S target_tracker -B target_tracker/build
cmake --build target_tracker/build -j$(nproc)
ctest --test-dir target_tracker/build --output-on-failure

测试共 27 条(kalman_box_filter_test 5 条 / tracklet_test 12 条 / target_tracker_test 10 条),其中 target_tracker_test.cpp 逐条覆盖 技术路线 §9.4 六条验收用例:

  1. EmptyFramesCoastThenLost:空检测帧 Coast 持续 max_lost_frames 帧 (is_predicted=true),第 max_lost_frames+1 帧 tracked=false;
  2. IdStrictlyIncreasingNoReuse:ID 严格递增不复用,Reset 后归零;
  3. LockNotStolenAndSwitchAfterDeath:Coast 期间高置信度新目标不抢占锁定, 锁定目标死亡后切换;
  4. MinHitsFallbackAllowsNewTrack:无已确认实测轨道时新轨道(hits<min_hits) 可当选主目标;
  5. CostExactlyOneDoesNotMatch:纯距离项构造 cost=1.0 恰好不匹配, 并附 cost<1.0 对照帧;
  6. HigherConfidenceTrackWinsDetection:两轨道竞争同一检测,置信度高者获胜。

其余为补充行为测试(丢失输出字段约定、Coast 兜底选举、锁定延续、 默认画面中心、IoU 边界、滤波器收敛性等)。

六、排查 / 修改要点

  • 主目标"甩头":确认是否误改锁定解除逻辑——锁定只能在轨道死亡删除处 或 Reset() 解除;选举分支不得覆盖存活锁定。
  • Coast 时间不对:死亡判定为 lost_count > max_lost(严格大于), 默认允许 Coast 8 帧、第 9 帧判丢;改成 >= 属于行为变更。
  • 关联不上 / 乱匹配:检查 max_association_dist 与目标帧间位移是否 匹配;cost 判据为严格小于 1.0;dist/iou 权重之和建议保持 1.0。
  • 滤波发散或跳变:检查 Q/R 动态噪声是否按状态 w/h 缩放(R 用 abs 且 乘 2、Q 不用 abs,该差异是规格要求);4×4 求逆已加 1e-9 正则化。
  • 测试链接失败(找不到 so):测试可执行文件通过 CMAKE_BUILD_RPATH 定位库;若把测试挪到别处运行需设置 LD_LIBRARY_PATH。
  • 修改任何行为后必须同步更新本文档与 docs/视觉追踪技术路线.md 的对照注释。

七、未来接线指引(接入主工程视频链路)

本库以独立动态库交付,主工程接入建议采用:

  1. 订阅解码帧主题作为每帧节拍:接线部件订阅解码帧 Topic,以解码帧 到达为帧节拍驱动追踪(保证与推理节奏一致);
  2. 按 frame_sequence 聚合 DetectionResult:YOLO 每目标发布一条 DetectionResult(同帧共享 frame_sequence),接线部件按 frame_sequence 聚合成 std::vector<Detection> 后调用 Update();
  3. 主工程 include/common/types.h 新增 TrackedTarget 消息与 kTrackedTarget 主题:由接线部件把 TrackResult 转换为 TrackedTarget 发布,供控制线程(20Hz PID)订阅消费;Coast (is_predicted=true)时调用方清 PID 积分、tracked=false 时零速刹车 (技术路线 §1.2 上游行为约定)。

主工程链接该 so 的 CMake 片段示例:

# 主工程 CMakeLists.txt 中引入视觉追踪库(独立子工程)
add_subdirectory(target_tracker)

target_link_libraries(drone_control PRIVATE target_tracker)
# 部署时需将 libtarget_tracker.so.1 一并打包到可执行文件目录
# (或设置 INSTALL_RPATH=$ORIGIN,与 docs/部署与打包.md 目录式部署约定一致)