IT

OpenCV에서 동영상 파일의 재생 및 저장

유병혁 2020. 4. 15. 14:28

안녕하세요? 이번 글은 OpenCV에서 동영상 파일을 재생하고 저장하는 방법을 학습해 보도록 하겠습니다. 이 방법은 아래 OpenCV 공식 홈페이지에 자세히 안내되어 있는데요, 약간의 코드 수정과 추가 설명을 통해 재정리한 내용이 되겠습니다.

 

Getting Started with Videos | https://docs.opencv.org/master/dd/d43/tutorial_py_video_display.html

 

OpenCV: Getting Started with Videos

Goal Learn to read video, display video, and save video. Learn to capture video from a camera and display it. You will learn these functions : cv.VideoCapture(), cv.VideoWriter() Capture Video from Camera Often, we have to capture live stream with a camera

docs.opencv.org

학습을 위해 각자 동영상을 하나 준비합니다. 저는 치악산국립공원의 수달 동영상(otter.avi)을 사용했습니다.

https://www.youtube.com/watch?v=wM4L0s_wJsU

첫번째 실습할 내용은 OpenCV를 통해 위 동영상을 재생하는 것입니다.

일단, 필요한 모듈을 호출합니다.

# 모듈 호출
import numpy as np
import cv2 as cv
import os

동영상 파일을 불러오기 전에, 현재 작업 디렉토리를 확인합니다.

os.getcwd() # 현재 작업 디렉토리 확인

현재 작업 디렉토리를 동영상 파일 위치로 변경합니다.

os.chdir('D:/GEODATA') # 현재 작업 디렉토리 변경
os.getcwd()

동영상 파일을 지정하여 아래와 같이 VideoCapture 객체를 정의합니다.

# VideoCapture 객체 정의
cap = cv.VideoCapture('otter.avi')

동영상의 프레임 너비와 높이, 그리고 초당 프레임(FPS: Frames Per Second)를 확인합니다.

이때 프레임 너비/높이의 단위는 화소(pixel), 초당 프레임 수의 단위는 밀리초(millisecond)입니다.

# 프레임 너비/높이, 초당 프레임 수 확인
width = cap.get(cv.CAP_PROP_FRAME_WIDTH) # 또는 cap.get(3)
height = cap.get(cv.CAP_PROP_FRAME_HEIGHT) # 또는 cap.get(4)
fps = cap.get(cv.CAP_PROP_FPS) # 또는 cap.get(5)
print('프레임 너비: %d, 프레임 높이: %d, 초당 프레임 수: %d' %(width, height, fps))

동영상 파일은 연속적인 프레임으로 구성된 정보를 제공되며, 우리는 OpenCV를 통해 각각의 프레임에 컴퓨터비전 및 머신러닝 알고리즘을 적용해볼 수 있습니다. 그러기 위해서는 동영상을 구성하는 각각의 프레임에 접근, 제어할 수 있어야 합니다. 아래 코드는 그 기본 골격을 구성합니다.

while cap.isOpened(): # cap 정상동작 확인
    ret, frame = cap.read()
    # 프레임이 올바르게 읽히면 ret은 True
    if not ret:
        print("프레임을 수신할 수 없습니다(스트림 끝?). 종료 중 ...")
        break
    frame = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
    cv.imshow('Otter', frame)
    if cv.waitKey(42) == ord('q'):
        break
# 작업 완료 후 해제
cap.release()
cv.destroyAllWindows()

앞서 정의한 cap 객체는 잘못된 경로/이름이 지정되더라도 오류가 발생되지 않습니다. 따라서, 현재 정의된 cap 객체가 정상동작하는지 확인이 필요한데 반복문의 조건에 명시된 cap.isOpened()가 그러한 역할을 수행합니다. 만약 otter.avi가 존재하지 않는다면 반복문은 실행되지 않을 것입니다.

 

cap.read() 함수가 반환하는 frame을 통해 동영상을 구성하는 프레임을 하나하나 접근해볼 수 있습니다. ret은 프레임이 올바르게 읽히면 True를 반환합니다. cap.isOpened()를 통해 정상동작은 이미 확인되었으므로 ret은 True로 시작할 것입니다. ret이 False를 반환하는 경우는 프레임을 수신할 수 없는 상태이므로 이 불린(boolean) 변수를 통해 동영상의 끝 지점을 식별할 수 있습니다.

 

cv.cvtColor()는 색 공간(color space)을 변환하는 함수입니다. 여기서는 RGB 이미지를 회색(gray)으로 변환하는 옵션이 적용되었습니다.

 

