MOT 多目标跟踪

什么是多目标跟踪

多目标跟踪(Multi-Object Tracking, MOT)从视频序列中持续跟踪多个目标(行人、车辆等),为每个目标分配唯一 ID 并记录运动轨迹。核心流程四步:

  1. 目标检测:每帧用检测器(YOLO、Faster R-CNN)定位目标框;
  2. 特征提取:提取外观特征(ReID 网络生成嵌入向量)与运动特征;
  3. 数据关联:用 IoU、马氏距离、外观相似度计算代价矩阵,匈牙利算法求最优匹配;
  4. 轨迹更新:卡尔曼滤波预测位置,结合新观测更新轨迹,管理目标的新增/消失。

主流算法框架

Tracking-by-Detection(检测后跟踪)

  • SORT:卡尔曼滤波预测 + 匈牙利算法 IoU 匹配,简单高效,但遮挡场景易 ID 切换;
  • DeepSORT:SORT + ReID 外观特征 + 级联匹配,遮挡鲁棒性显著提升,工业界最常用;
  • JDE / FairMOT:检测与嵌入联合训练,一次前向同时出框和特征,减少冗余计算。

Transformer 驱动

  • MOTR / TrackFormer:端到端,用自注意力隐式做轨迹关联,长时跟踪更稳。

应用场景

安防监控(异常行为轨迹追踪)、自动驾驶(车辆/行人感知)、无人机与机器人(导航避障)、智能交通、体育分析等。

技术挑战

  • 遮挡:级联匹配 + 外观特征融合(DeepSORT 的思路);
  • 实时性:轻量检测器(YOLOv5s/tiny)或 GPU 加速;
  • 相似目标干扰:运动模型(马氏距离)+ 深度外观特征结合;
  • ID 生命周期:确认阈值(连续几帧匹配才给 ID)、丢失计数(超过阈值删除轨迹)。

评估与数据集

  • 指标:MOTA(综合精度)、IDF1(身份一致性)、MT/M L(跟踪的完整性);
  • 数据集:MOTChallenge(行人,MOT17/20)、KITTI(自动驾驶多类目标)。

C++ 实现:YOLO + 匈牙利匹配的 SORT 简化版

1. YOLO 检测(OpenCV DNN)

class YOLODetector {
public:
    YOLODetector(const string& cfg, const string& weights, const string& classesFile) {
        net = readNetFromDarknet(cfg, weights);
        net.setPreferableBackend(DNN_BACKEND_OPENCV);
        net.setPreferableTarget(DNN_TARGET_CPU);
        // 读取类别名...
    }
    vector<Rect> detect(Mat& frame) {
        Mat blob;
        blobFromImage(frame, blob, 1/255.0, Size(416,416), Scalar(0,0,0), true, false);
        net.setInput(blob);
        vector<Mat> outs;
        net.forward(outs, getOutputNames(net));
        vector<Rect> boxes;
        for (auto& out : outs)
            for (int i = 0; i < out.rows; ++i) {
                Mat scores = out.row(i).colRange(5, out.cols);
                double conf; Point cls;
                minMaxLoc(scores, 0, &conf, 0, &cls);
                if (conf > 0.5) {
                    int cx = out.at<float>(i,0)*frame.cols;
                    int cy = out.at<float>(i,1)*frame.rows;
                    int w  = out.at<float>(i,2)*frame.cols;
                    int h  = out.at<float>(i,3)*frame.rows;
                    boxes.emplace_back(cx-w/2, cy-h/2, w, h);
                }
            }
        return boxes;
    }
private:
    Net net;
    vector<string> getOutputNames(const Net& net) { /* 取未连接输出层名 */ }
};

2. IoU 代价矩阵 + 匈牙利/KM 关联

struct Track { Rect box; int id; int lost = 0; };
vector<Track> tracks;
int nextId = 1;

vector<pair<int,int>> associate(const vector<Rect>& dets) {
    // cost[i][j] = 1 - IoU(track[i], det[j])
    vector<vector<double>> cost(tracks.size(), vector<double>(dets.size()));
    for (int i = 0; i < (int)tracks.size(); ++i)
        for (int j = 0; j < (int)dets.size(); ++j)
            cost[i][j] = 1 - iou(tracks[i].box, dets[j]);
    vector<int> match = hungarian(cost);   // 匈牙利/KM 最优匹配
    vector<pair<int,int>> res;
    for (int i = 0; i < (int)match.size(); ++i)
        if (match[i] != -1 && cost[i][match[i]] < 0.7)  // IoU 阈值过滤
            res.emplace_back(i, match[i]);
    return res;
}

3. 主循环:关联 → 更新 → 新增 → 清理

while (cap.read(frame)) {
    auto dets = detector.detect(frame);
    auto matches = associate(dets);
    vector<bool> used(dets.size(), false);
    for (auto& [ti, di] : matches) {
        tracks[ti].box = dets[di];
        tracks[ti].lost = 0;
        used[di] = true;
    }
    for (int i = 0; i < (int)dets.size(); ++i)      // 新目标
        if (!used[i]) tracks.push_back({dets[i], nextId++, 0});
    tracks.erase(remove_if(tracks.begin(), tracks.end(),
        [](Track& t){ return t.lost++ > 5; }), tracks.end());  // 丢失清理
    // 绘制 ID 框...
}

优化方向

  • 实时性:换轻量模型(YOLOv5s/tiny)或 CUDA 推理(DNN_TARGET_CUDA);
  • 遮挡稳定性:代价矩阵融合 ReID 特征、加卡尔曼滤波预测;
  • ID 稳定性:工程上直接上 DeepSORT 或 ByteTrack(低置信度检测也能用,ID 切换更少)。
滚动至顶部