OpenCV

cv2.VideoCapture

OpenCV, cv2.VideoCapture에 대해서 설명한다.

cv2.VideoCapture()

cv2.VideoCapture()이 무엇인가?

cv2.VideoCapture는 OpenCV에서 영상 파일이나 카메라를 열어서 프레임을 하나씩 읽어오는 객체이다.

import cv2

cap = cv2.VideoCapture("video.mp4")

확장자 안에 저장되어 있는 코덱 정보를 읽어 프레임을 한 장씩 가져올 수 있는 것이다.

video.avi 파일 열기
↓
AVI 컨테이너 구조 확인
↓
비디오 트랙 찾기
↓
비디오 코덱 정보 확인
예: MJPEG, Xvid, H.264 등
↓
그 코덱을 해석할 수 있는 디코더 준비 시도

기본적으로 다음과 같이 사용할 수 있다.

import cv2

cap = cv2.VideoCapture("video.mp4")

if not cap.isOpened():
    raise FileNotFoundError("비디오를 열 수 없다.")

while True:
    ret, frame = cap.read()

    if not ret:
        break

    cv2.imshow("frame", frame)

    if cv2.waitKey(1) == ord("q"):
        break

cap.release()
cv2.destroyAllWindows()

cap.isOpened()란?

.isOpened()는 이 영상 파일을 읽을 준비를 성공했는가를 보는 것이다.

cap = cv2.VideoCapture("video.mp4")

이 코드가 실행되면 OpenCV는 내부적으로 다음과 같은 일을 한다.

파일 경로 확인
↓
파일 열기 시도
↓
컨테이너 확인
예: MP4, AVI, MOV
↓
컨테이너 안에서 비디오 트랙 찾기
↓
비디오 코덱 정보 확인
예: H.264, H.265, MJPEG, Xvid
↓
해당 코덱을 처리할 디코더 준비
↓
성공하면 VideoCapture 객체가 열린 상태가 됨

위 모든 단계는 .isOpened()가 검증해야할 위혐 요소들이다.

모든 단계를 문제 없이 지나와야 True를 반환하는 것이다.

왜 False가 나오는가?

.isOpened()가 False라는 것은 읽을 준비 단계에서 실패했다는 것이다.

위험 요소는 다음과 같이 나누어볼 수 있다.

  1. 파일 경로가 잘못 되었거나 존재하지 않을 때, 존재는 하지만 파일이 아닐 때
  2. 코덱 문제
    • 코덱을 읽기 실패했을 때
    • 해당 컨테이너 안에 들어 있는 코덱으로 압축 데이터를 해석하기 어려울 때
  3. 디코더 문제
    • 코덱은 알지만 디코더가 존재하지 않을 때 => 디코더 초기화 실패한 것

1., 3.같은 경우는 단순 분기문으로 해결할 수 있다.

# 1. 파일 존재 확인
if not video_path.exists():
    raise FileNotFoundError(f"비디오 파일이 존재하지 않습니다: {video_path}")

# 2. 실제 파일인지 확인
if not video_path.is_file():
    raise ValueError(f"비디오 경로가 파일이 아닙니다: {video_path}")

# 3. OpenCV로 열기
cap = cv2.VideoCapture(str(video_path))

if not cap.isOpened():
    raise ValueError(
        f"OpenCV가 비디오를 열 수 없습니다: {video_path}"
    )

# 4. 첫 프레임 읽기 확인
ret, frame = cap.read()

if not ret:
    raise ValueError(
        f"비디오를 열었지만 첫 프레임을 읽을 수 없습니다: {video_path}\n"
        "코덱 디코딩 문제이거나 영상 데이터가 손상되었을 수 있습니다."
    )

하지만, 2. 같은 경우는 프로그래머가 직접 ffprobe를 이용하여 원인을 분석해야 한다. 우선, ffmpeg를 설치하여 터미널에 다음과 같은 명령어를 입력한다.

