Для своих приложений компьютерного зрения у меня нет значительных вычислительных мощностей. Поэтому приходится довольствоваться малым и использовать простые, но эффективные методы.
Здесь мы рассмотрим один из таких методов для выделения движущихся объектов на фоне видео-сцены статически установленной камеры.
Этот сценарий не редкость. Например, подобные используют камеры видеонаблюдения ГИБДД.
Временная Медианная Фильтрация
Чтобы понять суть идеи, которую мы собираемся описать, рассмотрим более простую задачу в одномерном пространстве 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% случаев пиксель принадлежащий фону не перекрыт.
Результат показан ниже

Разделение кадров
Следующий очевидный вопрос заключается в том, можем ли мы создать маску для каждого кадра, которая выделит движущиеся части изображения.
Это достигается за несколько шагов.
- Преобразуем кадр в оттенки серого.
- По циклу всех кадров в видео. Извлекаем текущий кадр и преобразуйте его в оттенки серого.
- Вычисляем абсолютную разницу между текущим кадром и медианным кадром.
- Вычисляем пороговое значение приведенного выше изображение для удаления шума и приводим выходной сигнала к бинарному виду.
Посмотрим код.
# 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)