logo

OpenCV와 이미지·영상 입출력

OpenCV

OpenCV(Open Source Computer Vision Library)는 이미지 처리와 컴퓨터 비전을 위한 오픈 소스 라이브러리임.

  • 이미지 파일 읽기와 저장
  • 필터링, 색 변환, 경계 검출 등 이미지 처리
  • 동영상 파일과 카메라 프레임 처리
  • 특징점, 기하학적 변환, 카메라 교정
  • 객체 탐지와 추적 등 컴퓨터 비전

OpenCV의 핵심은 C++로 작성되어 있으며 Python, Java 등 여러 언어에서 사용 가능함. OpenCV-Python에서 이미지는 NumPy 배열로 표현됨.

설치와 불러오기

pip install opencv-python
from pathlib import Path

import cv2 as cv
from PIL import Image

cv.__version__

opencv-python은 이미지·영상 처리와 데스크톱 창 표시 기능을 포함함. 화면 출력 기능이 필요 없는 서버 환경에서는 opencv-python-headless를 사용할 수 있음.

이미지 파일 읽기

image = cv.imread("balloon.webp")

cv.imread()는 파일을 디코딩하여 NumPy 배열을 반환함. 파일이 없거나 지원하지 않는 형식이면 예외 대신 None을 반환할 수 있으므로 바로 확인함.

if image is None:
    raise FileNotFoundError("이미지를 읽을 수 없습니다")
type(image)  # 자료형

배열의 크기와 자료형

image.shape  # (세로, 가로, 채널)
image.dtype  # 픽셀 자료형
image.size  # 배열을 구성하는 전체 값의 개수

PIL과 OpenCV 배열에서 size의 의미가 다름.

표현의미
PIL img.size(가로, 세로)
OpenCV image.shape(세로, 가로, 채널)
NumPy image.size세로×가로×채널인 전체 원소 수
height, width, channels = image.shape
height, width, channels

일반적인 컬러 이미지는 uint8 자료형을 사용하며 채널마다 0–255 범위의 값을 가짐.

읽기 모드

cv.imread()의 두 번째 인자로 이미지의 읽기 방식을 지정 가능함.

color = cv.imread(str(image_path), cv.IMREAD_COLOR)
gray = cv.imread(str(image_path), cv.IMREAD_GRAYSCALE)
unchanged = cv.imread(str(image_path), cv.IMREAD_UNCHANGED)
  • IMREAD_COLOR: 컬러 이미지로 읽음. 알파 채널이 있어도 제거
  • IMREAD_GRAYSCALE: 1채널 그레이스케일 이미지로 읽음
  • IMREAD_UNCHANGED: 알파 채널을 포함하여 원본 채널 수를 가능한 한 유지
color.shape, gray.shape, unchanged.shape

컬러 배열은 일반적으로 (세로, 가로, 3), 그레이스케일 배열은 (세로, 가로) 형태임.

BGR과 RGB

PIL은 컬러 이미지를 RGB 순서로 다루지만, cv.imread()가 반환하는 컬러 배열은 기본적으로 BGR 순서임.

PIL:    R, G, B
OpenCV: B, G, R

OpenCV 배열을 그대로 PIL 이미지로 만들면 빨강과 파랑이 뒤바뀌어 보임. 표시하기 전에 채널 순서를 변환함.

rgb = cv.cvtColor(image, cv.COLOR_BGR2RGB)
Image.fromarray(rgb)

반복해서 사용하도록 표시 함수를 만들 수 있음.

def show(image):
    if image.ndim == 2:
        return Image.fromarray(image)

    rgb = cv.cvtColor(image, cv.COLOR_BGR2RGB)
    return Image.fromarray(rgb)
show(image)

색 공간과 채널 변환은 다음에 다룸.

이미지 파일 저장

cv.imwrite()는 파일 확장자에 맞는 인코더를 선택하여 배열을 이미지 파일로 저장함.

saved = cv.imwrite("balloon_gray.png", gray)
saved  # 저장 성공 여부
  • 반환값 True: 저장 성공
  • 반환값 False: 인코더 또는 출력 조건 문제로 저장 실패
  • 존재하지 않는 상위 폴더는 자동으로 만들지 않음
  • JPEG 품질이나 PNG 압축률은 세 번째 인자로 지정 가능
cv.imwrite(
    "balloon_q90.jpg",
    image,
    [cv.IMWRITE_JPEG_QUALITY, 90],
)

데스크톱 창에서 이미지 보기

cv.imshow()는 로컬 데스크톱에 별도의 창을 띄움.

cv.imshow("balloon", image)
cv.waitKey(0)
cv.destroyAllWindows()
  • cv.waitKey(0): 키 입력이 있을 때까지 대기
  • cv.destroyAllWindows(): OpenCV가 만든 창 닫기
  • 원격 서버나 일반적인 Colab 환경에서는 GUI 창을 사용할 수 없음

주피터 노트북에서는 앞에서 만든 show()가 더 편리함.

동영상은 프레임의 연속

동영상은 시간 순서대로 저장된 이미지 프레임의 연속임.

frame 0 → frame 1 → frame 2 → ···
  • 프레임(frame): 동영상을 구성하는 이미지 한 장
  • FPS(frames per second): 1초 동안 재생되는 프레임 수
  • 해상도: 각 프레임의 가로·세로 픽셀 수
  • 코덱(codec): 프레임을 압축하고 해제하는 방식
  • 컨테이너(container): 영상과 부가 정보를 담는 파일 형식

동영상 파일 열기