ffprobe "video.mp4"

만약 정상이라면 보통 다음과 같은 정보가 출력된다.

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'video.mp4':
  Stream #0:0: Video: h264, yuv420p, 1920x1080, 30 fps
  Stream #0:1: Audio: aac, 44100 Hz, stereo

컨테이너 읽음

비디오 스트림 찾음

비디오 코덱 h264 확인함

오디오 코덱 aac 확인함

하지만, ffprobe 자체가 실패하는 경우도 있다.

Invalid data found when processing input

이 경우는 파일 손상, 컨테이너 구조 손상으로 생각해볼 수 있을 것이다.

또한, 컨테이너 안의 코덱 조합이 불안정할 수 있다.

이럴 때는, 프로그래머가 트랜스코딩 또는 리먹싱을 이용하여 해결해야 한다.

트랜스코딩이란?

트랜스코딩은 코덱 자체를 바꾸기 위해 다시 압축하는 과정이다.

기존 코덱으로 압축 해제
↓
새 코덱으로 다시 압축

이다.

ffmpeg -i "input.avi" -c:v libx264 -c:a aac "output.mp4"

-i "input.avi"는 입력 파일로 input.avi를 사용한다는 것이다.

-c:v libx264에서 c는 codec의 줄임이고 v는 video의 줄임이다. H.264로 input.avi를 압축하라는 뜻이다.

-c:a aac에서 a는 audio이다. AAC 코덱으로 다시 압축하라는 뜻이다.

input.avi 읽기
↓
기존 비디오 코덱으로 압축 해제
↓
H.264로 다시 압축
↓
오디오는 AAC로 다시 압축
↓
MP4 파일로 저장

주의할 점은, 트랜스코딩을 하게 되면 압축 데이터를 건들기 때문에 화질이 손상될 수 있는 가능성이 있다.

리먹싱이란?

리먹싱은 압축 데이터는 그대로 두고 컨테이너만 바꾸는 과정이다.

ffmpeg -i "input.avi" -c copy "output.mp4"

여기서 -c copy는 비디오/오디오 압축 데이터를 그대로 복사하고 컨테이너만 바꾸라는 뜻이다.

주의!! 단순히 windows에서 제공하는 이름 바꾸기로 avi확장명을 mp4로 바꾼다고 해서 컨테이너는 바뀌지 않는다. 원래 파일이 다음과 같다고 하자.

input.avi
├─ 컨테이너: AVI
├─ 비디오 코덱: Xvid
└─ 오디오 코덱: MP3

이름만 바꾸게 되면

input.mp4
├─ 실제 컨테이너: AVI
├─ 비디오 코덱: Xvid
└─ 오디오 코덱: MP3

즉, 겉 이름은 .mp4가 되었지만 파일 내부는 여전히 .avi이다. 그래서 리먹싱을 하는 것이다.

cap.read()란?

영상으로부터 프레임 한 장을 읽어온다.

ret, frame = cap.read()

여기서 반환값은 ret와 frame 두 개이다.

ret는 프레임을 제대로 읽었는지 여부 frame은 실제 이미지 프레임을 의미한다.

여기서 frame은 높이, 너비, 채널 수를 가진다.

frame이란 무엇인가?

frame 안에는 프레임 이미지가 숫자 numpy 배열로 표현된다.

우선, 영상 크기가 만약 1920 x 1080 이면

print(frame.shape)
(1080, 1920, 3)

로 출력된다.

의미는 각각 (세로 픽셀 수, 가로 픽셀 수, 색상 채널 수) 이다. OpenCV에서는 색상 채널을 RGB가 아니라 BGR로 사용한다.

이미지 전체는 다음과 같이 생겼다고 보면 된다.

frame
=
[
  [픽셀, 픽셀, 픽셀, 픽셀, ...],
  [픽셀, 픽셀, 픽셀, 픽셀, ...],
  [픽셀, 픽셀, 픽셀, 픽셀, ...],
  ...
]

