YOLOYOLOv5+Deep Sort 实现MOT评估(开源数据集+自定义数据集)

Posted 摇曳的树

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了YOLOYOLOv5+Deep Sort 实现MOT评估(开源数据集+自定义数据集)相关的知识,希望对你有一定的参考价值。

引言

YOLOv5+Deep Sort 实现目标跟踪,并利用MOTChallengeEvalKit实现多目标跟踪结果的评估。
YOLOv5+Deep Sort 实现目标跟踪可以参考笔者的【YOLOv5】yolov5目标识别+DeepSort目标追踪

实现步骤

1 安装MATLAB

安装MATLAB
MATLAB是一款商业数学软件,用于算法开发、数据可视化、数据分析以及数值计算的高级技术计算语言和交互式环境,主要包括MATLAB和Simulink两大部分,可以进行矩阵运算、绘制函数和数据、实现算法、创建用户界面、连接其他编程语言的程序等,主要应用于工程计算、控制设计、信号处理与通讯、图像处理、信号检测、金融建模设计与分析等领域。
直接附上安装的参考链接:https://mp.weixin.qq.com/s/LAatgqNf55zpxzOYlpn8fg

2 必要的开源

  1. MOT评估工具:dendorferpatrick-MOTChallengeEvalKit
  2. 开源的评估数据库:例如MOT 17,其他链接如下
  3. Deep Sort目标跟踪:mikel-brostrom-Yolov5_DeepSort_Pytorch
  4. YOLOv5目标识别:ultralytics-yolov5

下载上述开源压缩包,进行解压,文件的部署如下:

-----------------------------------------------------------------------------------------------------------
# 1.准备数据集(树形框架)
|-MOTChallengeEvalKit(评估工具源码)# 源码1
	|-MOT  # 多目标评估
		|-matlab_devkit  
		|-evalMOT.py  # 评估主代码1
		|-Evaluator.py  # 评估主代码2
		|-Metrics.py
		|-MOT_metrics.py
		|-MOTVisualization.py 
		|-Visualize.py   
|-Yolov5_DeepSort_Pytorch(目标跟踪) # 源码2
	|-deep_sort  # 跟踪源码
	|-yolov5  # 源码3:该文件夹是空的,需要把YOLOv5的源码项目放进去
|-Evaluation-MOTdata(开源数据库) # 笔者自定义的名称(用于存放开源数据库)
	|-MOT16  # 开源数据集
	|-MOT17  
	|-MOT20
-----------------------------------------------------------------------------------------------------------

3 环境部署

首先利用Anaconda创建新的环境,以优先满足YOLOv5的环境部署,特别是python的版本不能低于YOLOv5要求的下限。

conda create -n yolov5-6 python=3.7
conda activate yolov5-6  # 激活环境

3.1 MATLAB编译

笔者MATLAB的版本是:MATLAB R2021a
终端进入MATLAB的安装路径:D:\\Program Files\\Polyspace\\R2021a\\extern\\engines\\python 自定义的安装路径

cd D:\\Program Files\\Polyspace\\R2021a\\extern\\engines\\python
# 执行如下指令
python setup.py build --build-base="builddir" install
python setup.py install --prefix="installdir"
python setup.py build --build-base="builddir" install --prefix="installdir" 
python setup.py install --user

执行完上述指令之后,MATLAB的安装路径下新增文件如下:

打开MATLAB完成编译
打开评估工具源码项目文件,并将如下文件复制到MOT文件夹下面,这一步的复制操作十分重要,否则即使编译成功,评估也会失败。

复制后MOT文件夹下的文件如下:

根据截图完成编译

若没有复制上述文件,则评估是会有如下的错误:

3.2 DeepSort +YOLOv5环境搭建

优先满足YOLOv5的环境部署,DeepSort没有特殊的要求,在运行时缺啥补啥
参考链接【YOLOv5】6.0环境搭建(不定时更新)

4 评估测试

测试上述所搭建的环境是否能在开源的MOT17实现评估

4.1 下载现成的跟踪结果

