主要内容

跟踪移动车辆的行人

此示例显示如何使用安装在移动车中的摄像头跟踪行人。

概述

这个例子展示了如何在移动摄像机的视频中对人进行自动检测和跟踪。它展示了适应于移动摄像机的跟踪系统的灵活性,这是汽车安全应用的理想选择。不像固定摄像机的例子,基于运动的多目标跟踪,此示例包含几个额外的算法步骤。这些步骤包括人们检测,定制非最大抑制和启发式,以识别和消除误报轨道。有关更多信息,请参阅多个对象跟踪

这个例子是一个函数,主体位于顶部,助手例程的形式为什么是嵌套函数?以下。

函数PedestrianTrackingFromMovingCameraExample ()
%创建用于读取视频、加载先决数据文件、检测行人和显示结果的系统对象。videoFile =“vippedtracking.mp4”;scaleDataFile =“pedScaleTable.mat”%辅助文件,有助于确定不同像素位置的行人大小。obj = setupSystemObjects(videoFile, scaleDataFile); / /设置对象探测器= peopleDetectorACF ('caltech');创建一个空轨道数组。跟踪= initializeTracks ();%下一个曲目的ID。nextId = 1;%设置全局参数。选择。ROI= [40 95 400 140];%限制处理区域在地面位置的矩形[x, y, w, h]。option.scThresh=0.3;在估计被检测行人的尺度时,控制误差容忍度的阈值。选择。gatingThresh = 0.9;%在检测和轨迹之间拒绝候选匹配的阈值。选择。gatingCost = 100;%分配成本矩阵的一个大值,强制拒绝候选匹配。option.costofnonassignment = 10;控制创建新音轨的可能性的调谐参数。option.timeWindowSize=16;用于指定稳定音轨置信度所需的帧数的调谐参数。选择。confidenceThresh = 2;确定跟踪是真、正还是假警报的阈值。option.ageThresh=8;%阈值,以确定轨道所需的最小长度为真正的正。option.visthresh = 0.6;%用于确定轨迹的最小可见性值为真正值的阈值。%检测人和跟踪他们的视频帧。stopframe = 1629;停在一个有趣的画面上,画面上有几个行人fNum = 1:stopFrame frame = readFrame(obj.reader);[centroids, bboxes, scores] = detectPeople();predictNewLocationsOfTracks ();[assignments, unassigndtracks, unassignddetections] =...detectionToTrackAssignment ();updateAssignedTracks ();updateUnassignedTracks ();deleteLostTracks ();createNewTracks ();displayTrackingResults ();%如果视频播放器图形已关闭,则退出循环。如果~ isOpen (obj.videoPlayer)打破结束结束

跟踪系统的辅助输入和全局参数

该跟踪系统需要一个数据文件,该数据文件包含将图像中的像素位置与标记行人位置的边界框的大小相关联的信息。该先验知识存储在向量中可耕地.第n项可耕地表示成年人的估计身高(以像素为单位)。该指数n参考行人脚的近似Y坐标。

为了获得这样一个向量,我们从与测试环境相同的视角和相似的场景中采集了一组训练图像。训练图像包含了距离摄像机不同距离的行人图像。使用图片标志应用程序,对图像中行人的边界框进行手动标注。利用边界框的高度和行人在图像中的位置,通过回归生成尺度数据文件。以下是一个辅助函数,用于显示拟合线性回归模型的算法步骤:helperTableOfScales.m

还有一组全局参数可以优化跟踪性能。您可以使用下面的描述来了解这些参数如何影响跟踪性能。

  • ROI:兴趣区域,形式为[x, y, w, h]。它将处理区域限制在地面。

  • SCTHRESH.:规模估计的公差阈值。当检测到的刻度和预期比例之间的差异超过公差时,认为候选检测被认为是不现实的并且从输出中移除。

  • gatingThresh:距离测量的门控参数。当检测到的边界盒与预测的边界盒的匹配代价超过阈值时,系统将两个边界盒的关联从跟踪考虑中去除。

  • 加廷成本:分配代价矩阵的值,以阻止可能的跟踪到检测分配。

  • costOfNonAssignment:不分配检测或轨迹的分配成本矩阵的值。设置太低会增加创造新轨道的可能性,并可能导致轨道分裂。设置太高可能会导致一个单独的轨道对应一系列独立的移动物体。

  • timeWindowSize:估计跑道可信度所需的帧数。

  • confidenceThresh:信心阈值确定曲目是否是真正的积极。

  • 阿格斯瑞斯:轨道的最小长度为真正数。

  • 维斯瑞斯:最低能见度阈值,以确定是否跑道是一个真正的积极。

