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

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

Обзор

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

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

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(obj.reader);

    [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. n-я запись в pedScaleTable представляет предполагаемый рост взрослого человека в пикселях. Область индекса n ссылается на приблизительную Y-координату ног пешехода.

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

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

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

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

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

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

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

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

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

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

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

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

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

The pedScaleTable вектор, который хранится в файле масштабных данных, кодирует наши предыдущие знания о цели и сцене. Как только вы обучили регрессор из ваших выборок, можно вычислить ожидаемую высоту при каждом возможном положении Y на изображении. Эти значения хранятся в векторе. n-я запись в 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 = VideoReader(videoFile);

        % 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

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

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

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

  • id : Целочисленный идентификатор дорожки.

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

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

  • scores : Вектор N-by-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

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

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

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

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

  • scores Вектор N-by-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 функция. Функция берет матрицу затрат и стоимость не назначения обнаружений дорожке.

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

The assignDetectionsToTracks функция использует версию венгерского алгоритма Munkres, чтобы вычислить назначение, которое минимизирует общую стоимость. Она возвращает матрицу 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

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

The 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

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

The 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

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

The 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

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

The 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