Основанный на движении несколько возражают отслеживанию

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

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

  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