Используйте фильтр Калмана для отслеживания объектов

В этом примере показано, как использовать vision.KalmanFilter объект и configureKalmanFilter функция для отслеживания объектов.

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

function kalmanFilterForTracking

Введение

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

  • Предсказание будущего местоположения объекта

  • Уменьшение шума, вносимого неточными обнаружениями

  • Облегчение процесса ассоциации нескольких объектов к их трекам

Проблемы отслеживания объектов

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

showDetections();

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

Чтобы легко визуализировать всю траекторию объекта, мы накладываем все видеокадры на одно изображение. Метки «+» указывают на центроиды, вычисленные с помощью анализа больших двоичных объектов.

showTrajectory();

Можно отметить две проблемы:

  1. Центр области обычно отличается от центра мяча. Другими словами, существует ошибка в измерении местоположения мяча.

  2. Расположение мяча недоступно, когда он закрыт коробкой, т.е. измерение отсутствует.

Обе эти проблемы можно решить с помощью фильтра Калмана.

Отслеживайте один объект с помощью фильтра Калмана

Используя видео, которое было замечено ранее, trackSingleObject функция показывает, как:

  • Создание vision.KalmanFilter при помощи configureKalmanFilter

  • Использование predict и correct методы в последовательности для устранения шума, присутствующего в системе слежения

  • Использование predict метод сам по себе, чтобы оценить расположение мяча, когда он окклюдирован коробкой

Выбор параметров фильтра Калмана может оказаться сложным. The configureKalmanFilter функция помогает упростить эту задачу. Подробнее об этом можно узнать далее в примере.

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

frame            = [];  % A video frame
detectedLocation = [];  % The detected location
trackedLocation  = [];  % The tracked location
label            = '';  % Label for the ball
utilities        = [];  % Utilities used to process the video

Процедура отслеживания одного объекта показана ниже.

function trackSingleObject(param)
  % Create utilities used for reading video, detecting moving objects,
  % and displaying the results.
  utilities = createUtilities(param);

  isTrackInitialized = false;
  while hasFrame(utilities.videoReader)
    frame = readFrame(utilities.videoReader);

    % Detect the ball.
    [detectedLocation, isObjectDetected] = detectObject(frame);

    if ~isTrackInitialized
      if isObjectDetected
        % Initialize a track by creating a Kalman filter when the ball is
        % detected for the first time.
        initialLocation = computeInitialLocation(param, detectedLocation);
        kalmanFilter = configureKalmanFilter(param.motionModel, ...
          initialLocation, param.initialEstimateError, ...
          param.motionNoise, param.measurementNoise);

        isTrackInitialized = true;
        trackedLocation = correct(kalmanFilter, detectedLocation);
        label = 'Initial';
      else
        trackedLocation = [];
        label = '';
      end

    else
      % Use the Kalman filter to track the ball.
      if isObjectDetected % The ball was detected.
        % Reduce the measurement noise by calling predict followed by
        % correct.
        predict(kalmanFilter);
        trackedLocation = correct(kalmanFilter, detectedLocation);
        label = 'Corrected';
      else % The ball was missing.
        % Predict the ball's location.
        trackedLocation = predict(kalmanFilter);
        label = 'Predicted';
      end
    end

    annotateTrackedObject();
  end % while

  showTrajectory();
end

Существует два различных сценария, которые адресует фильтр Калмана:

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

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

Вы можете увидеть траекторию мяча, наложив все видеокадры.

param = getDefaultParameters();  % get Kalman configuration that works well
                                 % for this example

trackSingleObject(param);  % visualize the results

Исследуйте опции строения фильтра Калмана

Настройка фильтра Калмана может оказаться очень сложной задачей. Помимо базового понимания фильтра Калмана, он часто требует экспериментов в порядок, чтобы придумать набор подходящих параметров конфигурации. The trackSingleObject функция, определенная выше, помогает вам исследовать различные опции строения, предлагаемые configureKalmanFilter функция.

The configureKalmanFilter функция возвращает объект фильтра Калмана. Необходимо указать пять входных параметров.

kalmanFilter = configureKalmanFilter(MotionModel, InitialLocation,
         InitialEstimateError, MotionNoise, MeasurementNoise)

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

param = getDefaultParameters();         % get parameters that work well
param.motionModel = 'ConstantVelocity'; % switch from ConstantAcceleration
                                        % to ConstantVelocity
% After switching motion models, drop noise specification entries
% corresponding to acceleration.
param.initialEstimateError = param.initialEstimateError(1:2);
param.motionNoise          = param.motionNoise(1:2);

trackSingleObject(param); % visualize the results

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

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

param = getDefaultParameters();  % get parameters that work well
param.initialLocation = [0, 0];  % location that's not based on an actual detection
param.initialEstimateError = 100*ones(1,3); % use relatively small values

trackSingleObject(param); % visualize the results

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

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

param = getDefaultParameters();
param.segmentationThreshold = 0.0005; % smaller value resulting in noisy detections
param.measurementNoise      = 12500;  % increase the value to compensate
                                      % for the increase in measurement noise

trackSingleObject(param); % visualize the results

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

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

Примечание: В порядок упрощения процесса строения в приведенных выше примерах мы использовали configureKalmanFilter функция. Эта функция делает несколько предположений. Для получения дополнительной информации см. документацию функции. Если вам требуется больший уровень контроля над процессом строения, можно использовать vision.KalmanFilter объект непосредственно.

