一、 MOTR模型核心概念

        在传统方法中,每一帧的检测是独立的。而 MOTR 认为,跟踪本质上是对目标状态在时间维度上的演变进行预测,与以往“先检测再关联”(Tracking-by-Detection)不同,它实现了真正意义上的端到端transformer序列化追踪。

        1. 核心设计哲学:Sequence-to-Sequence

        MOTR 将多目标跟踪视为一个“序列到序列(Seq2Seq)”的预测问题。它不再像之前的模型(如 ByteTrack)那样需要依赖繁琐的人工规则(如匈牙利算法、卡尔曼滤波或 IOU 匹配),而是通过 Transformer 的注意力机制,直接让模型学习如何跨帧关联目标。

        2. 关键创新:Track Query(跟踪查询)

        在检测器 DETR 中,Object Query 用于预测图像中的目标。MOTR 引入了 Track Query 的概念:

  • 持久性:一旦某个 Object Query 在第一帧匹配到了目标,它就会演变成 Track Query。
  • 跨帧传递:该 Track Query 会携带该目标的特征信息进入下一帧,直接在下一帧寻找相同目标的位置。
  • 动态更新:通过时间聚合(Temporal Aggregation)不断更新目标状态。

二、 模型的体系结构与关键流程

        1. 双重查询机制 (Dual Query Mechanism)

        在每一帧送入 Decoder 时,输入由两部分组成:

  • Detect Queries:是一组无记忆的固定的、可学习的参数,专门负责“寻找”图像中新出现的物体。它们每次进入 Decoder 时都是初始状态,仅仅代表“空间位置的某种可能性”。
  • Track Queries:是有记忆且进化的。这是从上一帧“传承”下来的。它们包含了目标的特征和位置信息,专门负责“跟踪”已知物体。

        图a中可以看到先通过卷积神经网络(CNN)和Transformer编码器得到图像特征(image feature),MOTR这主要基于Deformable DETR的基础上进行更改的。其中骨干网络通常采用Resnet-50,encoder这里采用的是可变形注意力机制 (Deformable Attention),让每个点关注周围一小部分关键的采样点,这大大提高了计算效率,使得模型能够处理更高分辨率的特征图。

        在DETR中,每一个图片都是独立的,object queries相当于一百张空白卡片每遇到一个图片都需要填写一次,它不知道这个图片里面的这个A目标和上个图片的B目标是同一个人。但是在MOTR中,序列是连续的,每个目标都有一个track queries,当第一帧识别出目标后,query不会丢弃,而是带着该目标的特征到下一帧。

        在图b中可以看到一个环形路径,这就是迭代更新(iterative update),模型会将上一帧预测的结果通过一个“更新机制”反馈给query。右侧可以看到图中的 Track-i, Track-j 不再只是框,而是带有时间属性的线(序列)。这意味着模型在 Decoder 内部就已经完成了“关联”动作,不需要在模型外面再写复杂的代码去匹配。MOTR的decoder接收两种query,一个是object queries负责发现新目标,而track queries负责维护旧目标,二者缺一不可。

        这张对比图揭示了 MOTR 解决多目标跟踪的三个关键维度:

        1)端到端的一致性: 在 DETR 中,如果想做跟踪,必须在模型外面用“匈牙利算法”去对比两帧之间的框。而 MOTR 示意图中展示的 Decoder 循环,意味着模型自己就把匹配做了,输出直接就是完整的轨迹(Tracks)。

        2)处理遮挡的能力: 由于 Track Queries 携带了目标的特征,即使目标在 T2​ 帧被挡住,Query 依然存在。它会在后续帧中通过 Attention 机制继续搜索,从而实现“断线重连”。

        3)计算范式的简化: DETR 是 N 个 Query 对应 N 个结果;MOTR 则是通过时序反馈循环,让模型学习如何随着时间的推移不断修正和更新目标的位置。

       【MOT】多目标追踪学习笔记之MOTR_motr复现-CSDN博客

        在这个博客中了解到代码里面发现这里的track queries其实是包括300个初始化的 detect queries + n个track queries(下文中假定每一帧中有n个track queries),得到了当前帧的目标后,即更新track queries。

        2. 目标的生命周期管理 (Lifecycle Management)

        这是 MOTR 处理目标进入、消失和遮挡的关键:

  • 诞生 (Spawn):当一个 Detect Query 的预测得分超过阈值,它在下一帧就会进化为 Track Query。
  • 维持 (Survival):Track Query 在连续帧中匹配到目标,则继续存在。
  • 消亡 (Death):如果某个目标消失(出画或长期遮挡),其对应的 Track Query 预测得分会持续下降,直到被剔除。

        这张图通过右侧的方块矩阵,清晰地展示了 MOTR 如何处理目标的“出现”与“持续”。图上方框可以看到每一帧都会有一组固定的detect query,它们负责在画面中寻找新出现的目标。图中从上方指向下方的斜向箭头,代表一个新目标(如 T1​ 帧的 Object 1)被检测到,并被“激活”成为了一个跟踪目标。图下方框的track query负责锁定并跟随已知的目标。图中的横向箭头代表了 ID 的延续性。一旦 Object 1 被识别,它就变成了一个持续存在的 Track Query,在 T1​→T2​→T3​… 的时间轴上不断传递。

        这张图解释了 MOTR 如何处理现实世界(左侧)到模型逻辑(右侧)的映射:

        1)新生(Birth):当 Object 1 在 T1​ 出现时,它触发了一个 Detect Query,该 Query 随后转化为 Track Query(横向长条的开始)。

        2)跟随(Persistence):在随后的 T2​,T3​,T4​ 帧中,模型不再通过 Detect Query 重新寻找 Object 1,而是直接利用上一帧传下来的 Track Query 进行定位。这保证了目标 ID 的一致性,不会出现“虽然检测到了,但不知道是谁”的情况。

