15장. 비디오 처리와 실시간 영상처리
실습 2 — 프레임마다 경계 찾고 새 동영상으로 저장하기
컨베이어 동영상의 모든 프레임에 흑백 → Gaussian 블러 → Canny 를 걸어 경계 동영상(out_ch15_edges.mp4)으로 저장합니다. 저장한 파일을 다시 열어 프레임 수와 모양을 확인하고, 대표 프레임 세 장을 원본과 나란히 봅니다.
1단계. 재생기와 녹화기 만들기
import cv2
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams["font.family"] = "Malgun Gothic"
plt.rcParams["axes.unicode_minus"] = False
cap = cv2.VideoCapture("ch15_conveyor.mp4")
fps = cap.get(cv2.CAP_PROP_FPS)
w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# VideoWriter_fourcc(*"mp4v"): 코덱 이름표 네 글자(four character code). mp4v 는 MPEG-4 방식
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
writer = cv2.VideoWriter(
"out_ch15_edges.mp4", # 저장할 파일 이름(data 폴더 안의 상대 경로)
fourcc, # 코덱 이름표
fps, # 1초에 몇 장으로 재생할지: 원본과 같게
(w, h), # 크기는 (가로, 세로) 순서
isColor=False, # 흑백(채널 1) 프레임을 넣는다
)
print("재생기 열림:", cap.isOpened(), "· 녹화기 열림:", writer.isOpened())
print(f"녹화 설정: {w}×{h}, {fps}fps, 흑백")재생기 열림: True · 녹화기 열림: True
녹화 설정: 320×240, 10.0fps, 흑백2단계. 프레임마다 처리하고 쓰기
ratios, samples = [], {}
no = 0
while True:
ok, frame = cap.read()
if not ok:
break
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 3차시: 흑백으로
blur = cv2.GaussianBlur(gray, (5, 5), 0) # 6차시: 잡음을 줄인다
edges = cv2.Canny(blur, 50, 150) # 11차시: 경계만 흰색
# write(프레임): 녹화기에 한 장을 쓴다. 크기와 채널이 설정과 맞아야 쓰인다
writer.write(edges)
ratios.append(float((edges > 0).mean())) # 장마다 경계 칸의 비율
if no in (10, 40, 70): # 대표 프레임 세 장을 따로 둔다
samples[no] = (frame, edges)
no += 1
cap.release()
writer.release() # 닫아야 파일이 마무리된다
print("처리하고 쓴 프레임:", no, "장")
print("경계 칸 비율: 가장 작은", round(min(ratios), 3), "· 가장 큰", round(max(ratios), 3))처리하고 쓴 프레임: 80 장
경계 칸 비율: 가장 작은 0.011 · 가장 큰 0.0193단계. 저장한 파일 다시 열어 보기
import os
check = cv2.VideoCapture("out_ch15_edges.mp4")
print("다시 연 파일의 프레임 수:", int(check.get(cv2.CAP_PROP_FRAME_COUNT)), "· FPS:", check.get(cv2.CAP_PROP_FPS))
ok, first = check.read()
check.release()
# 흑백으로 저장했어도 다시 읽으면 BGR 3채널로 읽힌다
print("다시 읽은 한 장의 모양:", first.shape)
# 0 과 255 뿐이던 경계 그림이 mp4 로 줄여 담기며 몇 가지 값이 되었는지 센다
print("들어 있는 값의 가짓수:", len(np.unique(first)))
print(f"파일 크기: {os.path.getsize('out_ch15_edges.mp4'):,}바이트")다시 연 파일의 프레임 수: 80 · FPS: 10.0
다시 읽은 한 장의 모양: (240, 320, 3)
들어 있는 값의 가짓수: 22
파일 크기: 202,171바이트4단계. 원본과 경계를 나란히 보기
fig, axes = plt.subplots(2, 3, figsize=(11, 5))
for col, (no, (frame, edges)) in enumerate(samples.items()):
axes[0, col].imshow(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
axes[0, col].set_title(f"{no}번 원본")
axes[1, col].imshow(edges, cmap="gray")
axes[1, col].set_title(f"{no}번 경계 (비율 {ratios[no]:.3f})")
for ax in axes.flat:
ax.axis("off")
plt.tight_layout()
plt.show()
코드 설명
cv2.VideoWriter(이름, fourcc, fps, (w, h), isColor=False)는 흑백 녹화기입니다. 크기는(가로, 세로)순서라 배열의shape(세로, 가로)와 거꾸로 적습니다.- 반복 안의 세 줄은 3장, 6장, 11장에서 사진 한 장에 쓰던 코드 그대로입니다. 동영상 처리는 이것을 장마다 되풀이할 뿐입니다.
- 경계 칸의 비율은 장마다 0.011~0.019 사이였습니다. 상자가 화면에 둘 보이는 40번(0.018)이 하나뿐인 10번(0.014)보다 경계가 많습니다.
- 저장한 경계 동영상을 다시 열면 80장, 10.0fps 로 처리한 장 수와 같지만, 모양은
(240, 320, 3)으로 3채널로 읽힙니다. 0 과 255 뿐이던 경계 그림이 mp4 로 줄여 담기며 22가지 값으로 바뀌었고, 파일은 202,171바이트로 원본(78,558바이트)보다 커졌습니다. 가는 선이 많은 그림은 코덱이 줄이기 어렵기 때문입니다. 경계를 다시 쓰려면 읽은 뒤 흑백으로 바꾸고 127 로 이진화합니다.