Отслеживание пешеходов от движущегося автомобиля

Этот пример показывает, как отследить пешеходов, использующих камеру, смонтированную в движущемся автомобиле.

Обзор

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

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

function PedestrianTrackingFromMovingCameraExample()
% Create system objects used for reading video, loading prerequisite data file, detecting pedestrians, and displaying the results.
videoFile       = 'vippedtracking.mp4';
scaleDataFile   = 'pedScaleTable.mat'; % An auxiliary file that helps to determine the size of a pedestrian at different pixel locations.

obj = setupSystemObjects(videoFile, scaleDataFile);

detector = peopleDetectorACF('caltech');

% Create an empty array of tracks.
tracks = initializeTracks();

% ID of the next track.
nextId = 1;

% Set the global parameters.
option.ROI                  = [40 95 400 140];  % A rectangle [x, y, w, h] that limits the processing area to ground locations.
option.scThresh             = 0.3;              % A threshold to control the tolerance of error in estimating the scale of a detected pedestrian.
option.gatingThresh         = 0.9;              % A threshold to reject a candidate match between a detection and a track.
option.gatingCost           = 100;              % A large value for the assignment cost matrix that enforces the rejection of a candidate match.
option.costOfNonAssignment  = 10;               % A tuning parameter to control the likelihood of creation of a new track.
option.timeWindowSize       = 16;               % A tuning parameter to specify the number of frames required to stabilize the confidence score of a track.
option.confidenceThresh     = 2;                % A threshold to determine if a track is true positive or false alarm.
option.ageThresh            = 8;                % A threshold to determine the minimum length required for a track being true positive.
option.visThresh            = 0.6;              % A threshold to determine the minimum visibility value for a track being true positive.

% Detect people and track them across video frames.
stopFrame = 1629; % stop on an interesting frame with several pedestrians
for fNum = 1:stopFrame
    frame   = readFrame();

    [centroids, bboxes, scores] = detectPeople();

    predictNewLocationsOfTracks();

    [assignments, unassignedTracks, unassignedDetections] = ...
        detectionToTrackAssignment();

    updateAssignedTracks();
    updateUnassignedTracks();
    deleteLostTracks();
    createNewTracks();

    displayTrackingResults();

    % Exit the loop if the video player figure is closed.
    if ~isOpen(obj.videoPlayer)
        break;
    end
end

Вспомогательный вход и глобальные параметры системы слежения

Эта система слежения требует файла данных, который содержит информацию, которая связывает пиксельное местоположение в изображении к размеру ограничительной рамки, отмечающей местоположение пешехода. Эти предварительные знания хранятся в векторном pedScaleTable. Энная запись в pedScaleTable представляет предполагаемую высоту взрослого человека в пикселях. Индекс n ссылается на аппроксимированную Y-координату ног пешехода.

Чтобы получить такой вектор, набор учебных изображений был взят с той же точки зрения и в подобной сцене к тестовой среде. Обучение отображает содержавшие изображения пешеходов на переменных расстояниях от камеры. Используя imageLabeler приложение, вручную аннотировались ограничительные рамки пешеходов в изображениях. Высота ограничительных рамок вместе с местоположением пешеходов в изображении использовалась, чтобы сгенерировать файл данных шкалы посредством регрессии. Вот функция помощника, чтобы показать алгоритмические шаги, чтобы соответствовать модели линейной регрессии: helperTableOfScales.m