각 픽셀은 다시 색상값 3개를 가진다.

픽셀 = [B, R, G]

cap.read()란?

.read()는 영상에서 frame 한 장을 읽는다.

여기서도 반환값은 ret, frame으로 두 개이다.

cap.release()란?

.release()란 열어둔 비디오 파일이나 카메라를 닫는다. 정확하게 말하면 OpenCV가 잡고 있던 영상 입력 장치를 해제해주는 함수다.

만약 .release()를 해주지 않는다면, 카메라가 계속 사용 중으로 남거나, 같은 코드 내에서 다시 카메라를 열 때 실패하거나, 환경에 따라서 데이터를 바로 삭제하거나 수정하지 못 할 수 있다.

그러므로, 영상 처리가 끝나면 꼭 해제를 해주는 것이 좋다.

영상 정보(FPS, FRAME_COUT, WIDTH, HEIGHT 등)을 가져오는 법

VideoCapture는 영상 정보도 가져올 수 있다.

fps = cap.get(cv2.CAP_PROP_FPS) # 초당 프레임 수
frame_count = cap.get(cv2.CAP_PROP_FRAME_COUNT) # 전체 프레임 수
width = cap.get(cv2.CAP_PROP_FRAME_WIDTH) # 영상 너비
height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT) # 영상 높이

번외, 코덱이 무엇인가?

Codec는 Coder와 Decoder의 합성어이다.

코덱은 영상이나 음성을 압축하고 다시 풀어주는 방식이다.

영상은 사실상 이미지 수천 장과 음성이 빠르게 이어지는 것이다. 이것을 그대로 저장하게 되면 용량이 매우 커지므로 코덱을 이용하여 압축해 mp4나 avi 같은 영상 파일로 저장한다.

다시 재생하거나 OpenCV로 읽을 때는 코덱으로 압축을 해제하고 프레임 이미지로 변환하여 화면에 출력한다.

하나의 영상 파일을 압축하거나 재생할 때는 비디오 코덱 1개, 오디오 코덱 1개, 컨테이너 1개(확장자)가 사용된다.

그럼 확장자(mp4, avi)와 코덱은 같은 개념인가?

확장자와 코덱은 다르다.

확장자는 코덱으로 압축된 영상 데이터를 어떤 식으로 정리할 건지에 대한 정의이다.

avi는 다음과 같이 압축된 데이터가 정리된다.

video.avi

RIFF AVI 헤더
├─ 이 파일은 AVI다
├─ 비디오 트랙 정보
├─ 오디오 트랙 정보
├─ FPS 정보
└─ **코덱 정보** <- 여기서 OpenCV가 코덱 정보를 읽어 영상을 재생한다. 

movi 영역
├─ 비디오 조각 A
├─ 오디오 조각 a
├─ 비디오 조각 B
├─ 오디오 조각 b
├─ 비디오 조각 C
├─ 오디오 조각 c
├─ 비디오 조각 D
└─ 오디오 조각 d

index 영역
├─ A는 파일의 몇 번째 위치에 있음
├─ B는 파일의 몇 번째 위치에 있음
├─ C는 파일의 몇 번째 위치에 있음
└─ D는 파일의 몇 번째 위치에 있음

보이는 것과 같이 avi는 헤더와 데이터 덩어리들 그리고 단순 인덱스로 구성되어 있다.

avi는 옛날 방식이라 구조가 단순하고 특히 시간 정보가 단순하여 복잡한 시간 정보 처리에 약하다.

mp4는 다음과 같이 압축된 데이터가 정리된다.

video.mp4

ftyp box
├─ 이 파일은 MP4 계열이다

moov box
├─ 영상 전체 정보
├─ 비디오 트랙 정보
├─ 오디오 트랙 정보
├─ 각 조각의 시간 정보
├─ 각 조각의 위치 정보
├─ 각 조각의 크기 정보
├─ **코덱 설정 정보** <- 여기서 OpenCV가 코덱 정보를 읽어 영상을 재생한다. 
└─ 재생 순서 정보

