使用OpenCV进行简单的对象跟踪

对象跟踪是以下过程:

1、获取一组初始对象检测(例如边界框坐标的输入集)

2、为每个初始检测创建唯一ID

3、然后在视频中的帧周围移动时跟踪每个对象,保持唯一ID的分配

此外,对象跟踪允许我们为每个被跟踪对象应用唯一ID,使我们可以计算视频中的唯一对象。对象跟踪对于构建人员计数器至关重要。

理想的对象跟踪算法将:

仅需要对象检测阶段一次(即,最初检测到对象时);将非常快 - 比运行实际物体探测器本身快得多;

能够处理被跟踪对象“消失”或移动到视频帧边界之外的情况;对强大的闭合能力;能够拾取它在帧之间“丢失”的对象;

对于任何计算机视觉或图像处理算法而言,这是一个很高的要求,我们可以使用各种技巧来帮助改进我们的对象跟踪器。

但在我们构建这样一个强大的方法之前,我们首先需要研究对象跟踪的基础知识。

在今天的博客文章中,您将学习如何使用OpenCV实现质心跟踪,OpenCV是一种易于理解且高效的跟踪算法。

在此对象跟踪系列的未来帖子中,我将开始研究更高级的基于内核和基于关联的跟踪算法。要了解如何开始使用OpenCV构建第一个对象跟踪,请继续阅读!

使用OpenCV进行简单的对象跟踪

我们将使用OpenCV库实现一个简单的对象跟踪算法。该对象跟踪算法被称为质心跟踪,因为它依赖于(1)现有对象质心(即,质心跟踪器之前已经看到的对象)和(2)视频中后续帧之间的新对象质心之间的欧几里德距离。

我们将在后续部分中更深入地回顾质心算法。从那里我们将实现一个Python类来包含我们的质心跟踪算法,然后创建一个Python脚本来实际运行对象跟踪器并将其应用于输入视频。

最后,我们将运行我们的对象跟踪器并检查结果,同时注意算法的正面和缺点。

质心跟踪算法

质心跟踪算法是一个多步骤的过程。我们将审核本节中的每个跟踪步骤。

一、接受边界框坐标并计算质心


要使用质心跟踪构建简单的对象跟踪算法,第一步是接受来自对象检测器的边界框坐标并使用它们来计算质心。

质心跟踪算法假设我们在每个帧中为每个检测到的对象传入一组边界框(x,y) 坐标。

这些边界框可以由您想要的任何类型的物体探测器(color thresholding + contour extraction, Haar cascades, HOG + Linear SVM, SSDs, Faster R-CNNs, etc.)生成,只要它们是针对每个帧计算的在该视频里。

一旦我们有了边界框坐标,我们就必须计算“质心”,或者更简单地说,计算边界框的中心(x,y) 坐标。上面的图1演示了接受一组边界框坐标并计算质心。

由于这些是我们的算法中提供的第一组初始边界框,因此我们将为它们分配唯一ID。

计算新边界框与现有对象界框之间的欧几里德距离


此图像中存在三个对象,用于使用Python和OpenCV进行简单的对象跟踪。我们需要计算每对原始质心(红色)和新质心(绿色)之间的欧几里德距离。

对于视频流中的每个后续帧,我们应用计算对象质心的步骤#1;但是,我们首先需要确定是否可以将新对象质心(黄色)与旧对象质心(紫色)相关联,而不是为每个检测到的对象分配新的唯一ID(这会破坏对象跟踪的目的)。为了完成这个过程,我们计算每对现有对象质心和输入对象质心之间的欧几里德距离(用绿色箭头突出显示)。

从图2中可以看出,我们这次在图像中检测到了三个对象。两对靠近的是两个现有对象。

然后我们计算每对原始质心(黄色)和新质心(紫色)之间的欧几里德距离。但是,我们如何使用这些点之间的欧几里德距离来实际匹配它们并将它们联系起来?

答案在第3步。

步骤#3:更新(x,y) 现有对象的坐标


我们的简单质心对象跟踪方法具有最小化对象距离的关联对象。我们如何处理左下方的物体呢?

质心跟踪算法的主要假设是给定对象可能在后续帧之间移动,但帧Ft和Ft+1的质心之间的距离将小于对象之间的所有其他距离。

因此,如果我们选择将质心与后续帧之间的最小距离相关联,我们就可以构建我们的对象跟踪器。

