4장. 채널 처리와 히스토그램 분석
실습 1 — 채널 나누고 합치기
노을 그림(ch04_scenes/sunset.png)을 cv2.split 으로 세 장으로 나누고, cv2.merge 로 다시 합쳐 원본과 같은지 확인합니다. 인덱싱으로 꺼낸 채널과 split 의 복사본이 어떻게 다른지도 직접 봅니다.
1단계. 읽고 채널 나누기
import cv2
import numpy as np
# 노을 그림을 읽는다. 연습용으로 도형을 그려 만든 가상의 그림이다
img = cv2.imread("ch04_scenes/sunset.png")
print("원본 모양:", img.shape)
# cv2.split: 채널마다 한 장씩 흑백 그림 세 장을 돌려준다(B, G, R 순서)
b, g, r = cv2.split(img)
print("나눈 모양:", b.shape, g.shape, r.shape)
# 해 한가운데(세로 140, 가로 200) 칸을 원본과 세 장에서 각각 읽어 견준다
print("원본의 한 칸:", [int(v) for v in img[140, 200]])
print("세 장에서 읽은 값:", int(b[140, 200]), int(g[140, 200]), int(r[140, 200]))원본 모양: (240, 320, 3)
나눈 모양: (240, 320) (240, 320) (240, 320)
원본의 한 칸: [120, 220, 255]
세 장에서 읽은 값: 120 220 2552단계. 인덱싱과 split 의 차이 보기
# 인덱싱으로 꺼낸 0번 채널은 원본을 보는 창(뷰)이다
view = img[:, :, 0]
# np.shares_memory: 두 배열이 같은 숫자 저장소를 쓰면 True
print("인덱싱은 원본과 저장소를 함께 쓰나:", np.shares_memory(view, img))
print("split 은 원본과 저장소를 함께 쓰나:", np.shares_memory(b, img))
# split 으로 받은 b 를 고쳐 본다: 왼쪽 위 칸을 0 으로
before = int(img[0, 0, 0])
b[0, 0] = 0
print(f"b 를 고친 뒤 원본의 같은 칸: {int(img[0, 0, 0])} (고치기 전 {before})")
# 인덱싱으로 받은 창을 고치면 원본도 바뀐다. 실험이 끝나면 되돌리려고 값을 기억해 둔다
saved = int(view[0, 0])
view[0, 0] = 0
print("view 를 고친 뒤 원본의 같은 칸:", int(img[0, 0, 0]))
view[0, 0] = saved # 원본을 원래대로 되돌린다
b[0, 0] = saved # b 도 원래대로 되돌린다인덱싱은 원본과 저장소를 함께 쓰나: True
split 은 원본과 저장소를 함께 쓰나: False
b 를 고친 뒤 원본의 같은 칸: 70 (고치기 전 70)
view 를 고친 뒤 원본의 같은 칸: 03단계. 다시 합쳐 원본과 같은지 보기
# cv2.merge: 흑백 그림 목록을 채널로 겹친다. 넣는 순서가 B, G, R 자리가 된다
merged = cv2.merge([b, g, r])
print("합친 모양:", merged.shape)
# np.array_equal: 모양과 모든 숫자가 같으면 True
print("원본과 같은가:", np.array_equal(merged, img))
# 순서를 [r, g, b] 로 넣으면 빨강과 파랑 자리가 바뀌어 원본과 달라진다
wrong = cv2.merge([r, g, b])
print("순서를 바꿔 합치면 같은가:", np.array_equal(wrong, img))
# 몇 칸이 달라졌는지 센다. 칸마다 세 숫자 가운데 하나라도 다르면 다른 칸이다
changed = int(np.any(wrong != img, axis=2).sum())
print(f"달라진 칸: {changed:,}칸 / {img.shape[0] * img.shape[1]:,}칸")합친 모양: (240, 320, 3)
원본과 같은가: True
순서를 바꿔 합치면 같은가: False
달라진 칸: 76,800칸 / 76,800칸4단계. 세 장을 흑백으로 나란히 보기
import matplotlib.pyplot as plt
# 그래프의 한글이 깨지지 않게 맑은 고딕을 쓰고, 빼기 기호가 네모로 나오지 않게 한다
plt.rcParams["font.family"] = "Malgun Gothic"
plt.rcParams["axes.unicode_minus"] = False
fig, axes = plt.subplots(1, 4, figsize=(11, 2.8))
panels = [("컬러 원본", cv2.cvtColor(img, cv2.COLOR_BGR2RGB), None),
("B 채널", b, "gray"), ("G 채널", g, "gray"), ("R 채널", r, "gray")]
for ax, (title, arr, cmap) in zip(axes, panels):
# vmin, vmax: 0 은 검정, 255 는 흰색으로 고정한다. 주지 않으면 채널마다 밝기 기준이 달라진다
ax.imshow(arr, cmap=cmap, vmin=0, vmax=255)
ax.set_title(title)
ax.axis("off")
plt.tight_layout()
plt.show()
코드 설명
cv2.split(img)는 (240, 320, 3) 그림을 (240, 320) 흑백 그림 세 장으로 나눕니다. 해 한가운데 칸의[120, 220, 255]가 세 장에 하나씩 나뉘어 들어갑니다.np.shares_memory로 보면 인덱싱(img[:, :, 0])은 원본과 저장소를 함께 쓰고(True),cv2.split은 따로 씁니다(False). 그래서b를 고쳐도 원본은 그대로지만,view를 고치면 원본이 함께 바뀝니다.cv2.merge([b, g, r])는 원본과 숫자가 모두 같습니다. 순서를[r, g, b]로 넣으면 76,800칸이 모두 달라집니다. 노을 그림에는 빨강과 파랑 숫자가 같은 칸이 하나도 없기 때문입니다.imshow의vmin=0, vmax=255는 밝기 기준을 고정합니다. 주지 않으면 채널마다 가장 어두운 값과 가장 밝은 값을 검정과 흰색으로 늘려 보여 주어, 채널끼리 밝기를 견줄 수 없습니다.