본문 바로가기

OpenCV

OpenCV를 사용하여 비디오 읽기 및 쓰기

반응형

 

OpenCV에서 비디오를 읽고 쓰는 것은 이미지를 읽고 쓰는 것과 매우 유사합니다. 비디오는 프레임 이라고도 하는 일련의 이미지일 뿐입니다 . 따라서 비디오 시퀀스의 모든 프레임을 반복하고 한 번에 한 프레임씩 처리하기만 하면 됩니다.

 

이 글에서는 파일 , 이미지 시퀀스 , 웹캠 에서 비디오를 읽고, 표시하고, 쓰는 방법을 보여드리겠습니다 . 또한 이 과정에서 발생할 수 있는 몇 가지 오류를 살펴보고 해결 방법을 이해하는 데 도움을 드리겠습니다.

 

 

 

이 시리즈의 튜토리얼 목록은 다음과 같습니다. 

 

1. OpenCV 초보자는 여기서 시작하세요.

2. OpenCV를 사용하여 이미지 읽기, 표시 및 쓰기

3. OpenCV를 사용하여 비디오 읽기 및 쓰기

4. OpenCV 이미지 크기 조정

5. OpenCV 이미지 자르기

6. OpenCV 이미지 변환 및 회전

7. OpenCV 이미지에 글씨 쓰기 - LearnOpenCV

8. OpenCV 색 공간 C++, Python

9. OpenCV 컨볼루션 기반 이미지 필터링

10. OpenCV 이미지 임계값 처리 Thresholding

11. OpenCV 블롭 탐지 (Python, C++)

12. OpenCV 에지 검출

13. OpenCV GUI에서 마우스와 트랙바

14. OpenCV 윤곽선 탐지 Python, C++

15. OpenCV 동영상 배경 단순 추정 C++, Python

16. OpenCV DNN 모듈을 활용한 딥 러닝: 완벽 가이드

 

 

Table of Contents

 

비디오 읽기

파일에서

이미지 시퀀스에서

웹캠에서

영상 쓰기

비디오를 읽거나 쓸 때 발생할 수 있는 오류

요약

 

먼저 비디오 파일을 읽는 코드 예제를 살펴보겠습니다. 이 코드에는 디스크에서 비디오를 읽고 표시하는 함수들이 기본적으로 포함되어 있습니다. 앞으로 진행하면서 이 구현에 사용된 함수들을 자세히 살펴보겠습니다.

 

파이썬

 

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 비디오 I/O의 주요 기능은 다음과 같습니다.

 

  1. cv2.VideoCapture– 비디오를 스트리밍하거나 표시하는 데 도움이 되는 비디오 캡처 객체를 만듭니다.
  2. cv2.VideoWriter– 출력 비디오를 디렉토리에 저장합니다.
  3. 또한, 프레임 높이, 너비, fps 등과 같은 비디오 메타데이터를 읽는 데 사용되는 방법 과 같은 다른 필요한 함수도 논의합니다 cv2.imshow().cv2.waitKey()get()

 

게시물의 입력 비디오를 보여주는 GIF입니다.

 

이 예에서는 위의 비디오('Cars.mp4')를 읽고 표시합니다.

 

시스템에 이미 OpenCV가 설치되어 있다고 가정합니다. OpenCV를 설치해야 하는 경우 아래 관련 링크를 방문하세요.

  • Windows에 OpenCV 설치
  • MacOS에 OpenCV 설치
  • Ubuntu에 OpenCV 설치

 

여러분의 학습과 연습을 위해 Python과 C++ 언어의 코드를 제공했습니다.

 

이제 시작해 보겠습니다.

 

먼저 OpenCV 라이브러리를 임포트합니다. C++의 경우 일반적으로 cv::function()을 사용하지만, cv 네임스페이스(using namespace cv)를 선택했기 때문에 함수 이름 앞에 cv::를 붙이지 않고도 OpenCV 함수에 직접 접근할 수 있습니다. 

 

파이썬

 

