OpenCV 비전 실무3장 · 디지털 영상과 색공간 변환16 / 21← 이전목차다음 →MetaContents

3장. 디지털 영상과 색공간 변환

실습 3 — BGR, RGB, Gray 바꾸기

BGR 그림을 RGB 로 바꾸는 두 방법이 같은 결과를 내는지 확인합니다. 이어서 흑백 변환 식을 사과 한 칸에 손으로 계산하고, NumPy 로 그림 전체에 계산해 cv2.cvtColor 결과와 견줍니다. 마지막으로 색 견본(color_chips.png, 가상 그림)에서 가중 평균과 단순 평균의 차이를 봅니다.

1단계. RGB 로 바꾸는 두 방법 견주기

Python
import cv2
import numpy as np
import matplotlib.pyplot as plt

plt.rcParams["font.family"] = "Malgun Gothic"
plt.rcParams["axes.unicode_minus"] = False

img = cv2.imread("fruit_basket.png")
# 방법 1: cvtColor 로 순서를 바꾼다
rgb1 = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 방법 2: 마지막 방향(채널)을 거꾸로 읽는다
rgb2 = img[..., ::-1]

print("사과 칸 BGR:", [int(v) for v in img[135, 140]])
print("사과 칸 RGB:", [int(v) for v in rgb1[135, 140]])
# 두 방법의 결과가 모든 칸에서 같은가
print("두 방법이 같은가:", np.array_equal(rgb1, rgb2))
# C_CONTIGUOUS: 숫자가 메모리에 차례대로 놓여 있으면 True. 뒤집어 읽기만 한 rgb2 는 False
print("rgb1 차례대로 놓였나:", rgb1.flags["C_CONTIGUOUS"], "/ rgb2:", rgb2.flags["C_CONTIGUOUS"])
출력
사과 칸 BGR: [34, 37, 206]
사과 칸 RGB: [206, 37, 34]
두 방법이 같은가: True
rgb1 차례대로 놓였나: True / rgb2: False

2단계. 사과 한 칸을 손으로 흑백으로 바꾸기

Python
# cvtColor 로 그림 전체를 흑백으로. 채널 칸이 없어져 (세로, 가로) 모양이 된다
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
print("흑백 모양:", gray.shape)

# 같은 칸을 식으로 계산한다: 0.299 × R + 0.587 × G + 0.114 × B
b, g, r = (int(v) for v in img[135, 140])
by_hand = 0.299 * r + 0.587 * g + 0.114 * b
print(f"0.299×{r} + 0.587×{g} + 0.114×{b} = {by_hand:.3f}")
print("반올림:", round(by_hand), "/ cvtColor 결과:", int(gray[135, 140]))
출력
흑백 모양: (240, 320)
0.299×206 + 0.587×37 + 0.114×34 = 87.189
반올림: 87 / cvtColor 결과: 87

3단계. 그림 전체를 NumPy 로 계산해 견주기

Python
# 채널을 인덱싱으로 하나씩 꺼내 float32 로 바꾼다(곱하기에 소수가 나오므로)
B = img[..., 0].astype(np.float32)
G = img[..., 1].astype(np.float32)
R = img[..., 2].astype(np.float32)
mine = np.round(0.299 * R + 0.587 * G + 0.114 * B).astype(np.uint8)

# 빼기 전에 int16 으로 바꾼다. uint8 끼리 빼면 음수가 255 쪽으로 돌아 버린다
diff = np.abs(mine.astype(np.int16) - gray.astype(np.int16))
print("차이의 최댓값:", int(diff.max()))
print(f"값이 다른 칸: {int((diff > 0).sum())}칸 / {diff.size:,}칸")
출력
차이의 최댓값: 1
값이 다른 칸: 6칸 / 76,800칸

4단계. 가중 평균과 단순 평균 견주기

Python
chips = cv2.imread("color_chips.png")
weighted = cv2.cvtColor(chips, cv2.COLOR_BGR2GRAY)
# 단순 평균: 세 채널을 더해 3으로 나눈다. mean(axis=2) 는 채널 방향의 평균
simple = chips.mean(axis=2).round().astype(np.uint8)

# 견본 칸의 가운데 자리 (x, y)
centers = {"Red": (40, 28), "Green": (259, 28), "Blue": (113, 92)}
for name, (x, y) in centers.items():
    print(f"{name:5} BGR {[int(v) for v in chips[y, x]]} → 가중 {int(weighted[y, x])}, 단순 {int(simple[y, x])}")

fig, axes = plt.subplots(1, 3, figsize=(10, 2.6))
axes[0].imshow(cv2.cvtColor(chips, cv2.COLOR_BGR2RGB))
axes[0].set_title("원본")
axes[1].imshow(weighted, cmap="gray", vmin=0, vmax=255)
axes[1].set_title("가중 평균 (cvtColor)")
axes[2].imshow(simple, cmap="gray", vmin=0, vmax=255)
axes[2].set_title("단순 평균")
for ax in axes:
    ax.axis("off")
plt.tight_layout()
plt.show()
출력
Red   BGR [40, 40, 220] → 가중 94, 단순 100
Green BGR [60, 170, 60] → 가중 125, 단순 97
Blue  BGR [200, 80, 30] → 가중 79, 단순 103
색 견본의 원본, 가중 평균 흑백, 단순 평균 흑백을 나란히 놓은 결과

코드 설명

  • cv2.cvtColor(img, cv2.COLOR_BGR2RGB) 와 img[..., ::-1] 은 같은 숫자를 냅니다. 다만 뒤집어 읽기만 한 쪽은 숫자가 차례대로 놓이지 않아(C_CONTIGUOUS 가 False), 그 위에 그리려면 .copy() 가 필요합니다.
  • 흑백 식 0.299×R + 0.587×G + 0.114×B 에 사과 칸의 숫자를 넣은 결과를 반올림하면 cvtColor 의 값과 같습니다.
  • 그림 전체를 NumPy 로 계산하면 76,800칸 가운데 6칸만 값이 다르고, 그 차이도 1 입니다. OpenCV 가 속도를 위해 정수로 계산하며 생기는 반올림 차이입니다.
  • vmin=0, vmax=255 를 주면 Matplotlib 이 숫자를 0~255 그대로 회색에 대응시킵니다. 주지 않으면 그림 안의 가장 작은 값과 큰 값에 맞춰 늘려 보여 줍니다.
← 이전 절3장 · 실습 2 — 해상도와 데이터형 살펴보기다음 절 →3장 · 실습 4 — HSV 로 바꿔 세 숫자 보기