mdat box
├─ 압축된 비디오 조각 A
├─ 압축된 오디오 조각 a
├─ 압축된 비디오 조각 B
├─ 압축된 오디오 조각 b
├─ 압축된 비디오 조각 C
├─ 압축된 오디오 조각 c
├─ 압축된 비디오 조각 D
└─ 압축된 오디오 조각 d

보이는 것과 같이 mp4는 avi와 다르게 매우 상세하게 box로 분리하여 설계되어 있다.

mp4는 현대 방식이라 구조가 매우 복잡하고 시간 정보를 매우 자세하게 저장할 수 있다.

예를 들어

압축된 영상 조각이 있다고 해보자.

A B C D

AVI는 이런 식으로 단순 저장된다.

1번째 조각 → 0초
2번째 조각 → 1/30초
3번째 조각 → 2/30초
4번째 조각 → 3/30초

즉, 프레임이 일정하게 쭉 간다는 식의 단순한 구조에 가깝다.

반면 MP4는 더 자세하게 저장할 수 있다.

A → 0.000초에 보여줌
B → 0.033초에 보여줌
C → 0.066초에 보여줌
D → 0.100초에 보여줌

그래서, avi는 현대적인 코덱과 호환이 되지 않는 경우가 많다. 그래서 보통 현대적인 코덱은 복잡한 구조를 정리할 수 있는 mp4를 많이 사용한다.

avi, mp4와 audio codecs, video codecs

| 비디오 코덱 | 설명 | AVI와 궁합 | | —————– | ——————– | ———– | | MJPEG | 프레임 하나하나를 JPEG처럼 압축 | 좋음 | | Xvid | 예전 AVI 영상에서 많이 사용 | 좋음 | | DivX | 예전 영화/동영상 파일에서 많이 사용 | 좋음 | | MPEG-4 Part 2 | Xvid/DivX 계열과 관련 | 좋음 | | DV | 캠코더 계열 영상 | 가능 | | Uncompressed | 압축 거의 안 함 | 가능, 대신 용량 큼 | | H.264 | 현대 코덱 | 가능은 하지만 비추천 | | H.265 | 더 최신 코덱 | 거의 비추천 |

오디오 코덱 설명 AVI와 궁합
PCM 압축 안 된 원본 소리 좋음
MP3 오래되고 호환성 좋은 압축 오디오 좋음
AC3 돌비 디지털 계열 가능
WMA 윈도우 계열 오디오 가능하지만 환경 의존

그래서 AVI는 보통 다음과 같은 조합을 사용한다.

AVI + Xvid + MP3

AVI + DivX + MP3

AVI + MJPEG + PCM

AVI + DV + PCM


비디오 코덱 설명 MP4와 궁합
H.264 / AVC 가장 흔하고 호환성 좋음 매우 좋음
H.265 / HEVC 용량을 더 줄일 수 있음 좋음, 단 디코더 필요
MPEG-4 Part 2 예전 MPEG-4 계열 가능
AV1 최신 고효율 코덱 가능하지만 환경 의존
H.266 / VVC 차세대 코덱 가능성은 있지만 호환성 낮음
오디오 코덱 설명 MP4와 궁합
AAC MP4에서 가장 흔한 오디오 코덱 매우 좋음
MP3 오래된 오디오 코덱 가능
ALAC Apple 무손실 오디오 가능
AC3 돌비 디지털 가능
E-AC3 향상된 돌비 디지털 가능
Opus 최신 오디오 코덱 가능하지만 환경 의존

그래서 MP4는 보통 다음과 같은 조합을 사용한다.

MP4 + H.264 + AAC

MP4 + H.265 + AAC

MP4 + AV1 + Opus

MP4 + MPEG-4 Part 2 + AAC

Related records

읽던 위치가 있어요.
확대 이미지