# 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를 참조하세요 .

 

파이썬

 

# 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() 메서드는 여기에 문서화된 열거형 옵션 목록에서 단일 인수를 받습니다. 

 

아래 예시에서는 프레임 속도(CAP_PROP_FPS)와 프레임 수(CAP_PROP_FRAME_COUNT)에 해당하는 숫자 값 5와 7을 제공했습니다. 숫자 값이나 이름을 모두 사용할 수 있습니다.

 

파이썬

 

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()비디오 프레임 사이에 20ms 동안 일시 정지하는 데도 이 함수를 사용합니다. 이 waitKey()함수를 호출하면 키보드에서 사용자 입력을 모니터링할 수 있습니다. 예를 들어, 사용자가 ' q' 키를 누르면 루프가 종료됩니다.

 

파이썬

 

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)를 해제하고 창을 닫습니다:

 

파이썬

 

# Release the objects

vid_capture.release()

cv2.destroyAllWindows()

 

C++

 

// Release video capture object

vid_capture.release();

destroyAllWindows();

 

이미지 시퀀스 읽기

 

이미지 시퀀스에서 이미지 프레임을 처리하는 것은 비디오 스트림에서 프레임을 처리하는 것과 매우 유사합니다. 읽고 있는 이미지 파일만 지정하면 됩니다.

 

아래 예에서,

 

  • 비디오 캡처 객체를 계속 사용합니다.
  • 하지만 비디오 파일을 지정하는 대신 이미지 시퀀스를 지정하기만 하면 됩니다.
  • 아래에 표시된 표기법(Cars%04d.jpg)을 사용하면, 여기서 %04d는 4자리 시퀀스 명명 규칙(예: Cars0001.jpg, Cars0002.jpg, Cars0003.jpg 등)을 나타냅니다. 
  • “Race_Cars_%02d.jpg”를 지정했다면 다음 형식의 파일을 찾게 됩니다.

 

첫 번째 예에서 설명한 다른 모든 코드는 동일합니다.

 

파이썬

 

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

 

C++

 

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

 

웹캠에서 비디오 읽기

 

웹 카메라에서 비디오 스트림을 읽는 것도 위에서 설명한 예시와 매우 유사합니다. 어떻게 가능한 걸까요? OpenCV의 비디오 캡처 클래스는 다양한 입력 인수를 받는 여러 오버로드된 함수를 제공하여 편의성을 높였습니다. 비디오 파일이나 이미지 시퀀스의 소스 위치를 지정하는 대신, 아래와 같이 비디오 캡처 장치 인덱스만 지정하면 됩니다.

 

  • 시스템에 내장 웹캠이 있는 경우 카메라의 장치 인덱스는 ' 0'입니다.
  • 시스템에 두 개 이상의 카메라가 연결되어 있는 경우, 각 추가 카메라와 연관된 장치 인덱스가 증가합니다(예 1: 2, 등).

 

파이썬

 

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

 

C++

 

VideoCapture vid_capture(0);

 

CAP_DSHOW 플래그에 대해 궁금해하실 수 있습니다. 이는 선택적 인자이므로 필수 사항은 아닙니다. CAP_DSHOW는 단순히 또 다른 비디오 캡처 API 환경 설정으로, 비디오 입력을 통한 DirectShow의 약칭입니다.

 

영상 쓰기

 

이제 비디오 작성 방법을 살펴보겠습니다. 비디오 읽기와 마찬가지로, 비디오 파일, 이미지 시퀀스, 웹캠 등 어떤 소스에서든 비디오를 작성할 수 있습니다. 비디오 파일을 작성하려면 다음과 같이 하세요.

 

  • 이 메서드를 사용하여 이미지 프레임의 높이와 너비를 검색합니다 get().
  • 이전에 설명한 소스를 사용하여 비디오 스트림을 메모리로 읽어오기 위해 비디오 캡처 객체를 초기화합니다(이전 섹션에서 설명).
  • 비디오 작성기 객체를 만듭니다.
  • 비디오 작성기 객체를 사용하여 비디오 스트림을 디스크에 저장합니다.

 

