主体思想
FairMOT采用centernet思想,检测网络有三个分支:目标类别分类(hm),目标的宽高(wh),目标中心点的偏置(xy)。
(1)目标类别分类(hm):通过C通道的heatmap来预测,heatmap的大小是原图下采样4倍之后的大小,如果图像中有某个类别的目标,那么这个目标的中心点在heatmap上的概率值为1,其余周围概率成高斯分布逐渐衰减;
(2)wh和xy通过回归得到,目标ID通过交叉熵分类得到。
训练
数据集类JointDataset在src\lib\datasets\dataset\jde.py文件中,__getitem__方法中读取图片和对应的标签,做简单的数据增强。
损失函数
损失包括:heatmap分类loss,宽高回归loss,目标中心点偏移回归loss,目标ID分类loss。src\lib\trains\mot.py中MotTrainer类可查看loss名:
MOTloss类定义了这几个loss:
class MotLoss(torch.nn.Module):
def __init__(self, opt):
super(MotLoss, self).__init__()
self.crit = torch.nn.MSELoss() if opt.mse_loss else FocalLoss() #分类loss
self.crit_reg = RegL1Loss() if opt.reg_loss == 'l1' else \
RegLoss() if opt.reg_loss == 'sl1' else None #中心点x,y的offset
self.crit_wh = torch.nn.L1Loss(reduction='sum') if opt.dense_wh else \
NormRegL1Loss() if opt.norm_wh else \
RegWeightedL1Loss() if opt.cat_spec_wh else self.crit_reg #宽高回归loss
self.opt = opt
self.emb_dim = opt.reid_dim #reid特征长度
self.nID = opt.nID #所有目标的ID数
self.classifier = nn.Linear(self.emb_dim, self.nID)
self.IDLoss = nn.CrossEntropyLoss(ignore_index=-1) #ID loss
#self.TriLoss = TripletLoss()
self.emb_scale = math.sqrt(2) * math.log(self.nID - 1)
self.s_det = nn.Parameter(-1.85 * torch.ones(1))
self.s_id = nn.Parameter(-1.05 * torch.ones(1))
def forward(self, outputs, batch):
opt = self.opt
hm_loss, wh_loss, off_loss, id_loss = 0, 0, 0, 0
for s in range(opt.num_stacks):
output = outputs[s]
if not opt.mse_loss:
output['hm'] = _sigmoid(output['hm'])
hm_loss += self.crit(output['hm'], batch['hm']) / opt.num_stacks #修改过后的focal loss
#wh_loss
if opt.wh_weight > 0:
if opt.dense_wh:
mask_weight = batch['dense_wh_mask'].sum() + 1e-4
wh_loss += (
self.crit_wh(output['wh'] * batch['dense_wh_mask'],
batch['dense_wh'] * batch['dense_wh_mask']) /
mask_weight) / opt.num_stacks
else:
wh_loss += self.crit_reg(
output['wh'], batch['reg_mask'],
batch['ind'], batch['wh']) / opt.num_stacks
#xyoffset_loss
if opt.reg_offset and opt.off_weight > 0:
off_loss += self.crit_reg(output['reg'], batch['reg_mask'],
batch['ind'], batch['reg']) / opt.num_stacks
#id loss
if opt.id_weight > 0:
id_head = _tranpose_and_gather_feat(output['id'], batch['ind'])
id_head = id_head[batch['reg_mask'] > 0].contiguous()
id_head = self.emb_scale * F.normalize(id_head)
id_target = batch['ids'][batch['reg_mask'] > 0]
id_output = self.classifier(id_head).contiguous()
id_loss += self.IDLoss(id_output, id_target)
#id_loss += self.IDLoss(id_output, id_target) + self.TriLoss(id_head, id_target)
#loss = opt.hm_weight * hm_loss + opt.wh_weight * wh_loss + opt.off_weight * off_loss + opt.id_weight * id_loss
det_loss = opt.hm_weight * hm_loss + opt.wh_weight * wh_loss + opt.off_weight * off_loss
loss = torch.exp(-self.s_det) * det_loss + torch.exp(-self.s_id) * id_loss + (self.s_det + self.s_id)
loss *= 0.5
#print(loss, hm_loss, wh_loss, off_loss, id_loss)
loss_stats = {'loss': loss, 'hm_loss': hm_loss,
'wh_loss': wh_loss, 'off_loss': off_loss, 'id_loss': id_loss}
return loss, loss_stats
实战
环境配置
conda create -n FairMOT python=3.8
conda activate FairMOT
# conda
conda install pytorch==1.7.0 torchvision==0.8.0 cudatoolkit=10.2 -c pytorch
# pip+CPU
pip install torch==1.7.0+cpu torchvision==0.8.0+cpu torchaudio==0.7.0 -f https://download.pytorch.org/whl/torch_stable.html
# pip+CUDA 10.1
pip install torch==1.7.0+cu101 torchvision==0.8.0+cu101 torchaudio==0.7.0 -f https://download.pytorch.org/whl/torch_stable.html
pip install -r requirements.txt # cython-bbox注掉单独装
# 装cython_bbox
pip install cython
# pip装
pip install git+https://github.com/yanfengliu/cython\_bbox.git # 失败的话源码装
# 源码装
git clone https://github.com/yanfengliu/cython_bbox.git
cd cython_bbox-master
python setup.py install
# 装ffmpeg, src/demo.py中将所有帧存成视频时用到
# linux
apt update
apt install ffmpeg
# windows
参考:https://blog.csdn.net/gaowenhui2008/article/details/131003056
编译项目
cd src/lib/models/networks
git clone -b pytorch_1.7 https://github.com/ifzhang/DCNv2.git
cd DCNv2
sh make.sh
下载预训练模型
链接:https://pan.baidu.com/s/1MlXrSGiXYF2cV07Arbw7EQ
提取码:zwh9
跑通demo
运行以下代码,详细参数见`src/lib/opts.py`,默认视频放在../videos中
cd src
# CPU
python demo.py --task mot --load_model ../models/all_dla34.pth --conf_thres 0.4 --gpus -1 --output-root ../results
# GPU
python demo.py --task mot --load_model ../models/all_dla34.pth --conf_thres 0.4 --output-root ../results
cd ../
<frame>, <id>, <bb_left>, <bb_top>, <bb_width>, <bb_height>, <conf>, <x>, <y>, <z>
每行代表一个检测的物体。第一列代表第几帧,第二个代表轨迹编号(或者是物体ID),bb开头的4个数代表物体框的左上角坐标及长宽。conf代表置信度,最后3个是MOT3D用到的内容,2D检测总是为-1.
训练数据集准备
以CUHKSYSU数据集为例,项目文件夹下新建`dataset/MOT`,将CUHK-SYSU数据集解压到这里,重命名为`CUHKSYSU`,目录结构如下(训练自己的数据集也要弄成这样的格式,images和labels_with_ids的名称不能改):
参考:CVPR 2020 多目标跟踪算法 FairMOT代码demo运行及训练_fairmot运行_村民的菜篮子的博客-CSDN博客
train
修改`src/lib/cfg/data.json`文件。一会儿运行`experiments/all_dla34.sh`会进入到`src`目录下,所以这里的root用`../dataset`(除了train其他的貌似都没用到)
{
"root":"../dataset/MOT",
"train":
{
"cuhksysu":"./data/cuhksysu.train"
},
"test_emb":
{
"mot15":"./data/cuhksysu.val"
},
"test":
{
"mot15":"./data/cuhksysu.val"
}
}
`src/lib/opts.py`中修改数据集位置:
--data_cfg '../src/lib/cfg/data.json'
--data_dir "../datset"
运行以下代码开启训练:
cd src
python train.py --task mot --exp_id all_dla34 --num_epochs 10 --gpus 0 --batch_size 2 --load_model ../models/ctdet_coco_dla_2x.pth --num_workers 8
cd ..
开启训练之后如下:
训练log解读: [5]表示第5轮训练,500表示batch的数量(总图片为1000,bachsize为2),loss是总损失,hm是类别损失,wh是宽高损失,off是中心点偏移损失,id_loss是id损失,time是总时间。
模型与日志参数默认保存在exp/mot/all_dla34下, log.txt中记录了损失函数变化:
val
将`mot16`数据集解压到`dataset/MOT16`目录下,包含train和test两个文件夹,运行以下命令。若想保存视频,将`track.py`最后一行的`save_videos=False`改成`save_videos=True`,结果保存在`dataset/MOT16/outputs`下。数据集介绍参考:多目标跟踪数据集 :mot16、mot17数据集介绍以及多目标跟踪指标评测_一个小呆苗的博客-CSDN博客
cd src
python track.py --task mot --val_mot16 True --load_model ../exp/mot/all_dla34/model_last.pth --conf_thres 0.6 --save_all
cd ..
启动之后:
评价指标
MOTA: 跟踪的准确度,和出现FN,FP,IDs的数量负相关,可能出现负值。1为最佳情况,数值越高代表跟踪精确度越好
MOTP:衡量目标位置的精确程度;
IDF1:IDF1指标代表被检测和跟踪的目标中获取正确的ID的检测目标的比例,综合考虑ID准确率和ID召回率,代表两者的调和均值。
其中,IDP代表ID跟踪的准确率,IDR代表ID跟踪的召回率。IDF1指标更聚焦于跟踪算法跟踪某个目标的时间长短,考察跟踪的连续性和重识别的准确性。IDF1以1为最佳情况,数值越高代表跟踪特定目标的精度越好。
IDS: ID改变的总数量;
MT:代表在80%的帧中被正确跟踪的轨迹占所有轨迹的比重;
ML:代表在80%的帧中没有被正确跟踪的轨迹占所有轨迹的比重。