在图3中,您可以看到我们的质心跟踪器算法如何选择关联质心,以最小化它们各自的欧几里德距离。

但左下角的孤点呢?它没有任何关联 - 我们用它做什么?

步骤#4:注册新对象


在使用Python和OpenCV示例的对象跟踪中,我们有一个与现有对象不匹配的新对象,因此它被注册为对象ID#3。

如果有比跟踪的现有对象更多的输入检测对象,我们需要注册新对象。“注册”只是意味着我们将新对象添加到我们的跟踪对象列表中:

1、为其分配新的对象ID

2、存储该对象的边界框坐标的质心

然后我们可以回到步骤#2,为视频流中的每一帧重复步骤流程。

图4演示了使用最小欧几里德距离关联现有对象ID然后注册新对象的过程。

步骤#5:取消注册旧对象

任何合理的对象跟踪算法都需要能够处理对象丢失,消失或离开视野的时间。

确切地说,如何处理这些情况实际上取决于要部署对象跟踪器的位置,但是对于此实现,我们将在旧对象无法与任何现有对象匹配时在N个后续帧之后取消注册。

对象跟踪项目结构

要在终端中查看今天的项目结构,只需使用tree命令:

centroidtracker.py包含CentroidTracker类的。

centroidTracker类是object_tracker.py驱动程序脚本中使用的重要组件。

其余的.prototxt和.caffemodel文件是OpenCV深度学习面部检测器的一部分。它们是今天的人脸检测+跟踪方法所必需的,但您可以轻松使用其他形式的检测(稍后会详细介绍)。

在继续之前,请确保已安装NumPy,SciPy和imutils:

除了安装OpenCV 3.3+之外。如果你按照我的一个OpenCV安装教程,请确保替换wget命令的尾端至少获取OpenCV 3.3(并更新CMake命令中的路径)。您需要3.3+才能确保拥有DNN模块。

使用OpenCV实现质心跟踪

在我们将对象跟踪应用于输入视频流之前,我们首先需要实现质心跟踪算法。在您消化此质心跟踪器脚本时,请记住上面的步骤1-5并根据需要查看步骤。

正如您将看到的,将步骤转换为代码需要相当多的思考,而当我们执行所有步骤时,由于我们各种数据结构和代码构造的性质,它们不是线性的。

建议:阅读上面的步骤;阅读质心跟踪器的代码说明;最后再次阅读上述步骤。

这个过程将带来一切完整的循环,让你头脑围绕算法。

一旦你确定你理解了质心跟踪算法中的步骤,打开pyimagesearch模块中的centroidtracker.py并让我们查看代码:

在第2-4行,我们导入我们所需的包和模块 - distance,OrderedDict和numpy。

我们的CentroidTracker类在第6行定义。构造函数接受一个参数,给定对象必须丢失/消失的最大连续帧数,直到我们从跟踪器中删除它(第7行)。

我们的构造函数构建了四个类变量:

1、nextObjectID:用于为每个对象分配唯一ID的计数器(第12行)。在对象离开帧并且没有返回maxDisappeared帧的情况下,将分配新的(下一个)对象ID。

2、objects :字典使用对象ID作为键;质心(x,y) 坐标作为值(第13行)。

3、disappeared:保持特定对象ID(键)的连续帧数(值)已被标记为“丢失”(第14行)。

4、maxDisappeared:在我们取消注册对象之前,允许将对象标记为“丢失/消失”的连续帧数。

让我们定义一个注册方法,它负责向跟踪器添加新对象:

register方法在第21行定义。它接受质心,然后使用下一个可用的对象ID将其添加到对象字典中。

在disappeared的字典中,对象消失的次数被初始化为0(第25行)。

最后,我们递增nextObjectID,以便在新对象进入视图时,它将与唯一ID相关联(第26行)。

与我们的register方法类似,我们还需要一个deregister方法:

就像我们可以向跟踪器添加新对象一样,我们还需要能够从输入帧本身中删除丢失或消失的旧对象。

deregister方法在第28行定义。它只是分别删除objectID在objects和disappeared的字典中(第31和32行)。

我们的质心跟踪器实现的核心在于update方法:

在第34行定义的更新方法接受边界框矩形的列表,可能来自对象检测器(Haar cascade, HOG + Linear SVM, SSD, Faster R-CNN等)。 rects参数的格式假定为具有以下结构的元组:(startX,startY,endX,endY)。