为跟踪系统初始化创建系统对象

setupsystemobjects.函数创建用于读取和显示视频帧的系统对象,并加载缩放数据文件。

可耕地向量,存储在比例数据文件中,编码我们的目标和场景的先验知识。一旦从样本中训练出回归器,就可以计算图像中每个可能y位置的预期高度。这些值存储在向量中。第n项可耕地代表我们在像素中成年人的估计高度。该指数n参考行人脚的近似Y坐标。

函数obj = setupSystemObjects (videoFile scaleDataFile)%初始化视频I/O创建对象从文件中读取视频,绘制%检测和跟踪每一帧的人,并播放视频。%创建视频文件读取器。obj。读者= VideoReader (videoFile);创建一个视频播放器。obj。放像机=愿景。放像机(“位置”, [29, 597,643, 386]);%加载比例数据文件ld =负载(scaleDataFile,“pedScaleTable”);obj。pedScaleTable = ld.pedScaleTable;结束

初始化曲目

初始化跟踪函数创建一个轨道数组,其中每个轨道是表示视频中一个移动对象的结构。该结构的目的是维护被跟踪对象的状态。状态由用于探测到航迹分配、航迹终止和显示的信息组成。

该结构包含以下字段:

  • id: track的整数ID。

  • 颜色:用于显示的曲目颜色。

  • bboxes: n × 4矩阵,表示对象的边界框,当前框位于最后一行。每一行都有[x, y,宽,高]的形式。

  • 分数:一个逐个1向量,以记录来自人检测器的分类分数,并在最后一行中的检测分数。

  • 卡尔曼弗特:用于基于运动的跟踪的卡尔曼滤波对象。我们在图像中跟踪物体的中心点;

  • 年龄:自轨道初始化以来的帧数。

  • totalVisibleCount:对象被检测到的帧总数(可见)。

  • 信心:一对两个数字表示我们对赛道的信任程度。它在预定义的时间窗口内存储过去的最大和平均检测分数。

  • predPosition:下一帧中的预测边界框。

函数跟踪= initializeTracks ()%创建一个空的曲目数组跟踪=结构(...“id”{},...“颜色”{},...“bboxes”{},...“分数”{},...“kalmanFilter”{},...“年龄”{},...“totalVisibleCount”{},...“信心”{},...“predPosition”, {});结束

检测人

detectPeople函数返回被检测人员的质心、包围框和分类分数。它对返回的检测器的原始输出进行滤波和非最大抑制peopleDetectorACF

  • 质心: n × 2矩阵,每行为[x,y]形式。

  • bboxes: n × 4矩阵,每行为[x, y, width, height]形式。

  • 分数:一个包含每个元素的n × 1向量是对应帧的分类评分。

函数[质心,虚拟机,分数] =侦探人员()%调整图像大小以增加行人的分辨率。这有助于检测离相机较远的人。resizeRatio = 1.5;frame = imresize(frame, resizeatio,'抗锯齿'、假);%在感兴趣的区域内运行ACF人员检测器%检测候选对象。[bboxes,scores] =检测(探测器,帧,选项.ROI,...“WindowStride”2,...“NumScaleLevels”,4,...“SelectStrongest”, 错误的);根据行人双脚的位置,估计他们的身高。height = bboxes(:, 4) / resizeatio;y = (bboxes(:,2)-1) / resizeatio + 1;yfoot = min(length(obj.pedScaleTable), round(y + height));estHeight = obj.pedScaleTable (yfoot);%删除大小偏离预期大小的检测,%由校正后的比例尺估算。无效的= abs (estHeight-height) > estHeight * option.scThresh;Bboxes (invalid,:) = [];Scores (invalid,:) = [];%应用非最大抑制来选择最强的包围框。[bboxes, scores] = selectStrongestBbox(bboxes, scores,...“RatioType”“最小值”“OverlapThreshold”, 0.6);计算质心如果isempty(bboxes)心针= [];其他的[(bboxes(:, 1) + bboxes(:, 3) / 2],...(bboxes(:, 2) + bboxes(:, 4) / 2)];结束结束

预测现有轨道的新位置

使用卡尔曼滤波器预测当前帧中每条轨迹的质心,并相应地更新其边界框。我们将上一帧中边界框的宽度和高度作为当前的大小预测。