Существует также набор глобальных параметров, которые могут быть настроены, чтобы оптимизировать производительность отслеживания. Можно использовать описания ниже, чтобы узнать о том, как эти параметры влияют на производительность отслеживания.

  • ROI: видимая область в форме [x, y, w, h]. Это ограничивает область обработки, чтобы основать местоположения.

  • scThresh: порог Допуска для оценки шкалы. Когда различие между обнаруженной шкалой и ожидаемой шкалой превышает допуск, обнаружение кандидата считается нереалистичным и удалено из вывода.

  • gatingThresh: Пропускание параметра для меры по расстоянию. Когда стоимость соответствия с обнаруженной ограничительной рамкой и предсказанной ограничительной рамкой превышает порог, система удаляет ассоциацию этих двух ограничительных рамок от отслеживания фактора.

  • gatingCost: Значение для матрицы стоимости присвоения, чтобы препятствовать возможному отслеживанию к присвоению обнаружения.

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

  • timeWindowSize: Количество кадров, требуемых оценить уверенность дорожки.

  • confidenceThresh: порог Уверенности, чтобы определить, является ли дорожка положительной истиной.

  • ageThresh: Минимальная длина дорожки, являющейся положительной истиной.

  • visThresh: Минимальный порог видимости, чтобы определить, является ли дорожка положительной истиной.

Создание системных объектов для инициализации системы слежения

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

Вектор pedScaleTable, который хранится в файле данных шкалы, кодирует наши предварительные знания цели и сцены. Если вам обучили регрессор от ваших выборок, можно вычислить ожидаемую высоту в каждом возможном Y-position в изображении. Эти значения хранятся в векторе. Энная запись в pedScaleTable представляет нашу предполагаемую высоту взрослого человека в пикселях. Индекс n ссылается на аппроксимированную Y-координату ног пешехода.

    function obj = setupSystemObjects(videoFile,scaleDataFile)
        % Initialize Video I/O
        % Create objects for reading a video from a file, drawing the
        % detected and tracked people in each frame, and playing the video.

        % Create a video file reader.
        obj.reader = vision.VideoFileReader(videoFile, 'VideoOutputDataType', 'uint8');

        % Create a video player.
        obj.videoPlayer = vision.VideoPlayer('Position', [29, 597, 643, 386]);

        % Load the scale data file
        ld = load(scaleDataFile, 'pedScaleTable');
        obj.pedScaleTable = ld.pedScaleTable;
    end

Инициализируйте дорожки

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

Структура содержит следующие поля:

  • id: целочисленный ID дорожки.

  • color: цвет дорожки для цели отображения.

  • bboxes: матрица N-4, чтобы представлять ограничительные рамки объекта с текущим полем в последней строке. Каждая строка имеет форму [x, y, ширина, высота].

  • scores: вектор N-1, чтобы записать счет классификации от детектора человека с текущим обнаружением выигрывает в последней строке.

  • kalmanFilter: объект Фильтра Калмана используется для основанного на движении отслеживания. Мы отслеживаем центральную точку объекта в изображении;

  • age: количество кадров начиная с дорожки было инициализировано.

  • totalVisibleCount: общее количество кадров, в которых объект был обнаружен (видимый).

  • confidence: пара двух чисел, чтобы представлять, как уверенный мы доверяем дорожке. Это хранит максимум и средние очки обнаружения в прошлом в предопределенном окне времени.

  • predPosition: предсказанная ограничительная рамка в следующем кадре.

    function tracks = initializeTracks()
        % Create an empty array of tracks
        tracks = struct(...
            'id', {}, ...
            'color', {}, ...
            'bboxes', {}, ...
            'scores', {}, ...
            'kalmanFilter', {}, ...
            'age', {}, ...
            'totalVisibleCount', {}, ...
            'confidence', {}, ...
            'predPosition', {});
    end

Считайте кадр видео

Считайте следующий кадр видео из видеофайла.

    function frame = readFrame()
        frame = step(obj.reader);
    end

Обнаружьте людей

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

  • centroids: матрица N-2 с каждой строкой в форме [x, y].

  • bboxes: матрица N-4 с каждой строкой в форме [x, y, ширина, высота].

  • scores: вектор N-1 с каждым элементом является счетом классификации в соответствующем кадре.

    function [centroids, bboxes, scores] = detectPeople()
        % Resize the image to increase the resolution of the pedestrian.
        % This helps detect people further away from the camera.
        resizeRatio = 1.5;
        frame = imresize(frame, resizeRatio, 'Antialiasing',false);

        % Run ACF people detector within a region of interest to produce
        % detection candidates.
        [bboxes, scores] = detect(detector, frame, option.ROI, ...
            'WindowStride', 2,...
            'NumScaleLevels', 4, ...
            'SelectStrongest', false);

        % Look up the estimated height of a pedestrian based on location of their feet.
        height = bboxes(:, 4) / resizeRatio;
        y = (bboxes(:,2)-1) / resizeRatio + 1;
        yfoot = min(length(obj.pedScaleTable), round(y + height));
        estHeight = obj.pedScaleTable(yfoot);

        % Remove detections whose size deviates from the expected size,
        % provided by the calibrated scale estimation.
        invalid = abs(estHeight-height)>estHeight*option.scThresh;
        bboxes(invalid, :) = [];
        scores(invalid, :) = [];

        % Apply non-maximum suppression to select the strongest bounding boxes.
        [bboxes, scores] = selectStrongestBbox(bboxes, scores, ...
                            'RatioType', 'Min', 'OverlapThreshold', 0.6);

        % Compute the centroids
        if isempty(bboxes)
            centroids = [];
        else
            centroids = [(bboxes(:, 1) + bboxes(:, 3) / 2), ...
                (bboxes(:, 2) + bboxes(:, 4) / 2)];
        end
    end

Предскажите новые местоположения существующих дорожек

Используйте Фильтр Калмана, чтобы предсказать центроид каждой дорожки в текущем кадре и обновить его ограничительную рамку соответственно. Мы берем ширину и высоту ограничительной рамки в предыдущем кадре как наш текущий прогноз размера.

    function predictNewLocationsOfTracks()
        for i = 1:length(tracks)
            % Get the last bounding box on this track.
            bbox = tracks(i).bboxes(end, :);

            % 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.
            tracks(i).predPosition = [predictedCentroid - bbox(3:4)/2, bbox(3:4)];
        end
    end

Присвойте обнаружения дорожкам

Присвоение обнаружений объектов в текущем кадре к существующим дорожкам сделано путем минимизации стоимости. Стоимость вычисляется с помощью функции bboxOverlapRatio и является отношением перекрытия между предсказанной ограничительной рамкой и обнаруженной ограничительной рамкой. В этом примере мы принимаем, что человек будет постепенно перемещаться в последовательные кадры из-за высокой частоты кадров видео и низкой скорости движения человека.

Алгоритм включает два шага:

Шаг 1: Вычислите стоимость присвоения каждого обнаружения к каждой дорожке с помощью меры bboxOverlapRatio. Когда люди двигают или далеко от камеры, их движение не будет точно описано одной только центроидной точкой. Стоимость учитывает расстояние на плоскости изображения, а также шкале ограничительных рамок. Это предотвращает обнаружения присвоения далеко от камеры до дорожек ближе к камере, даже если их центроиды совпадают. Выбор этой функции стоимости упростит вычисление, не обращаясь к более сложной динамической модели. Результаты хранятся в матрице MxN, где M является количеством дорожек, и N является количеством обнаружений.

Шаг 2: Решите проблему присвоения, представленную матрицей стоимости использование функции assignDetectionsToTracks. Функция берет матрицу стоимости и стоимость не присвоения любых обнаружений к дорожке.

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

Функция assignDetectionsToTracks использует версию Манкреса венгерского алгоритма, чтобы вычислить присвоение, которое минимизирует общую стоимость. Это возвращает M x 2 матрицы, содержащие соответствующие индексы присвоенных дорожек и обнаружений в ее двух столбцах. Это также возвращает индексы дорожек и обнаружений, которые остались неприсвоенными.

    function [assignments, unassignedTracks, unassignedDetections] = ...
            detectionToTrackAssignment()

        % Compute the overlap ratio between the predicted boxes and the
        % detected boxes, and compute the cost of assigning each detection
        % to each track. The cost is minimum when the predicted bbox is
        % perfectly aligned with the detected bbox (overlap ratio is one)
        predBboxes = reshape([tracks(:).predPosition], 4, [])';
        cost = 1 - bboxOverlapRatio(predBboxes, bboxes);

        % Force the optimization step to ignore some matches by
        % setting the associated cost to be a large number. Note that this
        % number is different from the 'costOfNonAssignment' below.
        % This is useful when gating (removing unrealistic matches)
        % technique is applied.
        cost(cost > option.gatingThresh) = 1 + option.gatingCost;

        % Solve the assignment problem.
        [assignments, unassignedTracks, unassignedDetections] = ...
            assignDetectionsToTracks(cost, option.costOfNonAssignment);
    end

Обновите присвоенные дорожки

Функция updateAssignedTracks обновляет каждую присвоенную дорожку с соответствующим обнаружением. Это вызывает метод correct vision.KalmanFilter, чтобы исправить оценку местоположения. Затем, это хранит новую ограничительную рамку путем взятия среднего значения размера недавних до () 4 полей и увеличивает возраст дорожки и общего видимого количества на 1. Наконец, функция настраивает наш счет уверенности к дорожке на основе предыдущих очков обнаружения.

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

            % Stabilize the bounding box by taking the average of the size
            % of recent (up to) 4 boxes on the track.
            T = min(size(tracks(trackIdx).bboxes,1), 4);
            w = mean([tracks(trackIdx).bboxes(end-T+1:end, 3); bbox(3)]);
            h = mean([tracks(trackIdx).bboxes(end-T+1:end, 4); bbox(4)]);
            tracks(trackIdx).bboxes(end+1, :) = [centroid - [w, h]/2, w, h];

            % Update track's age.
            tracks(trackIdx).age = tracks(trackIdx).age + 1;

            % Update track's score history
            tracks(trackIdx).scores = [tracks(trackIdx).scores; scores(detectionIdx)];

            % Update visibility.
            tracks(trackIdx).totalVisibleCount = ...
                tracks(trackIdx).totalVisibleCount + 1;

            % Adjust track confidence score based on the maximum detection
            % score in the past 'timeWindowSize' frames.
            T = min(option.timeWindowSize, length(tracks(trackIdx).scores));
            score = tracks(trackIdx).scores(end-T+1:end);
            tracks(trackIdx).confidence = [max(score), mean(score)];
        end
    end

Обновите неприсвоенные дорожки

Функция updateUnassignedTracks отмечает каждую неприсвоенную дорожку как невидимая, увеличивает ее возраст на 1 и добавляет предсказанную ограничительную рамку к дорожке. Уверенность обнуляется, поскольку мы не уверены, почему она не была присвоена дорожке.

    function updateUnassignedTracks()
        for i = 1:length(unassignedTracks)
            idx = unassignedTracks(i);
            tracks(idx).age = tracks(idx).age + 1;
            tracks(idx).bboxes = [tracks(idx).bboxes; tracks(idx).predPosition];
            tracks(idx).scores = [tracks(idx).scores; 0];

            % Adjust track confidence score based on the maximum detection
            % score in the past 'timeWindowSize' frames
            T = min(option.timeWindowSize, length(tracks(idx).scores));
            score = tracks(idx).scores(end-T+1:end);
            tracks(idx).confidence = [max(score), mean(score)];
        end
    end

Удалите потерянные следы

Функция deleteLostTracks удаляет дорожки, которые были невидимы для слишком многих последовательных кадров. Это также удаляет недавно созданные дорожки, которые были невидимы для многих кадров в целом.

