本文主要是翻译人数统计器 。感谢Adrian Rosebrock分享视频人数统计的技术。先上图看效果,再做技术分享。主要分四个部分:基础支持,目标检测与目标追踪,实践人数统计器,视频检验效果。
一、基础支持
需要安装第三方包:NumPy、OpenCV、dlib、imutils。使用anconda命令conda install xxx。
二、目标检测与目标追踪
当我们应用对象检测时,我们确定对象在图像/帧中的位置。对象检测器通常在计算上也比对象跟踪算法更昂贵,因此更慢。物体检测算法的示例包括Haar级联,HOG +线性SVM和基于深度学习的物体检测器,诸如更快的R-CNN,YOLO和单次检测器(SSD)
对象跟踪器将接受对象在图像中的位置的输入(x,y)坐标,并将:
1、为该特定对象分配唯一ID
2、跟踪对象在视频流中移动时,根据帧的各种属性(渐变,光流等)预测下一帧中的新对象位置对象跟踪算法的示例包括MedianFlow,MOSSE,GOTURN,kernalized相关滤波器和判别相关滤波器,仅举几个例子。如果您有兴趣了解OpenCV中内置的对象跟踪算法的更多信息,请务必参考此博客文章。
结合对象检测和对象跟踪
高度精确的对象跟踪器将对象检测和对象跟踪的概念结合到一个算法中,通常分为两个阶段:
1、检测:在检测阶段,我们运行计算上更昂贵的对象跟踪器,以(1)检测新对象是否已进入我们的视图,以及(2)查看是否可以找到在跟踪阶段“丢失”的对象。对于每个检测到的对象,我们使用新的边界框坐标创建或更新对象跟踪器。由于我们的对象检测器在计算上更加昂贵,我们每N帧只运行一次这个阶段。
2、跟踪:当我们不处于“检测”阶段时,我们处于“跟踪”阶段。对于我们检测到的每个对象,我们创建一个对象跟踪器,以便在对象围绕框架移动时跟踪对象。我们的对象跟踪器应该比对象检测器更快,更有效。我们将继续跟踪,直到我们到达第N帧,然后重新运行我们的物体探测器。然后整个过程重复。
这种混合方法的好处是我们可以应用高度精确的物体检测方法,而不会产生很大的计算负担。我们将实施这样的跟踪系统来建立我们的人数统计器。
三、实践人数统计器
项目结构
让我们回顾一下今天博客文章的项目结构。从“下载”部分获取代码后,可以使用tree命令检查目录结构:
归入最重要的两个目录。就是:
1、pyimagesearch /:该模块包含质心跟踪算法。质心跟踪算法在“组合对象跟踪算法”部分中介绍,但代码不是。有关质心跟踪代码(centroidtracker.py)的评论,请参阅本系列的第一篇文章质心算法。
2、mobilenet_ssd /:包含Caffe深度学习模型文件。我们将使用MobileNet单次检测器(SSD),该文章在本篇博文的顶部“用于物体检测的单次检测器”一节中介绍。
今天项目的核心包含在people_counter.py脚本中 - 这就是我们花费大部分时间的地方。我们今天还将审查trackableobject.py脚本。
结合对象跟踪算法
要实现我们的人员计数器,我们将同时使用OpenCV和dlib。我们将OpenCV用于标准计算机视觉/图像处理功能,以及用于人数统计的深度学习对象检测器。
然后我们将使用dlib来实现相关过滤器。我们也可以在这里使用OpenCV;但是,dlib对象跟踪实现对于此项目来说更容易使用。
除了dlib的对象跟踪实现,我们还将使用几周前的质心跟踪实现。查看整个质心跟踪算法超出了本博文的范围,但我在下面简要介绍了一下。
1、我们接受一组边界框并计算它们对应的质心(即边界框的中心)
在上面的图像中,您可以看到我们在算法的初始迭代中有两个要跟踪的对象。
2、我们计算任何新质心(黄色)和现有质心(紫色)之间的欧几里德距离:
质心跟踪算法假设它们之间具有最小欧几里德距离的质心对必须是相同的对象ID。
在上面的示例图像中,我们有两个现有的质心(紫色)和三个新的质心(黄色),这意味着已经检测到一个新的对象(因为还有一个新的质心与旧的质心)。
然后箭头表示计算所有紫色质心和所有黄色质心之间的欧几里德距离。
一旦我们得到欧几里德距离,我们就会尝试在步骤3中关联对象ID:
你可以看到我们的质心跟踪器已选择关联质心,以最大限度地减少各自的欧几里德距离。
但是左下角的那个点怎么样?它没有任何关联 - 我们该怎么办?
4、要回答这个问题,我们需要执行步骤#4,注册新对象
注册只是意味着我们将新对象添加到我们的跟踪对象列表中:
1、为其分配新的对象ID
2、存储新对象的边界框坐标的质心
如果一个对象丢失或离开了视野,我们可以简单地取消注册该对象(步骤#5)。当对象“丢失”或“不再可见”时,您究竟如何处理实际上取决于您的确切应用程序,但对于我们的人员计数器,当他们无法与任何现有人员对象匹配40个连续帧时,我们将取消注册人员ID 。同样,这只是质心跟踪算法的简要概述。有关更详细的评论,包括用于实现质心跟踪的源代码的说明,请务必参考这篇文章。
创建“可跟踪对象”
为了跟踪和计算视频流中的对象,我们需要一种简单的方法来存储有关对象本身的信息,包括:
1、对象ID
2、以前的质心(所以我们可以很容易地计算物体移动的方向)
3、该对象是否已被计算
为了实现所有这些目标,我们可以定义TrackableObject的实例 - 打开trackableobject.py文件并插入以下代码:
TrackableObject构造函数接受objectID + centroid并存储它们。 centroids变量是一个列表,因为它将包含一个对象的质心位置历史记录。
构造函数还将count初始化为False,表示该对象尚未计数。
使用OpenCV + Python实现我们的人员计数器
有了我们所有支持的Python帮助工具和类,我们现在可以构建我们的OpenCV人员计数器了。
打开你的people_counter.py文件并插入以下代码:
我们首先导入必要的包:
从pyimagesearch模块,我们导入自定义的CentroidTracker和TrackableObject类。
1、imutils.video中的VideoStream和FPS模块将帮助我们使用网络摄像头并计算估计的每秒帧数(FPS)吞吐率。
2、我们需要为其OpenCV便利功能提供imutils。
3、dlib库将用于其相关跟踪器实现。
4、OpenCV将用于深度神经网络推理,打开视频文件,编写视频文件以及向屏幕显示输出帧。
现在所有的工具都在我们手里,让我们解析命令行参数:
我们有六个命令行参数,允许我们在运行时从终端将信息传递给我们的人员计数器脚本:
1、--prototxt:Caffe“部署”原型文件的路径。
2、--model:Caffe预训练CNN模型的路径。
3、--input:可选的输入视频文件路径。如果未指定路径,则将使用您的网络摄像头。
4、--output:可选的输出视频路径。如果未指定路径,则不会录制视频。
5、--confidence:默认值为0.4,这是有助于滤除弱检测的最小概率阈值。
6、--skip-frames:在跟踪对象上再次运行DNN检测器之前要跳过的帧数。请记住,对象检测在计算上很昂贵,但它确实有助于我们的跟踪器重新评估帧中的对象。默认情况下,我们使用OpenCV DNN模块和CNN单发探测器模型在检测对象之间跳过30帧。
现在我们的脚本可以在运行时动态处理命令行参数,让我们准备我们的SSD:
首先,我们将初始化CLASSES - 我们的SSD支持的类列表。如果您使用的是“下载”中提供的模型,则不应更改此列表。我们只对“人”类感兴趣,但你也可以计算其他移动物体(但是,如果你的“盆栽植物”,“沙发”或“电视监视器”长出腿并开始移动,你应该跑出你的房子尖叫而不是计数它们!?)。
在38行上,我们加载了用于检测物体的预训练的MobileNet SSD(但同样,我们只对检测和跟踪人员感兴趣,而不是任何其他类别)。要了解有关MobileNet和SSD的更多信息,请参阅我之前的博客文章。
从那里我们可以初始化我们的视频流:
首先,我们处理我们使用网络摄像头视频流的情况(第41-44行)。否则,我们将从视频文件中捕获帧(第47-49行)。
在开始循环帧之前,我们仍然需要执行一些初始化:
其余的初始化包括:
1、写:写视频。如果我们要写视频,我们稍后会实例化这个对象。
2、W和H:我们的框架尺寸。我们需要将它们插入到cv2.VideoWriter中。
3、ct:我们的CentroidTracker。有关CentroidTracker实施的详细信息,请务必参考几周前的博客文章。
4、跟踪器:用于存储dlib关联跟踪器的列表。要了解dlib相关性跟踪,请继续关注下周的帖子。
5、trackableObjects:将objectID映射到TrackableObject的字典。
6、totalFrames:处理的帧总数。
7、totalDown和totalUp:向下或向上移动的对象/人的总数。这些变量衡量脚本的实际“人数统计”结果。
8、fps:我们的每秒帧数估算器用于基准测试。
注意:如果您在下面的while循环中迷了,您应该参考这个重要变量的项目符号列表。
现在我们已经完成了所有的初始化,让我们循环传入的帧:
我们开始在第76行循环。在循环的顶部我们抓住下一帧(第79和80行)。如果我们已经到达视频的末尾,我们将突破循环(第84和85行)。预处理帧发生在第90和91行。这包括调整大小和交换颜色通道,因为dlib需要rgb图像。
我们抓取视频编写器的帧尺寸(第94和95行)。
如果通过命令行参数提供输出路径(第99-102行),我们将从那里实例化视频编写器。要了解有关将视频写入磁盘的更多信息,请务必参阅此文章。
现在让我们检测人使用SSD:
我们在第107行初始化状态为“等待”。可能的状态状态包括:
1、等待:在这种状态下,我们正在等待人们被发现和跟踪。
2、检测:我们正在积极地使用MobileNet SSD检测人员。
3、跟踪:在框架中跟踪人员,我们计算totalUp和totalDown。
我们的rects列表将通过检测或跟踪填充。我们继续并在第108行初始化rects。
重要的是要理解深度学习对象检测器在计算上非常昂贵,特别是如果您在CPU上运行它们。
为了避免在每一帧上运行我们的对象检测器,并加快我们的跟踪管道,我们将跳过每N帧(由命令行参数设置--skip-frames,其中30是默认值)。我们只需每N帧就可以使用SSD进行物体检测。否则,我们将只是跟踪中间的移动物体。
使用第112行的模运算符,我们确保每N帧只执行if语句中的代码。
假设我们已经登陆了多个skip_frames,我们将状态更新为“Detecting”(第114行)。
然后我们初始化新的跟踪器列表(第115行)。
接下来,我们将通过对象检测进行推理。我们首先从图像中创建一个blob,然后将blob传递到网中以获得检测(第119-121行)。
现在我们将遍历每个检测,希望找到属于“person”类的对象:
在第124行的检测中循环,我们继续获取置信度(第127行)并过滤掉弱结果+那些不属于“人”类的行(第131-138行)。
现在我们可以为每个人计算一个边界框并开始相关性跟踪:
计算我们的边界框发生在第142和143行。
然后我们在148行实例化我们的dlib相关跟踪器,然后将对象的边界框坐标传递给dlib.rectangle,将结果存储为rect(第149行)。
随后,我们开始在150行跟踪,并将跟踪器附加到154行的跟踪器列表中。
这是我们每N个跳帧所做的所有操作的包装!
让我们来处理在else块中进行跟踪的典型操作:
大多数时候,我们没有重新设置跳帧skip-frame功能。在此期间,我们将利用我们的跟踪器来跟踪我们的对象,而不是应用检测。
我们开始在160行的可用跟踪器上循环。
我们继续将状态更新为“跟踪”(第163行)并抓住对象位置(第166和167行)。
从那里我们提取位置坐标(第170-173行),然后填写我们的rects列表中的信息。
现在让我们绘制一条水平可视化线(人们必须交叉才能被跟踪)并使用质心跟踪器来更新我们的对象质心:
在第181行,我们绘制了水平线,我们将用它来形象化人们“穿越” - 一旦人们越过这条线,我们将增加我们各自的柜台
然后在第185行,我们利用CentroidTracker实例来接受rects列表,无论它们是通过对象检测还是对象跟踪生成的。我们的质心跟踪器将对象ID与对象位置相关联。
在下一个块中,我们将检查一个人在框架中向上或向下移动的逻辑:
我们首先循环遍历对象ID的更新的边界框坐标(第188行)。
在第191行,我们尝试为当前objectID获取TrackableObject。
如果objectID不存在TrackableObject,我们创建一个(第194和195行)。
否则,已经存在TrackableObject,因此我们需要确定对象(人)是向上还是向下移动。
为此,我们获取给定对象的所有先前质心位置的y坐标值(第204行)。然后我们通过获取当前质心位置和所有先前质心位置的平均值之间的差来计算方向(线205)。
我们采用均值的原因是为了确保我们的方向跟踪更加稳定。如果我们只为那个人存储了前一个质心位置,那么我们就会对错误的方向计数进行开放。请记住,对象检测和对象跟踪算法并不“神奇” - 有时它们会预测可能略微偏离您所期望的边界框;因此,通过取平均值,我们可以使我们的人更准确。
如果TrackableObject尚未计算(第209行),我们需要确定它是否已准备好进行计数(第213-222行),方法是:
检查方向是否为负(表示物体向上移动)并且质心位于中心线上方。在这种情况下,我们增加totalUp。
或者检查方向是否为正(表示物体向下移动)并且质心低于中心线。如果这是真的,我们增加totalDown。
最后,我们将TrackableObject存储在trackableObjects字典中(第225行),这样我们就可以在捕获下一帧时抓取并更新它。
我们在基础扩展!
接下来的三个代码块处理:
1、Display :显示(绘制和书写文本到框架)
2、将帧写入磁盘上的视频文件(如果存在--output命令行参数)
3、捕捉按键
4、清理
首先,我们将在框架上绘制一些信息为可视化:
在这里,我们在框架上覆盖以下数据:
1、ObjectID:每个对象的数字标识符。
2、质心:物体的中心将由一个“点”表示,该点通过填充圆形而形成。
3、info:包括totalUp,totalDown和status
有关绘图操作的评论,请务必参阅此博客文章。
然后我们将帧写入视频文件(如果需要)并处理按键:
在这个块中我们:
1、Write the frame,如有必要,将帧写入输出视频文件(第249行和第250行)
2、显示框架和手柄按键(第253-258行)。如果按下“q”,我们就会跳出帧处理循环。
3、更新我们的fps计数器(第263行)
我们没有太多的混乱,但现在是时候清理:
为了完成脚本,我们向终端显示FPS信息,释放所有指针,并关闭所有打开的窗口。
之后只有283行代码,我们现在完成了吗?
四、视频检验效果
要查看我们的OpenCV人员计数器,请确保使用此博客文章的“下载”部分下载源代码和示例视频。
从那里,打开一个终端并执行以下命令:
在这里,您可以看到我们的人员计数器正在计算以下人数:
1、正在进入百货商店(dowm)
2、和离开的人数(up)
在第一个视频结束时,你会看到有7个人进入,3个人离开。
此外,检查终端输出,您将看到我们的人员计数器能够实时运行,从而获得34 FPS。尽管我们正在使用深度学习对象检测器来进行更准确的人物检测。
1、通过我们的两阶段流程,我们可以实现34 FPS的全部费率:
2、每30帧检测一次
然后在其间的所有帧中应用更快,更有效的对象跟踪算法。使用OpenCV计算人数的另一个例子如下:
我在下面添加了一个简短的GIF,让您了解算法的工作原理:
这次有2人进入百货商店,14人已经离开。
您可以看到这个系统对于对流量分析感兴趣的商店所有者有多大用处。
使用OpenCV计算人流量的相同类型的系统可用于计算OpenCV的汽车流量,我希望在未来的博客文章中涵盖该主题。