函数predictNewLocationsOfTracks ()i = 1:长度(跟踪)%获取此轨道上的最后一个边界框。bbox = (i)。bboxes (,);预测赛道的当前位置。predictedCentroid =预测(跟踪(i) .kalmanFilter);移动边界框,使其中心在预测的位置。跟踪(i)。predPosition = [predictedCentroid - bbox(3:4)/2, bbox(3:4)];结束结束

为轨道分配检测

将当前帧中的对象检测分配到现有轨道是通过最小化代价来完成的。成本是用BBOX重叠率功能,并且是预测边界框和检测到的边界框之间的重叠比。在该示例中,由于视频的高帧速率和人的低运动速度,我们假设该人将在连续帧中逐渐移动。

算法包括两个步骤:

步骤1:计算分配每个探测到每个轨道的成本BBOX重叠率措施。随着人们朝向或远离相机,其动作将不会被质心点精确描述。成本考虑了图像平面上的距离以及边界框的比例。这可以防止将检测分配远离相机以追踪更接近相机,即使它们的质心重点。这种成本函数的选择将缓解计算,而无需诉诸更复杂的动态模型。结果存储在MXN矩阵中,其中M是轨道的数量,n是检测次数。

第2步:解决成本矩阵表示的赋值问题使用assignDetectionsToTracks函数。该函数采用代价矩阵和不为轨道分配任何检测的代价。

不为轨道分配检测的代价值取决于代价函数返回的值的范围。这个值必须通过实验来调整。设置太低会增加创造新轨道的可能性,并可能导致轨道分裂。设置太高可能会导致一个单独的轨道对应一系列独立的移动物体。

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

函数[assignments, unassigndtracks, unassignddetections] =...detectionToTrackAssignment ()计算预测框与的重叠率%检测框,并计算分配每个检测的成本%到每个轨道。当预测箱为时,成本最小与检测到的bbox完全对齐%(重叠率为1)predBboxes =重塑([跟踪(:)。predPosition], []) ';cost = 1 - bboxOverlapRatio(predBboxes, bboxes);%强制优化步骤忽略一些匹配%将相关成本设置为大量。注意这一点%数字与下面的“Costofnonassignment”不同。%这在选择时很有用(移除不现实的匹配)%技术应用。成本(成本>选项.gatingthresh)= 1 + Option.gatingcost;解决分配问题。[assignments, unassigndtracks, unassignddetections] =...assignDetectionsToTracks(成本、option.costOfNonAssignment);结束

更新分配的曲目

updateAssignedTracks函数使用相应的检测更新每个指定的轨迹。它称之为正确的的方法愿景。KalmanFilter修正位置估计。接下来,它通过取最近(最多)4个框的大小的平均值来存储新的边界框,并将轨道的年龄和总可见计数增加1。最后,该函数根据之前的检测分数调整轨迹的置信度分数。

函数updateassignedtracks()numassignedtracks = size(分配,1);i = 1:numAssignedTracks trackIdx = assignments(i, 1);detectionIdx = assignments(i, 2);质心=质心(detectionIdx,:);bbox = bboxes(detectionIdx,:);修正对物体位置的估计%使用新的检测。正确的(跟踪(trackIdx)。kalmanFilter、质心);%通过尺寸的平均值稳定边界框%轨道上最近(最多)4个箱子的数量。T = min(size(tracks(trackIdx).bboxes,1), 4);w =意味着([跟踪(trackIdx)。bboxes (end-T + 1:最终,3);bbox (3)]);h =意味着([跟踪(trackIdx)。bboxes (end-T + 1:最终,4);bbox (4)]);跟踪(trackIdx)。bboxes(end+1, :) = [centroid - [w, h]/2, w, h];%更新曲目的年龄。轨道(trackIdx)。年龄=轨道(trackIdx)。年龄+1;%更新曲目的分数历史曲目(trackIDX).scores = [tracks(trackidx).cores;分数(检测克)];%更新的可见性。跟踪(trackIdx)。totalVisibleCount=...曲目(TrackIDX).totalvisiblecount + 1;%根据最大检测值调整航迹置信度在过去的'timeWindowSize'帧中的%得分。T = min(选项。timeWindowSize,length(tracks(trackIdx).scores)); score = tracks(trackIdx).scores(end-T+1:end); tracks(trackIdx).confidence = [max(score), mean(score)];结束结束

更新未赋值的跟踪

