基于OpenCV目标跟踪实现人员计数器

2022-07-16,,,,

在本教程中,您将学习如何使用 opencv 和 python 构建人员计数器。使用 opencv,我们将实时计算进或出百货商店的人数。

在今天博客文章的第一部分,我们将讨论如何利用两者来创建更准确的人员计数器。之后,我们将查看项目的目录结构,然后实施整个人员计数项目。最后,我们将检查将 opencv 的人数统计应用到实际视频中的结果。

1.了解对象检测与对象跟踪

在继续本教程的其余部分之前,您必须了解对象检测和对象跟踪之间的根本区别。

当我们应用对象检测时,我们是在确定一个对象在图像/帧中的位置。与目标跟踪算法相比,目标检测器通常在计算上更昂贵,因此也更慢。目标检测算法的例子包括haar级联、hog +线性支持向量机(hog + linear svm)和基于深度学习的目标检测器,如faster r-cnn、yolo和single shot检测器(ssd)。

另一方面,对象跟踪器将接受对象在图像中位置的输入 (x, y) 坐标,并将:

1.为该特定对象分配唯一 id

2.在对象围绕视频流移动时跟踪对象,根据帧的各种属性(梯度、光流等)预测下一帧中的新对象位置

对象跟踪算法的示例包括 medianflow、mosse、goturn、核化相关滤波器和判别相关滤波器等。

2.结合对象检测和对象跟踪

高精度目标跟踪器将目标检测和目标跟踪的概念结合到一个算法中,通常分为两个阶段:

1.阶段1 检测:在检测阶段,我们正在运行计算成本更高的对象跟踪器,以 (1) 检测是否有新对象进入我们的视野,以及 (2) 看看我们是否可以找到在跟踪阶段“丢失”的对象。对于每个检测到的对象,我们使用新的边界框坐标创建或更新对象跟踪器。由于我们的目标检测器的计算成本更高,我们每 n 帧只运行一次此阶段。

2.阶段2 跟踪:当我们不处于“检测”阶段时,我们处于“跟踪”阶段。对于我们检测到的每个对象,我们创建一个对象跟踪器来跟踪对象在框架周围的移动。我们的目标跟踪器应该比目标检测器更快、更高效。我们将继续跟踪,直到我们到达第 n 帧,然后重新运行我们的目标检测器。然后重复整个过程。

这种混合方法的好处是我们可以应用高度准确的对象检测方法,而无需太多的计算负担。我们将实施这样一个跟踪系统来建立我们的人员计数器。

3.项目结构

让我们回顾一下今天博客文章的项目结构。获取代码后,您可以使用 tree 命令检查目录结构:

最重要的两个目录:

1.pyimagesearch/:该模块包含质心跟踪算法。 “组合对象跟踪算法”部分介绍了质心跟踪算法。

2.mobilenet_ssd/:包含 caffe 深度学习模型文件。

今天项目的核心包含在 people_counter.py 脚本中——这是我们将花费大部分时间的地方。今天我们还将回顾 trackableobject.py 脚本。

4.结合对象跟踪算法

为了实现我们的人员计数器,我们将同时使用 opencv 和 dlib。我们将 opencv 用于标准的计算机视觉/图像处理功能,以及用于人数统计的深度学习对象检测器。

然后我们将使用 dlib 来实现相关过滤器。我们也可以在这里使用 opencv;但是,对于这个项目,dlib 对象跟踪实现更容易使用。

除了 dlib 的对象跟踪实现,我们还将使用质心跟踪实现。回顾整个质心跟踪算法超出了这篇博文的范围,但我在下面提供了一个简短的概述。

在步骤#1,我们接受一组边界框并计算它们对应的质心(即边界框的中心):

要使用 python 通过质心脚本构建简单的对象跟踪,第一步是接受边界框坐标并使用它们来计算质心。

边界框本身可以由以下任一方式提供:

1.目标检测器(如 hog + linear svm、faster r-cnn、ssds 等)

2.或对象跟踪器(例如相关过滤器)

在上图中,您可以看到我们在算法的初始迭代中有两个对象要跟踪。