models/qim.py

        3. 时间聚合网络 (TAN, Temporal Aggregation Network)

        博客中特别提到了这个模块。由于目标在运动过程中外观会发生变化,TAN 通过引入时间维度上的特征融合,让 Track Query 能够记住目标之前的样子,从而在发生短暂遮挡后仍能准确找回目标。

        在上面的视频流(video stream)可以看到图的水平排列的序列结构。不同于传统检测器孤立地处理每一帧,MOTR 将视频视为一个连续的整体。

        1) 递归式的时间线演进(从 T1​ 到 TN​)

        在T1帧(启动期),作为序列的开头,decoder此时只接收到detect query(qd),qd扫描画面并发现第一批目标。在Tt​ 帧(持续期),从第二帧开始,模型进入了“预测+跟踪”的稳态。此时 decoder 同时处理两股信息,形成了图中的 “C” (Concatenation,拼接) 节点。

        2) 双重查询(Query)的协同作战

        图中可以看到进入 decoder 的两个箭头,一个是detect query(qd​)和一个是track query(qtr​)。其中qd负责“找新人”,它是一个固定的、预设的查询库,每一帧都会进入 Decoder,负责捕捉画面中新出现的物体(例如新走进监控范围的行人)。 qtr负责“记旧人”:这是由 QIM(查询交互模块) 根据上一帧的预测结果动态生成的。它携带了目标的身份信息和位置预测,告诉decoder上一帧在这里的那个人,请在这一帧继续盯住他。

        3)QIM 模块的“中转站”作用

        图中最底部的 QIM 模块 闭环了整个逻辑。它接收来自上一帧 Decoder 的特征输出。同时决定哪些目标应该被保留(继续生成 qtr​ 传给下一帧),哪些目标应该被剔除(目标消失)。最后结果输出 (Yt​),每一帧都会产生对应的预测框和唯一的 ID。

        4. 冲突处理机制 (Query Interaction)

        通过 Transformer 的自注意力机制(Self-Attention),不同的 Query 之间会相互“交流”。例如,当两个目标靠得很近时,模型能通过相互关系判断谁在前、谁在后,有效减少 ID Switch(身份跳变)。

        1) 左半部分:QIM 中query的筛选

        这部分解释了模型如何决定哪些 Query 有资格进入下一帧。它分为两个通道:一个是object entrance(新目标入场通道),它的输入来自本帧检测到的newborn objects(新目标,图中以编号 3为例),同时模型为每个候选目标计算一个score(置信度分数)。可以看到编号 3 的score为0.84,这说明它很可能是一个真实的行人,于是通过filter(过滤) 成功“转正”。其他的0.15、 0.09被视为背景噪声,直接丢弃。这对应了代码中将detect query转化为track query的过程。

        另一个是object exit(旧目标退场通道),它的输入来自上一帧已经在跟踪的tracked/exited objects(图中以编号 1 和 2为例)。与object entrance同样进行分数评估。可以看到编号 1的score为 0.87,说明目标还在画面中,继续保留。编号 2 的score为0.23,可能目标已经出界或者被完全遮挡,于是通过filter从跟踪序列中剔除。这保证了模型不会去跟踪已经消失的目标,维持了 query 池的精简。

        2) 右半部分:TAN中query的进化

        一旦确定了哪些 query(旧目标1 和新目标3)能够留下来,它们就会进入 TAN (Temporal Aggregation Network,时间聚合网络) 进行深度加工。

        由于留下的 query 只是初步的“身份卡”,它们需要根据当前帧的视觉特征进行自我更新。TAN 本质上是一个标准的 Transformer 结构(MHA + FFN)。

        TAN内部逻辑拆解:

        MHA(多头注意力机制):这是最重要的步。它让 query 1 和 query 3 互相“打个招呼”。能够防止 ID 冲突。例如,如果两个 query 靠得太近,自注意力机制会让它们意识到彼此,从而调整各自的预测位置,避免两个 ID 抢同一个目标。

        Add & Norm(残差连接与归一化):保证信息在传递过程中不会丢失或梯度爆炸。

        FFN(前馈网络):对聚合后的特征进行非线性变换,提取更高级的语义特征。


        ① 为什么 TAN(编号1)传到 C,和编号3没有直接联系?

        这其实揭示了模型“递归更新”的本质:编号 1 代表的是经过 TAN 处理后的“历史经验”,它汇聚了过去多帧的稳定特征并注入到节点 C(拼接点)中,作为当前帧处理的先验信息;而编号 3 则是当前帧经过 Decoder 预测出的“最新表现”,这个最新的表现并不会立即反作用于当前的输入,而是会被送入记忆库,在处理“下一帧”图像时,它才会通过 TAN 演变成编号 1。因此,它们之间存在着一种“时空错位”的联系,这种延迟连接确保了模型能用过去的记忆来指导当下的判断。


        ② K、Q、V 是什么?

        在这个过程中,K、Q、V 承担了注意力机制下的核心交互职能:Q(Query)代表“搜索指令”,即模型想要寻找的目标特征;K(Key)代表“特征索引”,即画面中现有的各种信息标识;V(Value)则是“内容特征”,即目标的实际视觉属性。通过计算 Q 和 K 的相似度,模型知道要把注意力集中在哪个 V 上。


        ③ 为什么 qtr​ 先传到 “+” 分出 K、Q,而 V 是在那之前分出来的?

        V 被提前分出是为了保护目标的“纯净身份”,确保目标的视觉外观特征不被后续频繁变动的位置信息所污染,从而维持跟踪的稳定性。而 Q 和 K 经过“+”运算是为了注入“空间方位感”,因为在多目标跟踪中,我们不仅要找“长得像”的目标,还要找“位置合理”的目标。

        通过给 Q 和 K 加上位置编码(图中的“+”),模型就能在匹配时同时兼顾外观的相似性和运动的连续性。这种设计让 MOTR 即使在面对两个长相极度相似的目标时,也能依靠位置特征的差异(由 Q、K 携带)和纯净的身份特征(由 V 携带)将它们精准区分,从而实现了图中从 T1​ 到 TN​ 稳定且连贯的轨迹传递。

三、实验验证

        1.安装环境

        创建环境

conda create -n motr python=3.8
conda activate motr

        配置pytorch环境,这个是我之前就下载在电脑D盘里面了,需要的话得镜像

pip install torch-2.0.1+cu117-cp38-cp38-win_amd64.whl --no-deps --no-index
pip install torchvision-0.15.2+cu117-cp38-cp38-win_amd64.whl --no-deps --no-index

        安装其余requirements.txt要求的依赖和多目标跟踪评测工具motmetrics,可以通过下面的代码来查看pytorch是否完整安装。

python -c "import torch; print(torch.__version__); print(torch.cuda.is_available())"

        补全依赖后,编译CUDA算子(非常关键,否则无法运行):

python setup.py build install

        一开始较低配置的cudatoolkit并不包含nvcc,所以我按照我需要的版本又下载了一个新版本。这边我选择的是CUDA11.7,网址为CUDA Toolkit 11.7 Downloads | NVIDIA Developer

        按照成功后输入nvcc -V便会弹出

nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2022 NVIDIA Corporation
Built on Tue_May__3_19:00:59_Pacific_Daylight_Time_2022
Cuda compilation tools, release 11.7, V11.7.64
Build cuda_11.7.r11.7/compiler.31294372_0

        2.下载模型权重 

       这里直接使用 MOT17 的模型权重来跑 MOT16,将权重文件model_motr_final.pth放在仓库根目录下的 exps/ 文件夹中。

        这个权重似乎直接去训练MOT16/17的数据集会过拟合,得到的结果和第五部分评测的结果一样,MOTA这种指标特别高,测评的结果不对。所以为了让模型的指标变得正常能够和其他bytetrack、deepsort进行比较,博主决定采用Deformable-DETR的权重r50_deformable_detr-checkpoint.pth,毕竟MOTR的代码是在这个基础上进行更改的,这样能让指标的数值正常一点,不会发送过拟合。

        3.运行demo文件

        这一部分参考了使用vscode初次运行项目MOTR的demo——debug - 旷野之外 - 博客园这个博客,但遇到问题不太一样,只要是查看下环境以及权重是否安装完毕,MOTR模型是否能运行。

        这里先用demo.py文件进行测试,选用的视频文件是MOTR仓库里自带的figs/demo.avi。

        我尝试了很多种终端输入,最后只有下面这种才有效。

