多目标跟踪算法JDE在 UA-DETRAC数据集上训练

环境:win10,cuda 10.1 , GTX1060

一、数据处理

1、数据集获取:

链接:https://pan.baidu.com/s/1K3rI9PvzHc1KqOJITNMdVg 
提取码:lox4

2、数据集格式

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第1张图片

数据格式也不一定完全按照上面这种,但是必须得保证图片和标签的名字相同。以MOT17(JDE的Modelzoo中下载得到)为例:

文件夹结构:

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第2张图片

labels_with_ids文件夹里面是用转换工具将gt.txt生成对应的JDE训练所需的标注文件,对应每一个视频序列的每一帧图片。

而这片博客是要在UA-DETRAC数据集上训练JDE,所以先看看UA-DETRAC原始数据集(所有图片分辨率为960x540)

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第3张图片

标签是XML格式,且一个xml对应一个视频序列,每一个xml内容包含该视频序列中所有帧的标注信息:

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第4张图片

其中一帧中包括多个车辆标注,标注信息包括:车辆ID,box坐标,以及一些属性:方向,速度,轨迹长度,遮挡率,车辆类别。要想在JDE中训练,需要进行转换,JDE要求的标注格式:

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第5张图片

编写脚本,解析原始xml标注文件,生成上述的标注txt文件,因为FairMOT算法和JDE用的是同一个数据处理方式,甚至是完全相同的数据集,因此我直接在FairMOT的数据转换工具基础上做了修改,内容如下:

import os.path as osp
import os
import numpy as np
import shutil
import xml.dom.minidom as xml
import abc

def mkdirs(d):
    if not osp.exists(d):
        os.makedirs(d)


seq_root = 'F:/dataset/MOT/UA-DETRAC/DETRAC-train-data/Insight-MVT_Annotation_Train'#图片
xml_root = 'F:/dataset/MOT/UA-DETRAC/DETRAC-Train-Annotations-XML'  #原始xml标注
label_root="F:/dataset/MOT/UA-DETRAC/DETRAC-Train-Annotations-track" #新生成的标签保存目录

#mkdirs(label_root)
seqs = [s for s in os.listdir(seq_root)]

'''
读取xml文件
'''

class XmlReader(object):
    __metaclass__ = abc.ABCMeta
    def __init__(self):
        pass
    def read_content(self,filename):
        content = None
        if (False == os.path.exists(filename)):
            return content
        filehandle = None
        try:
            filehandle = open(filename,'rb')
        except FileNotFoundError as e:
            print(e.strerror)
        try:
            content = filehandle.read()
        except IOError as e:
            print(e.strerror)
        if (None != filehandle):
            filehandle.close()
        if(None != content):
            return content.decode("utf-8","ignore")
        return content

    @abc.abstractmethod
    def load(self,filename):
        pass

class XmlTester(XmlReader):
    def __init__(self):
        XmlReader.__init__(self)
    def load(self, filename):
        filecontent = XmlReader.read_content(self,filename)
        #print(filecontent)
        seq_gt=[]
        
        if None != filecontent:
            dom = xml.parseString(filecontent)
            root = dom.getElementsByTagName('sequence')[0]
            if root.hasAttribute("name"):
                seq_name=root.getAttribute("name")
                print ("*"*20+"sequence: %s" %seq_name +"*"*20)
            #获取所有的frame
            frames = root.getElementsByTagName('frame')
            
            for frame in frames:
                if frame.hasAttribute("num"):
                    frame_num=int(frame.getAttribute("num"))
                   
                    print ("-"*10+"frame_num: %s" %frame_num +"-"*10)

                target_list = frame.getElementsByTagName('target_list')[0]
                #获取一帧里面所有的target
                targets = target_list.getElementsByTagName('target')
                targets_dic={}
                for target in targets:
                    if target.hasAttribute("id"):
                        tar_id=int(target.getAttribute("id"))
                        #print ("id: %s" % tar_id)

                    box = target.getElementsByTagName('box')[0]
                    if box.hasAttribute("left"):
                        left=box.getAttribute("left")
                        #print ("  left: %s" % left)
                    if box.hasAttribute("top"):
                        top=box.getAttribute("top")
                        #print ("  top: %s" %top )
                    if box.hasAttribute("width"):
                        width=box.getAttribute("width")
                        #print ("  width: %s" % width)
                    if box.hasAttribute("height"):
                        height=box.getAttribute("height")
                        #print ("  height: %s" %height )
                    #中心坐标
                    x=float(left)+float(width)/2
                    y=float(top)+float(height)/2
                    #宽高中心坐标归一化
                    # x/=img_w
                    # y/=img_h
                    # width=float(width)/img_w
                    # height=float(height)/img_h

                    attribute = target.getElementsByTagName('attribute')[0]
                    if attribute.hasAttribute("vehicle_type"):
                        type=attribute.getAttribute("vehicle_type")
                        if type=="car":
                            type=0
                        if type=="van":
                            type=1
                        if type=="bus":
                            type=2
                        if type=="others":
                            type=3

                    #anno_f.write(str(type)+" "+tar_id+" %.3f"%x+" %.3f"%y+" %.3f"%width+" %.3f"%height+"\n")
                    seq_gt.append([frame_num,tar_id,x,y,float(width),float(height),type])         
        return seq_gt



