Чтение и запись видео в OpenCV очень похоже на чтение и запись изображений. Видео — это не что иное, как серия изображений, которые часто называют кадрами. Итак, все, что вам нужно сделать, это перебрать все кадры в видеопоследовательности, а затем обрабатывать по одному кадру за раз. В этом посте мы покажем, как читать, отображать и записывать видео из файла, последовательности изображений и веб-камеры. Мы также рассмотрим некоторые ошибки, которые могут возникнуть в процессе, и поможем понять, как их исправить.

Этот пост является частью серии статей, посвященных инструментам, основанными на OpenCV, для работы с медиаконтетом. Наиболее яркие из них:

  1. Геометрия формирования изображений
  2. Калибровка камеры с использованием с OpenCV
  3. Дополненная реальность с маркерами ArUco в OpenCV
  4. Примеры гомографии с использованием OpenCV
  5. Ещё много чего…

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

Рекомендую ознакомиться с этими материалами. А здесь — практика работы с видео!

Чтение видео   

Cначала рассмотрим пример кода для чтения видеофайла. Он, по сути, содержит функции для чтения видео с диска и его отображения. По мере продвижения подробно обсудим функции, используемые в этой реализации.

import cv2 

# Создаем объект захвата видео, в этом случае мы читаем видео из файла
import cv2 

# Создаем объект захвата видео, в этом случае мы читаем видео из файла
vid_capture = cv2.VideoCapture('Resources/is42.mp4')

if (vid_capture.isOpened() == False):
	print("Ошибка открытия видеофайла")
# Чтение fps и количества кадров
else:
	# Получить информацию о частоте кадров
	# Можно заменить 5 на CAP_PROP_FPS, это перечисления
	fps = vid_capture.get(5)
	print('Фреймов в секунду: ', fps,'FPS')

	# Получить количество кадров
	# Можно заменить 7 на CAP_PROP_FRAME_COUNT, это перечисления
	frame_count = vid_capture.get(7)
	print('Частота кадров: ', frame_count)
	print('\n-----------------------------\nДля завершения нажмите "q" или Esc...')

file_count = 0
while(vid_capture.isOpened()):
        # Метод vid_capture.read() возвращают кортеж, первым элементом является логическое значение
        # а вторым кадр
	ret, frame = vid_capture.read()
	if ret == True:
		cv2.imshow('Look', frame)
		file_count += 1
		print('Кадр {0:04d}'.format(file_count))
		#writefile = 'Resources/Image_sequence/is42_{0:04d}.jpg'.format(file_count)
		#cv2.imwrite(writefile, frame)
		# 20 в миллисекундах, попробуйте увеличить значение, скажем, 50 и
		# понаблюдайте за изменениями в показе
		key = cv2.waitKey(20)
		
		if (key == ord('q')) or key == 27:
			break
	else:
		break

# Освободить объект захвата видео
vid_capture.release()
cv2.destroyAllWindows()

Это основные функции ввода-вывода видео OpenCV, которые мы здесь обсудим:

  1. cv2.VideoCapture — Создает объект захвата видео, который помогает передавать или отображать видео.
  2. cv2.VideoWriter — сохраняет выходное видео в каталог.
  3. Кроме того, мы также обсуждаем другие необходимые функции, такие как cv2.imshow(), cv2.waitKey() и метод get(), который используется для чтения метаданных видео, таких как высота, ширина кадра, частота кадров и т. д.

 
Уже традиционно, в качестве видео будем использовать заставку с портала студентов Бизнес-информатики, а вы будете читать видео выше («is42.mp4») и отображать его. После того, как вы удачно просмотрите этот ролик, в приведенном выше коде раскомментируйте строки 33 и 34 для того, чтобы в папку /Resources/Image_sequence записать раскадровку этого ролика. Должно получится 246 файлов в формате .jpg, которые нам потребуются в следующем упражнении.

из файла   

Разберём предложенный код более подробно. Здесь используется класс VideoCapture() для создания объекта VideoCapture, который затем будем использовать для чтения видеофайла. Синтаксис использования этого класса показан ниже:

VideoCapture(path, apiPreference)

Первый аргумент — это имя файла/путь к видеофайлу. Второй — необязательный аргумент, указывающий на предпочтение API. Некоторые параметры, связанные с этим необязательным аргументом, будут рассмотрены ниже. Чтобы узнать больше об apiPreference, пройдите на официальную ссылку документации VideoCaptureAPIs.

# Создаем объект захвата видео, в этом случае мы читаем видео из файла
vid_capture = cv2.VideoCapture('Resources/is42.mp4')

Для всех примеров, приведенных ниже, можно использовать один и тот же видеофайл, который я использовал здесь и который доступен в папке Resources или по ссылке, но это совсем не обязательно. Вы можете использовать свой собственный видеофайл.

Теперь, когда у нас есть объект захвата видео, мы можем использовать метод isOpened(), чтобы подтвердить, что видеофайл был успешно открыт. Метод isOpened() возвращает логическое значение, которое указывает, действителен ли видеопоток. В противном случае вы получите сообщение об ошибке. Сообщение об ошибке может означать многое. Одна из них — повреждено все видео или повреждены некоторые кадры. Предполагая, что видеофайл был успешно открыт, можно использовать метод get() для получения важных метаданных, связанных с видеопотоком. Обратите внимание, что этот метод не применяется к веб-камерам. Метод get() принимает единственный аргумент из перечисленного списка параметров, описанных здесь. В приведенном ниже примере мы предоставили числовые значения 5 и 7, которые соответствуют частоте кадров (CAP_PROP_FPS) и количеству кадров (CAP_PROP_FRAME_COUNT). Можно указать числовое значение или имя.

if (vid_capture.isOpened() == False):
	print("Ошибка при открытии видеофайла")
else:
	# Получить информацию о частоте кадров

	fps = int(vid_capture.get(5))
	print("Частота кадров: ", fps, "кадров в секунду")	

	# Получить количество кадров
	frame_count = vid_capture.get(7)
	print("Количество кадров: ", frame_count)

После получения желаемых метаданных, связанных с видеофайлом, мы готовы читать каждый кадр изображения из файла. Это достигается путем создания цикла и считывания одного кадра из видеопотока с помощью метода vid_capture.read().

Метод vid_capture.read() возвращает кортеж, где первый элемент — логическое значение, а следующий элемент — фактический видеокадр. Когда первый элемент имеет значение True, это означает, что видеопоток содержит кадр для чтения.

Если есть фрейм для чтения, вы можете затем использовать imshow() для отображения текущего фрейма в окне, в противном случае выйти из цикла. Обратите внимание, что вы также используете функцию waitKey() для паузы на 20 мс между видеокадрами. Вызов функции waitKey() позволяет отслеживать ввод данных пользователем с клавиатуры. В этом случае, например, если пользователь нажимает клавишу q или Esc, вы выходите из цикла.

file_count = 0
while(vid_capture.isOpened()):
        # Метод vid_capture.read() возвращают кортеж, первым элементом является логическое значение
        # а вторым кадр
	ret, frame = vid_capture.read()
	if ret == True:
		cv2.imshow('Look', frame)
		file_count += 1
		print('Кадр {0:04d}'.format(file_count))
		#writefile = 'Resources/Image_sequence/is42_{0:04d}.jpg'.format(file_count)
		#cv2.imwrite(writefile, frame)
		# 20 в миллисекундах, попробуйте увеличить значение, скажем, 50 и
		# понаблюдайте за изменениями в показе
		key = cv2.waitKey(20)
		
		if (key == ord('q')) or key == 27:
			break
	else:
		break

Обратите внимание на строки 10 и 11 в этом фрагменте. Если их раскомментировать, то в папке Resources/Image_sequence появятся 246 файлов графического формата is42_0000.jpg кадров, извлеченных из нашего видеоролика. Эти файлы потребуются для наших дальнейших упражнений.

Когда видеопоток полностью обработан или пользователь преждевременно выходит из цикла, вы освобождаете объект видеозахвата (vid_capture) и закрываете окно, используя следующий код:

# Освободить объекты
vid_capture.release()
cv2.destroyAllWindows()

из последовательности изображений   

Обработка кадров изображения из последовательности изображений очень похожа на обработку кадров из видеопотока. Просто укажите файлы изображений, которые читаются.

В приведенном ниже примере

  • Вы продолжаете использовать объект видеозахвата
  • Но вместо указания видеофайла вы просто указываете последовательность изображений.
    • Используя обозначение, показанное ниже (is42_%04d.jpg), где %04d обозначает четырехзначное соглашение об именах последовательностей (например, is42_0001.jpg, is42_0002.jpg, is42_0003.jpg и т. д.).
    • Если бы вы указали Race_is42_%02d.jpg, то формат имен файлов стал бы другим:
      (Race_is42_01.jpg, Race_is42_02.jpg, Race_is42_03.jpg и т. д.).

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