python demo.py --meta_arch motr --dataset_file e2e_mot --resume exps/model_motr_final.pth --input_video figs/demo.avi --output_dir outputs/demo --vis --num_queries 300 --sampler_lengths 1 --exp_name demo_test --with_box_refine --update_query_pos --extra_track_attn --query_interaction_layer QIM

        缺少部分参数可能会出现的问题以及修改原因:

        1) 查询向量数量:--num_queries 300

        从这行日志可以看出:

Skip loading parameter query_embed.weight, required shapetorch.Size([256, 512]), loaded shapetorch.Size([300, 512]).

        这说明 model_motr_final.pth 这个权重文件确实是使用 300 个查询向量(queries)训练出来的。

        2) 边界框微调参数:--with_box_refine

        从日志中大量的 Drop parameter transformer.decoder.bbox_embed.0... 到 5... 可以看出: 官方提供的预训练权重包含了 6个独立层(Layer-specific)的边界框预测头,这说明该模型是在开启了“边界框微调(Box Refinement)”模式下训练的。而之前的命令没有加这个参数,导致模型只有一个共享的预测头,形状对不上就被丢弃了。

        3) 查询向量更新参数:--update_query_pos

        日志中丢弃了大量的 update_attn 和 track_embed。在 MOTR 的架构中,如果开启了轨迹特征的自我更新(Tracklet Update),就会生成这些特定的注意力层。对应的命令行开关通常是 --update_query_pos。

        4)额外的注意力层

        代码里出现最后一类参数没有加载上:transformer.decoder.layers.X.update_attn

报错里一直提示 "Please make sure you set the correct --num_classes...", 其实这是原作者代码里的一个“误导性提示”。 在 MOTR 的源码中,只要模型在加载权重时发现“字典对不上”(无论是多出层、少出层还是尺寸不对),它就会统一打印这句提前写死的话。这里的 update_attn 根本不涉及类别数,它是一个额外的注意力层。

        在 MOTR 的官方网络架构中,除了轨迹自身的更新(--update_query_pos),为了让模型效果更好,作者还加入了一个叫做 Extra Track Attention(额外轨迹注意力机制) 的模块。model_motr_final.pth 权重正是开启了这个模块训练出来的。

        如果命令行不加对应的开关,代码就不会去实例化这个 update_attn 网络层,导致权重文件里这部分的参数无家可归,只能被丢弃。所以需要在命令行的最后,再补上这两个标准模型专属的参数: --extra_track_attn 和 --query_interaction_layer QIM

        下面是运行结果:

        可以看到改完参数之后运行效果还是很不错的。

         3.MOT16数据集

         从 MOTChallenge 官网下载MOT16 数据集并解压。由于MOTR 使用 COCO 格式的标注来读取 MOT16 数据,需要将 MOT16 的 .txt 标注转换为 .json 格式,代码来自于bytetrack的官方转换格式。 得到下图所示的内容:

        同时也需要逐帧文本标注文件(.txt),通过MOTR自带的datasets/data_path/gen_labels_16.py,得到labels_with_ids/train文件夹,下面是各个序列的txt文件。

        4.执行推理

        在这个部分可能会出现代码中硬编码(Hardcoded)的路径结构与你实际存放数据的路径不一致的问题,FileNotFoundError: [Errno 2] No such file or directory: 'D:\\room\\MOTR-main\\data\\MOT16\\MOT17\\images\\train\\MOT17-02-SDP\\img1\\000001.jpg'。这是因为在 MOTR 的 datasets/detmot.py(或者是对应的 Data Loader 脚本)中,由于该模型主要是针对 MOT17 开发的,开发者往往会在代码里自动拼接 MOT17/images/train 这样的子路径。

        这个问题最简单的解决方法只有按照要求重组目录结构,我试了其他方法基本都很麻烦。

python eval.py --meta_arch motr --dataset_file e2e_mot --mot_path data/MOT16 --resume exps/model_motr_final.pth --output_dir outputs/eval_mot16 --eval --num_queries 300 --sampler_lengths 1 --with_box_refine --update_query_pos --extra_track_attn --query_interaction_layer QIM

        运行结束后,outputs/eval_mot16/preds 中会生成识别目标后的图片以及类似 MOT16_02/gt.txt 的结果文件。

        5.评测指标

        使用标准的 TrackEval 工具:

python scripts/run_mot_challenge.py --BENCHMARK MOT16 --SPLIT_TO_EVAL train --TRACKERS_TO_EVAL MOTR --METRICS HOTA CLEAR Identity --USE_PARALLEL False --PRINT_ONLY_COMBINED False

        

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