Отслеживайте несколько объектов с помощью фильтра Калмана

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

  • Несколько обнаружений должны быть связаны с правильными треками

  • Вы должны обрабатывать новые объекты, появляющиеся в сцене

  • Тождества объекта должен быть сохранен, когда несколько объектов объединяются в одно обнаружение

The vision.KalmanFilter объект вместе со assignDetectionsToTracks функция может помочь решить проблемы

  • Назначение обнаружений трекам

  • Определение, соответствует ли обнаружение новому объекту, другими словами, создание трека

  • Так же, как и в случае окклюдированного отдельного объекта, предсказание может использоваться, чтобы помочь разделить объекты, которые близки друг к другу

Чтобы узнать больше об использовании фильтра Калмана для отслеживания нескольких объектов, смотрите пример под названием Motion-Based Multiple Object Tracking.

Служебные функции, используемая в примере

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

Получите параметры по умолчанию для создания фильтра Калмана и для сегментации мяча.

function param = getDefaultParameters
  param.motionModel           = 'ConstantAcceleration';
  param.initialLocation       = 'Same as first detection';
  param.initialEstimateError  = 1E5 * ones(1, 3);
  param.motionNoise           = [25, 10, 1];
  param.measurementNoise      = 25;
  param.segmentationThreshold = 0.05;
end

Обнаружите и аннотируйте мяч в видео.

function showDetections()
  param = getDefaultParameters();
  utilities = createUtilities(param);
  trackedLocation = [];

  idx = 0;
  while hasFrame(utilities.videoReader)
    frame = readFrame(utilities.videoReader);
    detectedLocation = detectObject(frame);
    % Show the detection result for the current video frame.
    annotateTrackedObject();

    % To highlight the effects of the measurement noise, show the detection
    % results for the 40th frame in a separate figure.
    idx = idx + 1;
    if idx == 40
      combinedImage = max(repmat(utilities.foregroundMask, [1,1,3]), im2single(frame));
      figure, imshow(combinedImage);
    end
  end % while

  % Close the window which was used to show individual video frame.
  uiscopes.close('All');
end

Обнаружите мяч в текущем видеокадре.

function [detection, isObjectDetected] = detectObject(frame)
  grayImage = rgb2gray(im2single(frame));
  utilities.foregroundMask = step(utilities.foregroundDetector, grayImage);
  detection = step(utilities.blobAnalyzer, utilities.foregroundMask);
  if isempty(detection)
    isObjectDetected = false;
  else
    % To simplify the tracking process, only use the first detected object.
    detection = detection(1, :);
    isObjectDetected = true;
  end
end

Показать текущие результаты обнаружения и отслеживания.

function annotateTrackedObject()
  accumulateResults();
  % Combine the foreground mask with the current video frame in order to
  % show the detection result.
  combinedImage = max(repmat(utilities.foregroundMask, [1,1,3]), im2single(frame));

  if ~isempty(trackedLocation)
    shape = 'circle';
    region = trackedLocation;
    region(:, 3) = 5;
    combinedImage = insertObjectAnnotation(combinedImage, shape, ...
      region, {label}, 'Color', 'red');
  end
  step(utilities.videoPlayer, combinedImage);
end

Показать траекторию мяча путем наложения всех видеокадров на верхнюю часть друг друга.

function showTrajectory
  % Close the window which was used to show individual video frame.
  uiscopes.close('All');

  % Create a figure to show the processing results for all video frames.
  figure; imshow(utilities.accumulatedImage/2+0.5); hold on;
  plot(utilities.accumulatedDetections(:,1), ...
    utilities.accumulatedDetections(:,2), 'k+');

  if ~isempty(utilities.accumulatedTrackings)
    plot(utilities.accumulatedTrackings(:,1), ...
      utilities.accumulatedTrackings(:,2), 'r-o');
    legend('Detection', 'Tracking');
  end
end

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

function accumulateResults()
  utilities.accumulatedImage      = max(utilities.accumulatedImage, frame);
  utilities.accumulatedDetections ...
    = [utilities.accumulatedDetections; detectedLocation];
  utilities.accumulatedTrackings  ...
    = [utilities.accumulatedTrackings; trackedLocation];
end

В целях рисунка выберите начальное местоположение, используемое фильтром Калмана.

function loc = computeInitialLocation(param, detectedLocation)
  if strcmp(param.initialLocation, 'Same as first detection')
    loc = detectedLocation;
  else
    loc = param.initialLocation;
  end
end

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

function utilities = createUtilities(param)
  % Create System objects for reading video, displaying video, extracting
  % foreground, and analyzing connected components.
  utilities.videoReader = VideoReader('singleball.mp4');
  utilities.videoPlayer = vision.VideoPlayer('Position', [100,100,500,400]);
  utilities.foregroundDetector = vision.ForegroundDetector(...
    'NumTrainingFrames', 10, 'InitialVariance', param.segmentationThreshold);
  utilities.blobAnalyzer = vision.BlobAnalysis('AreaOutputPort', false, ...
    'MinimumBlobArea', 70, 'CentroidOutputPort', true);

  utilities.accumulatedImage      = 0;
  utilities.accumulatedDetections = zeros(0, 2);
  utilities.accumulatedTrackings  = zeros(0, 2);
end
end