cv.waitKey() 함수는 프레임을 표출하는 시간을 정의합니다. 이때 단위는 밀리초, 즉 천분의 1초가 됩니다. 현재 otter.avi의 FPS는 24입니다. 초당 24 프레임으로 구성되어 있으니 1000밀리초 당 24 프레임, 역산하면 프레임 당 약 42 밀리초를 표출하고 있습니다. 이때 if 문은 프레임 당 42 밀리초를 표출하되, 'q'가 입력되었을 때는 프레임 표출이 종료되도록 설정하고 있습니다.

 

'q'가 입력되지 않으면 while 문은 동영상의 모든 프레임을 42 밀리초로 표출하고 ret이 False가 되는 시점에서 print()에 명시된 메시지를 반환하고 종료될 것입니다. 그 이후에는 사용한 cap 객체를 해제하고, 모든 창을 닫는 코드로 구성되어 있습니다.

 

두번째 실습할 내용은 OpenCV를 통해 위 동영상을 저장하는 것입니다. 이전 코드 중 일부는 재사용될 것입니다.  

cap = cv.VideoCapture('otter.avi') # VideoCapture 객체 정의
fourcc = cv.VideoWriter_fourcc(*'DIVX') # 코덱 정의
out = cv.VideoWriter('otter_out.avi', fourcc, fps, (int(width), int(height))) # VideoWriter 객체 정의

일단 cap 객체는 동일하게 선언합니다. 다음으로는 동영상을 저장하기 위해 필요한 코덱과 파일을 지정합니다. 코덱은 FourCC(Four-Character Code) 코드를 따릅니다. FourCC는 비디오 코덱을 지정하는데 사용되는 4바이트 코드(4자)로 Windows에서는 DIVX 사용을 명시하고 있습니다.

 

동영상을 저장할 때는 VideoWriter 객체를 정의합니다. 이때 저장할 동영상의 FPS, 프레임 너비/높이를 정의합니다. 프레임 너비/높이는 cap 객체의 해당 정보를 상속받고, 자료형은 int로 맞춰줘야 함을 유의해야 합니다.

 

아래 코드는 이전 코드의 재사용입니다. 일부 코드만 변경되었습니다.

while cap.isOpened(): # cap 정상동작 확인
    ret, frame = cap.read()
    # 프레임이 올바르게 읽히면 ret은 True
    if not ret:
        print("프레임을 수신할 수 없습니다(스트림 끝?). 종료 중 ...")
        break
    frame = cv.flip(frame, 1) # 프레임 반전. 1: 좌우 반전, 0: 상하 반전
    # 반전 프레임 저장
    out.write(frame)
    cv.imshow('Otter', frame)
    if cv.waitKey(42) == ord('q'):
        break
# 작업 완료 후 해제
cap.release()
out.release()
cv.destroyAllWindows()

 

일단 cv.flip()을 통해 cap 객체 프레임을 반전시키는 효과를 적용했습니다. 옵션에서 1은 좌우 반전, 0은 상하 반전에 해당됩니다. 이렇게 반전한 프레임을 out.write()을 통해 저장하고, 작업 완료 후에는 out.release()를 통해 해제하도록 코드를 추가하였습니다. 결과는 어떻게 되었을까요?! 아래 영상과 같습니다.

https://www.youtube.com/watch?v=1OHAdrE2Rtc

이전 동영상과 비교했을 때 프레임 좌우 반전이 적용된 것을 보실 수 있습니다. 또 하나의 차이는 동영상의 재생시간이 짧아진 점입니다. 원본 동영상은 FPS가 24이고 재생시간은 10초, 즉 총 240프레임으로 구성되어 있었는데 저장된 동영상의 재생시간은 5초로 짧아졌습니다. 물론 out 설정 시 FPS를 줄여주면 교정이 될 텐데, 왜 이런 결과가 도출되는지 약간 의아했습니다.

 

out 객체의 FPS는 24이고 재생시간은 5초이니 대략 120프레임 내외가 될 것 같은데, 혹시 cap 객체에 반복문을 적용할 때 240 프레임이 모두 읽혀지지 않는 것인가 해서 카운트를 해보니... 100프레임으로 집계가 됩니다. 즉, cap.read()가 동영상의 모든 프레임을 읽어와 처리하지는 않는다는 것입니다. 이와 관련하여 OpenCV 깃허브에서 다음 글을 확인하였습니다(일단 이 글에서는 이 정도로 일단락 지어 봅니다).

https://github.com/opencv/opencv/issues/4362 

 

Python cv2.VideoCapture.read() does not read all frames · Issue #4362 · opencv/opencv

Transferred from http://code.opencv.org/issues/1938 || Dmitry Kit on 2012-05-15 16:14 || Priority: Normal || Affected: None || Category: python bindings || Tracker: Bug || Difficulty: None || PR: N...

github.com