Основанные на Сопровождении нескольких объектов

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

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

  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 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

Инициализация дорожек

The 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

Обнаружение объектов

The 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. Это значение должно быть настроено экспериментально. Установка слишком низкой увеличивает вероятность создания новой дорожки и может привести к фрагментации дорожки. Установка его слишком высоким может привести к одной дорожке, соответствующей серии отдельных движущихся объектов.

The 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

Обновление назначенных треков

The 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

Удаление потерянных дорожек

The 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

Отображение результатов отслеживания

The 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