15장. 비디오 처리와 실시간 영상처리
실습 1 — 동영상 열고 속성과 프레임 읽기
컨베이어 동영상(ch15_conveyor.mp4)을 열어 프레임 수, FPS, 크기를 읽고, 모든 프레임을 읽은 뒤, 네 장을 골라 격자로 봅니다. 이 동영상은 OpenCV 로 도형을 그려 만든 가상 동영상입니다.
1단계. 열고 속성 읽기
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.0ms2단계. 끝까지 읽어 모으기
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단계. 번호로 건너뛰어 네 장 고르기
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 · 차례로 읽은 장과 같은가 True4단계. 네 장을 격자로 보기
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()
코드 설명
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). 코덱과 파일에 따라 근처 장이 나올 수도 있으므로, 중요한 일에는 이렇게 한 번 견줘 봅니다.