主要内容

从行驶中的汽车中追踪行人

这个例子展示了如何使用安装在移动汽车上的摄像头来跟踪行人。

概述

本示例展示了如何在移动摄像机的视频中自动检测和跟踪人。它展示了适用于移动摄像机的跟踪系统的灵活性,这是汽车安全应用的理想选择。与固定摄像机示例不同,基于运动的多目标跟踪,这个例子包含几个额外的算法步骤。这些步骤包括人员检测、定制的非最大抑制,以及识别和消除误报警轨迹的启发式方法。欲了解更多信息,请参见多目标跟踪

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

作用行人专用区从MovingCameraExample()开始
%创建用于读取视频、加载先决数据文件、检测行人和显示结果的系统对象。视频文件=“vippedtracking.mp4”;缩放数据文件=“pedScaleTable.mat”辅助文件,帮助确定行人在不同像素位置的大小。obj=设置系统对象(视频文件、缩放数据文件);检测器=peopleDetectorACF(加州理工学院的);创建一个空轨道数组。跟踪= initializeTracks ();%下一首曲目的ID。nextId = 1;%设置全局参数。选择。投资回报率= [40 95 400 140];%一个矩形[x,y,w,h],将加工区域限制在地面位置。选择。scThresh = 0.3;在估计被检测行人的尺度时,控制误差容忍度的阈值。option.gatingThresh=0.9;%在检测和轨迹之间拒绝候选匹配的阈值。选择。gatingCost = 100;%分配成本矩阵的一个大值,强制拒绝候选匹配。选择。costOfNonAssignment = 10;控制创建新音轨的可能性的调谐参数。选择。timeWindowSize = 16;用于指定稳定音轨置信度所需的帧数的调谐参数。option.confidenceThresh=2;确定跟踪是真、正还是假警报的阈值。选择。年龄Thresh = 8;%确定赛道为真正所需的最小长度的阈值。选择。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)打破终止终止

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

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

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

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

  • 投资回报率:以[x,y,w,h]形式表示的感兴趣区域。它将处理区域限制为地面位置。

  • scThresh:尺度估计的公差阈值。当检测到的尺度与预期尺度之间的差异超过容差时,认为候选检测是不现实的,并从输出中删除。

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

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

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

  • timeWindowSize:估计轨道置信度所需的帧数。

  • 自信:确定轨迹是否为真阳性的置信阈值。

  • ageThresh:赛道的最小长度为真正。

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

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

setupSystemObjects函数创建用于读取和显示视频帧的系统对象,并加载比例数据文件。

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

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

初始化跟踪

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

该结构包含以下字段:

  • 身份证件:轨迹的整数ID。

  • 颜色:显示轨道的颜色。

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

  • 分数: n × 1向量,记录人检测器的分类分数,当前检测分数在最后一行。

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

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

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

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

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

作用跟踪= initializeTracks ()创建一个空轨道数组跟踪=结构(...“身份证”, {},...“颜色”, {},...“bboxes”, {},...“分数”, {},...“kalmanFilter”, {},...“年龄”, {},...“totalVisibleCount”, {},...“信心”, {},...“预定位”, {});终止

检测人

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

  • 重心:一个N×2矩阵,每行的形式为[x,y]。

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

  • 分数:每个元素的N×1向量是对应帧的分类分数。

作用[centroids, bboxes, scores] = detectPeople()%调整图像的大小以增加行人的分辨率。%这有助于检测离摄像机更远的人。resizeRatio = 1.5;frame = imresize(frame, resizeatio,抗锯齿的、假);%在感兴趣的区域内运行ACF人员检测器%检测候选人。[bboxes, scores] =检测(检测器,帧,选项)。投资回报率,...“WindowStride”2,...“NumScaleLevels”4...“SelectStrongest”、假);%根据脚的位置查找行人的估计高度。高度=bboxes(:,4)/resizeRatio;y=(bboxes(:,2)-1)/resizeRatio+1;yfoot=min(长度(对象可缩放表),圆形(y+高度));estHeight=对象可缩放表(yfoot);%删除大小偏离预期大小的检测,%由校准刻度估计提供。无效=abs(estHeight高度)>estHeight*option.scThresh;bboxes(无效,:)=[];分数(无效,:)=[];%应用非最大抑制来选择最强的包围框。[bboxes,scores]=选择strongestbbox(bboxes,scores,...“比率类型”“最小值”“OverlapThreshold”, 0.6);计算质心如果Isempty (bboxes) centroids = [];其他的质心=[(b盒(:,1)+b盒(:,3)/2),...(b盒子(:,2)+b盒子(:,4)/2)];终止终止

预测现有轨道的新位置

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

作用predictNewLocationsOfTracks ()i=1:长度(轨道)获得这条轨道上的最后一个边界框。bbox = (i)。bboxes (,);预测赛道的当前位置。predictedCentroid =预测(跟踪(i) .kalmanFilter);%移动边界框,使其中心位于预测位置。跟踪(i)。predPosition = [predictedCentroid - bbox(3:4)/2, bbox(3:4)];终止终止

为轨道分配检测

将当前帧中的对象检测分配到现有轨道是通过最小化代价来完成的。成本是用bboxOverlapRatio函数,为预测的包围盒与检测到的包围盒的重叠率。在这个例子中,由于视频的高帧率和人的低运动速度,我们假设人会在连续的帧中逐渐移动。

该算法包括两个步骤:

步骤1:计算分配每个探测到每个轨道的成本bboxOverlapRatio衡量。当人们接近或远离相机时,他们的运动不能仅靠质心来准确描述。代价考虑了图像平面上的距离以及包围盒的规模。这就避免了将远离相机的探测对象分配到离相机更近的地方,即使它们的质心重合。这个代价函数的选择将简化计算,而无需诉诸更复杂的动态模型。结果存储在一个MxN矩阵中,其中M为跟踪数,N为检测数。

步骤2:用矩阵求解代价矩阵表示的分配问题分配检测到跟踪函数。该函数采用代价矩阵和不为轨道分配任何检测的代价。

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

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

作用[assignments, unassigndtracks, unassignddetections] =...detectionToTrackAssignment ()计算预测框与的重叠率%检测框,并计算分配每个检测的成本%到每个轨道。当预测箱为时,成本最小与检测到的bbox完全对齐%(重叠率为1)predBboxes =重塑([跟踪(:)。predPosition], []) ';cost = 1 - bboxOverlapRatio(predBboxes, bboxes);%强制优化步骤忽略某些匹配%设置关联成本为一个较大的数字。请注意,这% number与下面的“costOfNonAssignment”不同。%这在选通(删除不现实的匹配)时非常有用%技术应用。cost(cost > option.gatingThresh) = 1 + option.gatingCost;%解决分配问题。[assignments, unassigndtracks, unassignddetections] =...分配检测跟踪(成本、期权、不分配成本);终止

更新指定的跟踪

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)。年龄= tracks(trackIdx).age + 1;%更新轨道的得分历史跟踪(trackIdx)。分数= [tracks(trackIdx).scores; scores(detectionIdx)];%更新可见性。跟踪(trackIdx)。totalVisibleCount=...跟踪(trackIdx)。totalVisibleCount+ 1;%根据最大检测值调整航迹置信度在过去的'timeWindowSize'帧中的%得分。T=min(option.timeWindowSize,length(tracks(trackIdx).scores));scores=tracks(trackIdx).scores(end-T+1:end);tracks(trackIdx).confidence=[max(scores),mean(scores)];终止终止

更新未赋值的跟踪

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(选项。timeWindowSize,length(tracks(idx).scores)); score = tracks(idx).scores(end-T+1:end); tracks(idx).confidence = [max(score), mean(score)];终止终止

删除了跟踪

deleteLostTracks函数删除在太多连续帧中不可见的轨迹。它还删除最近创建的轨迹,这些轨迹在许多帧中不可见。

噪声检测往往会导致虚假轨道的产生。对于这个例子,我们在以下条件下删除了一个轨道:

  • 这个物体被跟踪了一段时间。这通常发生在错误检测出现了几帧,并为它启动了一个轨道。

  • 对于大多数帧,轨迹被标记为不可见。

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

作用deleteLostTracks ()如果isempty(跟踪)回来终止%计算轨道的年龄占它可见的百分比。年龄=(跟踪(:).age) ';totalVisibleCounts =(跟踪(:).totalVisibleCount) ';visibility = totalVisibleCounts ./年龄;%检查最大检测置信度得分。信心=重塑([轨迹(:).confidence],2,[]);最大置信度=置信度(:,1);找到“丢失”轨道的索引。lostdings = (ages <= option.)ageThresh& visibility <= option.visThresh) |...(maxConfidence < = option.confidenceThresh);%删除丢失的曲目。轨迹=轨迹(~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 =结构(...“身份证”nextId,...“颜色”,255*兰特(1,3),...“bboxes”bbox,...“分数”分数,...“kalmanFilter”kalmanFilter,...“年龄”, 1...“totalVisibleCount”, 1...“信心”(得分,分数),...“预定位”, bbox);%将它添加到轨道数组中。轨道(末端+1)=新轨道;%#好的%增加下一个id。nextId = nextId + 1;终止终止

显示跟踪结果

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

作用displayTrackingResults()displayRatio=4/3;frame=imresize(frame,displayRatio);如果~isempty(tracks) ages = [tracks(:).age]';信心=重塑([轨迹(:).confidence],2,[]);最大置信度=置信度(:,1);avgConfidence = confidence(:, 2);不透明度=最小(0.5,最大(0.1,avgConfidence / 3));nodispids = (ages < option. value);ageThresh& maxConfidence < option.confidenceThresh) |...(年龄<选项。ageThresh / 2);i=1:长度(轨道)如果~noDispInds(一)%缩放边框显示bb = (i)。bboxes(end, :); bb(:,1:2) = (bb(:,1:2)-1)*displayRatio + 1; bb(:,3:4) = bb(:,3:4) * displayRatio; frame = insertShape(frame,...“圆角反射角”bb,...“颜色”,跟踪.color(我),...“不透明度”,不透明度(i));帧=插入对象注释(帧,...“矩形”bb,...num2str (avgConfidence(我)),...“颜色”,跟踪(i) .color);终止终止终止= insertShape帧(帧,“矩形”,选择。投资回报率* displayRatio,...“颜色”, [255, 0,0],“线宽”3);步骤(obj。放像机、框架);终止
终止