video_path = Path("자전거.mp4")
video = cv.VideoCapture(str(video_path))
if not video.isOpened():
    raise FileNotFoundError(f"동영상을 열 수 없습니다: {video_path}")

VideoCapture는 동영상 파일, 이미지 시퀀스, 카메라에서 프레임을 읽는 공통 인터페이스임.

동영상 정보 확인

fps = video.get(cv.CAP_PROP_FPS)
frame_count = int(video.get(cv.CAP_PROP_FRAME_COUNT))
frame_width = int(video.get(cv.CAP_PROP_FRAME_WIDTH))
frame_height = int(video.get(cv.CAP_PROP_FRAME_HEIGHT))

{
    "FPS": fps,
    "프레임 수": frame_count,
    "프레임 크기": (frame_width, frame_height),
}

속성값은 동영상 코덱과 운영체제의 비디오 백엔드에 따라 일부 제공되지 않거나 근사값일 수 있음.

프레임 한 장 읽기

ret, frame = video.read()
  • ret: 프레임을 성공적으로 읽었는지 나타내는 불리언 값
  • frame: BGR 순서의 NumPy 이미지 배열
  • 동영상 끝에 도달하거나 프레임을 읽지 못하면 retFalse
if not ret:
    raise RuntimeError("첫 프레임을 읽지 못했습니다.")

frame.shape
show(frame)
video.release()

release()는 파일이나 카메라 장치를 해제함.

동영상 재생

동영상 파일을 다시 열고 프레임을 한 장씩 읽어 데스크톱 창에 표시함.

video = cv.VideoCapture(str(video_path))
fps = video.get(cv.CAP_PROP_FPS)
delay = max(1, round(1000 / fps)) if fps > 0 else 33

try:
    while video.isOpened():
        ret, frame = video.read()
        if not ret:
            break

        cv.imshow("video", frame)

        key = cv.waitKey(delay) & 0xFF
        if key == ord("q"):
            break
finally:
    video.release()
    cv.destroyAllWindows()
  • delay: 다음 프레임을 표시하기 전 기다릴 밀리초
  • q: 반복 재생 중단
  • tryfinally: 오류나 중단이 발생해도 장치와 창을 해제

GUI를 사용할 수 없는 원격 노트북에서는 한 프레임씩 show(frame)으로 확인하거나 별도의 노트북용 동영상 표시 기능을 사용함.

동영상 파일 저장

VideoWriter는 프레임 배열을 순서대로 인코딩하여 동영상 파일로 저장함.

reader = cv.VideoCapture(str(video_path))

if not reader.isOpened():
    raise FileNotFoundError(f"동영상을 열 수 없습니다: {video_path}")

fps = reader.get(cv.CAP_PROP_FPS)
width = int(reader.get(cv.CAP_PROP_FRAME_WIDTH))
height = int(reader.get(cv.CAP_PROP_FRAME_HEIGHT))

fourcc = cv.VideoWriter_fourcc(*"mp4v")
writer = cv.VideoWriter(
    "자전거_복사본.mp4",
    fourcc,
    fps,
    (width, height),
)

if not writer.isOpened():
    reader.release()
    raise RuntimeError("동영상 저장 장치를 열지 못했습니다.")
try:
    while True:
        ret, frame = reader.read()
        if not ret:
            break

        writer.write(frame)
finally:
    reader.release()
    writer.release()
  • fourcc: 사용할 코덱을 나타내는 네 문자 코드
  • fps: 출력 동영상의 재생 속도
  • (width, height): 저장할 모든 프레임의 크기
  • writer.write(frame): BGR 프레임을 출력 동영상에 추가

사용 가능한 코덱은 운영체제와 OpenCV 설치 환경에 따라 다름. VideoWriter는 영상 프레임을 저장하며 원본의 오디오 트랙은 복사하지 않음.

웹캠에서 한 프레임 읽기

동영상 파일 경로 대신 카메라 장치 번호를 전달함. 일반적으로 0은 기본 카메라임.

camera = cv.VideoCapture(0)

if not camera.isOpened():
    raise RuntimeError("카메라를 열 수 없습니다.")

원하는 프레임 크기를 요청한 뒤 실제로 적용된 값을 확인함. 카메라가 지원하지 않는 크기라면 가장 가까운 값이 사용될 수 있음.

camera.set(cv.CAP_PROP_FRAME_WIDTH, 640)
camera.set(cv.CAP_PROP_FRAME_HEIGHT, 480)

actual_width = int(camera.get(cv.CAP_PROP_FRAME_WIDTH))
actual_height = int(camera.get(cv.CAP_PROP_FRAME_HEIGHT))
actual_width, actual_height
try:
    ret, webcam_frame = camera.read()
    if not ret:
        raise RuntimeError("웹캠 프레임을 읽지 못했습니다.")
finally:
    camera.release()

show(webcam_frame)

웹캠 접근은 로컬 실행 환경과 운영체제의 카메라 권한이 필요함.

웹캠 영상 표시

camera = cv.VideoCapture(0)

if not camera.isOpened():
    raise RuntimeError("카메라를 열 수 없습니다.")

try:
    while True:
        ret, frame = camera.read()
        if not ret:
            break

        cv.imshow("webcam", frame)

        key = cv.waitKey(1) & 0xFF
        if key == ord("q"):
            break
finally:
    camera.release()
    cv.destroyAllWindows()

cv.waitKey(1)은 잠깐 키 입력을 확인하면서 화면 갱신을 가능하게 함. 원격 서버나 일반적인 Colab 환경에서는 로컬 웹캠과 데스크톱 창을 같은 방식으로 사용할 수 없음.

Previous
디지털 이미지의 구조와 PIL