В этом примере показано, как использовать vision.KalmanFilter
объект и configureKalmanFilter
функция для отслеживания объектов.
Этот пример является функцией с основным телом в верхней части и стандартными программами в виде вложенных функций.
function kalmanFilterForTracking
Фильтр Калмана имеет много применений, включая приложения в области управления, навигации, компьютерного зрения и эконометрики временных рядов. Этот пример иллюстрирует, как использовать фильтр Калмана для отслеживания объектов и фокусируется на трех важных функциях:
Предсказание будущего местоположения объекта
Уменьшение шума, вносимого неточными обнаружениями
Облегчение процесса ассоциации нескольких объектов к их трекам
Прежде чем показывать использование фильтра Калмана, давайте сначала рассмотрим проблемы отслеживания объекта в видео. На следующем видео показан зеленый мяч, движущийся слева направо на полу.
showDetections();
Белая область над мячом подсвечивает пиксели, обнаруженные с помощью vision.ForegroundDetector
, который отделяет движущиеся объекты от фона. Вычитание фона находит только фрагмент мяча из-за низкой контрастности между мячом и полом. Другими словами, процесс обнаружения не идеален и вводит шум.
Чтобы легко визуализировать всю траекторию объекта, мы накладываем все видеокадры на одно изображение. Метки «+» указывают на центроиды, вычисленные с помощью анализа больших двоичных объектов.
showTrajectory();
Можно отметить две проблемы:
Центр области обычно отличается от центра мяча. Другими словами, существует ошибка в измерении местоположения мяча.
Расположение мяча недоступно, когда он закрыт коробкой, т.е. измерение отсутствует.
Обе эти проблемы можно решить с помощью фильтра Калмана.
Используя видео, которое было замечено ранее, 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