2장. 이미지 입출력과 영상 시각화
실습 5 — 확대 보기와 savefig 로 남기기
상품 그림의 가격표 부분을 잘라 크게 키워 보고, 원본과 확대 그림을 한 장의 보고서용 그림(out_ch02_zoom.png)으로 저장합니다. 흑백 그림을 띄울 때 vmin, vmax 가 하는 일도 확인합니다.
1단계. 확대할 자리 잘라 내기
import cv2
src = cv2.imread("ch02_product.png")
# 가격표: x 172~231, y 20~57. 배열은 [y 시작:y 끝, x 시작:x 끝]
x1, y1, x2, y2 = 172, 20, 232, 58
tag = src[y1:y2, x1:x2]
print("잘라 낸 가격표:", tag.shape)
# 원본 위에 다홍빛 상자를 그려 어디를 키웠는지 보인다. 원본을 지키려고 copy() 에 그린다
marked = src.copy()
# cv2.rectangle(그림, 왼쪽 위 (x, y), 오른쪽 아래 (x, y), 색 BGR, 두께): 그림 함수는 (x, y) 순서
cv2.rectangle(marked, (x1, y1), (x2 - 1, y2 - 1), (38, 93, 254), 2)
print("상자를 그린 뒤에도 원본은 그대로인가:", bool((src == cv2.imread("ch02_product.png")).all()))잘라 낸 가격표: (38, 60, 3)
상자를 그린 뒤에도 원본은 그대로인가: True2단계. 세 칸으로 견주고 savefig 로 저장하기
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), layout="constrained")
axes[0].imshow(cv2.cvtColor(marked, cv2.COLOR_BGR2RGB))
axes[0].set_title("원본 (상자 부분을 키운다)")
# 같은 조각을 두 가지 보간으로 키운다
axes[1].imshow(cv2.cvtColor(tag, cv2.COLOR_BGR2RGB), interpolation="bilinear")
axes[1].set_title("bilinear: 섞어서 키움")
axes[2].imshow(cv2.cvtColor(tag, cv2.COLOR_BGR2RGB), interpolation="nearest")
axes[2].set_title("nearest: 네모 그대로")
for ax in axes:
ax.axis("off")
fig.suptitle("상품 가격표 확대 비교")
# 화면에 띄우기 전에 저장한다. dpi=120, 둘레 여백은 잘라 낸다
fig.savefig("out_ch02_zoom.png", dpi=120, bbox_inches="tight")
print("저장: out_ch02_zoom.png")
plt.show()저장: out_ch02_zoom.png
3단계. 두 저장 방법의 결과 견주기
import os
# 같은 가격표 조각을 imwrite 로도 저장해 본다
cv2.imwrite("out_ch02_tag.png", tag)
for name in ("out_ch02_tag.png", "out_ch02_zoom.png"):
saved = cv2.imread(name)
print(f"{name:18} 모양 {str(saved.shape):15} 파일 {os.path.getsize(name):,}바이트")
# 9인치 × 120dpi = 1,080 이 가로의 대략값이다(tight 로 여백을 잘라 조금 달라진다)
print("figsize 가로 × dpi =", 9 * 120)out_ch02_tag.png 모양 (38, 60, 3) 파일 2,144바이트
out_ch02_zoom.png 모양 (345, 1093, 3) 파일 67,400바이트
figsize 가로 × dpi = 10804단계. vmin, vmax 로 실제 밝기 보기
gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
dark = gray // 4 # 모든 칸을 4로 나눠 일부러 어둡게 만든다
print("어두운 그림의 범위:", int(dark.min()), "~", int(dark.max()))
fig, axes = plt.subplots(1, 2, figsize=(8, 3), layout="constrained")
# vmin, vmax 를 주지 않으면 가장 작은 값~가장 큰 값을 검정~흰색으로 늘려 보여 준다
axes[0].imshow(dark, cmap="gray")
axes[0].set_title("vmin, vmax 없음")
# 0 은 검정, 255 는 흰색으로 못 박으면 실제로 어두운 그림이 어둡게 보인다
axes[1].imshow(dark, cmap="gray", vmin=0, vmax=255)
axes[1].set_title("vmin=0, vmax=255")
for ax in axes:
ax.axis("off")
plt.show()어두운 그림의 범위: 22 ~ 63
결과를 읽어 봅니다. imwrite 로 저장한 가격표는 잘라 낸 배열과 똑같은 (38, 60, 3) 모양이고 파일은 2,144바이트입니다. savefig 로 저장한 그림은 (345, 1093, 3) 으로, 제목과 세 칸이 든 도화지 전체라 가로가 figsize × dpi 로 구한 1,080 에 가까운 1,093픽셀이 됐고 파일도 67,400바이트로 훨씬 큽니다. 4단계의 어두운 그림은 숫자가 22~63 뿐인데, vmin, vmax 없이 띄운 왼쪽은 22 를 검정, 63 을 흰색으로 늘려 원래 그림처럼 밝게 보입니다. 0~255 로 못 박은 오른쪽이 실제 밝기대로 어둡게 보이는 모습입니다.
코드 설명
cv2.rectangle(marked, (x1, y1), (x2 - 1, y2 - 1), 색, 2)는 (x, y) 순서로 네모를 그립니다. 같은 자리를 배열로 자를 때는src[y1:y2, x1:x2]처럼 행이 먼저입니다.interpolation="nearest"는 픽셀 네모를 그대로 키워 보여 주고,"bilinear"는 이웃 칸을 섞어 부드럽게 보여 줍니다. 배열의 숫자는 둘 다 바뀌지 않습니다.fig.savefig("out_ch02_zoom.png", dpi=120, bbox_inches="tight")는 제목과 세 칸이 든 도화지 전체를 저장합니다.plt.show()앞에서 부릅니다.imshow(dark, cmap="gray", vmin=0, vmax=255)는 0 을 검정, 255 를 흰색으로 정해 실제 밝기대로 보여 줍니다.