如果没有检测,我们将遍历所有objectIDs并递增它们消失的计数(第37-41行)。我们还将检查是否已达到给定对象被标记为缺失的最大连续帧数。如果是这种情况,我们需要将其从跟踪系统中删除(第46和47行)。由于没有更新的跟踪信息,我们继续在51号线提前返回。

否则,我们在更新方法中的下七个代码块上要做很多工作:

在第54行,我们将初始化一个NumPy数组来存储每个rect的质心。

然后,我们遍历边界框矩形(第57行)并计算质心并将其存储在inputCentroids列表中(第59-61行)。

如果我们当前没有跟踪的对象,我们将注册每个新对象:

否则,我们需要根据最小化它们之间的欧几里德距离的质心位置更新任何现有对象(x,y) 坐标:

对现有被跟踪对象的更新从第72行的else开始。目标是跟踪对象并保持正确的object IDs - 这个过程是通过计算所有objectCentroids和inputCentroids之间的欧几里德距离,然后关联来完成的。最小化欧几里德距离的object IDs。

在第72行开始的else块内,我们将:

1、获取objectIDs和objectCentroid值(第74和75行)。

2、计算每对现有对象质心和新输入质心之间的距离(第81行)。距离图D的输出NumPy数组形状将是((# of object centroids, # of input centroids)。

3、要执行匹配,我们必须(1)找到每行中的最小值,以及(2)根据最小值对行索引进行排序(第88行)。我们对列执行非常类似的过程,找到每列中的最小值,然后根据有序行对它们进行排序(第93行)。我们的目标是使索引值在列表的前面具有最小的相应距离。

下一步是使用距离来查看我们是否可以关联object IDs:

在上面的代码块中,我们:

1、初始化两个集合以确定我们已经使用了哪些行和列索引(第98和99行)。请记住,集合类似于列表,但它只包含唯一值。

2、然后我们遍历(row,col)索引元组(第103行)的组合,以便更新我们的对象质心:

如果我们已经使用了这个行或列索引,请忽略它并继续循环(第107和108行);

否则,我们找到了一个输入质心:

    1.与现有质心的欧几里得距离最小

    2.并没有与任何其他物体相匹配

    3.在这种情况下,我们更新对象质心(第113-115行)并确保将行和列添加到它们各自的usedRows和usedCols集合中

我们的usedRows + usedCols集中可能存在索引,我们还没有检查过:

因此,我们必须确定尚未检查的质心索引,并将它们存储在124和125行的两个新的未使用集合(unusedRows和unusedCols)中。

我们的最终检查处理任何丢失的物品或者它们可能已经消失的物体:

完成:

如果对象质心的数量大于或等于输入质心的数量(第131行):

    我们需要通过循环未使用的行索引来验证这些对象中是否有任何丢失或消失(第133行)。

    在循环中,我们将:

        1.增加字典中消失的数量(第137行)。

        2.检查消失的计数是否超过maxDisappeared阈值(第142行),如果是,我们将取消注册该对象(第143行)。

否则,输入质心的数量大于现有对象质心的数量,因此我们有新的对象来注册和跟踪:

我们循环遍历unusedCols索引(第149行)并注册每个新的质心(第150行)。最后,我们将把可跟踪对象集返回给调用方法(第153行)。

质心跟踪距离关系

我们的质心跟踪实现很长,不可否认,算法中最令人困惑的方面是81-93行。

如果您在跟踪代码正在执行的操作时遇到问题,则应考虑打开Python shell并执行以下实验:

使用python命令在终端中启动Python shell后,导入距离和numpy,如第1行和第2行所示。

然后,设置种子以获得再现性(第3行)并生成2个(随机的)现有的objectCentroids(第4行)和3个inputCentroids(第5行)。

从那里,计算两对之间的欧几里德距离(第6行)并显示结果(第7-9行)。结果是具有两行(现有对象质心的#)和三列(新输入质心的#)的距离矩阵D.

就像我们之前在脚本中所做的那样,让我们​​找到每行中的最小距离,并根据此值对索引进行排序:

首先,我们找到每行的最小值,允许我们找出哪个现有对象最接近新的输入质心(第10行和第11行)。然后对这些值进行排序(第12行),我们可以获得这些行的索引(第13行和第14行)。

在这种情况下,第二行(索引1)具有最小值,然后第一行(索引0)具有下一个最小值。

对列使用类似的过程:

我们首先检查列中的值,并找到具有最小列的值的索引(第15行和第16行)。

然后,我们使用现有行(第17-19行)对这些值进行排序。

让我们打印结果并分析它们:

最后一步是使用zip(第20行)组合它们。结果列表打印在第21行。

分析结果,我们发现:

1、D [1,2]具有最小的欧几里德距离,暗示第二现有对象将与第三输入质心匹配。

2、并且D [0,1]具有下一个最小的欧几里德距离,这意味着第一个现有对象将与第二个输入质心匹配。

我想在此重申,既然您已经查看了代码,那么您应该回过头来查看上一节中算法的步骤。从那里,您将能够将代码与此处概述的更线性的步骤相关联。

实现对象跟踪驱动程序脚本

现在我们已经实现了CentroidTracker类,让我们使用对象跟踪驱动程序脚本。

驱动程序脚本是您可以使用自己的首选对象检测器的地方,前提是它生成一组边界框。这可能是Haar Cascade, HOG + Linear SVM, YOLO, SSD, Faster R-CNN等。对于这个示例脚本,我正在使用OpenCV的深度学习面部检测器,但随意制作您自己的版本的实现不同检测器的脚本。

在这个脚本中,我们将:

1、使用实时VideoStream对象从网络摄像头中抓取帧

2、加载并利用OpenCV的深度学习人脸检测器

3、实例化我们的CentroidTracker并使用它来跟踪视频流中的面部对象

4、并显示我们的结果,其中包括在框架上覆盖的边界框和对象ID注释

当你准备好了,从今天的“下载”打开object_tracker.py并按照:

首先,我们指定我们的进口。最值得注意的是,我们正在使用我们刚刚审核过的CentroidTracker类。我们还将使用imutils和OpenCV的VideoStream。

我们有三个命令行参数,它们都与我们的深度学习面部检测器有关:

--prototxt:Caffe“部署”原型文件的路径。

--model:预训练模型模型的路径。

--confidence:我们过滤弱检测的概率阈值。我发现默认值为0.5就足够了。

原型文件和模型文件来自OpenCV的存储库,为方便起见,我将它们包含在“下载”中。

注意:如果您在本节开头错过了它,我将重复您可以使用您想要的任何探测器。例如,我们使用深度学习面部检测器来生成边界框。随意尝试其他探测器,只要确保你有能力的硬件来跟上更复杂的探测器(有些可能最好用GPU,但今天的面部探测器可以很容易地在CPU上运行)。

接下来,让我们执行初始化:

在上面的块中,我们:

实例化我们的CentroidTracker,ct(第21行)。回想一下上一节中的解释,该对象有三种方法:(1)寄存器,(2)注销,(3)更新。我们只会使用update方法,因为它会自动注册和取消注册对象。我们还将H和W(我们的帧尺寸)初始化为None(第22行)。

使用OpenCV的DNN模块(第26行)从磁盘加载我们的序列化深度学习人脸检测器模型。

启动我们的VideoStream,vs(第30行)。使用vs方便,我们将能够在下一个while循环中从我们的相机捕获帧。我们将让我们的相机在2.0秒内预热(第31行)。

现在让我们开始我们的while循环并开始跟踪面部对象:

我们在第34-47行上循环帧并将它们调整为固定宽度(同时保留纵横比)。我们根据需要抓取框架尺寸(第40和41行)。

然后我们将帧传递通过CNN对象检测器以获得预测和对象位置(第46-49行)。

我们初始化一个rects列表,我们在第50行上的边界框矩形。

从那里,让我们处理检测:

我们遍历从第53行开始的检测。如果检测超过我们的置信度阈值,指示有效检测,我们:

计算边界框坐标并将它们附加到rects列表(第59和60行)

在对象周围绘制一个边界框(第64-66行)

最后,让我们在我们的质心跟踪器对象上调用update,ct:

第70行的ct.update调用使用Python和OpenCV脚本处理简单对象跟踪器中的繁重工作。

如果我们不关心可视化,我们将在这里完成并准备好回到顶部。

但那没什么好玩的!

在第73-79行,我们将质心显示为填充圆和唯一对象ID号文本。现在,我们将能够可视化结果并检查我们的CentroidTracker是否通过将正确的ID与视频流中的对象相关联来正确跟踪我们的对象。

我们将在第82行显示帧,直到按下退出键(“q”)(第83-87行)。如果按下退出键,我们只需中断并执行清理(第87-91行)。

质心对象跟踪结果

要使用此博客文章的“下载”部分查看我们的质心跟踪器,请下载源代码和OpenCV人脸检测器。从那里,打开一个终端并执行以下命令:

您可以在下面看到一个被检测和跟踪的单个脸部(我的脸)的示例:

第二个示例包括正确检测和跟踪的两个对象:

请注意,一旦我将书籍封面移到相机视图之外,即使第二张脸“丢失”,我们的物体追踪也能够在视线进入时再次拾起脸部。如果面部在视野外存在超过50帧,则该对象将被取消注册。

此处的最终示例动画演示了跟踪三个唯一对象:

同样,尽管对象ID#2在某些帧之间未被成功检测到,但我们的对象跟踪算法能够再次找到它并将其与其原始质心相关联。

限制和缺点

虽然我们的质心跟踪器在此示例中运行良好,但此对象跟踪算法存在两个主要缺点。

首先,它要求在输入视频的每一帧上运行对象检测步骤。

对于必须在每个输入帧上运行检测器的非常快的物体检测器(即,颜色阈值和Haar级联)可能不是问题。

但是如果你(1)在(2)资源受限的设备上使用计算成本更高的物体检测器(如HOG +线性SVM或基于深度学习的检测器),那么您的帧处理流程将会大大减慢,因为您将花费整个管道运行一个非常慢的探测器。

第二个缺点与质心跟踪算法本身的基本假设有关 - 质心必须在后续帧之间靠近在一起。

这个假设通常都有,但请记住,我们用2D帧表示我们的3D世界 - 当一个物体与另一个物体重叠时会发生什么?

答案是可能发生对象ID切换。

如果两个或更多个对象彼此重叠到它们的质心相交的点并且相反具有到另一个相应对象的最小距离,则算法可以(在不知不觉中)交换对象ID。

重要的是要理解重叠/遮挡对象问题不是特定于质心跟踪 - 它也适用于许多其他对象跟踪器,包括高级跟踪器。

然而,质心跟踪问题更为明显,因为我们严格依赖于质心之间的欧几里德距离而没有额外的度量,启发式或学习模式。

只要您在使用质心跟踪时记住这些假设和限制,算法将为您精彩地工作。

总结

在今天的博客文章中,您学习了如何使用称为质心跟踪的算法使用OpenCV执行简单的对象跟踪。

质心跟踪算法的工作原理是:

接受每帧中每个对象的边界框坐标(可能是由某个对象检测器)。

计算输入边界框的质心与我们已经检查过的现有对象的质心之间的欧几里德距离。

基于具有最小欧几里德距离的新质心,将跟踪对象质心更新为其新质心位置。

如有必要,将对象标记为“消失”或完全取消注册。

我们的质心跟踪器在本示例教程中表现良好,但有两个主要缺点:

它要求我们为视频的每一帧运行一个物体探测器 - 如果你的物体探测器运行成本很高,你就不想使用这种方法。

它不能很好地处理重叠物体,并且由于质心之间的欧几里德距离的性质,我们的质心实际上可能“交换ID”,这远非理想。

尽管存在缺点,但质心跟踪可以用于很多物体跟踪应用程序中。(1)您的环境受到一定程度的控制,您不必担心可能重叠的物体。(2)您的物体探测器本身可以在实际中运行-时间。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 本文主要是翻译人数统计器。感谢Adrian Rosebrock分享视频人数统计的技术。先上图看效果,再做技术分享。...
    华叶6018阅读 4,684评论 1 5
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 11,242评论 6 13
  • 我们通过实现一种称为“质心跟踪”的简单对象跟踪算法来解决问题。今天,我们将采取下一步,看看内置于OpenCV中的八...
    华叶6018阅读 2,920评论 0 4
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,161评论 1 32
  • 你身披着月光 雪隔着窗对望 我梦见星空颠倒,一样的山川 琵琶曲子掀起滥觞 脚踏着流水和闪亮的光芒 你说闭上眼,梦就...
    时光微醺阅读 183评论 0 1