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 이미지 배열- 동영상 끝에 도달하거나 프레임을 읽지 못하면
ret은False
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: 반복 재생 중단try–finally: 오류나 중단이 발생해도 장치와 창을 해제
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 환경에서는 로컬 웹캠과 데스크톱 창을 같은 방식으로 사용할 수 없음.