什么是多目标跟踪
多目标跟踪(Multi-Object Tracking, MOT)从视频序列中持续跟踪多个目标(行人、车辆等),为每个目标分配唯一 ID 并记录运动轨迹。核心流程四步:
- 目标检测:每帧用检测器(YOLO、Faster R-CNN)定位目标框;
- 特征提取:提取外观特征(ReID 网络生成嵌入向量)与运动特征;
- 数据关联:用 IoU、马氏距离、外观相似度计算代价矩阵,匈牙利算法求最优匹配;
- 轨迹更新:卡尔曼滤波预测位置,结合新观测更新轨迹,管理目标的新增/消失。
主流算法框架
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 切换更少)。