tid_curr = 0
tid_last = -1  #用于在下一个视频序列时,ID数接着上一个视频序列最大值
for seq in seqs: #每一个视频序列
    print(seq)
    seq_width = 960
    seq_height = 540

    gt_xml = osp.join(xml_root, seq+'.xml')
    reader = XmlTester()
    gt=reader.load(gt_xml)
    #统计这个序列所有ID
    ids=[]
    for line in gt:
        if not line[1] in ids:
            ids.append(line[1])
    print (ids)
    #根据ID将同一ID的不同帧标注放在一起
    final_gt=[]
    for id in ids:
        for line in gt:
            if line[1]==id:
                final_gt.append(line)
    print(len(final_gt))

    seq_label_root = osp.join(label_root, seq)
    if not os.path.exists(seq_label_root):
        mkdirs(seq_label_root)
    
    for fid, tid, x, y, w, h, label in final_gt:
        label=int(label)
        print(" ",fid,label)
        fid = int(fid)
        tid = int(tid)
        if not tid == tid_last:
            tid_curr += 1
            tid_last = tid
        
        label_fpath = osp.join(seq_label_root, 'img{:05d}.txt'.format(fid))
        label_str = '{:d} {:d} {:.6f} {:.6f} {:.6f} {:.6f}\n'.format(int(label),
            tid_curr, float(x) / seq_width, float(y) / seq_height, float(w) / seq_width, float(h) / seq_height) #宽高中心坐标归一化
        with open(label_fpath, 'a') as f:
            f.write(label_str)

生成的标注:

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第6张图片

这里需要注意一点,就是生成的标签中,相邻两个视频序列的目标ID是连续的,而不是每个序列的目标ID全部从1开始。可以这么理解,假如上一个视频中共60个目标,那么在下一个视频开始,新目标就应该是61,62....以此类推,这样在后续训练时加载数据集统计ID时才不会错,我之前就是脚本写的有问题,60个视频序列的车辆ID才343个,这显然是不对的。后面对比了MOT的数据,发现MOT的原始数据标注都是按同一ID的标注放在一起,比如目标1出现了10帧,那么前10行就是目标1的标注,目标2出现了15帧,那么接下来的15行就是目标2的标注。因此我在转换工具中加入了一段代码,用来处理这个“放在一起”的过程:

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第7张图片

生成训练所需的xxxx.train文件,脚本如下:

import os
root_path="F:/dataset/MOT/UA-DETRAC"
label_flder="DETRAC-Train-Annotations-track"
img_folder="DETRAC-train-data/Insight-MVT_Annotation_Train"
seqs=os.listdir(root_path+"/"+label_flder)
train_f=open("UA-DETRAC.train","w")
count=0
for seq in seqs:
    print("seq:",seq)
    labels=os.listdir(root_path+"/"+label_flder+"/"+seq)
    for label in labels:
        img_name=label[:-4]+".jpg"
        save_str=root_path+"/"+img_folder+"/"+seq+'/'+img_name+"\n"
        print("img:",save_str)
        count+=1
        print(count)
        train_f.write(save_str)
train_f.close()

到此数据处理结束。

二、训练

1、训练相关代码修改

(1)因为前面数据集做了修改,所以要对应的修改dataset.py文件

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第8张图片

因为图片和标签的文件夹层次结构不同,所以这里替换图片的路径中的部分来得到标签路径。

