В этом примере показано, как отслеживать большое количество объектов. Генерируется большая стая птиц и глобальный ближайший соседний многообъектный трекер, trackerGNN, используется для оценки движения каждой птицы в стае.
Движение стада моделируется с использованием поведенческой модели, предложенной Рейнольдсом [1]. В этом примере стая состоит из 1000 имитированных птиц, называемых боидами, исходное положение и скорость которых ранее были сохранены. Они следуют трем правилам стекания: предотвращение столкновений, согласование скоростей и центрирование стеков. Каждое правило связано с весом, и общее поведение стада вытекает из относительного взвешивания каждого правила. При этом выбираются гири, которые заставляют стаю облетать определённую точку и создавать плотный центр. Другие настройки веса могут вызвать появление различных вариантов поведения.
Отслеживание такого большого и плотного стада сопряжено с двумя трудностями:
Как эффективно отслеживать 1000 boids?
Как можно отслеживать отдельные боиды в такой плотной среде?
Следующий код моделирует поведение флока в течение 100 шагов по 0,1 секунды. На графике слева показана стая в целом и сюжет справа увеличен на самой плотной части в центре стаи.
s = rng; % Keep the current state of the random number generator rng(2019); % Set the random number generator for repeatable results load("initialFlock.mat","x","v"); flock = helperFlock("NumBoids",size(x,1),"CollisionAviodanceWeight",0.5,... "VelocityMatchingWeight",0.1,"FlockCenteringWeight",0.5,"Velocity",v,... "Position",x,"BoidAcceleration",1); truLabels = string(num2str((1:flock.NumBoids)')); bound = 20; flockCenter = mean(x,1); [tp1,tp2] = helperCreateDisplay(x,bound); % Simulate 100 steps of flocking numSteps = 100; allx = repmat(x,[1 1 numSteps]); dt = 0.1; for i = 1:numSteps [x,v] = move(flock,dt); allx(:,:,i) = x; plotTrack(tp1.Plotters(1),x) inView = findInView(x,-bound+flockCenter,bound+flockCenter); plotTrack(tp2.Plotters(1),x(inView,:),truLabels(inView)) drawnow end
![]()
Трекер определяется так, как показано в примере «Как эффективно отслеживать большое количество объектов».
Видно, что тела следуют по криволинейной траектории и выбирают постоянную модель поворота, определяемую initctekf.
Чтобы ограничить время, необходимое для расчета затрат, уменьшите порог расчета грубых затрат в AssignmentThreshold до низкого значения.
Кроме того, вы выбираете более эффективные Jonker-Volgenant в качестве алгоритма назначения вместо алгоритма по умолчанию Munkres алгоритм.
Вы хотите, чтобы треки были быстро подтверждены и удалены, и установите пороги подтверждения и удаления [2 3] и [2 2] соответственно.
Наконец, вы знаете, что датчик сканирует только часть стада при любом данном сканировании, и поэтому вы устанавливаете HasDetectableTrackIDsInput кому true чтобы иметь возможность передавать обнаруживаемые идентификаторы дорожек в трекер.
В следующей строке показано, как трекер настроен с указанными выше свойствами. Как создать код для трекера, см. в разделе Как создать код C для трекера, и трекер для этого примера сохраняется в функции flockTracker_kernel.m
% tracker = trackerGNN("FilterInitializationFcn",@initctekf,"MaxNumTracks",1500,... % "AssignmentThreshold",[50 800],"Assignment","Jonker-Volgenant",... % "ConfirmationThreshold",[2 3],"DeletionThreshold",[2 2],... % "HasDetectableTrackIDsInput",true);
Затем вы запускаете сценарий и отслеживаете стаю.
Упрощенная модель датчика моделируется с помощью detectFlock поддерживающая функция. Он моделирует датчик, который сканирует стаю слева направо и фиксирует пятую часть пролета стада по оси X в каждом сканировании. Датчик имеет вероятность обнаружения 0,98, и шум моделируется с использованием нормального распределения со стандартным отклонением 0,1 метра относительно каждой составляющей положения.
Датчик сообщает currentScan границы, которые используются для предоставления обнаруживаемых идентификаторов дорожек трекеру.
clear flockTracker_kernel positionSelector = [1 0 0 0 0 0 0; 0 0 1 0 0 0 0; 0 0 0 0 0 1 0]; trackIDs = zeros(0,1,'uint32'); trax = zeros(0,3); bounds = inf(3,2); alltrax = zeros(size(allx)); allIDs = repmat({},1,numSteps); trup2 = tp2.Plotters(1); trap2 = tp2.Plotters(2); trup2.HistoryDepth = 2*trap2.HistoryDepth; clearPlotterData(tp1) clearPlotterData(tp2) for i = 1:numSteps t = i*dt; [detections, currentScan] = detectFlock(allx(:,:,i),t); bounds(1,:) = currentScan; tracksInScan = findInView(trax,bounds(:,1),bounds(:,2)); [tracks,info] = flockTracker_kernel(detections,t,trackIDs(tracksInScan,1)); trax = getTrackPositions(tracks,positionSelector); if ~isempty(tracks) trackIDs = uint32([tracks.TrackID]'); else trackIDs = zeros(0,1,'uint32'); end alltrax(1:size(trax,1),1:3,i) = trax; allIDs{i} = string(trackIDs); helperVisualizeDisplay(tp1,tp2,truLabels,allx,allIDs,alltrax,i) end rng(s); % Reset the random number generator to its previous state
![]()
Следующий GIF показывает производительность трекера в файле mex.

В этом примере показано, как отслеживать большое количество объектов в реалистичном сценарии, где сканирующий датчик сообщает только часть объектов в каждом сканировании. В примере показано, как настроить трекер для большого количества объектов и как использовать идентификаторы обнаруживаемых дорожек для предотвращения удаления дорожек.
[1] Крейг У. Рейнольдс, «Стаи, стада и школы: поведенческая модель», компьютерная графика, том 21, номер 4, июль 1987.
helperCreateDisplay
Функция создает пример отображения и возвращает дескриптор для обоих графиков кинотеатра.
function [tp1,tp2] = helperCreateDisplay(x,bound) f = figure("Visible", "off"); set(f,"Position",[1 1 1425 700]); movegui(f,"center") h1 = uipanel(f,"FontSize",12,"Position",[.01 .01 .48 .98],"Title","Flock View"); h2 = uipanel(f,"FontSize",12,"Position",[.51 .01 .48 .98],"Title","Flock Center"); flockCenter = mean(x,1); a1 = axes(h1,'Position',[0.05 0.05 0.9 0.9]); grid(a1,'on') tp1 = theaterPlot("Parent",a1); % Flock View (Truncated) halfspan = 250; tp1.XLimits = 100*round([-halfspan+flockCenter(1) halfspan+flockCenter(1)]/100); tp1.YLimits = 100*round([-halfspan+flockCenter(2) halfspan+flockCenter(2)]/100); tp1.ZLimits = 100*round([-halfspan+flockCenter(3) halfspan+flockCenter(3)]/100); trackPlotter(tp1,"DisplayName","Truth","HistoryDepth",0,"Marker","^","MarkerSize",4,"ConnectHistory","off"); set(findall(a1,"Type","line","Tag","tpTrackHistory_Truth"),"Color","k"); view(a1,3) legend('Location','NorthEast') % Flock center a2 = axes(h2,'Position',[0.05 0.05 0.9 0.9]); grid(a2,'on') tp2 = theaterPlot("Parent",a2); tp2.XLimits = 10*round([-bound+flockCenter(1) bound+flockCenter(1)]/10); tp2.YLimits = 10*round([-bound+flockCenter(2) bound+flockCenter(2)]/10); tp2.ZLimits = 10*round([-bound+flockCenter(3) bound+flockCenter(3)]/10); trackPlotter(tp2,"DisplayName","Truth","HistoryDepth",0,... "Marker","^","MarkerSize",6,"ConnectHistory","off","FontSize",1); set(findall(a2,"Type","line","Tag","tpTrackHistory_Truth"),"Color","k"); % Track plotters TrackColor = [0 0.4470 0.7410]; % Blue TrackLength = 50; trackPlotter(tp1,"DisplayName","Tracks","HistoryDepth",TrackLength,"ConnectHistory","off",... "Marker",".","MarkerSize",3,"MarkerEdgeColor",TrackColor,"MarkerFaceColor",TrackColor); set(findall(tp1.Parent,"Type","line","Tag","tpTrackHistory_Tracks"),... "Color",TrackColor,"MarkerSize",3,"MarkerEdgeColor",TrackColor); trackPlotter(tp2,"DisplayName","Tracks","HistoryDepth",TrackLength,"ConnectHistory","on",... "Marker","s","MarkerSize",8,"MarkerEdgeColor",TrackColor,"MarkerFaceColor","none","FontSize",1); set (findall(tp2.Parent,"Type","line","Tag","tpTrackPositions_Tracks"),"LineWidth",2); set(findall(tp2.Parent,"Type","line","Tag","tpTrackHistory_Tracks"),"Color",TrackColor,"LineWidth",1); view(a2,3) legend('Location','NorthEast') set(f,'Visible','on') end
detectFlock
Функция моделирует модель датчика. Он возвращает массив обнаружений и текущие пределы сканирования датчика.
function [detections,scanLimits] = detectFlock(x,t) persistent sigma allDetections currentScan numScans numBoids = size(x,1); pd = 0.98; if isempty(sigma) sigma = 0.1; oneDet = objectDetection(0,[0;0;0],"MeasurementNoise",sigma,'ObjectAttributes',struct); allDetections = repmat(oneDet,numBoids,1); currentScan = 1; numScans = 5; end % Vectorized calculation of all the detections x = x + sigma*randn(size(x)); [allDetections.Time] = deal(t); y = mat2cell(x',3,ones(1,size(x,1))); [allDetections.Measurement] = deal(y{:}); % Limit the coverage area based on the number of scans flockXSpan = [min(x(:,1),[],1),max(x(:,1),[],1)]; spanPerScan = (flockXSpan(2)-flockXSpan(1))/numScans; scanLimits = flockXSpan(1) + spanPerScan * [(currentScan-1) currentScan]; inds = and(x(:,1)>=scanLimits(1), x(:,1)<=scanLimits(2)); % Add Pd draw = rand(size(inds)); inds = inds & (draw<pd); dets = allDetections(inds); detections = num2cell(dets); % Promote the scan count currentScan = currentScan+1; if currentScan > numScans currentScan = 1; end end
findInView
Функция возвращает логический массив для позиций, которые находятся в пределах minBound и maxBound.
function inView = findInView(x,minBound,maxBound) inView = false(size(x,1),1); inView(:) = (x(:,1)>minBound(1) & x(:,1)<maxBound(1)) & ... (x(:,2)>minBound(2) & x(:,2)<maxBound(2)) & ... (x(:,3)>minBound(3) & x(:,3)<maxBound(3)); end
helperVisualizeDisplay
Функция отображает флок и дорожки после отслеживания.
function helperVisualizeDisplay(tp1,tp2,truLabels,allx,allIDs,alltrax,i) trup1 = tp1.Plotters(1); trap1 = tp1.Plotters(2); trup2 = tp2.Plotters(1); trap2 = tp2.Plotters(2); plotTrack(trup1,allx(:,:,i)) n = numel(allIDs{i}); plotTrack(trap1,alltrax(1:n,:,i)) bounds = [tp2.XLimits;tp2.YLimits;tp2.ZLimits]; inView = findInView(allx(:,:,i),bounds(:,1),bounds(:,2)); plotTrack(trup2,allx(inView,:,i),truLabels(inView)) inView = findInView(alltrax(1:n,:,i),bounds(:,1),bounds(:,2)); plotTrack(trap2,alltrax(inView,:,i),allIDs{i}(inView)) drawnow end