现成的跟踪结果笔者的参考链接:(python版 MOTChallengeEvalKit 评估工具运行,运行MOT评估工具
链接:https://pan.baidu.com/s/1CkPse7rcx53qOEy8YlU6qw
提取码:rlwj
将下载的文件放入开源数据集中:

4.2 运行评估

(1)打开评估代码MOTChallengeEvalKit-master\\MOT\\evalMOT.py
修改配置如下:

if __name__ == "__main__":
	eval = MOT_evaluator()

	benchmark_name = "MOT17"
	gt_dir = r"E:\\Alian\\yolov5\\MOTChallengeEvalKit-master\\MOT17"  # GT
	res_dir = r"E:\\Alian\\yolov5\\MOTChallengeEvalKit-master\\MOT17\\results\\test_track_MOT17_fuc_v0"  # 跟踪结果
	eval_mode = "train"  # MOT17下的train模式,因为只有train才有GT
	seqmaps_dir = r'E:\\Alian\\yolov5\\MOTChallengeEvalKit-master\\seqmaps'
	eval.run(
		benchmark_name = benchmark_name,
		gt_dir = gt_dir,
		res_dir = res_dir,
		eval_mode = eval_mode,
		seqmaps_dir=seqmaps_dir
	)

(2)打开MOTChallengeEvalKit-master\\seqmaps\\MOT17-train.txt
只保留下述的内容,多余的删除(因为只有下述文件的跟踪结果),检查基准、跟踪结果是否一一对应

评估基准

跟踪结果

终端执行命令,打印结果如下

python evalMOT.py

5 在自定义的数据下实现跟踪评估

5.1 修改评估代码

./MOT/evalMOT.py 修改如下

"""
2022.3.12
author:alian
实现跟踪结果的评估
"""
import sys, os
sys.path.append(os.path.abspath(os.getcwd()))
import math
from collections import defaultdict
from MOT_metrics import MOTMetrics
from Evaluator import Evaluator, run_metrics,My_Evaluator
import multiprocessing as mp
import pandas as pd
# 2、自定义数据集评估
class My_MOTevaluator(My_Evaluator):  # 继承父类
	def __init__(self):
		super().__init__()
		pass

	def my_eval(self):  # 重写评估函数
		arguments = []  # 传入参数: 项目名称,跟踪结果文件列表,基准文件列表
		for seq, res, gt in zip(self.sequences, self.tsfiles, self.gtfiles):  # 评估路段的名称,跟踪结果文件,基准文件
			arguments.append("metricObject": MOTMetrics(seq), "args": 
			"gtDataDir":  os.path.join(os.path.dirname(gt),seq),
			"sequence": str(seq),
			"pred_file":res,
			"gt_file": gt,
			"benchmark_name": self.benchmark_name)
		try:
			if self.MULTIPROCESSING:
				p = mp.Pool(self.NR_CORES)  # 实例化多进程并行池
				print("Evaluating on  cpu cores".format(self.NR_CORES))
				# 计算每个序列的指标
				processes = [p.apply_async(run_metrics, kwds=inp) for inp in arguments]  # 得到多进程处理结果
				self.results = [p.get() for p in processes]  # 评估结果
				p.close()
				p.join()
			else:
				self.results = [run_metrics(**inp) for inp in arguments]
			self.failed = False  # 成功
		except:
			self.failed = True
			raise Exception("<exc> MATLAB evalutation failed <!exc>")
		self.Overall_Results = MOTMetrics("OVERALL")  # 定义评估指标矩阵的名称
		return self.results,self.Overall_Results

./MOT/Evaluator.py

"""
2022.3.12
author:alian
"""
import sys, os
sys.path.append(os.getcwd())
import argparse
import traceback
import time
import pickle
import pandas as pd
import glob
from os import path
import numpy as np
class My_Evaluator(object):  # 多目标跟踪评估器类
	""" 评估器类运行每个序列的评估并计算基准的整体性能"""
	def __init__(self):
		pass

	def run(self, benchmark_name=None,  gt_dir=None, mot_dir=None,save_csv = None):
		self.benchmark_name = benchmark_name  # 项目名称
		start_time = time.time()
		self.gtfiles = glob.glob('%s/*_gt.txt'%gt_dir)  # 基准文件gt.txt全路径
		self.tsfiles = glob.glob('%s/*_mot.txt'%mot_dir)  # 评估结果全路径
		self.sequences = [os.path.split(x)[-1].split('_')[0] for x in self.gtfiles]
		print('Found  ground truth files and  test files.'.format(len(self.gtfiles), len(self.tsfiles)))
		# 设置多核处理
		self.MULTIPROCESSING = True  # 多处理
		MAX_NR_CORES = 10  # 最大的处理量
		if self.MULTIPROCESSING: self.NR_CORES = np.minimum(MAX_NR_CORES, len(self.tsfiles))
		try:
			""" 执行评估 """
			self.results,self.Overall_Results = self.my_eval()
			# 计算整体结果
			results_attributes = self.Overall_Results.metrics.keys()  # 评估指标(名称)
			for attr in results_attributes:
				""" 在所有序列上累积评估值 """
				try:
					self.Overall_Results.__dict__[attr] = sum(obj.__dict__[attr] for obj in self.results)
				except:
					pass
			cache_attributes = self.Overall_Results.cache_dict.keys()  # 缓存属性
			for attr in cache_attributes:
				""" accumulate cache values over all sequences """
				try:
					self.Overall_Results.__dict__[attr] = self.Overall_Results.cache_dict[attr]['func']([obj.__dict__[attr] for obj in self.results])
				except:
					pass
			print("evaluation successful")
			# 计算clearmot指标
			for res in self.results:
				res.compute_clearmot()
			self.Overall_Results.compute_clearmot()
			summary = self.accumulate_df(type="mail")
			# 将评估果写入csv文件
			summary.to_csv(save_csv)
		except:pass
		end_time=time.time()
		self.duration = (end_time - start_time)/60.

		# 收集评估错误
		print("Evaluation Finished")
		print("Your Results")
		print(self.render_summary())
		print("Successfully save results")
		return self.Overall_Results, self.results

	def my_eval(self):  # 实际使用时重写
		raise NotImplementedError

	def accumulate_df(self, type = None):
		""" create accumulated dataframe with all sequences """
		summary = None
		for k, res in enumerate(self.results):
			res.to_dataframe(display_name = True, type=type)
			if k == 0: summary = res.df
			else:summary = pd.concat([summary,res.df])
		summary = summary.sort_index()
		self.Overall_Results.to_dataframe(display_name=True, type=type)
		self.summary = pd.concat([summary,self.Overall_Results.df])
		return self.summary


	def render_summary( self, buf = None):  # 终端打印评估指标
		"""
		Params:
		summary : pd.DataFrame
		Kwargs:
		buf : StringIO-like, optional 写入缓存区
		formatters : dict, optional(为单个指标定义自定义格式化程序的字典)'mota': ':.2%'.format.MetricsHost.formatters
		namemap : dict, optional 'num_false_positives': 'FP'
		Returns:
		string
		"""
		output = self.summary.to_string(
			buf=buf,
			formatters=self.Overall_Results.formatters,
			justify="left"
		)
		return output
def run_metrics( metricObject, args ):
	""" Runs metric for individual sequences
	Params:
	-----
	metricObject: metricObject that has computer_compute_metrics_per_sequence function
	args: dictionary with args for evaluation function
	"""
	metricObject.compute_metrics_per_sequence(**args)  # 计算每个序列的指标
	return metricObject
if __name__ == "__main__":
	Evaluator()

5.2 基准和评估结果准备


(1)基准文件:name_gt.txt

从左到右分别是:
 1. frame: 第几帧图片
 2. ID:也就是轨迹的ID,可以看出gt里边是按照轨迹的ID号进行排序的
 3. bbox: 分别是左上角坐标和长宽
 4. bbox: 分别是左上角坐标和长宽
 5. bbox: 分别是左上角坐标和长宽
 6. bbox: 分别是左上角坐标和长宽
 7. bool类型:代表是否忽略:0代表忽略,1代表该目标被选
 8. classes:目标的类别个数(开源的MOT数据集中:驾驶场景包括12个类别,7代表的是静止的人, 第8个类代表错检,9-11代表被遮挡的类别,如下图),自定数据集就根据自己的目标类别数
 9. 最后一个代表目标运动时被其他目标包含、覆盖、边缘裁剪的情况,这里笔者将目标的置信度写入该列的值

(2)跟踪结果:name_mot.txt

从左到右分别是:
 1. frame: 第几帧图片
 2. ID:也就是轨迹的ID,可以看出gt里边是按照轨迹的ID号进行排序的
 3. bbox: 分别是左上角坐标和长宽
 4. bbox: 分别是左上角坐标和长宽
 5. bbox: 分别是左上角坐标和长宽
 6. bbox: 分别是左上角坐标和长宽
 7. conf:目标置信度
 8. MOT3D(x,y,z): 是在MOT3D(三维跟踪)中使用到的内容,这里关心的是MOT二维,所以都设置为-1

5.3 实现自定义数据集的MOT评估

打开./MOT/evalMOT.py 修改配置如下

if __name__ == "__main__":
	#  自定义数据集评估
	my_eval = My_MOTevaluator()
	benchmark_name = "MOT北京地铁9号线"
	gt_dir = r"E:\\Alian\\yolov5\\Yolov5_DeepSort_alian\\results"  # GT
	res_dir = r"E:\\Alian\\yolov5\\Yolov5_DeepSort_alian\\results"  # 跟踪结果
	my_eval.run(
		benchmark_name=benchmark_name,
		gt_dir=gt_dir,
		mot_dir=res_dir,
		save_csv='MOT北京地铁9号线.csv'
	)

6 完整代码汇总

6.1 evalMOT.py

"""
2022.3.12
author:alian
实现跟踪结果的评估
"""
import sys, os
sys.path.append(os.path.abspath(os.getcwd()))
import math
from collections import defaultdict
from MOT_metrics import MOTMetrics
from Evaluator import Evaluator, run_metrics,My_Evaluator
import multiprocessing as mp
import pandas as pd


# 1、开源跟踪评估
class MOT_evaluator(Evaluator):  # 继承父类
	def __init__(self):
		super().__init__()
		self.type = "MOT"

	def eval(self):  # 重写评估函数
		print("Check prediction files")  # 检查评估文件
		error_message = ""
		for pred_file in self.tsfiles:  # 遍历跟踪结果
			print(pred_file)
			df = pd.read_csv(pred_file, header=None, sep=",")  # 默认用逗号隔开
			if len(df.columns) == 1:  # 若只有一列
				f = open(pred_file, "r")
				error_message+= "<exc>Submission %s not in correct form. Values in file must be comma separated.<br>Current form:<br>%s<br>%s<br>.........<br><!exc>" % (pred_file.split("/")[-1], f.readline(),  f.readline())
				raise Exception(error_message)

			df.groupby([0,1]).size().head()  # 根据帧序,ID对数据进行分组(取前5行)
			count = df.groupby([0,1]).size().reset_index(name='count')  # 将分组索引(帧序,ID)设置为列,则此时的列有:帧序,ID,计数

			# 检查ID的独一性
			if any(count["count"]>1):
				doubleIDs  = count.loc[count["count"]>1][[0,1]].values
				error_message
        
                


对于目标跟踪,前提是能够对单张图片中的车辆进行检测,从而知道图片中车辆的位置,根据连续的图像中目标位置的轨迹预测,从而来实现跟踪。

跟踪的基本思想

如下图所示,设T1和T2是视频中连续的两帧图像, 如要在T2帧中跟踪T1中的红色框中的车辆,首先,在T2中进行车辆检测,检测到了三辆车,如黄色框所示;然后需要解决的问题是,要在T1中红色框和T2中黄色框之间建立关联,根据关联关系,确定T2中检测到的车哪辆是T1中的跟踪结果,并用该检测结果作为更新跟踪目标,进行后续T3时刻的跟踪。

在这类检测方法中,将视频理解成连续的图片,我们会发现,在视频中,车辆的位置是在连续变化的,如下图,如果我们持续将图中左侧汽车的检测框画出来,会发现红色框中的车辆一直在变化位置。

假设上图中的某一帧,因为光线影响或者图片质量为题造成红色小车在第315帧无法检测到,那么这一帧将会缺失检测框信息,但是会在320帧的时候重新被检测出来。

跟踪框与检测框

跟踪框:其实就相当与警察抓犯人,警察是跟踪框,犯人是检测框,警察去预测判断犯人的位置,从而去实现跟踪。

基本跟踪过程如下:

  1. 我们可以每帧知道检测框的位置

  2. 跟踪框会通过检测框当前帧的位置和运动状态,预测下一帧这辆车的位置

  3. 如果检测框与预测的跟踪框位置不同,要修正跟踪框的位置从而更好的对后面的跟踪框进行预测

然而我们要在这里理解的目标跟踪,并不完全等同于警匪片里的跟踪逃犯的概念,区别是:我们的警察追上了逃犯,他的任务就已经完成了;而跟踪框要尽量保证跟检测框位置的重合,从而更好的进行下一帧位置的预测。

检测框可以理解为是一个静态的概念,它主要针对单张图片找出明确车辆在其中的位置;跟踪框是一个动态的概念,关注的是连续视频流中图片之间汽车位置的关联

卡尔曼滤波算法—预测

卡尔曼滤波(Kalman filtering)是一种利用线性系统状态方程,通过系统输入输出观测数据,对系统状态进行最优估计的算法。由于观测数据中包括系统中的噪声和干扰的影响,所以最优估计也可看作是滤波过程。

首先我们给出卡尔曼滤波解决问题的场景和目标:卡尔曼滤波可以应用于有多个不同的观测数据,每个数据有一定的可靠程度时,我们希望能够得出尽量贴近真实情况的数据。卡尔曼滤波的一个典型实例是从一组有限的,包含噪声的,对物体位置的观察序列(可能有偏差)预测出物体的位置的坐标及速度。

以车辆举例,我们的车辆前方有一个障碍物,为了使距离测定更准确,我们有一个距离传感器获得车辆到前方障碍物的距离。

1) 在T1时刻,距离传感器告诉我们车辆距离前面障碍物的距离是10m。同时我们可以知道车辆速度是4m/s。为了简化问题,我们假设这两个数值是绝对准确的。