(2)修改网络定义配置cfg。JDE中使用的是YOLO v3,其中3个yolo层的anchor,尺寸都是针对行人比例大小特殊设置的,因为UA-DETRAC所有标注数据都是车辆,且车辆大多数都是近似1:1的框(没有像行人那么大的宽高比),因此我直接将三层yolo层的anchor都按照原始416x416大小的yolov3的cfg设置来修改,此外需要注意的是,类别个数,JDE中全部是行人,所以类别数为1,检测和分类分支的卷积通道数为24=4*(1+5),4表示每一个yolo层的anchor数,1表示类别数,5表示conf,x,y,w,h。现在UA-DETRAC数据集中车辆类别有4个:['car', 'van', 'bus','others'],每一个yolo层的anchor也改成了3,所以检测和分类分支的卷积通道数为27=3*(4+5)。

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第9张图片

(3)需要在数据配置文件中,将训练数据修改成刚生成的xxxx.train文件:

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第10张图片

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第11张图片

2、训练

设置训练参数:

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第12张图片

vscode中,ctrl+F5开始训练,或者命令行中python train.py开始训练(统计出总共5920个目标,训练集+测试集共8250个)。

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第13张图片

训练中各项loss收敛正常(图为训练到第7个epoch)

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第14张图片

不过训练中total loss出现负值,不知道为何,total loss会是负数, 这个问题还没弄清楚,有大佬若知道请不吝赐教。

#----------------------------------------------------------------------------------------------------------------------------------------------------

2020/0609更新

之在UA-DETRAC数据集上训练,使用了4个类别:['car', 'van', 'bus','others'],但是JDE默认是只有一类,也就是一个类别的多目标跟踪,例如行人多目标跟踪,车辆多目标跟踪。因此我把类别全部改成一类:car,对应的cfg文件就得修改:

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第15张图片

这里18=3*(1+5),1表示只有1类。

这次使用darknet53预训练模型fineturn训练,修改参数中的weights-from参数,修改成darknet53.conv.74文件所在目录。

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第16张图片

此外设置初始学习率:0.01,分辨率为[416,416]

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第17张图片 命令行中输入:python train.py ,开始训练,大概训练到26个epoch时的loss如下:

2020-06-04 14:19:15 [INFO]:    Epoch       Batch       box      conf        id     total  nTargets      time    cur_lr
2020-06-04 15:31:00 [INFO]:    26/29  6080/13459   0.00161  0.000657       5.9     -21.8      43.7     0.446    0.0001
2020-06-04 15:31:27 [INFO]:    26/29  6120/13459   0.00161  0.000657       5.9     -21.8      43.7     0.434    0.0001
2020-06-04 15:31:55 [INFO]:    26/29  6160/13459   0.00161  0.000657       5.9     -21.8      43.7     0.465    0.0001
2020-06-04 15:32:23 [INFO]:    26/29  6200/13459   0.00161  0.000657       5.9     -21.8      43.7     0.432    0.0001
2020-06-04 15:32:50 [INFO]:    26/29  6240/13459   0.00161  0.000657      5.89     -21.8      43.7     0.455    0.0001
2020-06-04 15:33:18 [INFO]:    26/29  6280/13459   0.00161  0.000656       5.9     -21.8      43.7     0.449    0.0001
2020-06-04 15:33:46 [INFO]:    26/29  6320/13459   0.00161  0.000656      5.89     -21.9      43.7     0.443    0.0001
2020-06-04 15:34:14 [INFO]:    26/29  6360/13459   0.00161  0.000656      5.89     -21.9      43.7     0.469    0.0001
2020-06-04 15:34:42 [INFO]:    26/29  6400/13459   0.00161  0.000655      5.89     -21.9      43.7     0.444    0.0001
2020-06-04 15:35:10 [INFO]:    26/29  6440/13459   0.00161  0.000655      5.89     -21.9      43.7      0.44    0.0001
2020-06-04 15:35:38 [INFO]:    26/29  6480/13459   0.00161  0.000654      5.89     -21.9      43.6     0.433    0.0001
2020-06-04 15:36:07 [INFO]:    26/29  6520/13459   0.00161  0.000654      5.89     -21.9      43.7      0.44    0.0001
2020-06-04 15:36:35 [INFO]:    26/29  6560/13459   0.00161  0.000655      5.89     -21.9      43.7      0.47    0.0001
2020-06-04 15:37:03 [INFO]:    26/29  6600/13459   0.00161  0.000655      5.89     -21.9      43.7      0.46    0.0001
2020-06-04 15:37:31 [INFO]:    26/29  6640/13459   0.00161  0.000655      5.89     -21.9      43.7     0.439    0.0001
2020-06-04 15:37:59 [INFO]:    26/29  6680/13459   0.00161  0.000659       5.9     -21.9      43.7     0.464    0.0001
2020-06-04 15:38:27 [INFO]:    26/29  6720/13459   0.00162  0.000669       5.9     -21.7      43.7     0.449    0.0001
2020-06-04 15:38:55 [INFO]:    26/29  6760/13459   0.00164  0.000684      5.92     -21.5      43.7     0.442    0.0001
2020-06-04 15:39:24 [INFO]:    26/29  6800/13459   0.00165  0.000691      5.93     -21.5      43.7     0.437    0.0001
2020-06-04 15:39:54 [INFO]:    26/29  6840/13459   0.00166  0.000705      5.94     -21.4      43.7     0.453    0.0001
2020-06-04 15:40:27 [INFO]:    26/29  6880/13459   0.00167  0.000715      5.95     -21.3      43.7     0.808    0.0001
2020-06-04 15:41:34 [INFO]:    26/29  6920/13459   0.00168  0.000719      5.96     -21.3      43.7     0.967    0.0001
2020-06-04 15:42:55 [INFO]:    26/29  6960/13459   0.00168  0.000724      5.97     -21.3      43.7      1.16    0.0001
2020-06-04 15:43:33 [INFO]:    26/29  7000/13459   0.00169  0.000726      5.97     -21.3      43.6     0.431    0.0001