get()실행 예제를 계속 진행하면서 비디오 프레임의 너비와 높이를 구하는 방법을 사용해 보겠습니다 .

 

파이썬

 

# 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;

 

 

앞서 논의한 바와 같이, VideoCapture() 클래스의 get() 메서드는 다음을 요구합니다:

 

  • 비디오 프레임과 관련된 다양한 메타데이터를 검색할 수 있는 열거형 목록의 단일 인수입니다.

 

사용 가능한 메타데이터는 광범위하며, 여기에서 확인할 수 있습니다 .

 

이 경우, 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: 0이 아니면 인코더는 컬러 프레임을 예상하고 인코딩합니다. 0이 아니면 회색조 프레임으로 작동합니다(이 플래그는 현재 Windows에서만 지원됩니다).

 

다음 코드는 클래스 output에서 비디오 작성기 객체를 생성합니다 VideoWriter(). 비디오 작성기 객체의 두 번째 인수로 필요한 4자리 코덱을 가져오는 데 특별한 편의 함수가 사용됩니다 cv2.

 

VideoWriter_fourcc('M', 'J', 'P', 'G') 파이썬으로.

VideoWriter::fourcc('M', 'J', 'P', 'G') C++로.

 

비디오 코덱은 비디오 스트림의 압축 방식을 지정합니다. 압축되지 않은 비디오를 압축된 형식으로 변환하거나, 반대로 압축되지 않은 비디오를 압축된 형식으로 변환합니다. AVI 또는 MP4 형식을 생성하려면 다음 fourcc 사양을 사용하세요.

 

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

MP4:cv2.VideoWriter_fourcc(*'XVID')

 

다음 두 개의 입력 인수는 프레임 속도(FPS)와 프레임 크기(너비, 높이)를 지정합니다.

 

파이썬

 

# 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);

 

이제 비디오 작성기 객체를 만들었으니, 아래 코드와 같이 이 객체를 사용하여 비디오 파일을 한 번에 한 프레임씩 디스크에 기록합니다. 여기서는 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

 

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;
                  }
        }
 }

 

마지막으로 아래 코드에서 비디오 캡처 및 비디오 작성기 객체를 해제합니다.

 

파이썬

 

# Release the objects

vid_capture.release()

output.release()

 

C++

 

// Release the objects

vid_capture.release();

output.release();

 

비디오를 읽거나 쓸 때 발생할 수 있는 오류

 

영상 판독

 

프레임을 읽는 동안 경로가 잘못되었거나 파일이 손상되었거나 프레임이 누락된 경우 오류가 발생할 수 있습니다. 그래서 while 루프 안에 if 문을 사용합니다. 이는 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가 height x width x channels 순서로 반환하므로 결과 값을 역순으로 변환해야 합니다. API 선호도 오류가 발생한다면 VideoCapture() 인자에 CAP_ANY 플래그를 전달해야 할 수 있습니다. 웹캠 예제에서 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)

 

요약

 

이 블로그에서는 비디오 캡처 객체를 사용하여 세 가지 소스의 비디오 스트림을 읽고 표시하는 방법을 알아보았습니다. 비디오 캡처 객체를 사용하여 비디오 스트림에서 중요한 메타데이터를 가져오는 방법도 살펴보았습니다. 또한, video-writer 객체를 사용하여 비디오 스트림을 디스크에 쓰는 방법도 보여주었습니다. 비디오 프레임의 크기를 조정하거나 모양과 텍스트로 주석을 추가하는 것도 유용할 수 있습니다. 이를 위해서는 개별 이미지 프레임만 수정하면 됩니다.

 

이제 비디오를 읽고 쓰는 방법을 알고 OpenCV 사용에 익숙해졌으니, 속도를 유지하고 계속해서 학습하세요. 

 

 

반응형

더욱 좋은 정보를 제공하겠습니다.~ ^^