1. 从检测到跟踪:FairMOT到底在解决什么问题
多目标跟踪(MOT)这个方向,说白了就是让机器在一段视频里持续盯住每一个人或物体,不仅要知道每一帧里有哪些目标,还得知道这一帧里的张三和上一帧里的张三是同一个人。这个任务听起来简单,做起来极其折磨人。早期的主流方案是“先检测后匹配”,也就是先用一个检测器把每一帧里的目标框出来,再用另一个模型提取特征做数据关联。这种分离式架构的问题非常明显:检测和重识别两个模块各自优化,推理时还要跑两次网络,速度慢不说,两个模块之间的信息完全不共享,检测框稍微抖一下,后面的匹配就可能全乱套。
FairMOT的核心贡献就是把这个流程统一到了一个端到端的网络里。它基于CenterNet这个无锚框检测框架,同时输出三个东西:目标中心点的热力图、中心点的偏移量、以及每个目标的ReID特征向量。检测和重识别共享同一个骨干网络,特征在底层就融合了,推理时一次前向传播就能拿到所有需要的信息。这个设计思路直接解决了分离式方案中特征不共享、速度慢、匹配容易断的问题。
我第一次接触FairMOT是在一个行人流量统计的项目里,当时用分离式方案跑出来的ID Switch(身份切换)高得离谱,尤其是人群密集的场景,一个人被遮挡两秒再出现,ID基本就换了。换成FairMOT之后,同样的视频序列,ID Switch直接降了将近一半。这个提升不是靠堆参数堆出来的,而是架构设计本身带来的红利。
这篇文章适合谁看?如果你正在做视频监控、客流分析、自动驾驶感知、体育视频分析这类需要跟踪多个目标的工程,或者你正在选型MOT方案,那FairMOT是一个绕不开的基线。即使你最终不用它,理解它的设计思路对你选其他方案也有极大帮助。下面我会从架构设计、核心细节、实操部署、问题排查几个维度,把FairMOT拆开讲透。
2. FairMOT架构设计拆解:为什么这样设计
2.1 骨干网络的选择逻辑
FairMOT的骨干网络用的是ResNet-34加上DLA(Deep Layer Aggregation)结构,而不是更常见的ResNet-50。这个选择背后有很实际的考量。MOT任务对分辨率非常敏感,输入图像通常要缩放到1088×608这种尺寸,如果骨干网络下采样太狠,小目标的特征就丢了。ResNet-34相比ResNet-50参数量少了不少,推理速度快,同时DLA结构通过多层特征聚合,把浅层的高分辨率特征和深层的语义特征融合在一起,弥补了ResNet-34表达能力相对弱的问题。
具体来说,DLA在ResNet-34的基础上做了迭代式的深层聚合,每一层的输出不仅传给下一层,还会跨层汇聚到后续节点。这种设计让网络在不同尺度上都能保留足够的信息。实测下来,用DLA-34作为骨干,在MOT17数据集上MOTA能到69%左右,而换成ResNet-50反而没有明显提升,速度还慢了一截。所以这个选择不是拍脑袋决定的,是在精度和速度之间反复权衡后的结果。
2.2 多任务头的设计:检测与ReID如何共存
FairMOT的输出头分三路:热力图头、偏移头、ReID头。热力图头负责预测每个目标中心点的位置,输出一个H×W×1的heatmap,峰值位置就是目标中心。偏移头输出H×W×2,用来修正中心点因为下采样导致的量化误差。ReID头输出H×W×128,每个目标中心点位置对应的128维向量就是该目标的特征表示。
这里有一个非常关键的设计细节:ReID特征只在目标中心点位置提取,而不是在整个目标框区域提取。这样做的好处是避免了背景信息的干扰,同时也让特征提取和检测在空间上对齐。传统ReID模型是在整张目标框上做全局池化,框里难免混入背景像素,FairMOT直接在中心点取特征,干净利落。
另一个值得说的点是,这三个头共享同一个骨干网络的特征。检测和ReID在底层就共享了卷积特征,这意味着ReID特征天然带有检测任务学到的空间信息,检测任务也能从ReID任务中获得语义层面的监督信号。这种多任务学习的方式让两个任务互相促进,而不是各自为战。
2.3 数据关联策略:为什么不用简单的IoU匹配
FairMOT在推理阶段的数据关联用的是卡尔曼滤波加匈牙利算法的经典组合,但匹配代价矩阵的构建方式有讲究。它同时考虑了预测框和检测框的IoU距离,以及ReID特征的余弦距离。最终的代价矩阵是这两者的加权和,权重通常设为0.98和0.02左右,也就是说IoU占主导,ReID特征作为辅助修正。
为什么这么设计?因为在大多数场景下,相邻帧之间目标的位移很小,IoU已经能提供很强的匹配信号。ReID特征的作用是在IoU模糊的时候(比如目标交叉、遮挡后重现)提供额外的区分度。如果反过来让ReID占主导,反而容易因为特征噪声导致误匹配。这个权重分配是经过大量实验验证的,不是随便设的。
另外,FairMOT还维护了一个轨迹池,对于当前帧没有匹配上的轨迹,会保留30帧左右,如果后续帧中有检测框和它匹配上,就恢复这条轨迹。这个机制对于处理短时遮挡非常有效,也是降低ID Switch的关键手段之一。
3. 核心细节解析与实操要点
3.1 热力图生成与高斯核参数
FairMOT的热力图目标值不是简单的0/1二值图,而是以目标中心点为中心的高斯分布。高斯核的半径根据目标框的大小自适应计算,公式大致是:半径 = 高斯核半径系数 × 目标框面积的对数函数。在代码实现中,这个系数通常设为0.05到0.1之间。
为什么要用高斯分布而不是二值图?因为目标中心点的标注本身就有一定的不确定性,尤其是当目标框由标注人员手工绘制时,中心点可能偏几个像素。用高斯分布可以让网络对中心点的小偏移不那么敏感,训练更稳定。另外,高斯分布在中心点附近提供了平滑的梯度,有助于网络收敛。
实操中有一个容易踩的坑:高斯核半径如果设得太大,相邻目标的中心点热力图会重叠,导致网络分不清两个靠近的目标;设得太小,又会让网络对中心点定位过于苛刻,泛化能力下降。我的经验是,对于行人跟踪任务,半径系数设在0.07左右比较稳妥,如果是小目标密集的场景,可以适当降到0.05。
3.2 ReID特征的维度选择与归一化
FairMOT的ReID特征维度是128维。这个维度不算高,但足够区分不同目标。维度太高会增加计算量,而且容易过拟合;太低则区分度不够。128维是一个经过验证的平衡点。
特征在输出后会做L2归一化,然后计算余弦距离。L2归一化的作用是让特征向量落在单位超球面上,这样余弦距离就等价于欧氏距离,计算更简单,数值也更稳定。在实际部署中,我建议把归一化后的特征缓存起来,因为同一轨迹的历史特征可以取平均或取最近几帧的特征来增强匹配鲁棒性。
有一个细节值得注意:FairMOT的ReID头在训练时用的是交叉熵损失,把每个目标ID当作一个类别。这意味着如果训练集中有1000个不同的人,ReID头就是一个1000类的分类器。但在推理时,我们并不需要这个分类器,只需要它输出的128维特征。所以部署时可以把最后的分类层去掉,只保留特征提取部分,能省一点计算量。
3.3 损失函数的组成与权重分配
FairMOT的总体损失由三部分组成:热力图损失、偏移损失、ReID损失。热力图损失用的是焦点损失(Focal Loss)的变体,用来处理正负样本极度不平衡的问题(一张图里目标中心点只占几十个像素,其余全是背景)。偏移损失用的是L1损失,只计算目标中心点位置的偏移。ReID损失用的是交叉熵损失。
三个损失的权重通常设为1:1:0.1左右,也就是说ReID损失的权重相对较低。这个权重分配的逻辑是:检测是跟踪的基础,如果检测不准,ReID特征再好也没用。所以检测相关的损失占主导,ReID损失作为辅助。如果ReID权重设得太高,网络可能会过度关注特征区分度而牺牲检测精度,反而得不偿失。
我在实际训练中发现,如果数据集里目标ID数量很多(比如超过500个),ReID损失可以适当提高到0.2左右,因为类别多了之后特征学习的难度增加,需要更强的监督信号。但如果ID数量少,保持0.1就够了。
4. 实操过程与核心环节实现
4.1 环境搭建与依赖安装
FairMOT的官方实现基于PyTorch,依赖主要包括PyTorch 1.7以上、torchvision、opencv-python、cython、以及DCNv2(可变形卷积)。DCNv2需要单独编译,这是环境搭建中最容易出问题的一步。
# 创建虚拟环境 conda create -n fairmot python=3.8 conda activate fairmot # 安装PyTorch(根据你的CUDA版本选择) pip install torch==1.9.0 torchvision==0.10.0 # 安装其他依赖 pip install opencv-python cython numpy scipy pip install cython-bbox # 编译DCNv2 cd src/lib/models/networks/DCNv2 python setup.py build_ext --inplaceDCNv2编译时最常见的报错是CUDA版本不匹配和gcc版本过高。如果遇到“unsupported gpu architecture”之类的错误,需要在setup.py里手动指定你的GPU架构,比如-gencode arch=compute_75,code=sm_75(对应RTX 20系列)。gcc版本建议用7.x或8.x,太新的版本(比如gcc 11)可能会有兼容性问题。
4.2 数据准备与标注格式转换
FairMOT训练需要的数据格式是每帧一张图片加一个标注文件,标注文件里每行是一个目标的信息:帧ID、目标ID、左上角坐标、宽、高、置信度、类别、可见度。这个格式和MOT Challenge的标准格式一致。
如果你手头的数据是COCO格式或者VOC格式,需要写脚本转换。转换的核心逻辑是把每张图的标注按帧组织起来,并且确保同一个目标在不同帧中的ID一致。这里有一个容易忽略的点:如果目标在某一帧中完全被遮挡,标注中应该跳过这一帧,而不是给一个宽高为0的框。否则训练时热力图目标值会出问题。
数据增强方面,FairMOT用了随机翻转、随机缩放、颜色抖动等常规手段。但要注意,随机翻转会改变目标的左右关系,对于ReID任务来说,翻转后的目标应该视为同一个ID,而不是新ID。官方实现里是通过在数据加载时对ID做映射来处理的,自己写数据管道时要注意这一点。
4.3 训练参数配置与调优
FairMOT的训练通常分两阶段:先在COCO或 CrowdHuman 上预训练检测部分,再在MOT数据集上联合训练检测和ReID。预训练阶段学习率设0.01,联合训练阶段降到0.001。Batch size根据显存来,1080Ti上一般能跑到8到12。
# 关键训练参数示例 lr = 0.001 batch_size = 8 num_epochs = 30 lr_step = [15, 22] # 在第15和22个epoch降学习率 weight_hm = 1.0 # 热力图损失权重 weight_off = 1.0 # 偏移损失权重 weight_reid = 0.1 # ReID损失权重训练过程中要重点监控两个指标:热力图损失的下降情况和ReID特征的类间距离。热力图损失如果下降太慢,可能是学习率太低或者高斯核半径设得太大。ReID特征可以用t-SNE可视化一下,如果不同ID的特征混在一起分不开,说明ReID损失权重需要调高,或者特征维度不够。
还有一个实操经验:训练初期不要冻结骨干网络,让整个网络一起训练。有些人习惯先冻结骨干只训练头部,但FairMOT的检测和ReID共享骨干,冻结骨干会导致ReID特征学不好。如果显存不够,宁可减小batch size也不要冻结骨干。
4.4 推理与跟踪流程实现
推理阶段的流程可以概括为:输入一帧图像,网络输出热力图、偏移和ReID特征,然后从热力图中提取峰值点作为检测框中心,结合偏移得到精确坐标,再提取对应位置的ReID特征,最后用卡尔曼滤波预测现有轨迹的位置,构建代价矩阵,用匈牙利算法做匹配。
# 简化的跟踪流程伪代码 tracks = [] # 维护的轨迹列表 for frame in video: hm, off, reid = model(frame) dets = extract_dets(hm, off) # 提取检测框 feats = extract_feats(reid, dets) # 提取ReID特征 # 卡尔曼滤波预测 for t in tracks: t.predict() # 构建代价矩阵 cost = compute_cost(tracks, dets, feats) # 匈牙利匹配 matches, unmatched_tracks, unmatched_dets = hungarian(cost) # 更新轨迹 for t, d in matches: t.update(dets[d], feats[d]) for t in unmatched_tracks: t.mark_lost() for d in unmatched_dets: tracks.append(new_track(dets[d], feats[d]))这里有一个性能优化的点:ReID特征的提取只在检测框中心点位置做,不需要对整张图做全局池化,所以速度很快。但如果检测框数量很多(比如超过100个),特征提取和代价矩阵计算会成为瓶颈。我的做法是把ReID特征提取和代价矩阵计算放到GPU上做,用矩阵运算代替循环,速度能提升3到5倍。
4.5 模型导出与部署注意事项
如果要把FairMOT部署到边缘设备上,通常需要把PyTorch模型转成ONNX或TensorRT。导出ONNX时要注意DCNv2层可能不被某些推理引擎支持,需要替换成普通卷积或者用支持DCN的推理框架。另外,热力图的后处理(峰值提取)在ONNX里不好实现,建议把后处理放到CPU上做,ONNX只负责网络前向。
TensorRT部署时,FP16精度下FairMOT的速度能到30FPS以上(1080Ti),精度损失很小。但如果用INT8量化,ReID特征的区分度会明显下降,ID Switch会增多。所以我的建议是:检测部分可以用INT8,ReID部分保持FP16,这样兼顾速度和精度。
5. 常见问题与排查技巧实录
5.1 ID Switch频繁发生怎么排查
ID Switch是MOT中最常见也最头疼的问题。FairMOT虽然比分离式方案好很多,但在某些场景下仍然会出现。排查思路按优先级排列:
第一,检查检测质量。如果检测框抖动严重或者漏检多,ReID特征再好也救不回来。可以把检测结果可视化出来,看看是不是有大量漏检或误检。如果是,需要调整热力图峰值提取的阈值,或者重新训练检测部分。
第二,检查ReID特征质量。把同一ID在不同帧的特征取出来算余弦相似度,如果相似度低于0.5,说明特征区分度不够。这时候可以尝试增大ReID损失权重,或者增加特征维度到256。
第三,检查匹配代价矩阵的权重。如果IoU权重太低,ReID噪声会导致误匹配;如果IoU权重太高,遮挡后重现的目标匹配不上。可以尝试在0.95到0.99之间调整IoU权重,找到适合你场景的值。
第四,检查轨迹保留帧数。默认是30帧,如果遮挡时间超过30帧,轨迹就被删了,目标重现时只能新建ID。如果你的场景遮挡时间较长,可以把这个值调到60甚至更高。
5.2 训练不收敛或损失震荡怎么办
FairMOT训练不收敛通常有以下几个原因:学习率太高、batch size太小、数据标注有问题、或者DCNv2编译不正确。
学习率方面,联合训练阶段建议从0.001开始,如果损失震荡就降到0.0001。Batch size如果只能设到4以下,建议用梯度累积来模拟大batch的效果。数据标注方面,重点检查是否有宽高为0的框、是否有重复标注、ID是否连续。DCNv2如果编译不正确,网络前向会报错或者输出异常值,这个可以通过单元测试来验证。
还有一个容易被忽略的点:预训练模型的选择。如果直接用ImageNet预训练的ResNet权重,检测部分收敛会慢一些。建议先用COCO或CrowdHuman预训练检测部分,再加载到FairMOT里联合训练,收敛速度和最终精度都会好很多。
5.3 显存不足的优化策略
FairMOT训练时显存占用主要来自三个方面:骨干网络的特征图、热力图和ReID头的输出、以及损失计算时的中间变量。1080Ti(11GB)上batch size设8基本能跑,如果显存不够,可以尝试以下策略:
- 降低输入分辨率,从1088×608降到864×480,显存能省30%左右,但小目标检测精度会下降。
- 用梯度检查点(gradient checkpointing),用时间换空间,显存能省40%但训练速度慢20%。
- 把ReID特征维度从128降到64,显存省得不多但速度有提升。
- 用混合精度训练(AMP),显存能省30%到40%,速度还能提升。
我个人的经验是,优先用混合精度,其次降分辨率,最后才考虑梯度检查点。因为梯度检查点对训练速度的影响太大了,有时候得不偿失。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| ID Switch频繁 | 检测抖动/ReID特征差/匹配权重不当 | 可视化检测框和特征相似度 | 调高ReID权重或增大特征维度 |
| 训练损失震荡 | 学习率过高/batch过小 | 打印每步损失 | 降学习率或用梯度累积 |
| 显存不足 | batch过大/分辨率过高 | nvidia-smi监控 | 用AMP或降分辨率 |
| DCNv2编译报错 | CUDA版本不匹配/gcc过高 | 检查编译日志 | 指定GPU架构或降gcc版本 |
| 推理速度慢 | 后处理在CPU/特征提取循环 | 计时各阶段耗时 | 后处理上GPU/矩阵化计算 |
| 遮挡后ID丢失 | 轨迹保留帧数不够 | 检查轨迹池配置 | 增大轨迹保留帧数 |
| 小目标漏检多 | 下采样太狠/热力图阈值高 | 可视化热力图 | 降热力图阈值/用更高分辨率 |
6. 从FairMOT延伸出去的几个实用改进方向
FairMOT本身是一个基线方案,实际工程中往往需要根据场景做针对性改进。我分享几个我试过且有效的方向。
第一个方向是加入运动补偿。FairMOT的卡尔曼滤波用的是匀速运动模型,对于变速运动的目标(比如跑步的人、转弯的车)预测不准。可以在卡尔曼滤波里加入加速度项,或者用相机运动补偿来消除背景运动的影响。我在一个车载视频项目里加了相机运动补偿后,ID Switch降了约15%。
第二个方向是特征更新策略。FairMOT默认用当前帧的ReID特征做匹配,但单帧特征噪声大。可以维护每条轨迹的特征队列,匹配时用队列里最近几帧特征的平均值。这个改动很小,但效果很明显,尤其是对于外观变化大的目标。
第三个方向是级联匹配。FairMOT用的是单轮匈牙利匹配,可以改成级联匹配:先匹配高置信度检测框,再匹配低置信度检测框,最后匹配剩余轨迹。这个思路借鉴了DeepSORT的级联匹配,对于处理检测置信度分布不均的场景很有效。
第四个方向是自适应阈值。热力图峰值提取的阈值如果固定,在不同场景下表现差异很大。可以根据当前帧的热力图分布动态调整阈值,比如用热力图最大值的某个比例作为阈值,而不是固定值。这个改动在光照变化大的场景下特别有用。
这些改进方向不需要大改网络结构,都是在推理和后处理层面做文章,落地成本低,效果立竿见影。如果你正在用FairMOT做实际项目,建议先从特征更新策略和自适应阈值入手,这两个改动最小,收益最直接。
我个人在实际操作中的体会是,FairMOT最大的价值不在于它本身的精度有多高,而在于它提供了一个干净、可扩展的端到端框架。你可以在这个框架上快速验证自己的想法,改一个损失函数、换一个匹配策略、加一个特征更新模块,都能很快看到效果。这种可迭代性对于工程落地来说比单纯的精度指标重要得多。