我是训练到26个eopch结束,跑一下demo,修改下cfg文件和训练好的权重目录,以及测试图片所在文件夹的目录,如下所示:

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第18张图片

原始的JDE只支持mp4格式的视频demo,参数是--input-vedio,我这里主要大多是h264的视频,为了测试还得去转成MP4格式,为了方便,我修改了这个参数为:--input-vedio-images,可以测视频,也可以测图片,修改下如下代码:

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第19张图片

在detaset.py中,赋值一份class LoadVideo类,改名为LoadImages,然后增加一个成员变量self.frame_rate=30,这个因为后面统一读取,默认是按视频格式,所以有帧率,这里也加上帧率这个参数,防止报错。

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第20张图片

开始测试:输入 python demo.py 在results/frame文件夹下生成了每一帧的跟踪结果

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第21张图片

然后在整个测试图片文件夹测试完后会将跟踪结果拼成一个mp4视频 

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第22张图片

视频截图如下: 

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第23张图片

三、采坑记录

 在跑demo时遇到一个问题,就是有些尺寸比例的车辆显示没有跟踪到,如下图所示:

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第24张图片

但是正面正对摄像头的车辆却效果很好,如下图所示:

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第25张图片

所以我一度以为是anchor问题,自己也在ua-detrac数据集的训练集上聚类出了一组anchor专门训练。但是后来发现不管怎么训练还是跟踪不到(没有跟踪框),一直觉得检测没训练好,各种检查训练数据,换anchor,调学习率,换网络,但还是同样的问题。

所以我觉得,先不管跟踪,先看看检测效果怎么样,在检测结束,显示一下检测结果,代码修改如下:multitracker.py中的 def update(self, im_blob, img0):函数,增加显示代码:

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第26张图片

结果显示出来是检测到了,有些没显示是因为置信度低于阈值,说明检测没问题。接下来就检查跟踪模块,发现跟踪过程也是完全正常,每次都有7、8个目标进行匹配,而且跟踪reid分支提取的特征,构成的距离矩阵也是正常的,相同车辆的距离最小。匹配结束也是有好多个目标被确认跟踪。所以跟踪模块也没问题。最后检查输出模块,问题就出在了这。。。

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第27张图片

这里对输出的跟踪框做了过滤, 由于JDE原始是做行人跟踪,所以过滤掉了宽高比大于1.6的跟踪框,所以导致很多符合这种比例的车辆全部被过滤,显示不出来。好了到此问题查清楚了,注释掉过滤语句,重新跑demo,天下太平,一切正常了。

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第28张图片

#---------------------------------------------------------------------------------------------------------------------

2020/06/10更新

JDE中训练时的数据增强:

1、原始图片

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第29张图片

2、csv增强50%,添加忽略区域(黑色部分,只针对UA-DETRAC数据集):

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第30张图片

3、Letterbox:resize+pad,就是将长边缩放到416,然后短边填充(127.5,127.5,127.5),这个值是0-255之间的中值。

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第31张图片

4、仿射变换( random_affine(img, labels, degrees=(-5, 5), translate=(0.10, 0.10), scale=(0.50, 1.20))):

旋转,平移

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第32张图片

5、水平翻转(概率0.5):

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第33张图片

6、随机裁剪(416x416,自己增加)

多目标跟踪算法JDE在 UA-DETRAC数据集上训练_第34张图片

你可能感兴趣的:(人工智能)