2장. 이미지 입출력과 영상 시각화
실습 1 — 읽기 모드 세 가지 견주기
투명 바탕의 별 스티커(ch02_sticker.png)를 세 가지 읽기 모드로 읽어 모양을 견주고, 알파 채널의 값을 세어 봅니다. 이 스티커는 OpenCV 로 도형을 그려 만든 연습용 가상 그림입니다.
1단계. 세 모드로 읽고 모양 견주기
import cv2
# 읽기 모드 이름과 값을 사전으로 짝지어 둔다
modes = {
"COLOR": cv2.IMREAD_COLOR, # 기본값: 언제나 BGR 세 채널
"GRAYSCALE": cv2.IMREAD_GRAYSCALE, # 읽으면서 흑백 한 채널로
"UNCHANGED": cv2.IMREAD_UNCHANGED, # 저장된 그대로(투명도 포함)
}
images = {}
for name, flag in modes.items():
# cv2.imread(파일 이름, 읽기 모드): 둘째 자리에 모드를 준다
img = cv2.imread("ch02_sticker.png", flag)
images[name] = img
# 결과: 모드마다 모양, 차원 수, 메모리 크기가 다르다
print(f"{name:10} 모양 {str(img.shape):15} 차원 {img.ndim} {img.nbytes:,}바이트")COLOR 모양 (160, 160, 3) 차원 3 76,800바이트
GRAYSCALE 모양 (160, 160) 차원 2 25,600바이트
UNCHANGED 모양 (160, 160, 4) 차원 3 102,400바이트2단계. 알파 채널의 값 세기
import numpy as np
# UNCHANGED 로 읽은 그림의 네 번째 숫자(번호 3)가 알파다
alpha = images["UNCHANGED"][:, :, 3]
total = alpha.size # 전체 칸 수 = 160 × 160
clear = int((alpha == 0).sum()) # 완전히 투명한 칸
solid = int((alpha == 255).sum()) # 완전히 불투명한 칸
half = total - clear - solid # 그 사이(가장자리)
print("전체 칸:", total)
for name, count in [("투명(0)", clear), ("불투명(255)", solid), ("반투명(1~254)", half)]:
# 비율 = 칸 수 ÷ 전체 칸 수. 소수 셋째 자리까지
print(f"{name:12} {count:6,}칸 비율 {round(count / total, 3)}")전체 칸: 25600
투명(0) 19,491칸 비율 0.761
불투명(255) 5,672칸 비율 0.222
반투명(1~254) 437칸 비율 0.0173단계. 같은 칸을 세 모드로 읽어 보기
# 왼쪽 위 구석(투명한 곳)과 별 한가운데. 배열은 [행, 열] 순서로 꺼낸다
spots = {"구석 [0, 0]": (0, 0), "별 가운데 [70, 80]": (70, 80)}
for spot, (y, x) in spots.items():
print(spot)
for name, img in images.items():
value = img[y, x]
# 흑백은 숫자 하나, 컬러는 숫자 여러 개라서 꼴을 맞춰 찍는다
shown = int(value) if img.ndim == 2 else [int(v) for v in value]
print(f" {name:10} {shown}")구석 [0, 0]
COLOR [0, 0, 0]
GRAYSCALE 0
UNCHANGED [0, 0, 0, 0]
별 가운데 [70, 80]
COLOR [60, 210, 255]
GRAYSCALE 206
UNCHANGED [60, 210, 255, 255]4단계. 세 결과 나란히 띄우기
import matplotlib.pyplot as plt
plt.rcParams["font.family"] = "Malgun Gothic"
plt.rcParams["axes.unicode_minus"] = False
fig, axes = plt.subplots(1, 3, figsize=(9, 3.4), layout="constrained")
# 컬러는 BGR→RGB, 흑백은 cmap="gray", 투명 그림은 BGRA→RGBA 로 바꿔 띄운다
axes[0].imshow(cv2.cvtColor(images["COLOR"], cv2.COLOR_BGR2RGB))
axes[1].imshow(images["GRAYSCALE"], cmap="gray", vmin=0, vmax=255)
axes[2].imshow(cv2.cvtColor(images["UNCHANGED"], cv2.COLOR_BGRA2RGBA))
for ax, name in zip(axes, images):
ax.set_title(f"IMREAD_{name}")
ax.axis("off")
plt.show()
결과를 읽어 봅니다. 같은 파일인데 모드에 따라 메모리 크기가 76,800바이트(160×160×3), 25,600바이트(160×160×1), 102,400바이트(160×160×4)로 달라졌습니다. 알파를 세어 보면 전체 25,600칸 가운데 19,491칸(76.1%)이 완전히 투명하고, 5,672칸(22.2%)이 불투명하며, 별의 가장자리 437칸(1.7%)만 반투명입니다. 투명한 구석의 색 숫자는 [0, 0, 0] 이라, 투명도를 버리는 IMREAD_COLOR 와 IMREAD_GRAYSCALE 그림에서는 바탕이 검게 보입니다. IMREAD_UNCHANGED 그림은 알파가 0 인 곳으로 Matplotlib 의 흰 바탕이 비쳐 보입니다. 별 가운데의 BGR [60, 210, 255] 는 흑백으로 읽으면 206 이 됩니다. 0.299×255 + 0.587×210 + 0.114×60 = 76.2 + 123.3 + 6.8 = 206.3 이기 때문이며, 이 식은 3장에서 자세히 봅니다.
코드 설명
cv2.imread(파일, cv2.IMREAD_UNCHANGED)는 저장된 그대로 읽습니다. 투명 png 는 채널이 4개(BGRA)라 모양이 (160, 160, 4) 입니다.images["UNCHANGED"][:, :, 3]은 모든 칸의 네 번째 숫자, 곧 알파만 꺼낸 (160, 160) 배열입니다.(alpha == 0).sum()은 칸마다 "0 인가?"를 True/False 로 따진 뒤 True 의 개수를 셉니다. True 를 1 로 세기 때문입니다.cv2.COLOR_BGRA2RGBA는 네 채널 그림의 색 순서를 바꾸면서 알파는 그대로 둡니다. Matplotlib 은 알파를 읽어 투명한 곳에 바탕을 비춰 줍니다.