В этом примере показано, как выполнять автоматическое обнаружение и отслеживание движущихся объектов в видео со стационарной камеры.
Обнаружение движущихся объектов и отслеживание движения являются важными компонентами многих приложений компьютерного зрения, включая распознавание активности, мониторинг движения и автомобильную безопасность. Проблему отслеживания объектов на основе движения можно разделить на две части:
Обнаружение движущихся объектов в каждом кадре
Связывание обнаружений, соответствующих одному и тому же объекту, с течением времени
При обнаружении движущихся объектов используется фоновый алгоритм вычитания, основанный на гауссовых моделях смесей. Морфологические операции применяются к полученной маске переднего плана для устранения шума. Наконец, анализ больших двоичных объектов обнаруживает группы связанных пикселей, которые, вероятно, соответствуют движущимся объектам.
Связь обнаружений с одним и тем же объектом основана исключительно на движении. Движение каждой дорожки оценивается фильтром Калмана. Фильтр используется для прогнозирования местоположения дорожки в каждом кадре и определения вероятности назначения каждого обнаружения каждой дорожке.
Ведение отслеживания становится важным аспектом этого примера. В любом данном кадре некоторые обнаружения могут быть назначены дорожкам, в то время как другие обнаружения и дорожки могут оставаться неназначенными. Назначенные дорожки обновляются с использованием соответствующих обнаружений. Неприсвоенные дорожки помечены как невидимые. Неназначенное обнаружение начинает новую дорожку.
Каждая дорожка ведет подсчет количества последовательных кадров, где она осталась неназначенной. Если счетчик превышает заданное пороговое значение, в примере предполагается, что объект покинул поле зрения и удаляет дорожку.
Дополнительные сведения см. в разделе Отслеживание нескольких объектов.
Этот пример является функцией с основным корпусом в верхней части и вспомогательными подпрограммами в виде вложенных функций.
function MotionBasedMultiObjectTrackingExample()
% Create System objects used for reading video, detecting moving objects, % and displaying the results. obj = setupSystemObjects(); tracks = initializeTracks(); % Create an empty array of tracks. nextId = 1; % ID of the next track % Detect moving objects, and track them across video frames. while hasFrame(obj.reader) frame = readFrame(obj.reader); [centroids, bboxes, mask] = detectObjects(frame); predictNewLocationsOfTracks(); [assignments, unassignedTracks, unassignedDetections] = ... detectionToTrackAssignment(); updateAssignedTracks(); updateUnassignedTracks(); deleteLostTracks(); createNewTracks(); displayTrackingResults(); end
![]()
![]()
Создание системных объектов, используемых для чтения видеокадров, обнаружения объектов переднего плана и отображения результатов.
function obj = setupSystemObjects() % Initialize Video I/O % Create objects for reading a video from a file, drawing the tracked % objects in each frame, and playing the video. % Create a video reader. obj.reader = VideoReader('atrium.mp4'); % Create two video players, one to display the video, % and one to display the foreground mask. obj.maskPlayer = vision.VideoPlayer('Position', [740, 400, 700, 400]); obj.videoPlayer = vision.VideoPlayer('Position', [20, 400, 700, 400]); % Create System objects for foreground detection and blob analysis % The foreground detector is used to segment moving objects from % the background. It outputs a binary mask, where the pixel value % of 1 corresponds to the foreground and the value of 0 corresponds % to the background. obj.detector = vision.ForegroundDetector('NumGaussians', 3, ... 'NumTrainingFrames', 40, 'MinimumBackgroundRatio', 0.7); % Connected groups of foreground pixels are likely to correspond to moving % objects. The blob analysis System object is used to find such groups % (called 'blobs' or 'connected components'), and compute their % characteristics, such as area, centroid, and the bounding box. obj.blobAnalyser = vision.BlobAnalysis('BoundingBoxOutputPort', true, ... 'AreaOutputPort', true, 'CentroidOutputPort', true, ... 'MinimumBlobArea', 400); end
initializeTracks функция создает массив дорожек, где каждая дорожка представляет собой структуру, представляющую движущийся объект в видео. Целью структуры является поддержание состояния отслеживаемого объекта. Состояние состоит из информации, используемой для обнаружения для отслеживания назначения, завершения дорожки и отображения.
Структура содержит следующие поля:
id : идентификатор целого числа дорожки
bbox : текущая ограничительная рамка объекта; используется для отображения
kalmanFilter : объект фильтра Калмана, используемый для отслеживания движения
age : количество кадров с момента первого обнаружения дорожки
totalVisibleCount Общее количество кадров, в которых была обнаружена дорожка (видимая)
consecutiveInvisibleCount : количество последовательных кадров, для которых дорожка не была обнаружена (невидима).
Шумные обнаружения, как правило, приводят к короткоживущим дорожкам. По этой причине в примере объект отображается только после отслеживания некоторого количества кадров. Это происходит, когда totalVisibleCount превышает указанный порог.
Если обнаружение не связано с дорожкой в течение нескольких последовательных кадров, в примере предполагается, что объект покинул поле зрения и удаляет дорожку. Это происходит, когда consecutiveInvisibleCount превышает указанный порог. Дорожка также может быть удалена как шум, если она отслеживалась в течение короткого времени и была помечена как невидимая для большинства кадров.
function tracks = initializeTracks() % create an empty array of tracks tracks = struct(... 'id', {}, ... 'bbox', {}, ... 'kalmanFilter', {}, ... 'age', {}, ... 'totalVisibleCount', {}, ... 'consecutiveInvisibleCount', {}); end
detectObjects функция возвращает центроиды и ограничивающие рамки обнаруженных объектов. Он также возвращает двоичную маску, которая имеет тот же размер, что и входной кадр. Пикселы со значением 1 соответствуют переднему краю, а пикселы со значением 0 соответствуют фону.
Функция выполняет сегментацию движения с помощью детектора переднего плана. Затем он выполняет морфологические операции над получаемой двоичной маской для удаления шумных пикселей и заполнения отверстий в оставшихся блобах.
function [centroids, bboxes, mask] = detectObjects(frame) % Detect foreground. mask = obj.detector.step(frame); % Apply morphological operations to remove noise and fill in holes. mask = imopen(mask, strel('rectangle', [3,3])); mask = imclose(mask, strel('rectangle', [15, 15])); mask = imfill(mask, 'holes'); % Perform blob analysis to find connected components. [~, centroids, bboxes] = obj.blobAnalyser.step(mask); end
Используйте фильтр Калмана для прогнозирования центроида каждой дорожки в текущем кадре и соответствующим образом обновите ее ограничивающую рамку.
function predictNewLocationsOfTracks() for i = 1:length(tracks) bbox = tracks(i).bbox; % Predict the current location of the track. predictedCentroid = predict(tracks(i).kalmanFilter); % Shift the bounding box so that its center is at % the predicted location. predictedCentroid = int32(predictedCentroid) - bbox(3:4) / 2; tracks(i).bbox = [predictedCentroid, bbox(3:4)]; end end
Назначение обнаружений объектов в текущем кадре существующим дорожкам выполняется путем минимизации затрат. Стоимость определяется как отрицательная логарифмическая вероятность обнаружения, соответствующего дорожке.
Алгоритм состоит из двух этапов:
Шаг 1: Расчет стоимости назначения каждого обнаружения каждой дорожке с помощью distance способ vision.KalmanFilter object™ системы. Стоимость учитывает евклидово расстояние между предсказанным центроидом дорожки и центроидом обнаружения. Он также включает в себя уверенность предсказания, которая поддерживается фильтром Калмана. Результаты сохраняются в матрице MxN, где M - количество дорожек, а N - количество обнаружений.
Шаг 2: Решение проблемы назначения, представленной матрицей затрат, с помощью assignDetectionsToTracks функция. Функция принимает матрицу затрат и затраты на то, чтобы не назначать никаких обнаружений дорожке.
Значение стоимости отсутствия назначения обнаружения дорожке зависит от диапазона значений, возвращаемых distance способ vision.KalmanFilter. Это значение должно быть настроено экспериментально. Установка слишком низкого значения увеличивает вероятность создания новой дорожки и может привести к фрагментации дорожки. Установка его слишком высоким может привести к одной дорожке, соответствующей ряду отдельных движущихся объектов.
assignDetectionsToTracks функция использует версию Munkres венгерского алгоритма для вычисления назначения, которое минимизирует общую стоимость. Он возвращает матрицу M x 2, содержащую соответствующие индексы назначенных дорожек и обнаружений в двух его столбцах. Он также возвращает индексы дорожек и обнаружений, которые остались неназначенными.
function [assignments, unassignedTracks, unassignedDetections] = ... detectionToTrackAssignment() nTracks = length(tracks); nDetections = size(centroids, 1); % Compute the cost of assigning each detection to each track. cost = zeros(nTracks, nDetections); for i = 1:nTracks cost(i, :) = distance(tracks(i).kalmanFilter, centroids); end % Solve the assignment problem. costOfNonAssignment = 20; [assignments, unassignedTracks, unassignedDetections] = ... assignDetectionsToTracks(cost, costOfNonAssignment); end
updateAssignedTracks функция обновляет каждую назначенную дорожку с соответствующим обнаружением. Он вызывает correct способ vision.KalmanFilter для корректировки оценки местоположения. Далее он сохраняет новую ограничивающую рамку и увеличивает возраст дорожки и общее количество видимых объектов на 1. Наконец, функция устанавливает невидимый счетчик в 0.
function updateAssignedTracks() numAssignedTracks = size(assignments, 1); for i = 1:numAssignedTracks trackIdx = assignments(i, 1); detectionIdx = assignments(i, 2); centroid = centroids(detectionIdx, :); bbox = bboxes(detectionIdx, :); % Correct the estimate of the object's location % using the new detection. correct(tracks(trackIdx).kalmanFilter, centroid); % Replace predicted bounding box with detected % bounding box. tracks(trackIdx).bbox = bbox; % Update track's age. tracks(trackIdx).age = tracks(trackIdx).age + 1; % Update visibility. tracks(trackIdx).totalVisibleCount = ... tracks(trackIdx).totalVisibleCount + 1; tracks(trackIdx).consecutiveInvisibleCount = 0; end end
Отметьте каждую неназначенную дорожку как невидимую и увеличьте ее возраст на 1.
function updateUnassignedTracks() for i = 1:length(unassignedTracks) ind = unassignedTracks(i); tracks(ind).age = tracks(ind).age + 1; tracks(ind).consecutiveInvisibleCount = ... tracks(ind).consecutiveInvisibleCount + 1; end end
deleteLostTracks удаляет дорожки, которые были невидимы слишком много последовательных кадров. Он также удаляет недавно созданные дорожки, которые были невидимы для слишком большого количества кадров в целом.
function deleteLostTracks() if isempty(tracks) return; end invisibleForTooLong = 20; ageThreshold = 8; % Compute the fraction of the track's age for which it was visible. ages = [tracks(:).age]; totalVisibleCounts = [tracks(:).totalVisibleCount]; visibility = totalVisibleCounts ./ ages; % Find the indices of 'lost' tracks. lostInds = (ages < ageThreshold & visibility < 0.6) | ... [tracks(:).consecutiveInvisibleCount] >= invisibleForTooLong; % Delete lost tracks. tracks = tracks(~lostInds); end
Создание новых дорожек из неназначенных обнаружений. Предположим, что любое неназначенное обнаружение является началом новой дорожки. На практике можно использовать другие сигналы для устранения шумных обнаружений, таких как размер, местоположение или внешний вид.
function createNewTracks() centroids = centroids(unassignedDetections, :); bboxes = bboxes(unassignedDetections, :); for i = 1:size(centroids, 1) centroid = centroids(i,:); bbox = bboxes(i, :); % Create a Kalman filter object. kalmanFilter = configureKalmanFilter('ConstantVelocity', ... centroid, [200, 50], [100, 25], 100); % Create a new track. newTrack = struct(... 'id', nextId, ... 'bbox', bbox, ... 'kalmanFilter', kalmanFilter, ... 'age', 1, ... 'totalVisibleCount', 1, ... 'consecutiveInvisibleCount', 0); % Add it to the array of tracks. tracks(end + 1) = newTrack; % Increment the next id. nextId = nextId + 1; end end
displayTrackingResults функция рисует ограничивающую рамку и идентификатор метки для каждой дорожки на видеокадре и маске переднего плана. Затем он отображает кадр и маску в соответствующих видеопроигрывателях.
function displayTrackingResults() % Convert the frame and the mask to uint8 RGB. frame = im2uint8(frame); mask = uint8(repmat(mask, [1, 1, 3])) .* 255; minVisibleCount = 8; if ~isempty(tracks) % Noisy detections tend to result in short-lived tracks. % Only display tracks that have been visible for more than % a minimum number of frames. reliableTrackInds = ... [tracks(:).totalVisibleCount] > minVisibleCount; reliableTracks = tracks(reliableTrackInds); % Display the objects. If an object has not been detected % in this frame, display its predicted bounding box. if ~isempty(reliableTracks) % Get bounding boxes. bboxes = cat(1, reliableTracks.bbox); % Get ids. ids = int32([reliableTracks(:).id]); % Create labels for objects indicating the ones for % which we display the predicted rather than the actual % location. labels = cellstr(int2str(ids')); predictedTrackInds = ... [reliableTracks(:).consecutiveInvisibleCount] > 0; isPredicted = cell(size(labels)); isPredicted(predictedTrackInds) = {' predicted'}; labels = strcat(labels, isPredicted); % Draw the objects on the frame. frame = insertObjectAnnotation(frame, 'rectangle', ... bboxes, labels); % Draw the objects on the mask. mask = insertObjectAnnotation(mask, 'rectangle', ... bboxes, labels); end end % Display the mask and the frame. obj.maskPlayer.step(mask); obj.videoPlayer.step(frame); end
В этом примере создана система на основе движения для обнаружения и отслеживания нескольких движущихся объектов. Попробуйте использовать другое видео, чтобы узнать, можно ли обнаружить и отследить объекты. Попробуйте изменить параметры для этапов обнаружения, назначения и удаления.
Отслеживание в этом примере было основано исключительно на движении с предположением, что все объекты движутся по прямой с постоянной скоростью. Когда движение объекта значительно отклоняется от этой модели, пример может привести к ошибкам отслеживания. Обратите внимание на ошибку в отслеживании человека с меткой # 12, когда он закрыт деревом.
Вероятность ошибок отслеживания может быть уменьшена с помощью более сложной модели движения, такой как постоянное ускорение, или с помощью нескольких фильтров Калмана для каждого объекта. Кроме того, можно включить другие сигналы для связывания обнаружений с течением времени, такие как размер, форма и цвет.
end