2) 在T2时刻(T1之后1秒),距离传稿器告诉我们当前距离障碍物的距离是7m。同时,在T2时刻,如果考虑到T1时刻我们的速度是4m/s,经过1s后我们应该从距离障碍物10m变成6m(10-4)。

于是,我们在T2时刻得到了两个数据:距离传感器告诉我们的结果7m,和我们通过计算的结果6m。

理想状况下,这个问题并不复杂:我们直接用距离传感器的数值不是就最准确了吗?但是现实是,距离传感器随着距离远近的不同,它的精确度是可能发生变化的;如果只是相信速度传感器,我们在1秒内的速度又不是绝对一致的。所以单独采用两者的任何一个都不够准确。因此如果我们希望T2时刻的结果更准确,我们最好将距离传感器的数值跟速度计算的数值做一下加权平均(予以不同的权重以算均值)。

例如,我们认为距离传感器的数值更加准确一些,认为它在T2时刻的准确度应该是90%,速度估算的值不够准确,因此给予80%的可信度。那么最终估计的距离结果就是:

这就是卡尔曼算法的主要思路:它将两个都不是那么可靠的方式做了融合,从而可以有效抵抗噪点等外部影响,得到相对准确的预测值。以T1时刻的最优的估计X_T1 为准,预测T2时刻的状态变量X_T2. 同时又对该状态进行观测得到观测变量Z_T2 ,再在预测和观测之间进行分析,或者说是以观测量对预测量进行修正,从而得到T2时刻的最优状态估计。

