主要内容

基于运动的多目标跟踪

这个例子展示了如何执行自动检测和基于运动的跟踪运动对象的视频从一个固定的摄像机。

运动目标检测和基于运动的跟踪是许多计算机视觉应用的重要组成部分,包括活动识别、交通监控和汽车安全。基于运动的目标跟踪问题可分为两部分:

  1. 检测每一帧的移动目标

  2. 随着时间的推移,将相应于同一对象的检测关联起来

运动目标的检测采用基于高斯混合模型的背景减法算法。形态学操作被应用到产生的前景蒙版,以消除噪声。最后,blob分析检测连接的像素组,这些像素组很可能与移动对象相对应。

对同一物体的探测仅仅基于运动。通过卡尔曼滤波估计每个航迹的运动。该滤波器用于预测航迹在每一帧中的位置,并确定为每个航迹分配每个检测的可能性。

跟踪维护是本示例的一个重要方面。在任何给定帧中,一些检测可能被分配到轨道,而其他检测和轨道可能保持未分配。使用相应的检测更新指定的轨迹。未分配的轨道被标记为不可见。一个未分配的检测开始一个新的轨道。

每个轨迹都保留未分配的连续帧数的计数。如果计数超过指定的阈值,则示例假定对象离开视野,并删除轨迹。

欲了解更多信息,请参见多个对象跟踪

这个例子是一个函数,主体位于顶部,助手例程以嵌套函数的形式出现。

函数MotionBasedMultiObjectTrackingExample ()

%创建系统对象,用于读取视频,检测移动对象,%并显示结果。obj = setupSystemObjects ();跟踪= initializeTracks ();%创建一个空的轨迹数组。nextId = 1;%下一个曲目的ID%检测移动物体,并在视频帧间跟踪它们。hasFrame(obj.reader) frame = readFrame(obj.reader);[质心,bboxes, mask] = detectObjects(frame);predictNewLocationsOfTracks ();[assignments, unassigndtracks, unassignddetections] =...detectionToTrackAssignment();updateAssignedTracks();updateUnassignedTracks();deleteLostTracks();createNewTracks();displayTrackingResults();结束

创建系统对象

创建用于读取视频帧、检测前景对象和显示结果的System对象。

函数obj=设置系统对象()%初始化视频I/O创建对象从文件中读取视频,绘制跟踪%对象在每一帧,并播放视频。创建一个视频阅读器。obj。读者= VideoReader (“atrium.mp4”);%创建两个视频播放器,一个显示视频,%和1显示前景掩码。obj。maskPlayer =愿景。放像机(“位置”, [740, 400, 700, 400]);obj。放像机=愿景。放像机(“位置”, [20, 400, 700, 400]);%创建用于前景检测和blob分析的系统对象%前景检测器用于从图像中分割运动对象%的背景。它输出一个二进制掩码,其中像素值%值1对应于前景,值0对应于%背景。obj.detector =愿景。ForegroundDetector (“NumGaussians”3,...“NumTrainingFrames”现年40岁的“MinimumBackgroundRatio”, 0.7);%连接的前景像素组可能与移动相对应%的对象。blob analysis System对象用于查找这样的组%(称为“blob”或“connected components”),并计算它们的%特征,如面积、质心和边界框。obj。blobAnalyser =愿景。BlobAnalysis (“BoundingBoxOutputPort”,真的,...“AreaOutputPort”,真的,“CentroidOutputPort”,真的,...“MinimumBlobArea”, 400);结束

初始化跟踪

这个initializeTracks函数创建一个轨迹数组,其中每个轨迹都是表示视频中移动对象的结构。该结构的目的是保持被跟踪对象的状态。该状态包括用于检测轨迹分配、轨迹终止和显示的信息。

该结构包含以下字段:

  • id:轨道的整数ID

  • 口技:对象的当前包围框;用于显示

  • kalmanFilter:用于基于运动的跟踪的卡尔曼滤波对象

  • 年龄:自跟踪第一次被检测到以来的帧数

  • 总可视计数:检测到轨道的帧总数(可见)

  • consecutiveInvisibleCount:未检测到轨迹的连续帧数(不可见)。

噪声检测往往会导致短暂的轨迹。由于这个原因,该示例只显示跟踪了一定帧数的对象。发生这种情况时总可视计数超过指定阈值。

