Чтение и запись видео с помощью OpenCV

232
Чтение и запись видео с помощью OpenCV
Чтение и запись видео с помощью OpenCV

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

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

Python

import cv2 
 
# Create a video capture object, in this case we are reading the video from a file
vid_capture = cv2.VideoCapture('Resources/Cars.mp4')
 
if (vid_capture.isOpened() == False):
  print("Error opening the video file")
# Read fps and frame count
else:
  # Get frame rate information
  # You can replace 5 with CAP_PROP_FPS as well, they are enumerations
  fps = vid_capture.get(5)
  print('Frames per second : ', fps,'FPS')
 
  # Get frame count
  # You can replace 7 with CAP_PROP_FRAME_COUNT as well, they are enumerations
  frame_count = vid_capture.get(7)
  print('Frame count : ', frame_count)
 
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:
    cv2.imshow('Frame',frame)
    # 20 is in milliseconds, try to increase the value, say 50 and observe
    key = cv2.waitKey(20)
     
    if key == ord('q'):
      break
  else:
    break
 
# Release the video capture object
vid_capture.release()
cv2.destroyAllWindows()

C++

// Include Libraries
#include<opencv2/opencv.hpp>
#include<iostream>
 
// Namespace to nullify use of cv::function(); syntax
using namespace std;
using namespace cv;
 
int main()
{
  // initialize a video capture object
  VideoCapture vid_capture("Resources/Cars.mp4");
 
  // Print error message if the stream is invalid
  if (!vid_capture.isOpened())
  {
    cout << "Error opening video stream or file" << endl;
  }
 
  else
  {
    // Obtain fps and frame count by get() method and print
    // You can replace 5 with CAP_PROP_FPS as well, they are enumerations
    int fps = vid_capture.get(5);
    cout << "Frames per second :" << fps;
 
    // Obtain frame_count using opencv built in frame count reading method
    // You can replace 7 with CAP_PROP_FRAME_COUNT as well, they are enumerations
    int frame_count = vid_capture.get(7);
    cout << "  Frame count :" << frame_count;
  }
 
 
  // Read the frames to the last frame
  while (vid_capture.isOpened())
  {
    // Initialise frame matrix
    Mat frame;
 
      // Initialize a boolean to check if frames are there or not
    bool isSuccess = vid_capture.read(frame);
 
    // If frames are present, show it
    if(isSuccess == true)
    {
      //display frames
      imshow("Frame", frame);
    }
 
    // If frames are not there, close it
    if (isSuccess == false)
    {
      cout << "Video camera is disconnected" << endl;
      break;
    }
     
    //wait 20 ms between successive frames and break the loop if key q is pressed
    int key = waitKey(20);
    if (key == 'q')
    {
      cout << "q key is pressed by the user. Stopping the video" << endl;
      break;
    }
 
 
  }
  // Release the video capture object
  vid_capture.release();
  destroyAllWindows();
  return 0;
}

Это основные функции ввода/вывода видео в OpenCV, которые мы собираемся обсудить в этой статье:

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

Теперь давайте приступим к работе.

Сначала мы импортируем библиотеку OpenCV. Обратите внимание, что в C++ обычно используется cv::function(), но поскольку мы решили использовать пространство имен cv (using namespace cv), мы можем обращаться к функциям OpenCV напрямую, без предварительного добавления cv:: к имени функции.

Python

# Import libraries
import cv2

C++

// Include Libraries
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;

Чтение видео из файла

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

VideoCapture(path, apiPreference)

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

Python

# Create a video capture object, in this case we are reading the video from a file
vid_capture = cv2.VideoCapture('Resources/Cars.mp4')

C++

# Create a video capture object, in this case we are reading the video from a file
VideoCapture vid_capture("Resources/Cars.mp4");

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

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

Python

if (vid_capture.isOpened() == False):
  print("Error opening the video file")
else:
  # Get frame rate information
 
  fps = int(vid_capture.get(5))
  print("Frame Rate : ",fps,"frames per second")  
 
  # Get frame count
  frame_count = vid_capture.get(7)
  print("Frame count : ", frame_count)

C++

if (!vid_capture.isOpened())
  {
    cout << "Error opening video stream or file" << endl;
  }
else
  {
            // Obtain fps and frame count by get() method and print
    int fps = vid_capture.get(5):
    cout << "Frames per second :" << fps;
    frame_count = vid_capture.get(7);
    cout << "Frame count :" << frame_count;
  }

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

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

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

Python

while(vid_capture.isOpened()):
  # vCapture.read() methods returns a tuple, first element is a bool 
  # and the second is frame
 
  ret, frame = vid_capture.read()
  if ret == True:
    cv2.imshow('Frame',frame)
    k = cv2.waitKey(20)
    # 113 is ASCII code for q key
    if k == 113:
      break
  else:
    break

C++

while (vid_capture.isOpened())
{
        // Initialize frame matrix
        Mat frame;
        // Initialize a boolean to check if frames are there or not
        bool isSuccess = vid_capture.read(frame);
        // If frames are present, show it
        if(isSuccess == true)
        {
            //display frames
            imshow("Frame", frame);
        }
 
        // If frames are not there, close it
        if (isSuccess == false)
        {
            cout << "Video camera is disconnected" << endl;
            break;
        }        
//wait 20 ms between successive frames and break the loop if key q is pressed
        int key = waitKey(20);
            if (key == 'q')
        {
            cout << "q key is pressed by the user. Stopping the video" << endl;
            break;
        }
    }

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

Python

# Release the objects
vid_capture.release()
cv2.destroyAllWindows()

C++

// Release video capture object
vid_capture.release();
destroyAllWindows();

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

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

В примере ниже,

  • Вы продолжаете использовать объект видеозахвата
  • Но вместо указания видеофайла вы просто указываете последовательность изображений
  • Используя обозначение, показанное ниже (Cars%04d.jpg), где %04d указывает на четырехзначную последовательность именования (например, Cars0001.jpg, Cars0002.jpg, Cars0003.jpg и т.д.).
  • Если бы вы указали “Race_Cars_%02d.jpg”, то вы бы нашли файлы такой формы: (Race_Cars_01.jpg, Race_Cars_02.jpg, Race_Cars_03.jpg, и т.д.).

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

Python

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

C++

VideoCapture vid_capture("Resources/Image_sequence/Cars%04d.jpg");

Чтение видео с веб-камеры

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

  • Если в вашей системе есть встроенная веб-камера, то индекс устройства для нее будет равен ‘0’.
  • Если к системе подключено более одной камеры, то индекс устройства, связанный с каждой дополнительной камерой, увеличивается (например, 1, 2 и т.д.).

Python

vid_capture = cv2.VideoCapture(0, cv2.CAP_DSHOW)

C++

VideoCapture vid_capture(0);

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

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

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

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

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

Python

# Obtain frame size information using get() method
frame_width = int(vid_capture.get(3))
frame_height = int(vid_capture.get(4))
frame_size = (frame_width,frame_height)
fps = 20

C++

// Obtain frame size information using get() method
Int frame_width = static_cast<int>(vid_capture.get(3));
int frame_height = static_cast<int>(vid_capture.get(4));
Size frame_size(frame_width, frame_height);
int fps = 20;

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

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

Имеются обширные метаданные, которые можно найти здесь.

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

Чтобы записать видеофайл, необходимо сначала создать объект video-writer из класса 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.
  • VideoWriter::fourcc('M', 'J', 'P', 'G')  в C++.

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

AVI: cv2.VideoWriter_fourcc('M','J','P','G')

MP4: cv2.VideoWriter_fourcc(*'XVID')

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

Python

# Initialize video writer object
output = cv2.VideoWriter('Resources/output_video_from_file.avi', cv2.VideoWriter_fourcc('M','J','P','G'), 20, frame_size)

C++

//Initialize video writer object
VideoWriter output("Resources/output.avi", VideoWriter::fourcc('M', 'J', 'P', 'G'),frames_per_second, frame_size);

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

Python

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

C++

while (vid_capture.isOpened())
{
        // Initialize frame matrix
        Mat frame;
 
          // Initialize a boolean to check if frames are there or not
        bool isSuccess = vid_capture.read(frame);
 
        // If frames are not there, close it
        if (isSuccess == false)
        {
            cout << "Stream disconnected" << endl;
            break;
        }
 
 
            // If frames are present
        if(isSuccess == true)
        {
            //display frames
            output.write(frame);
                  // display frames
                  imshow("Frame", frame);
 
                  // wait for 20 ms between successive frames and break        
                  // the loop if key q is pressed
                  int key = waitKey(20);
                  if (key == ‘q’)
                  {
                      cout << "Key q key is pressed by the user. 
                      Stopping the video" << endl;
                      break;
                  }
        }
 }

Наконец, в приведенном ниже коде освободите объекты video capture и video-writer.

Python

# Release the objects
vid_capture.release()
output.release()

C++

// Release the objects
vid_capture.release();
output.release();

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

Чтение видео

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

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

Неправильный путь:

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

Error opening the video file

Создание видео

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

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

When CAP_DSHOW is not passed:

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

When frame size is not correct:

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)

Итог

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

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