Отслеживание Нескольких Объектов на Основе Движения

В этом примере показано, как выполнить автоматическое обнаружение и основанное на движении отслеживание перемещения объектов в видео от стационарной камеры.

Обнаружение перемещения объектов и основанного на движении отслеживания является важными компонентами многих приложений компьютерного зрения, включая распознавание активности, контроль трафика и автомобильную безопасность. Проблема основанного на движении объектного отслеживания может быть разделена на две части:

  1. Обнаружение движущихся объектов в каждой системе координат

  2. Соединение обнаружений, соответствующих тому же объекту в зависимости от времени

Для обнаружения движущихся объектов используется алгоритм вычитания фона, основанный на смешанных гауссовских моделях. Морфологические операции применяются к полученной маске переднего плана для устранения шума. Наконец, анализ больших двоичных объектов обнаруживает группы связанных пикселей, которые, вероятно, соответствуют движущимся объектам.

Ассоциация обнаружений к тому же объекту базируется только на движении. Движение каждой дорожки оценивается Фильтром Калмана. Фильтр используется, чтобы предсказать местоположение дорожки в каждой системе координат и определить вероятность каждого обнаружения, присваиваемого каждой дорожке.

Обслуживание дорожки становится важным аспектом этого примера. В любой данной системе координат некоторые обнаружения могут быть присвоены дорожкам, в то время как другие обнаружения и дорожки могут остаться неприсвоенными. Присвоенные дорожки обновляются с помощью соответствующих обнаружений. Неприсвоенные дорожки отмечены невидимые. Неприсвоенное обнаружение начинает новый трек.

Каждая дорожка проводит подсчет количества последовательных систем координат, где это осталось неприсвоенным. Если количество превышает заданный порог, пример принимает, что объект покинул поле представления, и это удаляет дорожку.

Для получения дополнительной информации смотрите Несколько Объектное Отслеживание.

Этим примером является функция с основной частью наверху и стандартными программами помощника в форме вложенных функций ниже.

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