Фильтрация фона видео с OpenCV

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

Здесь мы рассмотрим один из таких методов для выделения движущихся объектов на фоне видео-сцены статически установленной камеры.

Этот сценарий не редкость. Например, подобные используют камеры видеонаблюдения ГИБДД.

Временная Медианная Фильтрация

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

Проведем эксперимент — каждые 10 миллисекунд мы что-то измеряем (скажем, температуру в комнате).

Допустим, температура в помещении составляет 70°F по Фаренгейту.

На приведенном выше рисунке мы показали измерения от двух термометров — хорошего и плохого
На приведенном выше рисунке мы показали измерения от двух термометров — хорошего и плохого

Хороший термометр, слева, показывает 70°F с некоторым гауссовским шумом. Чтобы получить более точную оценку температуры, мы можем просто усреднить значения в течение нескольких секунд. Поскольку шум является гауссовым с положительными и отрицательными значениями, то при вычислении среднего произойдет взаимная компенсация. Действительно, среднее значение в данном конкретном случае составляет 70,01°F.

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

На самом деле, если мы вычислим среднее показаний плохого термометра, то получим 71,07°F. Это явно завышенная оценка.

А мы сможем получить хорошую оценку температуры по этим показаниям?

Мой ответ — ДА. Когда данные содержат выбросы, медиана является более надежной оценкой значения, которое нам надо.

Медиана (статистика) — это значение в середине ряда отсортированных в порядке возрастания или убывания данных.

Медиана наших показаний составляет 70,05°F, что гораздо лучше, чем 71,07°F.

Единственным недостатком является то, что вычисление медианы является более медленным алгоритмом в сравнении с вычислением средне-взвешенного значения.

Использование медианы для оценки фона

Теперь давайте вернемся к проблеме оценки фона, когда камера установлена статично и не меняет своего положения.

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

Для видеоряда мы можем произвольно выбрать несколько кадров (скажем, 25 кадров).

Другими словами, для каждого пикселя мы теперь имеем 25 оценок его принадлежности к фону. До тех пор, пока пиксель не будет накрыт автомобилем или другим движущимся объектом более чем в 50% случаев, медиана пикселя над этими 25 кадрами даст хорошую оценку принадлежности фону в этом фрагменте.

Мы можем повторить это для каждого пикселя и, таким образом, восстановить весь фон.

import numpy as np
import cv2
from skimage import data, filters

# Open Video
cap = cv2.VideoCapture('video.mp4')

# Randomly select 25 frames
frameIds = cap.get(cv2.CAP_PROP_FRAME_COUNT) * np.random.uniform(size=25)

# Store selected frames in an array
frames = []
for fid in frameIds:
    cap.set(cv2.CAP_PROP_POS_FRAMES, fid)
    ret, frame = cap.read()
    frames.append(frame)

# Calculate the median along the time axis
medianFrame = np.median(frames, axis=0).astype(dtype=np.uint8)    

# Display median frame
cv2.imshow('frame', medianFrame)
cv2.waitKey(0)

Как вы можете видеть, мы случайным образом выбираем 25 кадров и вычисляем медиану каждого пикселя над 25 кадрами. Этот медианный кадр является хорошей оценкой фона, если по крайней мере в 50% случаев пиксель принадлежащий фону не перекрыт.

Результат показан ниже

Оценка фона — медианный кадр, вычисленный путем нахождения медианного значения каждого пикселя на 25 кадрах
Оценка фона — медианный кадр, вычисленный путем нахождения медианного значения каждого пикселя на 25 кадрах

Разделение кадров

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

Это достигается за несколько шагов.

  1. Преобразуем кадр в оттенки серого.
  2. По циклу всех кадров в видео. Извлекаем текущий кадр и преобразуйте его в оттенки серого.
  3. Вычисляем абсолютную разницу между текущим кадром и медианным кадром.
  4. Вычисляем пороговое значение приведенного выше изображение для удаления шума и приводим выходной сигнала к бинарному виду.

Посмотрим код.

# Reset frame number to 0
cap.set(cv2.CAP_PROP_POS_FRAMES, 0)

# Convert background to grayscale
grayMedianFrame = cv2.cvtColor(medianFrame, cv2.COLOR_BGR2GRAY)

# Loop over all frames
ret = True
while(ret):

  # Read frame
  ret, frame = cap.read()
  # Convert current frame to grayscale
  frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
  # Calculate absolute difference of current frame and 
  # the median frame
  dframe = cv2.absdiff(frame, grayMedianFrame)
  # Treshold to binarize
  th, dframe = cv2.threshold(dframe, 30, 255, cv2.THRESH_BINARY)
  # Display image
  cv2.imshow('frame', dframe)
  cv2.waitKey(20)

# Release video object
cap.release()

# Destroy all windows
cv2.destroyAllWindows()

Результаты

На видео ниже показан вывод оценки фона и дифференцирования кадров.

 

Источник вдохновения: Simple Background Estimation in Videos using OpenCV (C++/Python)

CC BY-NC 4.0 Фильтрация фона видео с OpenCV, опубликовано К ВВ, лицензия — Creative Commons Attribution-NonCommercial 4.0 International.


Респект и уважуха

Добавить комментарий