在步骤#2中,我们计算任何新质心(黄色)和现有质心(紫色)之间的欧几里得距离:

此图像中存在三个对象。我们需要计算每对原始质心(紫色)和新质心(黄色)之间的欧几里得距离。

质心跟踪算法假设它们之间具有最小欧几里德距离的质心对必须是相同的对象 id。

在上面的示例图像中,我们有两个现有的质心(紫色)和三个新的质心(黄色),这意味着已经检测到一个新对象(因为与旧质心相比,还有一个新质心)。

然后箭头表示计算所有紫色质心和所有黄色质心之间的欧几里得距离。一旦我们有了欧几里得距离,我们就会在步骤#3 中尝试关联对象 id:

您可以看到我们的质心跟踪器已选择关联使它们各自的欧几里得距离最小化的质心。但是左下角的点呢?它没有与任何东西相关联——我们该怎么办? 要回答这个问题,我们需要执行步骤#4,注册新对象:

注册意味着我们通过以下方式将新对象添加到我们的跟踪对象列表中:

1.为其分配一个新的对象 id

2.存储新对象的边界框坐标的质心

如果对象丢失或离开视野,我们可以简单地取消注册对象(步骤#5)。

5.创建可追踪对象

为了跟踪和计算视频流中的对象,我们需要一种简单的方法来存储有关对象本身的信息,包括:

  • 对象id
  • 以前的质心(所以我们可以很容易地计算出物体移动的方向)
  • 对象是否已被计数

为了实现所有这些目标,我们可以定义一个 trackableobject 实例——打开 trackableobject.py 文件并插入以下代码:

trackableobject 构造函数接受 objectid + centroid 并存储它们。 centroids 变量是一个列表,因为它将包含对象的质心位置历史记录。 构造函数还将 counted 初始化为 false ,表示该对象还没有被计数。

6.使用 opencv + python 实现我们的人员计数器

我们首先导入必要的包:

  • 从 pyimagesearch 模块,我们导入自定义的 centroidtracker 和 trackableobject 类。
  • imutils.video 中的 videostream 和 fps 模块将帮助我们使用网络摄像头并计算估计的每秒帧数 (fps) 吞吐率。
  • 我们需要 imutils 的 opencv 便利功能。
  • dlib 库将用于其相关跟踪器实现。
  • opencv 将用于深度神经网络推理、打开视频文件、写入视频文件以及在我们的屏幕上显示输出帧。

现在所有工具都触手可及,让我们解析命令行参数:

我们有六个命令行参数,它们允许我们在运行时从终端将信息传递给我们的人员计数器脚本:

  • --prototxt :caffe 部署 prototxt 文件的路径。
  • --model :caffe 预训练 cnn 模型的路径。
  • --input : 可选的输入视频文件路径。如果未指定路径,将使用您的网络摄像头。
  • --output :可选的输出视频路径。如果未指定路径,则不会录制视频。
  • --confidence :默认值为 0.4 ,这是有助于过滤掉弱检测的最小概率阈值。
  • --skip-frames :在跟踪对象上再次运行我们的 dnn 检测器之前要跳过的帧数。请记住,对象检测的计算成本很高,但它确实有助于我们的跟踪器重新评估帧中的

对象。默认情况下,我们在使用 opencv dnn 模块和我们的 cnn 单次检测器模型检测对象之间跳过 30 帧。

现在我们的脚本可以在运行时动态处理命令行参数,让我们准备我们的 ssd:

首先,我们将初始化 classes——ssd 支持的类列表。我们只对“人”类感兴趣,但您也可以计算其他移动对象。

我们加载用于检测对象的预训练 mobilenet ssd(但同样,我们只对检测和跟踪人感兴趣,而不是任何其他类)。

我们可以初始化我们的视频流:

首先,我们处理使用网络摄像头视频流的情况。否则,我们将从视频文件中捕获帧。在开始循环帧之前,我们还有一些初始化要执行:

其余的初始化包括:

  • writer:我们的视频写入器。如果我们正在写入视频,我们稍后会实例化这个对象。
  • w 和 h:我们的帧尺寸。我们需要将这些插入到 cv2.videowriter 中。
  • ct:我们的 centroidtracker。
  • trackers :存储 dlib 相关跟踪器的列表。
  • trackableobjects :将 objectid 映射到 trackableobject 的字典。
  • totalframes :处理的帧总数。
  • totaldown 和 totalup :向下或向上移动的对象/人的总数。
  • fps :我们用于基准测试的每秒帧数估计器。

现在我们所有的初始化都处理好了,让我们循环传入的帧:

我们开始循环。在循环的顶部,我们抓取下一帧。如果我们已经到达视频的结尾,我们将跳出循环。

帧进行预处理。这包括调整大小和交换颜色通道,因为 dlib 需要 rgb 图像。我们为视频编写器获取帧的尺寸。 如果通过命令行参数提供了输出路径,我们将从那里实例化视频编写器。

现在让我们使用 ssd检测人:

我们将状态初始化为waiting。可能的状态包括:

  • waiting:在这种状态下,我们正在等待检测和跟踪人员。
  • detecting:我们正在使用 mobilenet ssd 检测人员。
  • tracking:人们在帧中被跟踪,我们正在计算 totalup 和 totaldown 。

我们的 rects 列表将通过检测或跟踪来填充。我们继续初始化rects 。

重要的是要了解深度学习对象检测器的计算成本非常高,尤其是当您在 cpu 上运行它们时。

为了避免在每一帧上运行我们的目标检测器,并加快我们的跟踪管道,我们将跳过 n 帧(由命令行参数设置 --skip-frames ,其中 30 是默认值)。只有每 n 帧,我们才会使用 ssd 进行对象检测。否则,我们将只是跟踪中间的移动对象。

使用模运算符,我们确保每 n 帧执行一次 if 语句中的代码。 进入if语句后,我们会将状态更新为detecting。 然后我们初始化新的跟踪器列表。

接下来,我们将通过对象检测进行推理。我们首先从图像中创建一个 blob,然后将该 blob 通过网络传递以获得检测。 现在我们将遍历每个检测,希望找到属于person类的对象:

循环检测,我们继续获取置信度并过滤掉那些不属于人类的结果。

现在我们可以为每个人计算一个边界框并开始相关性跟踪:

计算我们的box。 然后实例化我们的 dlib 相关跟踪器,然后将对象的边界框坐标传递给 dlib.rectangle,将结果存储为 rect。 随后,我们开始跟踪,并将跟踪器附加到跟踪器列表中。 这是我们每 n 个跳帧执行的所有操作的封装! 让我们处理在 else 块中进行跟踪的典型操作:

大多数时候,并没有发生在跳帧倍数上。在此期间,我们将利用跟踪器来跟踪对象,而不是应用检测。 我们开始遍历可用跟踪器。 我们继续将状态更新为tracking并获取对象位置。 我们提取位置坐标,然后在我们的 rects 列表中填充信息。 现在让我们画一条水平可视化线(人们必须穿过它才能被跟踪)并使用质心跟踪器来更新我们的对象质心:

我们画一条水平线,我们将用它来可视化人们“越过”——一旦人们越过这条线,我们将增加各自的计数器 然后,我们利用 centroidtracker 实例化来接受 rects 列表,无论它们是通过对象检测还是对象跟踪生成的。我们的质心跟踪器会将对象 id 与对象位置相关联。 在下一个代码块中,我们将回顾一个人在帧中向上或向下移动的逻辑:

我们首先遍历更新后的对象id的边界框坐标。我们尝试为当前的objectid获取trackableobject。如果objectid的trackableobject不存在,我们就创建一个。否则,已经存在一个 trackableobject ,所以我们需要弄清楚对象(人)是向上还是向下移动。

为此,我们获取给定对象之前所有质心位置的y坐标值。然后,通过取当前质心位置与之前所有质心位置的平均值之间的差来计算方向。

我们取均值的原因是为了确保我们的方向跟踪更稳定。如果我们只存储这个人之前的质心位置,我们就有可能出现错误的方向计数。记住,目标检测和目标跟踪算法不是“魔术”——有时它们会预测出可能稍微偏离你预期的边界盒;因此,通过取均值,我们可以让我们的人计算得更准确。

如果trackableobject还没有被计数,我们需要确定它是否已经准备好被计数,通过:

1.检查direction是否为负(表示对象向上移动)并且质心在中心线上方。在这种情况下,我们增加 totalup。

2.或者检查direction是否为正(表示物体正在向下移动)且质心在中心线以下。如果这是真的,我们增加totaldown。

最后,我们将trackableobject存储在trackableobjects字典中,这样我们就可以在捕获下一帧时获取并更新它。

接下来的三个代码块处理:

  • 显示(绘图并向帧写入文本)
  • 将帧写入磁盘上的视频文件(如果存在--output命令行参数)
  • 捕获按键
  • 清理

首先,我们将在框架上绘制一些信息以进行可视化:

在这里,我们在帧上覆盖以下数据:

  • objectid :每个对象的id。
  • centroid :对象的中心将由一个点表示,该点是通过填充一个圆圈而创建的。
  • info : 包括 totalup 、 totaldown 和 status

然后我们将把帧写入视频文件(如果需要的话)并处理按键:

在这个代码块中我们:

  • 如果需要,将帧写入输出视频文件
  • 显示帧并处理按键。如果q被按下,我们将跳出帧处理循环。
  • 更新我们的fps计数器

现在是时候清理了:

为了完成脚本,我们向终端显示 fps 信息,释放所有指针,并关闭所有打开的窗口。

7.完整代码

people_counter.py

centroidtracker.py

(1)质心跟踪器是最可靠的跟踪器之一。

(2)为了简单起见,质心跟踪器计算包围框的质心。

(3)也就是说,边界框是图像中对象的(x, y)坐标。

(4)一旦我们的ssd获得了坐标,跟踪器就会计算包围框的质心(中心)。换句话说,就是物体的中心。

(5)然后为每一个被检测到的特定对象分配一个唯一的id,用于跟踪帧序列。

trackableobject.py

8.运行结果

打开终端,执行以下命令:

我们的人员计数正在计算以下人数:

  • 正进入百货商店(下)
  • 离开的人数(上)

在第一个视频的最后,你会看到有7个人进入,3个人离开。

此外,检查终端输出,你会发现我们的人计数器能够实时运行,达到34帧每秒。尽管我们正在使用深度学习对象检测器来更准确地检测人。

我们的 34 fps 帧率是通过我们的两个阶段过程实现的: 每 30 帧检测一次人 然后在其间的所有帧中应用更快、更有效的对象跟踪算法。

9.改进我们的人员计数器应用程序

为了构建我们的 opencv 人员计数器,我们使用了 dlib 的相关性跟踪器。此方法易于使用,并且只需要很少的代码。

然而,我们的实现有点低效——为了跟踪多个对象,我们需要创建关联跟踪器对象的多个实例。然后当我们需要在后续帧中计算对象的位置时,我们需要遍历所有 n 个对象跟踪器并获取更新的位置。

所有这些计算都将发生在我们脚本的主执行线程中,从而降低了我们的 fps 速率。

因此,提高性能的一种简单方法是使用dlib的多对象跟踪器,以使我们的 fps 速率提高 45%! 注意:opencv 也实现了多对象跟踪,但不是多进程(至少在撰写本文时)。 opencv 的多对象方法当然更容易使用,但如果没有多处理能力,在这种情况下它并没有多大帮助。

最后,为了获得更高的跟踪精度(但在没有快速 gpu 的情况下会牺牲速度),您可以研究基于深度学习的对象跟踪器,例如 deep sort。

bonus

前几天在github上看见一个改进版:

主要目标是将项目用作业务视角,随时可以扩展。

用例:实时统计商店/大楼/购物中心等的人数。

如果人数超过上限就会向工作人员发出警报。

自动化特性并优化实时流以获得更好的性能(使用线程)。

作为一项措施,以进行足迹分析,并在某种程度上应对covid-19。

到此这篇关于基于opencv目标跟踪实现人员计数器的文章就介绍到这了,更多相关opencv人员计数内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

《基于OpenCV目标跟踪实现人员计数器.doc》

下载本文的Word格式文档,以方便收藏与打印。