updateUnassignedTracks函数标记每个未分配的曲目都是不可见的,将其年龄提高1,并将预测的边界框附加到轨道。自信地设定为零,因为我们不确定为什么没有分配给轨道。

函数updateUnassignedTracks ()i = 1:length(unassigndtracks) idx = unassigndtracks (i);跟踪(idx)。年龄= tracks(idx).age + 1; tracks(idx).bboxes = [tracks(idx).bboxes; tracks(idx).predPosition]; tracks(idx).scores = [tracks(idx).scores; 0];%根据最大检测值调整航迹置信度过去'timewindowsize'框架中的%得分T=min(option.timeWindowSize,length(tracks(idx).scores));分数=轨迹(idx)。分数(结束-T+1:end);跟踪(idx)。置信度=[最大(分数)、平均(分数)];结束结束

删除了跟踪

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

嘈杂的检测往往会导致虚假轨道的创建。对于此示例,我们在以下条件下删除曲目:

  • 对象被跟踪了短时间。这通常会发生在错误的检测显示几帧并且启动轨道时。

  • 在大部分画面中,轨道都被标记为不可见。

  • 在过去的几帧内没有接收到强检测,表示为最大检测置信度得分。

函数deletelosttracks()如果isempty(跟踪)返回结束%计算轨道的年龄占它可见的百分比。年龄=(跟踪(:).age) ';totalVisibleCounts =(跟踪(:).totalVisibleCount) ';visibility = totalVisibleCounts ./年龄;%检查最大检测置信度得分。信心=重塑([跟踪(:)。信心]2 [])';maxConfidence = confidence(:, 1);%查找“丢失”轨迹的索引。lostdings = (ages <= option.)阿格斯瑞斯& visibility <= option.visThresh) |...(maxconfidence <= option.confidencEdhresh);删除丢失的轨迹。跟踪=跟踪(~ lostInds);结束

创建新的跟踪

从未分配的检测创建新的轨道。假设任何未分配的检测是一个新轨道的开始。在实践中,您可以使用其他线索来消除噪声检测,如大小、位置或外观。

函数createNewTracks() unassigndcentroids = centroids(unassignddetections,:);unassigndbboxes = bboxes(unassignddetections,:);unassignedScores =分数(unassignedDetections);i = 1:size(unassignddbboxes, 1) centroid = unassigndcentroids (i,:);bbox = unassign dbboxes (i,:);分数= unassignedScores(我);%创建一个卡尔曼过滤器对象。kalmanFilter = configureKalmanFilter (“ConstantVelocity”...质心,[2,1],[5,5],100);创建一个新的轨道。newTrack =结构(...“id”nextId,...“颜色”, 255 *兰德(1、3)...“bboxes”bbox,...“分数”, 分数,...“kalmanFilter”,kalmanfilter,...“年龄”, 1...“totalVisibleCount”, 1...“信心”,[得分,得分],...“predPosition”,bbox);%将其添加到轨迹数组中。track (end + 1) = newTrack;% #好< AGROW >%增加下一个id。nextid = Nextid + 1;结束结束

显示跟踪结果

displayTrackingResults函数为视频帧上的每个轨道绘制一个彩色边界框。盒子的透明度水平和显示的分数表明了检测和轨迹的可信度。

函数displayTrackingResults() displayRatio = 4/3;帧= imresize(frame, displayRatio);如果~isempty(tracks) ages = [tracks(:).age]';信心=重塑([跟踪(:)。信心]2 [])';maxConfidence = confidence(:, 1);avgConfidence = confidence(:, 2);不透明度=最小(0.5,最大(0.1,avgConfidence / 3));nodispids = (ages < option. value);阿格斯瑞斯& maxConfidence < option.confidenceThresh) |...(年龄<选项。ageThresh / 2);i = 1:长度(跟踪)如果~ noDispInds(我)显示%尺度边界框bb =曲目(i).bboxes(结束,:);BB(:,1:2)=(BB(:,1:2)-1)* displayratio + 1;BB(:,3:4)= BB(:,3:4)* Displayratio;框架= insertshape(帧,...“FilledRectangle”,bb,...“颜色”,跟踪.color(我),...“不透明度”,透明度(i));= insertObjectAnnotation帧(帧,...“矩形”,bb,...num2str (avgConfidence(我)),...“颜色”(i)颜色;结束结束结束框架= insertshape(帧,“矩形”,选择。ROI* displayRatio,...“颜色”,[255,0,0],'行宽',3);步骤(obj.videoplayer,框架);结束
结束