Шумные обнаружения имеют тенденцию приводить к созданию ложных дорожек. В данном примере мы удаляем дорожку при следующих условиях:

  • Объект был прослежен в течение короткого времени. Это обычно происходит, когда ложное обнаружение собирается для нескольких кадров, и дорожка инициировалась для него.

  • Дорожка была отмечена невидимая для большинства кадров.

  • Этому не удалось получить сильное обнаружение в прошлых немногих кадрах, которое выражается как максимальный счет уверенности обнаружения.

    function deleteLostTracks()
        if isempty(tracks)
            return;
        end

        % Compute the fraction of the track's age for which it was visible.
        ages = [tracks(:).age]';
        totalVisibleCounts = [tracks(:).totalVisibleCount]';
        visibility = totalVisibleCounts ./ ages;

        % Check the maximum detection confidence score.
        confidence = reshape([tracks(:).confidence], 2, [])';
        maxConfidence = confidence(:, 1);

        % Find the indices of 'lost' tracks.
        lostInds = (ages <= option.ageThresh & visibility <= option.visThresh) | ...
             (maxConfidence <= option.confidenceThresh);

        % Delete lost tracks.
        tracks = tracks(~lostInds);
    end

Создайте новые треки

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

    function createNewTracks()
        unassignedCentroids = centroids(unassignedDetections, :);
        unassignedBboxes = bboxes(unassignedDetections, :);
        unassignedScores = scores(unassignedDetections);

        for i = 1:size(unassignedBboxes, 1)
            centroid = unassignedCentroids(i,:);
            bbox = unassignedBboxes(i, :);
            score = unassignedScores(i);

            % Create a Kalman filter object.
            kalmanFilter = configureKalmanFilter('ConstantVelocity', ...
                centroid, [2, 1], [5, 5], 100);

            % Create a new track.
            newTrack = struct(...
                'id', nextId, ...
                'color', 255*rand(1,3), ...
                'bboxes', bbox, ...
                'scores', score, ...
                'kalmanFilter', kalmanFilter, ...
                'age', 1, ...
                'totalVisibleCount', 1, ...
                'confidence', [score, score], ...
                'predPosition', bbox);

            % Add it to the array of tracks.
            tracks(end + 1) = newTrack; %#ok<AGROW>

            % Increment the next id.
            nextId = nextId + 1;
        end
    end

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

Функция displayTrackingResults чертит цветную ограничительную рамку для каждой дорожки на кадре видео. Уровень прозрачности поля вместе с отображенным счетом указывает на уверенность обнаружений и дорожек.

    function displayTrackingResults()

        displayRatio = 4/3;
        frame = imresize(frame, displayRatio);

        if ~isempty(tracks),
            ages = [tracks(:).age]';
            confidence = reshape([tracks(:).confidence], 2, [])';
            maxConfidence = confidence(:, 1);
            avgConfidence = confidence(:, 2);
            opacity = min(0.5,max(0.1,avgConfidence/3));
            noDispInds = (ages < option.ageThresh & maxConfidence < option.confidenceThresh) | ...
                       (ages < option.ageThresh / 2);

            for i = 1:length(tracks)
                if ~noDispInds(i)

                    % scale bounding boxes for display
                    bb = tracks(i).bboxes(end, :);
                    bb(:,1:2) = (bb(:,1:2)-1)*displayRatio + 1;
                    bb(:,3:4) = bb(:,3:4) * displayRatio;


                    frame = insertShape(frame, ...
                                            'FilledRectangle', bb, ...
                                            'Color', tracks(i).color, ...
                                            'Opacity', opacity(i));
                    frame = insertObjectAnnotation(frame, ...
                                            'rectangle', bb, ...
                                            num2str(avgConfidence(i)), ...
                                            'Color', tracks(i).color);
                end
            end
        end

        frame = insertShape(frame, 'Rectangle', option.ROI * displayRatio, ...
                                'Color', [255, 0, 0], 'LineWidth', 3);

        step(obj.videoPlayer, frame);

    end
end