卡尔曼滤波算法目前已经应用于NASA(美国航空航天局)的阿波罗计划等计划中,用于进行飞行器的轨道预测等作用。它还有更多更广泛的使用场景。也许你会在你未来的某些应用场景中想到它并进行应用

简单来说,卡尔曼滤波算法就是根据你检测框的位置去预测目标在下一帧的位置,他是线性的

匈牙利算法----匹配

通过不断寻找增广路径的办法,寻找最大匹配数量。这就是匈牙利算法的主要思路

其实跟贪心算法差不多
我们有三样在售的食品,分别是面包、三明治、方便面。现在同时来了三个不同的客人A、B、C。其中A喜欢面包和三明治,B只喜欢面包,C只喜欢三明治:

对于车辆跟踪来说,就是将检测框跟踪框(卡尔曼滤波预测出来的跟踪框)他们匹配起来,找到最优(最佳)的匹配,从而实现跟踪

希望这篇文章对你有用!
谢谢点赞评论!

以上是关于YOLOYOLOv5+Deep Sort 实现MOT评估(开源数据集+自定义数据集)的主要内容,如果未能解决你的问题,请参考以下文章

基于yolov5与Deep Sort的流量统计与轨迹跟踪

跟踪算法-Deep sort详细简介

寒假学习进度

多目标跟踪SORT 与 Deep SORT(包含实例项目演示)

多目标跟踪方法:deep-sort

yolo_v3训练自己的模型(人脸及deep-sort)