在本教程中,您将学习如何使用dlib库有效地跟踪实时视频中的多个对象。
到目前为止,在这个关于对象跟踪的系列中,我们学会了如何:
使用OpenCV跟踪单个对象
使用OpenCV跟踪多个对象
使用dlib执行单个对象跟踪
跟踪和统计进入商家/商店的人
我们当然可以使用dlib跟踪多个对象;但是,为了获得最佳性能,我们需要利用多处理并将对象跟踪器分布在处理器的多个内核中。
正确使用多处理功能使我们能够将每秒dlib多对象跟踪帧数(FPS)吞吐率提高45%以上!
使用dlib进行多对象跟踪
在本指南的第一部分中,我将演示如何实现一个简单,原生We’ll start with our simple implementation in this section and then move on to the faster method in the next section.的dlib多对象跟踪脚本。该程序将跟踪视频中的多个对象;但是,我们会注意到脚本运行有点慢。
为了提高我们的FPS吞吐率,我将向您展示更快,更高效的dlib多对象跟踪器实现。
最后,我将讨论一些改进和建议,以便增强我们的多对象跟踪实现。
项目结构
从那里,您可以使用tree命令查看我们的项目结构:
mobilenet_ssd /目录包含我们的MobileNet + SSD Caffe模型文件,允许我们检测人(以及其他对象)。
我们今天将回顾两个Python脚本:
multi_object_tracking_slow.py:dlib多对象跟踪的简单“原生”方法。
multi_object_tracking_fast.py:利用多处理的高级快速方法。
其余三个文件是视频。我们有原始的race.mp4视频和两个处理过的输出视频。
“原生”的dlib多对象跟踪实现
今天要介绍的是第一个dlib多对象跟踪使用“原生方法”实现,意思是:
1.使用一个简单的跟踪器对象列表。
2.仅使用单个核心顺序更新每个跟踪器。
对于某些对象跟踪任务,这种实现将是绰绰有余的;但是,为了优化我们的FPS吞吐率,我们应该将对象跟踪器分布在多个进程中。
我们将从本节中的简单实现开始,然后转到下一节中的更快方法。
首先,打开multi_object_tracking_slow.py脚本并插入以下代码:
我们首先在第2-7行导入必要的包和模块。最重要的是,我们将使用dlib和OpenCV。我们还将使用我的imutils软件包中的一些功能,例如每秒帧数的计数器。
要安装imutils,只需在终端中使用pip:pip install --upgrade imutils
那么让我们解析一下我们的命令行参数:
如果您不熟悉终端和命令行参数,请阅读本文。
我们的脚本在运行时处理以下命令行参数:
--prototxt:Caffe“部署”原型文件的路径。
--model:伴随原型文本的模型文件的路径。
--video:输入视频文件的路径。我们将在此视频上使用dlib执行多对象跟踪。
--output:输出视频文件的可选路径。如果未指定路径,则不会将任何视频输出到磁盘。我建议输出到.avi或.mp4文件。
--confidence:对象检测置信度阈值为0.2的可选覆盖。该值表示从对象检测器过滤弱检测的最小概率。
让我们定义这个模型支持的CLASSES列表,以及从磁盘加载我们的模型:
MobileNet SSD预先培训的Caffe模型支持20个类和1个背景类。 CLASSES在第25-28行以列表形式定义。
注意:如果您使用的是“下载”中提供的Caffe模型,请不要修改此列表或类对象的顺序。同样,如果您碰巧加载了不同的模型,则需要定义模型支持的类(顺序很重要)。如果您对我们的物体探测器的工作原理感到好奇,请务必参考这篇文章。
我们只关注今天跑步比赛的“person”类,但您可以轻松修改下面的第95行(在本文后面介绍)来跟踪替代类。
在第32行,我们加载预先训练的物体探测器模型。我们将使用我们预先训练的SSD来检测视频中对象的存在。从那里我们将创建一个dlib对象跟踪器来跟踪每个检测到的对象。
我们还有一些初始化要执行:
在第36行,我们初始化我们的视频流 - 我们将一次从输入视频中读取帧。
随后,在第37行,我们的视频编写器初始化为None。我们将在即将到来的while循环中与视频编写器一起工作。
现在让我们在第41行和第42行初始化我们的跟踪器和标签列表。
最后,我们在第45行开始每秒帧数。我们都准备开始处理我们的视频了:
在第48行,我们开始循环帧,其中第50行实际上抓取帧本身。
在第53和54行进行快速检查以查看我们是否已到达视频文件的末尾并需要停止循环。
预处理在第58和59行进行。首先,将帧调整为600像素宽,保持纵横比。然后,帧被转换为rgb颜色通道排序,用于dlib兼容性(OpenCV的默认值为BGR,dlib的默认值为RGB)。
从那里我们在63-66行实例化视频编写器(如果需要)。要了解有关使用OpenCV将视频写入磁盘的更多信息,请查看我之前的博客文章。
让我们开始对象检测阶段:
为了执行对象跟踪,我们必须首先执行对象检测,或者:
Manually,通过停止视频流并手动选择每个对象的边界框。
Programmatically,使用受过训练的物体检测器来检测物体的存在(这就是我们在这里所做的)。
如果没有对象跟踪器(第70行),那么我们知道我们还没有执行对象检测。
我们通过SSD网络创建并传递blob以检测第72-78行上的对象。要了解cv2.blobFromImage函数,请务必参阅本文中的我的文章。
接下来,我们继续循环检测以找到属于“person”类的对象,因为我们的输入视频是百米短跑:
我们开始在第81行循环检测,我们:
1.过滤掉弱检测(第88行)。
2.确保每次检测都是“person”(第91-96行)。当然,您可以删除此行代码或根据自己的过滤需求对其进行自定义。
现在我们已经在帧中找到了每个“person”,让我们实例化我们的跟踪器并绘制我们的初始边界框+类标签:
要开始跟踪对象,我们:
·计算每个检测到的对象的边界框(第100和101行)。
·实例化边界框坐标并将其传递给跟踪器(第105-107行)。边界框在这里尤为重要。我们需要创建一个dlib。为边界框创建矩形并将其传递给start_track方法。从那里,dlib可以开始跟踪对象。
·最后,我们使用person跟踪器添加到跟踪器列表(第112行)。
因此,在下一个代码块中,我们将处理已经建立跟踪器的情况,我们只需要更新位置。
我们在初始检测步骤中执行了两项额外任务:
·将类标签附加到标签列表(第111行)。如果您正在跟踪多种类型的对象(例如“dog”+“person”),您可能希望知道每个对象的类型。
·绘制每个边界框矩形和对象上方的类标签(第116-119行)。
如果我们的检测列表的长度大于零,我们知道我们处于对象跟踪阶段:
在对象跟踪阶段,我们遍历第125行上的所有跟踪器和相应的标签。
然后我们继续更新每个对象位置(第128-129行)。为了更新位置,我们只需传递rgb图像。
在提取边界框坐标后,我们可以为每个被跟踪对象绘制边界框矩形区域标签(第138-141行)。
帧处理循环中的其余步骤包括写入输出视频(如果需要)并显示结果:
在这里,我们:
·如有必要,将帧写入视频(第144和145行)。
·显示输出帧并捕获按键(第148和149行)。如果按下“q”键(“退出”),我们就会退出循环。
·最后,我们更新每秒帧数信息以进行基准测试(第156行)。
剩下的步骤是在终端中打印FPS吞吐量信息并释放指针:
要结束,我们的fps统计数据被收集并打印(第159-161行),视频编写器被释放(第164和165行),我们关闭所有窗口+释放视频流。
让我们评估准确性和性能。
要继续并运行此脚本,请确保使用此博客文章的“下载”部分下载源代码+示例视频。
从那里,打开一个终端并执行以下命令:
看来我们的多物体跟踪器正在工作!
但正如你所看到的,我们只能获得~13 FPS。
对于某些应用程序,此FPS吞吐率可能已足够 - 但是,如果您需要更快的FPS,我建议您查看下面我们更高效的dlib多对象跟踪器。
其次,要了解跟踪精度并不完美。请参阅下面“改进和建议”部分中的第三个建议,并阅读我关于dlib对象跟踪的第一篇文章以获取更多信息。
快速,高效的dlib多对象跟踪实现
如果您运行上一节中的dlib多对象跟踪脚本并同时打开系统的活动监视器,您会注意到只使用了处理器的一个核心。
为了加速我们的对象跟踪管道,我们可以利用Python的多处理模块,类似于线程模块,但用来生成进程而不是线程。
Utlizing进程使我们的操作系统能够执行更好的进程调度,将进程映射到我们机器上的特定处理器核心(大多数现代操作系统能够有效地调度以并行方式使用大量CPU的进程)。
如果您不熟悉Python的多处理模块,我建议您阅读Sebastian Raschka的这篇精彩介绍。
否则,请继续并打开mutli_object-tracking_fast.py并插入以下代码:
Utlizing进程使我们的操作系统能够执行更好的进程调度,将进程映射到我们机器上的特定处理器核心(大多数现代操作系统能够有效地调度以并行方式使用大量CPU的进程)。
如果您不熟悉Python的多处理模块,我建议您阅读Sebastian Raschka的这篇精彩介绍。
否则,请继续并打开mutli_object-tracking_fast.py并插入以下代码:
我们的包在2-8号线导入。我们正在第3行导入多处理库。
好吧,使用Python Process类来生成一个新进程 - 每个新进程都独立于原始进程。
为了产生这个过程,我们需要提供一个Python可以调用的函数,然后Python将创建一个全新的进程并执行它:
start_tracker的前三个参数包括:
·boxl:我们要跟踪的对象的边界框坐标,可能由某种对象检测器返回,无论是手动还是程序化。
·Labe1]:对象的人类可读标签。
·rgb:我们将用于启动初始dlib对象跟踪器的RGB排序图像。
请记住Python多处理的工作原理-Python将调用此函数,然后创建一个全新的解释器来执行其中的代码。因此,每个start_tracker
衍生过程将独立于其父级。要与Python驱动程序脚本通信,我们需要利用管道或队列。两种类型的对象都是线程/进程安全的,使用锁和信号量完成。
实质上,我们正在创建一个简单的生产者/消费者关系:
1.我们的父进程将生成新帧并将它们添加到特定对象跟踪器的队列中。
2.子进程将使用框架,应用对象跟踪,然后返回更新的边界框坐标。
在此帖子中使用Queueobjects;但是,请记住,您可以使用您希望的管道 - 请务必参考Python多处理文档以获取有关这些对象的更多详细信息。
现在让我们开始一个无限循环,它将在这个过程中运行: