主要内容

基于运动的多目标跟踪

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

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

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

  2. 将对应的检测与随时间相应

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

检测到相同对象的关联仅基于运动。卡尔曼滤波器估计了每个轨道的运动。过滤器用于预测每个帧中的轨道的位置,并确定分配给每个轨道的每个检测的可能性。

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

每个轨道都会保持连续帧数的计数,在那里它仍然是未分配的。如果计数超过指定的阈值,则示例假定对象留下视野,删除轨道。

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

此示例是顶部和辅助程序的主体的函数,嵌套功能的形式。

函数MotionBasedMultiObjectTrackingExample()

%创建系统对象,用于读取视频,检测移动对象,%并显示结果。obj = setupSystemObjects ();跟踪= initializeTracks ();%创建一个空的曲目数组。nextId=1;下一曲目的%ID%检测移动物体,并在视频帧间跟踪它们。hasFrame(obj.reader)frame=readFrame(obj.reader);[centroids,bboxes,mask]=检测对象(frame);predictNewLocationsOfTracks();[assignments,unassignedTracks,unassignedDetections]=......detectiontotrackassignment();updateassignedtracks();updateUnassignedTracks();deletelosttracks();createNewtracks();displaytrackingresults();结尾

创建系统对象

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

函数obj = setupsystemobjects()%初始化视频I/O创建对象从文件中读取视频,绘制跟踪%对象在每一帧,并播放视频。%创建视频阅读器。obj。读者= VideoReader ('itrium.mp4');%创建两个视频播放器,一个用于显示视频,%和一个显示前景掩码。obj。maskPlayer =愿景。放像机(“位置”, [740, 400, 700, 400]);obj。放像机=愿景。放像机(“位置”,[20,400,700,400]);%为前台检测和BLOB分析创建系统对象%前景探测器用于将移动物体分段%背景。它输出一个二进制掩码,其中像素值1的%值对应于前景,值0对应%到背景。obj.detector = Vision.ForeCloundDetector('numgaussians',3,......'numtringframes'现年40岁的'最小背景',0.7);%连接的前景像素组可能与移动相对应%对象。blob分析系统对象用于查找此类组%(称为'blobs'或'连接的组件'),并计算它们%特征,如区域,质心和边界框。obj.blobanalyser = Vision.BlobanAlysis(“BoundingBoxOutputPort”是的,......“AreaOutputPort”是的,'centroidoutputport'是的,......“最小面积”, 400);结尾

初始化跟踪

初始化跟踪函数创建一系列曲目,其中每个轨道是表示视频中移动对象的结构。结构的目的是保持跟踪物体的状态。该州包括用于检测的信息以跟踪分配,跟踪终止和显示。

该结构包含以下字段:

  • id:轨道的整数ID

  • bbox.:对象的当前包围框;用于显示

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

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

  • totalvisiblecount.:检测到轨道的帧总数(可见)

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

嘈杂的检测往往会导致短暂的轨道。因此,该示例仅在跟踪某些帧之后显示对象。这是什么时候发生totalvisiblecount.超过指定阈值。

当没有与几个连续帧的轨道相关联的检测时,示例假定对象留下了视野并删除了轨道。这是什么时候发生consecutiveInvisibleCount超过指定阈值。如果跟踪的时间很短,并且大多数帧都被标记为不可见,那么轨道也可能被删除为噪声。

函数曲目= initializetracks()%创建一个空的轨迹数组tracks = struct(......'ID'{},......“bbox”{},......'kalmanfilter'{},......“年龄”{},......'totalvisiblecount'{},......'沟圈visiblecount',{});结尾

检测对象

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

该函数使用前景检测器执行运动分割。然后,它对生成的二值遮罩执行形态学操作,以去除噪声像素并填充剩余斑点中的孔洞。

函数[质心,虚拟机,掩码] =探测器(帧)%检测前景。掩码= obj.detector.step(框架);%应用形态操作以消除噪音并填充孔。Mask = imopen(Mask, strel()“矩形”,[3,3]));mask = imclose(面具,光线(“矩形”,[15,15]));mask = Imfill(掩码,“黑洞”);%执行blob分析以找到连接的组件。[〜,质心,bboxes] = obj.blobanalyser.step(mask);结尾

预测现有曲目的新位置

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

函数predictnewlocationsoftracks()为了I = 1:length(tracks) bbox = tracks(I).bbox;%预测轨道的当前位置。predgeedcentroid =预测(曲目(i).kalmanfilter);%换边框,以便其中心在%预测的位置。predgedcentroid = int32(预测中心) -  bbox(3:4)/ 2;曲目(i).bbox = [predgeedcentroid,bbox(3:4)];结尾结尾

分配检测以跟踪

通过最小化成本,将当前帧中的对象检测分配给现有轨道。成本被定义为对应于轨道的检测的负值 - 可能性。

算法包括两个步骤:

步骤1:使用使用的每个轨道计算为每个轨道分配每个检测的成本距离方法的方法Vision.KalmanFilter.系统对象™. 成本考虑了预测轨迹质心和检测质心之间的欧氏距离。它还包括由卡尔曼滤波器维持的预测置信度。结果存储在MxN矩阵中,其中M是轨迹数,N是检测数。

步骤2:用矩阵求解代价矩阵表示的分配问题assignDetectionsToTracks功能。该功能采用成本矩阵和未分配对轨道的任何检测的成本。

未为轨道分配检测的成本的值取决于返回的值范围距离方法的方法Vision.KalmanFilter.。必须通过实验调整此值。将其设置得太低提高了创建新曲目的可能性,并且可能导致轨道碎片。将其设置得太高可能导致对应于一系列单独移动对象的单个轨道。

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

函数[作业,未分配的触发,未分配的detections] =......detectionToTrackAssignment()nTracks=长度(轨迹);nDetections=大小(质心,1);%计算将每个检测分配给每个轨迹的成本。成本=零(nTrack,nDetection);为了i = 1:ntracks成本(i,:) =距离(曲目(i).kalmanfilter,质心);结尾%解决了分配问题。未分配的成本=20;[分配、未分配的跟踪、未分配的检测]=......assignDetectionsToTracks(成本、costOfNonAssignment);结尾

更新指定的跟踪

updateassignedtracks.函数用相应的检测更新每个分配的轨道。它调用正确的的方法Vision.KalmanFilter.纠正位置估计。接下来,它存储新的边界框,并增加轨道的年龄,并且总可见计数到1.最后,该功能将无形计数设置为0。

函数updateAssignedTracks() numAssignedTracks = size(赋值,1);为了i = 1:numassignedtracks trackIDX =作业(i,1);DetectionIdX =赋值(I,2);Centroid =质心(DetectionIdx,:);bbox = bboxes(detectionIdx,:);%校正对象位置的估计%使用新检测。正确(轨迹(trackIdx).kalmanFilter,形心);将预测的边界框替换为检测到的%边界框。曲目(trackIDX).bbox = bbox;%更新曲目的年龄。轨道(trackIdx)。年龄=轨道(trackIdx)。年龄+1;%更新的可见性。曲目(TrackIDX).totalvisiblecount =......曲目(TrackIDX).totalvisiblecount + 1;曲目(trackIDX).consechutivevisiblecount = 0;结尾结尾

更新未分配的曲目

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

函数updateUnassignedTracks ()为了i = 1:长度(UnassignedTracks)Ind = UnassignedTracks(i);曲目(IND).age =曲目(IND).age + 1;曲目(IND).consechutiveInvisiBlecount =......曲目(ind).consechutivevisiblecount + 1;结尾结尾

删除丢失的曲目

deletelosttracks.函数删除对于太多连续帧来说是不可见的曲目。它还删除最近创建的曲目,这对于太多帧来说是整体上的太多。

函数deletelosttracks()如果isempty(轨道)返回;结尾InvisibleFortoolong = 20;Agethreshold = 8;%计算曲目年龄的分数,它是可见的。年龄=[tracks(:).age];totalVisibleCounts=[tracks(:).totalVisibleCount];可见性=totalVisibleCounts./ages;%查找“丢失”轨迹的索引。dosinds =(年龄......(跟踪(:)。consecutiveInvisibleCount] > = invisibleForTooLong;删除丢失的轨迹。跟踪=跟踪(~ lostInds);结尾

创建新曲目

从未分配的检测创建新曲目。假设任何未分配的检测都是新轨道的开始。在实践中,您可以使用其他提示来消除嘈杂的检测,例如尺寸,位置或外观。

函数createNewtracks()心针=质心(未分配的DETECTESS,:);bboxes = bboxes(未分配的ddetections,:);为了i = 1:大小(质心,1)Centroid =质心(我,:);bbox = bboxes(i,:);%创建一个Kalman过滤器对象。kalmanfilter = configurekalmanfilter('constantvelocity'......质心,[200,50],[100,25],100);创建一个新的轨道。newtrack = struct(......'ID',nextid,......“bbox”,bbox,......'kalmanfilter'kalmanFilter,......“年龄”,1,......'totalvisiblecount',1,......'沟圈visiblecount',0);%将它添加到轨道数组中。曲目(结束+ 1)= NewTrack;%增加下一个id。nextId = nextId + 1;结尾结尾

显示跟踪结果

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

函数displaytrackingresults()%将帧和蒙版转换为UINT8 RGB。frame=im2uint8(frame);mask=uint8(repmat(mask[1,1,3])255;minVisibleCount=8;如果〜Isempty(轨道)%噪声检测往往会导致短寿命的轨迹。%仅显示超过的曲目超过%最小帧数。ReliacBletrackinds =......[曲目(:)。totalvisiblecount]> minvisiblecount;Reliablezracks =曲目(Reliablezrackinds);%显示对象。如果未检测到对象%在此帧中,显示其预测的边界框。如果〜Isempty(Reliablezracks)%获取边界框。Bboxes = Cat(1,Reliablezracks.Bbox);%获取身份证。IDS = INT32([ReliaBlezracks(:)。ID]);%为指示oble的对象创建标签%,我们显示的是预测值而不是实际值% 地点。标签= CELLSTR(INT2STR(IDS'));predictedtrackinds =......[reliableTracks(:)。consecutiveInvisibleCount) > 0;isPredicted =细胞(大小(标签));isPredicted (predictedTrackInds) = {' 预料到的'};标签= strcat(标签,isPredicted);%绘制框架上的对象。Frame = InsertObjectAnnotation(帧,“矩形”......Bboxes,标签);%绘制掩码上的对象。掩码=插入对象注释(掩码,“矩形”......Bboxes,标签);结尾结尾%显示遮罩和边框。obj.maskplayer.step(mask);obj.videoplayer.step(框架);结尾

总结

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

该示例中的跟踪仅基于运动,假设所有对象以恒定速度的直线移动。当物体的运动显着地偏离该模型时,该示例可以产生跟踪误差。注意当他被树上封闭时,跟踪标有#12的人的错误。

通过使用更复杂的运动模型,例如恒定加速度,或者通过为每个对象使用多个Kalman滤波器,可以减少跟踪错误的可能性。此外,您可以包含其他提示,以随着时间的推移将检测相关联,例如尺寸,形状和颜色。

结尾