Этот пример показывает, как выполнить автоматическое обнаружение и основанное на движении отслеживание перемещения объектов в видео от стационарной камеры.
Обнаружение перемещения объектов и основанного на движении отслеживания является важными компонентами многих приложений компьютерного зрения, включая распознавание действия, контроль трафика и автомобильную безопасность. Проблема основанного на движении объектного отслеживания может быть разделена на две части:
Обнаружение движущихся объектов в каждом кадре
Соединение обнаружений, соответствующих тому же объекту в зависимости от времени
Обнаружение перемещения объектов использует фоновый алгоритм вычитания на основе Гауссовых моделей смеси. Морфологические операции применяются к получившейся приоритетной маске, чтобы устранить шум. Наконец, анализ блоба обнаруживает группы связанных пикселей, которые, вероятно, будут соответствовать перемещению объектов.
Ассоциация обнаружений к тому же объекту базируется только на движении. Движение каждой дорожки оценивается Фильтром Калмана. Фильтр используется, чтобы предсказать местоположение дорожки в каждом кадре и определить вероятность каждого обнаружения, присваиваемого каждой дорожке.
Обслуживание дорожки становится важным аспектом этого примера. В любом данном кадре некоторые обнаружения могут быть присвоены дорожкам, в то время как другие обнаружения и дорожки могут остаться неприсвоенными. Присвоенные дорожки обновляются с помощью соответствующих обнаружений. Неприсвоенные дорожки отмечены невидимые. Неприсвоенное обнаружение начинает новый трек.
Каждая дорожка проводит подсчет количества последовательных кадров, где это осталось неприсвоенным. Если количество превышает заданный порог, пример принимает, что объект покинул поле представления, и это удаляет дорожку.
Для получения дополнительной информации смотрите Несколько Объектное Отслеживание.
Этим примером является функция с основной частью наверху и стандартными программами помощника в форме вложенных функций ниже.
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 ~isDone(obj.reader) frame = readFrame(); [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 file reader. obj.reader = vision.VideoFileReader('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
: целочисленный 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
Считайте следующий кадр видео из видеофайла.
function frame = readFrame() frame = obj.reader.step(); 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
использует версию Манкреса венгерского алгоритма, чтобы вычислить присвоение, которое минимизирует общую стоимость. Это возвращает 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
чертит ограничительную рамку и метку ID для каждой дорожки на кадре видео и приоритетной маске. Это затем отображает кадр и маску в их соответствующих видеоплеерах.
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