3장. 디지털 영상과 색공간 변환
HSV 색공간
페인트 가게에서 벽 색을 고르는 순서를 떠올려 봅시다. 먼저 색상환에서 "주황 계열"처럼 색을 고르고, 흰 물감을 섞어 옅게 할지, 검은 물감을 섞어 어둡게 할지 정합니다. HSV 는 색을 이 순서대로 적습니다. 어떤 색인지(H), 얼마나 진한지(S), 얼마나 밝은지(V)입니다.
세 상자가 HSV 의 세 숫자입니다. 왼쪽 H 는 색상환 위의 자리, 가운데 S 는 흰 물감을 덜 섞은 정도, 오른쪽 V 는 검은 물감을 덜 섞은 정도입니다. 여기서 색상환에서 고른 칸이 H, 흰 물감의 양이 S(많이 섞을수록 S 가 낮다), 검은 물감의 양이 V(많이 섞을수록 V 가 낮다)입니다. 비유와 다른 점도 있습니다. 실제 계산은 물감을 섞는 것이 아니라 R, G, B 세 숫자에서 바로 H, S, V 를 구합니다.
OpenCV 의 HSV 범위
맨 위 띠가 H 입니다. 0 의 빨강에서 시작해 노랑(30), 초록(60), 청록(90), 파랑(120), 자홍(150)을 지나 179 에서 다시 빨강으로 돌아옵니다. 가운데 띠는 S 가 커질수록, 아래 띠는 V 가 커질수록 진하고 밝은 빨강이 되는 모습입니다.
| 숫자 | 원래 뜻 | 보통의 범위 | OpenCV(uint8) 범위 |
|---|---|---|---|
| H 색상 | 색상환의 각도 | 0~360도 | 0~179 (각도를 2로 나눔) |
| S 채도 | 진한 정도 | 0~100% | 0~255 |
| V 명도 | 밝은 정도 | 0~100% | 0~255 |
H 가 0~179 인 까닭은 uint8 이 255 까지만 담기 때문입니다. 360도를 그대로 담을 수 없으니 2로 나눠 0~179 에 담습니다. 그래서 그림판 같은 프로그램에서 본 색상 각도가 60도(노랑)라면, OpenCV 에서는 60 ÷ 2 = 30 입니다.
대표 색의 HSV
| 색 | B, G, R | H | S | V |
|---|---|---|---|---|
| 빨강 | 0, 0, 255 | 0 | 255 | 255 |
| 노랑 | 0, 255, 255 | 30 | 255 | 255 |
| 초록 | 0, 255, 0 | 60 | 255 | 255 |
| 파랑 | 255, 0, 0 | 120 | 255 | 255 |
| 어두운 빨강 | 0, 0, 128 | 0 | 255 | 128 |
| 옅은 빨강 | 128, 128, 255 | 0 | 127 | 255 |
| 회색 | 128, 128, 128 | 0 | 0 | 128 |
어두운 빨강은 빨강에서 V 만 낮아졌고, 옅은 빨강은 S 만 낮아졌습니다. H 는 둘 다 0 그대로입니다. 그늘이 지거나 빛이 비쳐도 H 는 크게 바뀌지 않으므로, "빨간 것을 골라라" 같은 일에 HSV 가 편합니다.
import cv2
import numpy as np
# 1×3 컬러 그림: 빨강, 어두운 빨강, 옅은 빨강(BGR)
px = np.array([[[0, 0, 255], [0, 0, 128], [128, 128, 255]]], dtype=np.uint8)
# cv2.COLOR_BGR2HSV: BGR 을 HSV 로 바꾼다
hsv = cv2.cvtColor(px, cv2.COLOR_BGR2HSV)
for name, (h, s, v) in zip(["빨강", "어두운 빨강", "옅은 빨강"], hsv[0]):
print(name, int(h), int(s), int(v)) # 결과: H 는 셋 다 0, S 와 V 만 다르다주의할 점
- 회색, 흰색, 검정처럼 S 가 0 에 가까운 색은 H 가 뜻이 없습니다(OpenCV 는 0 으로 적습니다). 색으로 고를 때는 S 의 아래 한계를 함께 정해 이런 칸을 뺍니다.
- 빨강은 H 띠의 양 끝(0 근처와 179 근처)에 나뉘어 있습니다. 다음 절에서 이 점 때문에 범위를 두 번 잡습니다.
- 다른 프로그램의 HSV(0~360, 0~100%)를 OpenCV 로 옮길 때는 H 는 2로 나누고, S 와 V 는 2.55 를 곱합니다.