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