环境:win10,cuda 10.1 , GTX1060
链接:https://pan.baidu.com/s/1K3rI9PvzHc1KqOJITNMdVg
提取码:lox4
数据格式也不一定完全按照上面这种,但是必须得保证图片和标签的名字相同。以MOT17(JDE的Modelzoo中下载得到)为例:
文件夹结构:
labels_with_ids文件夹里面是用转换工具将gt.txt生成对应的JDE训练所需的标注文件,对应每一个视频序列的每一帧图片。
而这片博客是要在UA-DETRAC数据集上训练JDE,所以先看看UA-DETRAC原始数据集(所有图片分辨率为960x540)
标签是XML格式,且一个xml对应一个视频序列,每一个xml内容包含该视频序列中所有帧的标注信息:
其中一帧中包括多个车辆标注,标注信息包括:车辆ID,box坐标,以及一些属性:方向,速度,轨迹长度,遮挡率,车辆类别。要想在JDE中训练,需要进行转换,JDE要求的标注格式:
编写脚本,解析原始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)
生成的标注:
这里需要注意一点,就是生成的标签中,相邻两个视频序列的目标ID是连续的,而不是每个序列的目标ID全部从1开始。可以这么理解,假如上一个视频中共60个目标,那么在下一个视频开始,新目标就应该是61,62....以此类推,这样在后续训练时加载数据集统计ID时才不会错,我之前就是脚本写的有问题,60个视频序列的车辆ID才343个,这显然是不对的。后面对比了MOT的数据,发现MOT的原始数据标注都是按同一ID的标注放在一起,比如目标1出现了10帧,那么前10行就是目标1的标注,目标2出现了15帧,那么接下来的15行就是目标2的标注。因此我在转换工具中加入了一段代码,用来处理这个“放在一起”的过程:
生成训练所需的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)因为前面数据集做了修改,所以要对应的修改dataset.py文件
因为图片和标签的文件夹层次结构不同,所以这里替换图片的路径中的部分来得到标签路径。
(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)。
(3)需要在数据配置文件中,将训练数据修改成刚生成的xxxx.train文件:
设置训练参数:
vscode中,ctrl+F5开始训练,或者命令行中python train.py开始训练(统计出总共5920个目标,训练集+测试集共8250个)。
训练中各项loss收敛正常(图为训练到第7个epoch)
不过训练中total loss出现负值,不知道为何,total loss会是负数, 这个问题还没弄清楚,有大佬若知道请不吝赐教。
#----------------------------------------------------------------------------------------------------------------------------------------------------
2020/0609更新
之在UA-DETRAC数据集上训练,使用了4个类别:['car', 'van', 'bus','others'],但是JDE默认是只有一类,也就是一个类别的多目标跟踪,例如行人多目标跟踪,车辆多目标跟踪。因此我把类别全部改成一类:car,对应的cfg文件就得修改:
这里18=3*(1+5),1表示只有1类。
这次使用darknet53预训练模型fineturn训练,修改参数中的weights-from参数,修改成darknet53.conv.74文件所在目录。
此外设置初始学习率:0.01,分辨率为[416,416]
命令行中输入: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只支持mp4格式的视频demo,参数是--input-vedio,我这里主要大多是h264的视频,为了测试还得去转成MP4格式,为了方便,我修改了这个参数为:--input-vedio-images,可以测视频,也可以测图片,修改下如下代码:
在detaset.py中,赋值一份class LoadVideo类,改名为LoadImages,然后增加一个成员变量self.frame_rate=30,这个因为后面统一读取,默认是按视频格式,所以有帧率,这里也加上帧率这个参数,防止报错。
开始测试:输入 python demo.py 在results/frame文件夹下生成了每一帧的跟踪结果
然后在整个测试图片文件夹测试完后会将跟踪结果拼成一个mp4视频
视频截图如下:
在跑demo时遇到一个问题,就是有些尺寸比例的车辆显示没有跟踪到,如下图所示:
但是正面正对摄像头的车辆却效果很好,如下图所示:
所以我一度以为是anchor问题,自己也在ua-detrac数据集的训练集上聚类出了一组anchor专门训练。但是后来发现不管怎么训练还是跟踪不到(没有跟踪框),一直觉得检测没训练好,各种检查训练数据,换anchor,调学习率,换网络,但还是同样的问题。
所以我觉得,先不管跟踪,先看看检测效果怎么样,在检测结束,显示一下检测结果,代码修改如下:multitracker.py中的 def update(self, im_blob, img0):函数,增加显示代码:
结果显示出来是检测到了,有些没显示是因为置信度低于阈值,说明检测没问题。接下来就检查跟踪模块,发现跟踪过程也是完全正常,每次都有7、8个目标进行匹配,而且跟踪reid分支提取的特征,构成的距离矩阵也是正常的,相同车辆的距离最小。匹配结束也是有好多个目标被确认跟踪。所以跟踪模块也没问题。最后检查输出模块,问题就出在了这。。。
这里对输出的跟踪框做了过滤, 由于JDE原始是做行人跟踪,所以过滤掉了宽高比大于1.6的跟踪框,所以导致很多符合这种比例的车辆全部被过滤,显示不出来。好了到此问题查清楚了,注释掉过滤语句,重新跑demo,天下太平,一切正常了。
#---------------------------------------------------------------------------------------------------------------------
2020/06/10更新
JDE中训练时的数据增强:
1、原始图片
2、csv增强50%,添加忽略区域(黑色部分,只针对UA-DETRAC数据集):
3、Letterbox:resize+pad,就是将长边缩放到416,然后短边填充(127.5,127.5,127.5),这个值是0-255之间的中值。
4、仿射变换( random_affine(img, labels, degrees=(-5, 5), translate=(0.10, 0.10), scale=(0.50, 1.20))):
旋转,平移
5、水平翻转(概率0.5):
6、随机裁剪(416x416,自己增加)