# Импортировать необходимые пакеты
import cv2 

# Создаем объект захвата видео, в этом случае мы читаем из последовательности изображений
# Обратите внимание на имя файлов.
# Для is42_0000.jpg >> is42_%04d.jpg
# Для is42_000.jpg >> is42_%03d.jpg
# Для is42_00.jpg >> is42_%02d.jpg, но этого в нашем случае мало

vid_capture = cv2.VideoCapture('Resources/Image_sequence/is42_%04d.jpg')


if (vid_capture.isOpened() == False):
	print("Ошибка обработки последовательности изображений")

print('\n-----------------------------\nДля завершения нажмите "q" или Esc...')

file_count = 0
while(vid_capture.isOpened()):
        # Метод vCapture.read() возвращают кортеж, первым элементом является логическое значение
        # а вторым кадр
	ret, frame = vid_capture.read()
	if ret == True:
		cv2.imshow('look into', frame)
		# 20 в миллисекундах, попробуйте увеличить значение, скажем, до 50 и
		# понаблюдайте за изменениями в показе
		k = cv2.waitKey(20)
                # k == 113 - это код ASCII для клавиши q. Вы можете попробовать заменить его
                # нужным вам кодом ASCII, попробуйте 27, который соответствует ключу ESCAPE
		if (k == 113) or (k == 27):
			break
	else:
		break
# Освободить объект захвата видео
vid_capture.release()
cv2.destroyAllWindows()

с веб-камеры   

Чтение видеопотока с веб-камеры также очень похоже на рассмотренные выше примеры. Как такое возможно? Все это благодаря гибкости класса захвата видео в OpenCV, который для удобства имеет несколько перегруженных функций, которые принимают разные входные аргументы. Вместо того, чтобы указывать исходное местоположение для видеофайла или последовательности изображений, вам просто нужно указать индекс устройства захвата видео, как показано ниже.

  • Если ваша система имеет встроенную веб-камеру, то индекс устройства для камеры будет «0».
  • Если к вашей системе подключено более одной камеры,затем индекс устройства, связанный с каждой дополнительной камерой, увеличивается (например, 1, 2 и т. д.).
vid_capture = cv2.VideoCapture(0, cv2.CAP_DSHOW)

Вам может быть интересно узнать о флаге CAP_DSHOW. Это необязательный аргумент, поэтому его можно не записывать. CAP_DSHOW — это просто еще одно предпочтение API захвата видео, которое является сокращением от DirectShow через видеовход.

Запись видео   

А теперь давайте посмотрим, как писать видео. Как и при чтении видео, мы можем записывать видео из любого источника (видеофайл, последовательность изображений или веб-камера). Чтобы записать видеофайл:

  • Получите высоту и ширину кадра изображения с помощью метода get().
  • Инициализировать объект видеозахвата (как обсуждалось в предыдущих разделах) для чтения видеопотока в память, используя любой из ранее описанных источников.
  • Создайте объект записи видео.
  • Используйте объект записи видео для сохранения видеопотока на диск.

Продолжая наш текущий пример, начнем с использования метода get() для получения ширины и высоты видеокадра.

# Получить информацию о размере кадра с помощью метода get()
frame_width = int(vid_capture.get(3))
frame_height = int(vid_capture.get(4))
frame_size = (frame_width,frame_height)
fps = 20

Как обсуждалось ранее, метод get() из класса VideoCapture() требует:

  • Один аргумент из нумерованного списка, который позволяет извлекать множество метаданных, связанных с видеокадрами.

Доступные метаданные обширны, и их можно найти здесь.

  • В этом случае вы получаете ширину и высоту фрейма, указав 3 (CAP_PROP_FRAME_WIDTH) и 4 (CAP_PROP_FRAME_HEIGHT). Эти размеры будут использоваться ниже при записи видеофайла на диск.

Чтобы записать видеофайл, вам необходимо сначала создать объект записи видео из класса VideoWriter(), как показано в приведенном ниже коде.

Вот синтаксис VideoWriter():

VideoWriter(filename, apiPreference, fourcc, fps, frameSize[, isColor])

Класс VideoWriter() принимает следующие аргументы:

  • filename — путь к выходному видеофайлу
  • apiPreference — идентификатор серверной части API
  • fourcc — 4-символьный код кодека, используемый для сжатия кадров (fourcc)
  • fps — частота кадров созданного видеопотока.
  • frame_size — размер видеокадров.
  • isColor — если не ноль, то кодировщик ожидает и кодирует цветные кадры. В противном случае он будет работать с рамками в градациях серого (в настоящее время флаг поддерживается только в Windows).

