본문 바로가기
데이터사이언스/컴퓨터 비전 입문

CH03 색: RGB·HSV·Lab, 그리고 빨강의 경계

by 미스터탁 2026. 8. 30.

CH 03 컴퓨터 비전 입문 · Part 1 이미지라는 데이터

색: RGB·HSV·Lab, 그리고 "빨강"의 경계

"이 사진에서 빨간 부분을 찾아라"는 쉬워 보인다. 그런데 조명이 바뀌면 같은 물체의 RGB가 통째로 달라진다. 이 장에서는 세 색공간에서 같은 색 검출을 하고 조명을 바꿔 가며 IoU를 잰다. "지각적 색공간인 Lab이 가장 강건할 것"이라는 예상은 빗나갔다.

  • 01 직관
  • 02 수식 읽는 법
  • 03 손으로 풀기
  • 04 코드
  • 05 시각화

약어 및 기호 정의

RGB
Red·Green·Blue — 빛을 더해서 만드는 방식. 화면과 센서가 쓰는 좌표계
HSV
Hue·Saturation·Value — 색상(각도) · 채도 · 명도. 사람이 색을 말할 때의 어순에 가깝다
Lab
CIELAB — L(밝기) · a(초록↔빨강) · b(파랑↔노랑). 거리와 지각 차이가 비례하도록 설계
색상 H
hue — 색의 종류를 0~360도의 각도로 나타낸 것. 0도 빨강, 120도 초록, 240도 파랑
채도 S
saturation — 색이 얼마나 선명한가. 0이면 회색
색도
chroma — 무채색 축에서 얼마나 떨어졌는가. Lab에서는 √(a²+b²)
ΔE
Delta E — Lab 공간에서의 거리. 1이면 훈련된 눈이 겨우 구분하는 차이가 되도록 만든 척도
IoU
Intersection over Union — 두 영역의 교집합 ÷ 합집합. 1이면 완전히 같고 0이면 안 겹친다
클리핑
clipping — 값이 0 또는 255를 넘어 잘려 나가는 것. 되돌릴 수 없다
STEP 01

직관: 같은 색인데 숫자가 다르다

빨간 사과를 밝은 곳과 그늘에서 찍는다. 사람은 둘 다 "빨간 사과"라고 본다. 그런데 숫자는 이렇다.

밝은 곳 (200, 60, 60)    그늘 (100, 30, 30)

세 값이 전부 달라졌다. RGB로 "빨강"의 범위를 정해 두면 그늘의 사과는 놓친다. 범위를 넓히면 이번엔 갈색 책상까지 잡힌다.

문제의 정체

RGB의 세 축은 서로 얽혀 있다. 조명이 세지면 R·G·B가 동시에 올라간다. "무슨 색인가"와 "얼마나 밝은가"가 세 숫자에 뒤섞여 들어 있다.

그래서 나온 발상 — 축을 갈아 끼워서 "밝기"를 한 축에 몰아넣자. 그러면 나머지 축만 보고 색을 판단할 수 있다.

HSV와 Lab이 그 발상의 두 가지 구현이다.

1-2. 세 색공간의 성격

같은 색을 세 가지로 부르는 법
색공간 만들어진 이유 밝기 축
RGB R, G, B 화면이 빛을 그렇게 낸다 · 센서가 그렇게 받는다 없음(셋에 흩어져 있다)
HSV H, S, V 사람이 색을 말하는 방식에 맞추려고 V
Lab L, a, b 거리가 지각 차이에 비례하게 만들려고 L

RGB만 "장치의 좌표계"이고 나머지 둘은 "사람의 좌표계"다. 그래서 둘 다 RGB에서 계산해서 얻는다 — 카메라가 Lab을 직접 찍지는 않는다.

STEP 02

수식 읽는 법

2-1. RGB → HSV

V = max(R,G,B)    S = (max − min) / max

H = 60° × ( (G−B)/(max−min) mod 6 ) … max가 R일 때

읽는 법 — 먼저 세 값 중 가장 큰 것을 명도로 삼는다. 채도는 "가장 큰 값과 가장 작은 값이 얼마나 벌어졌나"를 최댓값으로 나눈 비율이다. 색상은 어느 채널이 이겼는지와 나머지 둘의 관계로 각도를 만든다.

여기서 중요한 성질

RGB에 상수 k를 곱하면 max도 min도 똑같이 k배가 된다. 그러면

S = (k·max − k·min) / (k·max) = (max − min)/max변하지 않는다.
H도 분자·분모에 k가 같이 붙어 변하지 않는다.
V만 k배가 된다.

즉 "조명이 k배 세졌다"는 변화가 V 한 축에만 나타난다. 이것이 HSV의 설계 목적이고, 4절에서 이 성질이 그대로 측정된다.

2-2. RGB → Lab

Lab은 두 단계다. 먼저 RGB를 XYZ라는 물리적 좌표로 옮기고(선형 변환), 거기서 세제곱근을 씌워 사람 눈의 비선형 반응을 흉내 낸다.

L = 116·f(Y/Yn) − 16    a = 500·( f(X/Xn) − f(Y/Yn) )

b = 200·( f(Y/Yn) − f(Z/Zn) ) , f(t) ≈ t1/3

읽는 법aX와 Y의 차이, bY와 Z의 차이다. 차이를 쓰기 때문에 "빨강 쪽이냐 초록 쪽이냐" 같은 대립 축이 된다. Xn, Yn, Zn은 기준 백색점이다.

그런데 — HSV와 결정적으로 다른 점

Lab에서 RGB에 k를 곱하면 어떻게 되나? X, Y, Z가 모두 k배가 되고, f(t) = t1/3이므로 세 항이 모두 k1/3배가 된다.

a = 500·(f(X) − f(Y)) → 500·k1/3·(f(X) − f(Y)) = k1/3·a

a와 b도 함께 줄어든다. L만 빼고 봐도 소용이 없다 — 어두워지면 색도 자체가 원점 쪽으로 끌려간다.

이것이 4절에서 Lab이 예상보다 훨씬 약했던 이유다. "지각적 색공간"과 "조명 불변"은 전혀 다른 성질이다. Lab은 앞을 위해 만들어졌지 뒤를 위해 만들어지지 않았다.

2-3. IoU

IoU(A, B) = |A ∩ B| / |A ∪ B|

읽는 법 — 두 영역이 겹치는 픽셀 수를, 둘 중 하나라도 포함된 픽셀 수로 나눈다. 완전히 같으면 1, 안 겹치면 0. 21·23·24장에서 계속 쓸 지표라 여기서 미리 만난다.

STEP 03

손으로 풀기

손풀이 A — (200, 60, 60)을 HSV로

0~1로 정규화: R=0.7843, G=0.2353, B=0.2353

max = 0.7843 (R이 이겼다), min = 0.2353

V = max = 0.7843
S = (0.7843 − 0.2353) / 0.7843 = 0.5490 / 0.7843 = 0.7000
H = 60 × ((G−B)/(max−min) mod 6) = 60 × ((0 / 0.5490) mod 6) = 0.0000도

H가 정확히 0도 — 순수한 빨강 방향이다. G와 B가 같으니 어느 쪽으로도 치우치지 않았다.

손풀이 B — 밝기를 절반으로 낮추면

(100, 30, 30)을 같은 식에 넣는다. max = 0.3922, min = 0.1176.

원본 (200,60,60) 절반 (100,30,30) 변화
H 색상 0.0000° 0.0000° 0.0000
S 채도 0.7000 0.7000 0.0000
V 명도 0.7843 0.3922 −0.3922

H와 S는 소수점 아래까지 정확히 같다. 변화가 V 하나에 완전히 몰렸다.

RGB로 보면 세 값이 전부 절반이 됐다. 같은 사건을 어느 좌표계에서 보느냐에 따라 "세 축이 다 움직였다"가 되기도 하고 "한 축만 움직였다"가 되기도 한다.

손풀이 C — Lab에서는 왜 안 되나

같은 절반 낮추기를 Lab에서 따라가 보자. XYZ가 모두 0.5배가 되고, f(t)=t1/3이므로 0.51/3 = 0.7937배가 된다.

ab0.7937배로 줄어든다. 즉 색도가 21% 감소한다.

그러면 "a, b가 기준색에서 얼마나 떨어졌나"로 만든 마스크는 기준점이 어긋나면서 무너진다. 4절에서 Lab의 IoU가 0.3610으로 나온 것이 정확히 이것이다.

STEP 04

코드: 조명을 바꿔 가며 같은 색을 찾는다

astronaut 사진에서 붉은 계열 영역을 세 색공간에서 각각 찾고, 사진을 세 가지로 망가뜨린 뒤 원래 찾은 영역과의 IoU를 잰다.

ch03_code.pynumpy · skimage 색 마스크와 IoU
def mask_rgb(im, ref, th):                # RGB 유클리드 거리
    return np.linalg.norm(im - ref, axis=-1) < th
def mask_hsv(im, ref, th):                # V는 아예 안 본다
    h = color.rgb2hsv(im); hr = color.rgb2hsv(ref)
    dh = np.abs(h[...,0] - hr[0])
    dh = np.minimum(dh, 1 - dh)           # H는 각도 — 0과 1이 이웃이다
    return (dh*2 < th) & (np.abs(h[...,1] - hr[1]) < th*3)
def mask_lab(im, ref, th):                # L은 아예 안 본다
    l = color.rgb2lab(im); lr = color.rgb2lab(ref)
    return np.linalg.norm(l[...,1:] - lr[1:], axis=-1) < th*100
# 세 가지로 망가뜨린다
dim    = ast * 0.6                                   # 조명이 약해짐
bright = np.clip(ast * 1.5, 0, 1)                    # 과노출 (클리핑 발생)
tint   = ast * [1.12, 0.98, 0.92]                    # 화이트밸런스 틀어짐
실험 — 조명이 바뀌어도 '같은 색 영역'을 찾을 수 있나 색공간 기준영역% x0.6 IoU x1.5 IoU 색틀어짐 IoU 평균 RGB 18.92 0.3817 0.3122 0.7217 0.4719 HSV 27.30 0.9988 0.5149 0.9268 0.8135 Lab 24.07 0.3610 0.3911 0.8166 0.5229 색 사이의 '거리'를 재면 사람 눈과 맞나 색 쌍 RGB 거리 Lab 거리(ΔE) 비율 짙은 남색 vs 검정 90.00 62.60 0.6956 밝은 하늘 vs 흰색 92.20 26.06 0.2827 빨강 vs 주황 91.10 36.09 0.3962 초록1 vs 초록2 35.00 20.54 0.5870 세 색공간에서 채널끼리 얼마나 얽혀 있나 (상관계수) RGB |r01|=0.8408 |r02|=0.7292 |r12|=0.9610 평균 0.8437 HSV |r01|=0.0550 |r02|=0.0111 |r12|=0.0436 평균 0.0366 Lab |r01|=0.0113 |r02|=0.0998 |r12|=0.7345 평균 0.2819

4-1. 예상이 절반만 맞았다

"HSV와 Lab 둘 다 밝기 축을 안 보니 잘 버틸 것" — Lab은 아니었다

어둡게(×0.6) 했을 때 IoU를 보자.

HSV 0.9988 — 거의 완벽하다. 2절의 k배 불변성이 그대로 나타났다.
Lab 0.3610RGB(0.3817)보다도 나쁘다.
RGB 0.3817

Lab이 무너진 이유는 2절 손풀이 C에서 이미 계산했다. L을 안 봐도 a·b가 k1/3배로 함께 줄어든다. RGB를 0.6배 하면 색도가 0.84배가 되고, 기준색에서의 거리가 그만큼 어긋난다.

반면 HSV의 H·S는 비율로 정의되어 있어 상수배에 진짜로 불변이다.

여기서 배울 것 — "Lab은 지각적 색공간이다"는 맞는 말이지만, 지각적인 것과 조명 불변인 것은 다른 성질이다. Lab은 "두 색이 얼마나 달라 보이나"를 위해 설계됐지 "조명이 변해도 같은 값"을 위해 설계되지 않았다.

그런데 HSV도 밝게(×1.5) 하면 0.5149로 반토막이 난다

어둡게 할 때는 0.9988이었는데 밝게 하면 왜 무너지나? 클리핑 때문이다.

(200, 60, 60)에 1.5를 곱하면 (300, 90, 90)이지만, 300은 저장할 수 없어 255로 잘린다. 결과는 (255, 90, 90)이다. 이제 max/min 비율이 달라졌고 S가 0.700 → 0.647로 어긋난다.

더 밝은 영역에서는 세 채널이 모두 255에 붙어 R=G=B가 되고 색상 자체가 사라진다. H가 정의되지 않는다.

과노출은 어떤 색공간으로도 복구할 수 없다. 정보가 이미 잘려 나갔기 때문이다 — 2장에서 본 "되돌릴 수 없는 변환"의 또 다른 얼굴이다.

4-2. 채널 독립성 — 여기서도 예상과 달랐다

가장 독립적인 것은 Lab이 아니라 HSV(0.0366)였다

RGB의 채널 간 상관은 0.8437이다. 특히 G–B가 0.9610 — 거의 같은 신호다. 밝아지면 셋이 함께 올라가니 당연하다.

Lab은 0.2819로 RGB보다는 훨씬 낫지만, a–b 사이 상관이 0.7345로 여전히 크다.

왜일까? 색공간의 성질이 아니라 사진의 성질이다. 자연 사진의 색은 대부분 주황–파랑 축에 몰려 있다(살색·나무·흙 vs 하늘·물). a와 b가 그 축을 따라 함께 움직인다.

HSV의 H는 각도라서 이 편향에 영향받지 않는다. 그래서 0.0366이 나왔다.

4-3. 거리 — RGB로 90인 세 쌍이 사람 눈에는 전혀 다르다

같은 RGB 거리, 세 배 차이 나는 ΔE

색 쌍 RGB 거리 Lab ΔE 사람이 보기에
짙은 남색 vs 검정 90.00 62.60 확실히 다르다
밝은 하늘 vs 흰색 92.20 26.06 비슷해 보인다
빨강 vs 주황 91.10 36.09 중간

앞의 세 쌍은 RGB 거리가 약 90으로 거의 같다. 그런데 ΔE는 62.60 대 26.06으로 2.4배 차이가 난다.

이유는 사람 눈의 반응이 로그에 가깝기 때문이다. 어두운 쪽에서 +90은 크게 느껴지고, 밝은 쪽에서 +90은 작게 느껴진다. 2장의 감마 이야기와 같은 뿌리다.

실무 규칙 — 색을 "얼마나 비슷한가"로 묶는 일(클러스터링·팔레트 추출·품질 검사)에는 반드시 Lab을 쓴다. RGB 거리는 사람의 판단과 어긋난다.

그래서 언제 무엇을 쓰나

RGB — 화면에 그릴 때, 신경망에 넣을 때. 변환 자체가 비용이고, CNN은 어차피 첫 층에서 자기 좌표계를 만든다.

HSV — 조명이 바뀌는 환경에서 규칙 기반 색 검출을 할 때. 이 장의 0.9988이 그 근거다.

Lab색 사이의 거리가 필요할 때. 클러스터링, 색 보정, 품질 관리.

셋 다 같은 정보를 다르게 적어 놓은 것이지 정보량이 다르지 않다(2장의 정리를 떠올리자). 좌표계를 바꾸는 이유는 내가 하려는 질문이 한 축에 몰리게 만들기 위해서다.

STEP 05

시각화

ch03-spaces
그림 1. 같은 사진을 세 좌표계로 분해했다. RGB 세 장은 서로 거의 같아 보인다 — 밝은 곳은 세 채널 모두 밝다. 이것이 상관 0.8437의 모습이다. HSV에서는 성격이 완전히 갈린다: H(색상)은 무지개 지도처럼 물체의 종류를 보여 주고, V(명도)는 흑백 사진 그 자체다. Lab의 L은 V와 비슷하지만, a는 살색과 붉은 옷에서, b는 노란 조명과 푸른 배경에서 각각 튄다. 눈여겨볼 것은 H와 a·b가 담고 있는 정보가 다르다는 점이다 — H는 각도 하나, a·b는 평면 위의 점. 이 차이가 그림 3의 결과를 가른다.
ch03-mask
그림 2. 실험 그 자체. 윗줄은 원본에서 세 색공간이 찾아낸 "붉은 계열" 마스크, 아랫줄은 사진을 0.6배로 어둡게 한 뒤 같은 기준으로 다시 찾은 결과다. RGB는 영역이 눈에 띄게 쪼그라들었다(IoU 0.382) — 값이 통째로 내려가면서 기준 반경 밖으로 나가 버렸다. Lab도 마찬가지로 무너진다(0.361). HSV거의 그대로다(0.999). 두 장을 겹쳐 놓으면 구분이 안 될 정도다. H와 S가 상수배에 수학적으로 불변이기 때문이고, 그 증명이 2절 손풀이에 있다.
ch03-robust
그림 3. 왼쪽 — 세 가지 망가뜨리기에 대한 IoU. HSV×0.6에서 0.999로 압도적이지만 ×1.5에서 0.515로 반토막이 난다. 비대칭이다. 어두워지는 것은 비율을 지키지만 밝아지는 것은 255에서 잘리면서(클리핑) 비율을 깨기 때문이다 — 과노출은 어떤 색공간으로도 복구할 수 없다. LabRGB와 별 차이 없다는 것이 이 장에서 예상이 빗나간 지점이다: L을 버려도 a·b가 k1/3배로 함께 줄어든다. 오른쪽 — 채널이 서로 얼마나 얽혀 있나. RGB는 0.844(G–B는 0.961로 거의 같은 신호), HSV는 0.037. Lab의 0.282는 a–b 상관 0.735가 끌어올린 값인데, 이건 색공간 탓이 아니라 자연 사진의 색이 주황–파랑 축에 몰려 있어서다.
ch03-dist
그림 4. "거리"가 사람 눈과 맞는지. 왼쪽 색 견본을 직접 보라 — 짙은 남색과 검정은 확실히 다르게 느껴지고, 밝은 하늘색과 흰색은 꽤 비슷해 보인다. 그런데 RGB 거리90.0과 92.2로 거의 같다. Lab ΔE로 재면 62.6과 26.1로 2.4배 갈라진다 — 이쪽이 우리가 느끼는 것과 맞다. 이유는 2장의 감마와 같은 뿌리다: 사람 눈은 어두운 쪽에서 훨씬 예민하다. 그래서 색을 묶거나 비교하는 일(팔레트 추출, 색 품질 검사, 클러스터링)에는 RGB 거리를 쓰면 안 된다.
ch03-hsv
그림 5. 손계산을 그림으로. 왼쪽은 V=1인 HSV 단면 — H는 각도, S는 반지름이다. 중심이 회색이고 바깥으로 갈수록 선명해진다. 동그라미가 손풀이의 (200,60,60): H=0°(정확히 빨강 방향), S=0.70. 오른쪽 — 밝기를 절반으로 낮췄을 때 세 축의 변화. H와 S는 막대 길이가 완전히 같고 V만 절반이 된다. 같은 사건을 RGB에서 보면 세 값이 전부 바뀌었다가 되고 HSV에서 보면 한 값만 바뀌었다가 된다. 좌표계를 바꾸는 목적이 이것이다 — 정보를 늘리는 것이 아니라(2장의 정리), 내가 던지는 질문이 한 축에 몰리게 만드는 것.

이것만 기억하자

  1. RGB는 장치의 좌표계고 HSV·Lab은 사람의 좌표계다. 정보량은 같고 질문이 몰리는 축만 다르다.
  2. 조명 변화에 강한 것은 HSV다(×0.6에서 IoU 0.9988). H·S가 비율로 정의되어 상수배에 불변이기 때문이다. 단 과노출(×1.5)에서는 0.5149로 무너진다 — 클리핑은 복구 불가.
  3. 색 사이의 거리가 필요하면 Lab이다. RGB로 90인 두 쌍이 ΔE로는 62.6 대 26.1로 갈라진다.

흔한 오해

  1. "Lab은 지각적 색공간이니 조명 변화에도 강하다"이 장에서 내가 틀린 부분이다. ×0.6에서 Lab의 IoU는 0.3610으로 RGB(0.3817)보다도 낮았다. L을 버려도 a·b가 k1/3배로 함께 줄어든다. 지각적인 것과 조명 불변인 것은 다른 성질이다.
  2. "HSV로 바꾸면 조명 문제가 해결된다" — 어두워지는 쪽만이다. 밝아져서 255에서 잘리면 R=G=B가 되어 색상 자체가 사라진다(0.5149).
  3. "Lab은 축이 독립적이다" — 자연 사진에서 a–b 상관이 0.7345로 꽤 크다. 색이 주황–파랑 축에 몰려 있기 때문이다. 가장 독립적인 것은 HSV(0.0366)였다.
  4. "신경망에도 HSV/Lab을 넣으면 더 좋다" — 대개 그렇지 않다. CNN은 첫 층에서 스스로 좌표계를 만든다(20장에서 확인한다). 색공간 변환은 규칙을 손으로 쓸 때 값어치가 있다.

댓글