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

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

실습 2 — 프레임마다 경계 찾고 새 동영상으로 저장하기

컨베이어 동영상의 모든 프레임에 흑백 → Gaussian 블러 → Canny 를 걸어 경계 동영상(out_ch15_edges.mp4)으로 저장합니다. 저장한 파일을 다시 열어 프레임 수와 모양을 확인하고, 대표 프레임 세 장을 원본과 나란히 봅니다.

1단계. 재생기와 녹화기 만들기

Python
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단계. 프레임마다 처리하고 쓰기

Python
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.019

3단계. 저장한 파일 다시 열어 보기

Python
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단계. 원본과 경계를 나란히 보기

Python
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()
컨베이어 동영상의 10번, 40번, 70번 프레임 원본과 Canny 경계를 위아래 두 줄로 놓은 격자

코드 설명

  • 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 로 이진화합니다.
← 이전 절15장 · 실습 1 — 동영상 열고 속성과 프레임 읽기다음 절 →15장 · 실습 3 — 프레임 차이로 움직임 찾기