当连续几帧没有检测到与轨迹相关联时,本例假设物体已经离开视场并删除轨迹。发生这种情况时consecutiveInvisibleCount超过指定阈值。如果跟踪的时间很短,并且大多数帧都被标记为不可见,那么轨道也可能被删除为噪声。

函数跟踪= initializeTracks ()创建一个空轨道数组轨道=结构(...“id”{},...“bbox”{},...“卡尔曼过滤器”{},...“年龄”{},...“totalVisibleCount”{},...“consecutiveInvisibleCount”, {});结束

检测对象

这个detectObjects函数返回被检测对象的质心和边界框。它还返回与输入帧大小相同的二进制掩码。值为1的像素对应前景,值为0的像素对应背景。

该函数使用前景检测器进行运动分割。然后,它对产生的二元掩模进行形态学操作,以去除噪声像素,并填补剩余斑点中的洞。

函数[质心、B盒、遮罩]=检测对象(帧)%检测前景。掩码= obj.detector.step(框架);%应用形态学操作以消除噪声并填充孔。Mask = imopen(Mask, strel()“矩形”, [3,3])); 掩码=imclose(掩码,strel(“矩形”,[15,15]);掩码=imfill(掩码,“黑洞”);%执行blob分析以找到连接的组件。[~, centroids, bboxes] = obj.blobAnalyser.step(mask);结束

预测现有轨道的新位置

利用卡尔曼滤波来预测当前帧中每个轨迹的质心,并相应地更新其边界框。

函数predictNewLocationsOfTracks()I = 1:length(tracks) bbox = tracks(I).bbox;%预测轨道的当前位置。预测质心=预测(轨迹(i).Kalman滤波器);移动边界框,使其中心位于%预测的位置。predictedCentroid = int32(predictedCentroid) - bbox(3:4) / 2;跟踪(i)。口技= [predictedCentroid, bbox(3:4)];结束结束

为轨迹分配检测

将当前帧中的对象检测分配到现有轨道是通过最小化代价来完成的。代价定义为对应于轨迹的检测的负对数似然。

算法包括两个步骤:

步骤1:使用距离的方法卡曼过滤器系统对象™。该代价考虑了轨迹预测质心与检测质心之间的欧氏距离。它还包括预测的置信度,这是由卡尔曼滤波器保持的。结果存储在一个MxN矩阵中,其中M为跟踪数,N为检测数。

步骤2:用矩阵求解代价矩阵表示的分配问题assignDetectionsToTracks函数。该函数采用成本矩阵和不向轨道分配任何检测的成本。

属性返回的值的范围决定了不将检测赋给轨道的代价值距离的方法卡曼过滤器.这个值必须通过实验来调整。设置太低会增加创造新轨道的可能性,并可能导致轨道分裂。设置太高可能会导致一个单独的轨道对应一系列独立的移动物体。

这个assignDetectionsToTracks函数使用Munkres版本的匈牙利算法来计算使总成本最小化的分配。它返回一个M x 2矩阵,在其两列中包含分配的轨迹和检测的相应索引。它还返回未分配的轨迹和检测的索引。

函数[assignments, unassigndtracks, unassignddetections] =...detectionToTrackAssignment() nTracks = length(tracks);nDetections = size(质心,1);计算分配每个检测到每个轨道的成本。cost = 0 (nTracks, nDetections);i=1:nTracks成本(i,:)=距离(轨迹(i)。Kalman滤波器,质心);结束解决分配问题。costOfNonAssignment = 20;[assignments, unassigndtracks, unassignddetections] =...assignDetectionsToTracks(成本、costOfNonAssignment);结束

更新指定的跟踪

这个updateAssignedTracks函数用相应的检测更新每个分配的轨道。它调用对的方法卡曼过滤器修正位置估计。接下来,它存储新的边界框,并将曲目的年龄和总可见计数增加1。最后,该函数将不可见计数设置为0。

函数updateAssignedTracks() numAssignedTracks = size(赋值,1);i=1:numAssignedTracks trackIdx=assignments(i,1);detectionIx=assignments(i,2);质心=centroids(detectionIx,:);bbox=bboxes(detectionIx,:);%更正对对象位置的估计%使用新的检测。正确的(跟踪(trackIdx)。kalmanFilter、质心);将预测的边界框替换为检测到的%边界框。轨道(trackIdx).bbox=bbox;%更新曲目的年龄。跟踪(trackIdx)。年龄= tracks(trackIdx).age + 1;%更新的可见性。曲目(trackIdx).总可视次数=...曲目(trackIdx).totalVisibleCount+1;曲目(trackIdx).ConcertiveInvisibleCount=0;结束结束

更新未分配的轨迹

将每个未分配的轨迹标记为不可见,并将其年龄增加1。

函数updateUnassignedTracks ()i=1:length(unassignedTracks)ind=unassignedTracks(i);tracks(ind)。age=tracks(ind)。age+1;tracks(ind)。连续可视计数=...轨道(ind)。连续可视计数+1;结束结束

删除丢失的曲目

这个删除LostTracks函数删除在过多连续帧中不可见的轨道。它还删除了最近创建的轨迹,这些轨迹在太多帧中都是不可见的。

函数deleteLostTracks ()如果isempty(跟踪)返回;结束invisibleForTooLong = 20;ageThreshold = 8;%计算轨迹的可见年龄部分。年龄=(跟踪(:).age);totalVisibleCounts =(跟踪(:).totalVisibleCount);visibility = totalVisibleCounts ./年龄;找到“丢失”轨道的索引。lostInds=(年龄<年龄阈值&能见度<0.6)|...(跟踪(:)。consecutiveInvisibleCount] > = invisibleForTooLong;删除丢失的轨迹。跟踪=跟踪(~ lostInds);结束

创建新曲目

从未分配的检测创建新轨迹。假设任何未分配的检测都是新轨迹的开始。实际上,您可以使用其他提示来消除噪音检测,例如大小、位置或外观。

函数createNewTracks() centroids = centroids(unassignddetections,:);bboxes = bboxes(unassignddetections,:);I = 1:size(质心,1)Bbox = bboxes(i,:);%创建一个卡尔曼滤波器对象。kalmanFilter=配置kalmanFilter(“康斯坦特维洛西蒂”,...质心,[200,50],[100,25],100);创建一个新的轨道。newTrack=struct(...“id”,nextId,...“bbox”bbox,...“卡尔曼过滤器”kalmanFilter,...“年龄”, 1...“totalVisibleCount”, 1...“consecutiveInvisibleCount”, 0);%将它添加到轨道数组中。track (end + 1) = newTrack;%增加下一个id。nextId = nextId + 1;结束结束

显示跟踪结果

这个displayTrackingResults函数为视频帧和前景掩码上的每个轨道绘制边界框和标签ID。然后在各自的视频播放器中显示帧和掩码。

函数displayTrackingResults()%转换帧和掩码的uint8 RGB。= im2uint8帧(帧);Mask = uint8(repmat(Mask, [1, 1, 3])) .* 255;minVisibleCount = 8;如果~i空(轨道)%噪声检测往往会导致短暂的轨迹。%只显示可见时间超过的轨道%最小帧数。可靠轨道=...[曲目(:).totalVisibleCount]>minVisibleCount;reliableTracks=曲目(reliableTrackInds);%显示对象。如果未检测到对象%在此帧中,显示其预测的边界框。如果~isempty(可靠轨道)获得边界框。bboxes=类别(1,reliableTracks.bbox);%得到id。id = int32 ([reliableTracks (:) .id]);%为对象创建标签%,我们显示的是预测值而不是实际值%地点。标签= cellstr (int2str (ids));predictedTrackInds =...[reliableTracks(:)。consecutiveInvisibleCount) > 0;isPredicted =细胞(大小(标签));isPredicted (predictedTrackInds) = {“预测”};标签= strcat(标签,isPredicted);%在框架上绘制对象。= insertObjectAnnotation帧(帧,“矩形”,...b盒子、标签);在蒙版上绘制对象。= insertObjectAnnotation(面具,面具“矩形”,...b盒子、标签);结束结束%显示掩码和帧。obj.maskPlayer.step(面具);obj.videoPlayer.step(框架);结束

总结

这个例子创建了一个基于运动的系统,用于检测和跟踪多个运动物体。尝试使用不同的视频,看看你是否能够检测和跟踪物体。尝试修改检测、分配和删除步骤的参数。

本例中的跟踪完全基于所有物体以匀速直线运动的假设。当一个物体的运动明显偏离这个模型时,这个例子可能会产生跟踪误差。注意跟踪标签为#12的人时的错误,他被树挡住了。

通过使用更复杂的运动模型(如恒定加速度)或对每个对象使用多个卡尔曼滤波器,可以降低跟踪误差的可能性。此外,您还可以结合其他线索来关联随时间变化的检测,如大小、形状和颜色。

结束