Следующий код создает объект записи видео, выводимый из класса VideoWriter(). Для получения четырехзначного кодека используется специальная удобная функция,требуется в качестве второго аргумента объекта записи видео, cv2.

VideoWriter_fourcc(‘M’, ‘J’, ‘P’, ‘G’) в Python.

Видеокодек определяет способ сжатия видеопотока. Он конвертирует несжатое видео в сжатый формат или наоборот. Чтобы создать форматы AVI или MP4, используйте следующие спецификации fourcc:

Для AVI — cv2.VideoWriter_fourcc('M', 'J', 'P', 'G')

Для MP4 — cv2.VideoWriter_fourcc(* 'XVID')

Следующие два входных аргумента указывают частоту кадров в FPS и размер кадра (ширина, высота).

# Инициализировать объект записи видео
output = cv2.VideoWriter('Resources/output_video_from_file.avi', cv2.VideoWriter_fourcc('M','J','P','G'), 20, frame_size)

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

while(vid_capture.isOpened()):
    # vid_capture.read() methods returns a tuple, first element is a bool 
    # and the second is frame

    ret, frame = vid_capture.read()
    if ret == True:
           # Write the frame to the output files
           output.write(frame)
    else:
         print(‘Stream disconnected’)
           break

Наконец, в приведенном ниже коде освободите объекты захвата видео и записи видео.

# Освободить объекты
vid_capture.release()
output.release()

Ошибки или с чем можно столкнуться при чтении или записи видео   

Чтение видео

При чтении фреймов может возникнуть ошибка, если путь неверен, файл поврежден или фрейм отсутствует. Вот почему используется оператор if внутри цикла while. Что можно увидеть в строке, если ret == True. Таким образом, он будет обрабатывать его только при наличии фреймов. Ниже приведен пример журнала ошибок, который наблюдается в этом случае. Это не полный журнал, включены только ключевые параметры.

cap_gstreamer.cpp:890: error: (-2) GStreamer: unable to start pipeline  in function

По неверному пути:

Когда вы указываете неправильный путь к видео, при использованием класса VideoCapture() не будет показано никаких ошибок или предупреждений. Проблемы возникнут, когда вы попытаетесь выполнить какие-либо операции с видеокадрами. Для этого вы можете использовать простой блок if для проверки того, прочитали ли вы видеофайл или нет, как мы это сделали в нашем примере. Должно быть напечатать следующее сообщение.

Ошибка при открытии видеофайла

Запись видео

На этом этапе могут возникнуть различные ошибки. Наиболее распространенными являются ошибка размера кадра и ошибка предпочтения API. Если размер кадра не похож на размер видео, то даже если мы получим видеофайл в выходном каталоге, он будет пустым. Если вы используете метод формы NumPy для получения размера кадра,не забудьте отменить вывод, поскольку OpenCV вернет высоту x ширину x каналов. Если он выдает ошибку предпочтения api, нам может потребоваться передать флаг CAP_ANY в аргументе VideoCapture(). Это можно увидеть в примере веб-камеры, где мы используем CAP_DHOW, чтобы избежать генерации предупреждений.

Ниже приведены примеры журналов ошибок:

Если CAP_DSHOW не передан:

[WARN:0]...cap_msmf.cpp(438) …. terminating async callback

Если размер кадра неправильный:

cv2.error: OpenCV(4.5.2) :-1: error: (-5:Bad argument) in function 'VideoWriter'
> Overload resolution failed:
>  - Can't parse 'frameSize'. Sequence item with index 0 has a wrong type
>  - VideoWriter() missing required argument 'frameSize' (pos 5)
>  - VideoWriter() missing required argument 'params' (pos 5)
>  - VideoWriter() missing required argument 'frameSize' (pos 5)

Резюме   

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

Теперь, когда вы умеете читать и писать видео и комфортно используете OpenCV, не отставайте. И продолжайте учиться. Все примеры, здесь описанные, доступны на Яндекс.Диске.

Использованы материалы Reading and Writing Videos using OpenCV

Опубликовано Вадим В. Костерин

ст. преп. кафедры ЦЭиИТ. Автор более 130 научных и учебно-методических работ. Лауреат ВДНХ (серебряная медаль).

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *