主要内容

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

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

概述

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

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

函数PedestrianTrackingFromMovingCameraExample ()
%创建用于读取视频、加载先决数据文件、检测行人和显示结果的系统对象。videoFile =“vippedtracking.mp4”;scaleDataFile ='pedscaletable.mat'%一个辅助文件,有助于确定不同像素位置处的行人的大小。obj = setupSystemObjects(videoFile, scaleDataFile); / /设置对象探测器= peopleDetectorACF (加州理工学院的);创建一个空轨道数组。跟踪= initializeTracks ();%下一个曲目的ID。nextId = 1;%设置全局参数。选择。ROI= [40 95 400 140];%限制处理区域在地面位置的矩形[x, y, w, h]。option.scthresh = 0.3;在估计被检测行人的尺度时,控制误差容忍度的阈值。选择。gatingThresh = 0.9;%在检测和轨迹之间拒绝候选匹配的阈值。选择。gatingCost = 100;%分配成本矩阵的一个大值,强制拒绝候选匹配。选择。costOfNonAssignment = 10;控制创建新音轨的可能性的调谐参数。option.timewindowsize = 16;用于指定稳定音轨置信度所需的帧数的调谐参数。选择。confidenceThresh = 2;确定跟踪是真、正还是假警报的阈值。option.ageThresh = 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坐标。

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

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

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

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

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

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

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

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

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

  • 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;结束

初始化跟踪

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

该结构包含以下字段:

  • id: track的整数ID。

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

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

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

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

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

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

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

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

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

检测人

detectPeople函数返回被检测人员的质心、包围框和分类分数。它对返回的检测器的原始输出进行滤波和非最大抑制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”、假);根据行人双脚的位置,估计他们的身高。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) centroids = [];其他的[(bboxes(:, 1) + bboxes(:, 3) / 2],...(bboxes(:, 2) + bboxes(:, 4) / 2)];结束结束

预测现有轨道的新位置

使用Kalman滤波器预测当前帧中的每个轨道的质心,并相应地更新其边界框。我们在前一帧中获取边界框的宽度和高度,作为我们对大小的当前预测。

函数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:用矩阵求解代价矩阵表示的分配问题assignDetectionsToTracks函数。该函数采用代价矩阵和不为轨道分配任何检测的代价。

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

assignDetectionsToTracks函数使用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] =...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).age =曲目(TrackIDX).age + 1;%更新轨道的得分历史跟踪(trackIdx)。分数= [tracks(trackIdx).scores; scores(detectionIdx)];%更新的可见性。跟踪(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).cores));得分=曲目(IDX).scores(结束t + 1:结束);曲目(idx).confidence = [max(得分),平均值(得分)];结束结束

删除了跟踪

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

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

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

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

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

函数deleteLostTracks ()如果isempty(跟踪)返回结束%计算轨道的年龄占它可见的百分比。年龄=(跟踪(:).age) ';totalVisibleCounts =(跟踪(:).totalVisibleCount) ';visibility = totalVisibleCounts ./年龄;%检查最大检测置信度得分。信心=重塑([跟踪(:)。信心]2 [])';maxConfidence = confidence(:, 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 =结构(...“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);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,...“FilledRectangle”bb,...'颜色',跟踪.color(我),...“不透明度”,透明度(i));= insertObjectAnnotation帧(帧,...“矩形”bb,...num2str (avgConfidence(我)),...'颜色',曲目(i).color);结束结束结束= insertShape帧(帧,“矩形”,选择。ROI* displayRatio,...'颜色', [255, 0,0],“线宽”3);步骤(obj。放像机、框架);结束
结束