OpenCV 비전 실무15장 · 비디오 처리와 실시간 영상처리15 / 22← 이전목차다음 →MetaContents

15장. 비디오 처리와 실시간 영상처리

실습 1 — 동영상 열고 속성과 프레임 읽기

컨베이어 동영상(ch15_conveyor.mp4)을 열어 프레임 수, FPS, 크기를 읽고, 모든 프레임을 읽은 뒤, 네 장을 골라 격자로 봅니다. 이 동영상은 OpenCV 로 도형을 그려 만든 가상 동영상입니다.

1단계. 열고 속성 읽기

Python
import cv2
import matplotlib.pyplot as plt

# 그래프의 한글이 깨지지 않게 맑은 고딕을 쓰고, 빼기 기호가 네모로 나오지 않게 한다
plt.rcParams["font.family"] = "Malgun Gothic"
plt.rcParams["axes.unicode_minus"] = False

# cv2.VideoCapture(파일 이름): video capture. 동영상을 여는 재생기를 만든다
cap = cv2.VideoCapture("ch15_conveyor.mp4")
# isOpened(): 제대로 열렸으면 True. 파일이 없거나 읽을 수 없으면 False
print("열렸나:", cap.isOpened())

# cap.get(속성): CAP_PROP = capture property. 값이 실수(80.0)로 오므로 장 수와 칸 수는 int 로 바꾼다
count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))     # 전체 프레임 수
fps = cap.get(cv2.CAP_PROP_FPS)                    # 1초에 몇 장
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))     # 가로 칸 수
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))   # 세로 칸 수
print(f"프레임 수 {count}장 · FPS {fps} · 크기 {width}×{height}")
# 결과: 길이 = 장 수 ÷ FPS, 한 장의 시간 = 1000ms ÷ FPS
print(f"길이: {count} ÷ {fps} = {count / fps}초")
print(f"한 장이 보이는 시간: 1000 ÷ {fps} = {1000 / fps}ms")
출력
열렸나: True
프레임 수 80장 · FPS 10.0 · 크기 320×240
길이: 80 ÷ 10.0 = 8.0초
한 장이 보이는 시간: 1000 ÷ 10.0 = 100.0ms

2단계. 끝까지 읽어 모으기

Python
import os

frames = []
while True:
    # read(): 다음 프레임 한 장을 읽어 (성공 여부, 그림)을 돌려준다
    ok, frame = cap.read()
    if not ok:              # 더 읽을 프레임이 없으면 ok 가 False 다
        break
    frames.append(frame)
# release(): 재생기를 닫아 파일을 놓아 준다
cap.release()

print("읽은 프레임:", len(frames), "장")
print("한 장의 모양:", frames[0].shape, "· 자료형:", frames[0].dtype)
# 끝을 지난 read() 는 (False, None) 을 돌려준다
print("마지막 read 의 결과:", ok, frame)
# 모두 펼친 크기와 파일 크기를 견준다
total = sum(f.nbytes for f in frames)
print(f"펼친 크기: {frames[0].nbytes:,} × {len(frames)} = {total:,}바이트")
print(f"파일 크기: {os.path.getsize('ch15_conveyor.mp4'):,}바이트")
출력
읽은 프레임: 80 장
한 장의 모양: (240, 320, 3) · 자료형: uint8
마지막 read 의 결과: False None
펼친 크기: 230,400 × 80 = 18,432,000바이트
파일 크기: 78,558바이트

3단계. 번호로 건너뛰어 네 장 고르기

Python
import numpy as np

cap = cv2.VideoCapture("ch15_conveyor.mp4")
picked = []
for no in (0, 20, 40, 60):
    # set(CAP_PROP_POS_FRAMES, 번호): 다음에 읽을 프레임 번호를 옮긴다(position frames)
    cap.set(cv2.CAP_PROP_POS_FRAMES, no)
    ok, frame = cap.read()
    picked.append((no, frame))
    # 건너뛰어 읽은 장이 2단계에서 차례로 읽은 같은 번호의 장과 똑같은지 본다
    same = np.array_equal(frame, frames[no])
    print(f"{no:2}번 → {no / fps:.1f}초 · 읽기 {ok} · 차례로 읽은 장과 같은가 {same}")
cap.release()
출력
 0번 → 0.0초 · 읽기 True · 차례로 읽은 장과 같은가 True
20번 → 2.0초 · 읽기 True · 차례로 읽은 장과 같은가 True
40번 → 4.0초 · 읽기 True · 차례로 읽은 장과 같은가 True
60번 → 6.0초 · 읽기 True · 차례로 읽은 장과 같은가 True

4단계. 네 장을 격자로 보기

Python
fig, axes = plt.subplots(1, 4, figsize=(12, 2.6))
for ax, (no, frame) in zip(axes, picked):
    # OpenCV 의 BGR 을 Matplotlib 의 RGB 로 바꿔 띄운다
    ax.imshow(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
    ax.set_title(f"{no}번 프레임 ({no / fps:.1f}초)")
    ax.axis("off")
plt.tight_layout()
plt.show()
컨베이어 동영상의 0번, 20번, 40번, 60번 프레임을 가로로 놓아 상자가 오른쪽으로 옮겨 가는 모습을 보인 격자

코드 설명

  • cv2.VideoCapture("ch15_conveyor.mp4") 는 재생기를 만들고, isOpened() 로 열렸는지 확인합니다. 열리지 않아도 오류가 나지 않으므로 이 확인을 빼먹지 않습니다.
  • cap.get(cv2.CAP_PROP_FRAME_COUNT) 처럼 속성을 읽으면 80.0 같은 실수가 옵니다. 장 수와 칸 수는 int(...) 로 바꿔 씁니다. 80장 ÷ 10fps 로 길이 8초, 1000 ÷ 10 으로 한 장 100ms 입니다.
  • while True: 안에서 cap.read() 를 되풀이하고 ok 가 False 면 멈춥니다. 끝을 지난 뒤의 결과는 False None 입니다.
  • 80장을 펼치면 18,432,000바이트인데 파일은 78,558바이트입니다. 코덱이 약 235분의 1 로 줄여 담았습니다.
  • cap.set(cv2.CAP_PROP_POS_FRAMES, no) 로 건너뛰어 읽은 네 장은 이 동영상에서 차례로 읽은 같은 번호의 장과 모두 똑같았습니다(True). 코덱과 파일에 따라 근처 장이 나올 수도 있으므로, 중요한 일에는 이렇게 한 번 견줘 봅니다.
← 이전 절15장 · 실습 지도다음 절 →15장 · 실습 2 — 프레임마다 경계 찾고 새 동영상으로 저장하기