deepsort用来跟踪被检测对象。网上常见的yolov5+deepsort,是pytorch版。此版本用ZQPei Github: https://github.com/ZQPei/deep_sort_pytorch#training-the-re-id-model
ZQPei提供的ReID:deep模型文件ckpt.t7为行人特征,由market1501数据集训练获得。本文提供一种针对车辆特征的训练方法,训练数据集为veri-wild。
ReID personal Re-identification,针对不同对象的重识别。deepsort中deep就是一个ReID模型。
deepsort从Extractor获得跟踪图像的分类特征,其ReID由Net类定义,在deep_sort_pytorch/deep_sort/deep/model.py中。
论文“deep learning in video multi-object tracking: a survey”描述多目标跟踪MOT的主要处理步骤:
(1)给定视频原始帧
(2)目标检测器(Faster R-CNN, yolov5, SSD等)获取目标检测框
(3)将目标框中对应的目标抠出来,进行特征提取(包括外观特征和运动特征)
(4)相似度计算,计算前后两帧目标间的匹配程度(前后属于同一目标的距离小,不同目标的距离较大)。
(5)数据关联,为每个对象分配目标ID。
ReID:DeepSort中采用了一个简单的CNN来提取被检测框中物体的外观特征,在每次(每帧)检测+追踪后,进行一次物体外观特征的提取并保存。后面每执行一步时,都要执行一次当前帧被检测物体外观特征与之前存储的外观特征的相似度计算,这个相似度将作为一个重要的判别依据(将运动特征与外观特征结合作为判别依据,运动特征就是Sort中卡尔曼滤波做的事)。
那么这个小型CNN网络长什么样?论文SIMPLE ONLINE AND REALTIME TRACKING WITH A DEEP ASSOCIATION METRIC 给出Net网络如下:
ZQPei给出代码实现(deep_sort_pytorch/deep_sort/deep/model.py)
class Net(nn.Module): def __init__(self, num_classes=751 ,reid=False): super(Net,self).__init__() self.conv = nn.Sequential( nn.Conv2d(3,64,3,stride=1,padding=1), nn.BatchNorm2d(64), nn.ReLU(inplace=True), nn.MaxPool2d(3,2,padding=1), ) self.layer1 = make_layers(64,64,2,False) self.layer2 = make_layers(64,128,2,True) self.layer3 = make_layers(128,256,2,True) self.layer4 = make_layers(256,512,2,True) self.avgpool = nn.AvgPool2d((8,4),1) self.reid = reid self.classifier = nn.Sequential( nn.Linear(512, 256), nn.BatchNorm1d(256), nn.ReLU(inplace=True), nn.Dropout(), nn.Linear(256, num_classes), ) def forward(self, x): x = self.conv(x) # in 128x64, out 64x32, channel=64 x = self.layer1(x) # in 64x32, out 64x32, channel=64 x = self.layer2(x) # in 64x32, out 32x16, channel=128 x = self.layer3(x) # in 32x16, out 16x8, channel=256 x = self.layer4(x) # in 16x8, out 8x4, channel=512 x = self.avgpool(x) # in 8x4, out 1x1, channel=512 x = x.view(x.size(0),-1) if self.reid: x = x.div(x.norm(p=2,dim=1,keepdim=True)) return x # classifier x = self.classifier(x) return x
由于DeepSort主要被用来做行人追踪的,输入图像的大小为128(h)x 64(w)的矩形框。若针对车辆等其他物体追踪,需要把网络模型的输入进行修改。车辆目标和行人相比,其矩形框应为64(h)x128(w)。
于是对Net网络参数的修改
(1)平均池化avg.pool修改如下:
self.avgpool = nn.AvgPool2d((4,8),1)
即把原来的kernal size从8x4改成4x8。因为,经前面卷积和规划层等,输入的车辆图像从64x128变成4x8(行人图像则是128x64变成8x4),经avg.pool layer缩减成1x1特征项。512个1x1特征项进入分类器self.classifier进行分类判决,得到检测框目标的外观特征分类。
(2)修改分类数量
class Net(nn.Module): def __init__(self, num_classes=537, reid=False):
将num_classes=751修改成num_classes=车辆数据集ID数。
(3)另外,在feature_extractor.py中,
class Extractor(object): def __init__(self, model_path, use_cuda=True): ... ... self.size = (128,64)
原代码为:self.size=(64, 128),此处self.size=(width,height) 要注意。
训练数据集采用veri-wild,此数据集提供了25GB车辆图片,其中image文件夹有23个压缩项目,每个1GB。这数据量实在太大,训练很辛苦。此处用了5个压缩项,从中选出537辆车的图片,每辆车图片数量在16~20之间,构成train_537目录。另外建立test_537目录,从train_537目录中,每辆车的子目录中取出4个图像放入test_537目录。
由此完成车辆训练数据集train_537和test_537,两个目录下,00029, 00030,…等目录分别是车辆ID号,也是ReID的外观特征分类号(号码是什么数字不重要),车辆ID号目录下存放对应的车辆图片。一个小程序完成训练和测试数据集生成。
import os from shutil import copyfile, copytree, rmtree src_dir = "/home/your_name/AI_dataset/veri_wild_images01_05" train_dir="dataset/train_list015" test_dir="dataset/test_list015" if os.path.isdir(train_dir): rmtree(train_dir) #删除文件夹,包含文件夹及文件夹下的所有文件 if os.path.isdir(test_dir): rmtree(test_dir) os.mkdir(train_dir) os.mkdir(test_dir) # 提取符合要求的ID子目录文件 for subdir in os.listdir( src_dir ): # ID子目录名称 src=src_dir+"/"+subdir file_num = sum([os.path.isfile(os.path.join(src, listx)) for listx in os.listdir(src)]) #从ID子目录中获得文件数 if file_num>=16 and file_num<20: # 提取 ID子目录中文件数>=16且<20的子目录 print(src," :", file_num) train_ID_dir=train_dir+"/"+subdir test_ID_dir=test_dir+"/"+subdir if os.path.isdir(train_ID_dir): rmtree(train_ID_dir) if os.path.isdir(test_ID_dir): rmtree(test_ID_dir) os.mkdir(train_ID_dir) os.mkdir(test_ID_dir) # copy src 4个文件到test, 其他剩余文件到train ID1=0 for file_name in os.listdir(src): # 提取ID子目录中文件名称 if ID1<4: copyfile(src+"/"+file_name, test_ID_dir+"/"+file_name) # 拷贝4个文件到test_ID_dir ID1=ID1+1 else: copyfile(src+"/"+file_name, train_ID_dir+"/"+file_name) # 拷贝其他文件到train_ID_dir
完成train目录和test目录后,将这两个目录放到your_training_dir目录下。
train.py --data-dir your_training_dir
此处,需要对train.py做一点修改:
(1)输入train目录和test目录
(2)修改图像变换
将图像尺寸从原来的(128,64)改为适合车辆形状的(64,128),并对transform_train中增加一项:
torchvision.transforms.Resize((64, 128))
至于为什么?说不清,但增加此项可以提高训练收敛速度。训练过程如下:
cls537_ep120.png 训练结果, ~/deep_sort_pytorch/cls537_ep120.png
图片尺寸64x128, 数据集ID_num=537, epoch=120,
train: loss=0.18, acc=96.2%
test: loss=0.65, acc=80.2%
增大epoch,加大训练时间,则匹配精度会得到提高,但从曲线看,这个过程将很漫长。
epoch=120大约需要50分钟(RTX2060)。
训练结束,得到ReID模型ckpt.t7,可以放到deep_sort_pytorch/deep_sort/deep/checkpoint目录下,替换掉原来的ckpt.t7,进行跟踪试验验证。验证过程可见本博另一篇:
pytorch yolo5+Deepsort实现目标检测和跟踪
试验结果不太理想,所训练得到的ckpt.t7与原ZQPei提供的ckpt.t7略有不同,区别并不大。并且此车辆ReID与原有的行人ReID模型ckpt.t7,对行人跟踪效果也相同,并未因车辆特征而破坏对行人特征的跟踪效果,难以理解。
进一步试验发现,调整deep的参数(deep_sort_pytorch/configs/deep_sort.yaml)会获得较好的跟踪效果。
DEEPSORT: REID_CKPT: "deep_sort_pytorch/deep_sort/deep/checkpoint/ckpt.t7" MAX_DIST: 0.2 #0.2 MIN_CONFIDENCE: 0.3 #0.3 NMS_MAX_OVERLAP: 0.5 # 0.5 MAX_IOU_DISTANCE: 0.3 # 0.7 MAX_AGE: 70 N_INIT: 3 NN_BUDGET: 100
其中,减小最大余弦距离(MAX_IOU_DISTANCE=0.3),或增大最小置信度(MIN_CONFIDENCE=0.7)可提高跟踪稳定性,遮挡被跟踪目标仍可实现目标ID保持不变,但被跟踪目标较小时会失去跟踪。这容易理解:排除干扰,提高目标跟踪门槛将提高稳定性,但也会失去灵敏度。
本文针对车辆目标,摘取veri-wild车辆数据集部分数据,构造deep ReID网络训练数据集。修改deep Net网络参数,以适应车辆的长宽比。训练过程较为满意,获得收敛结果。但所得到的车辆外观分类特征模型并没有多大的跟踪性能改善,而修改deep参数却得到较好的跟踪性能。这是为什么,还需要进一步讨论。