색 공간과 이미지 보정
실습 준비
import cv2 as cv
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
image = cv.imread("balloon.webp")
이미지 표시
show() 함수는 OpenCV와 이미지·영상 입출력에서 정의한 함수를 사용함.
색 공간
색 공간(color space)은 색을 수치로 표현하는 규칙임. 같은 픽셀도 목적에 따라 서로 다른 색 공간으로 표현 가능함.
| 색 공간 | 채널 | 주요 용도 |
|---|---|---|
| BGR | 파랑·초록·빨강 | OpenCV의 기본 컬러 배열 |
| Gray | 밝기 | 문서 처리, 명암 분석, 이진화 |
| HSV | 색상·채도·명도 | 색 범위 선택, 밝기 보정 |
OpenCV의 BGR은 채널의 저장 순서이며 RGB와 표현할 수 있는 색의 범위는 같음. HSV는 색의 종류와 밝기를 분리하므로 색상 기반 처리에 편리함.


색 공간 변환
cv.cvtColor()에 원본과 변환 규칙을 전달함.
gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
Image.fromarray(gray)
그레이스케일 이미지는 (세로, 가로) 형태의 1채널 배열임.
gray.shape
BGR을 HSV로 변환하면 색상(Hue), 채도(Saturation), 명도(Value)를 각각 다룰 수 있음.
hsv = cv.cvtColor(image, cv.COLOR_BGR2HSV)
hsv.shape
8비트 OpenCV 이미지에서 HSV 채널의 범위는 다음과 같음.
| 채널 | 의미 | 범위 |
|---|---|---|
| H | 색상환 위의 위치 | 0–179 |
| S | 색의 선명한 정도 | 0–255 |
| V | 밝기 | 0–255 |
색상 H는 원래 0도 이상 360도 미만의 각도로 표현함. OpenCV의 기본 8비트 HSV에서는 색상 각도를 2도 단위로 저장하므로 H 값의 범위는 0–179임.
채널 분리와 결합
cv.split()은 채널을 각각의 2차원 배열로 분리하고 cv.merge()는 다시 결합함.
b, g, r = cv.split(image)
b.shape, g.shape, r.shape
채널 하나만 그레이스케일로 표시하면 해당 색 성분의 강도를 확인할 수 있음.
Image.fromarray(b)
B 채널만 남긴 컬러 이미지는 나머지 채널을 0으로 채워 결합함.
zero = np.zeros_like(b)
blue_only = cv.merge((b, zero, zero))
show(blue_only)


HSV 채널도 같은 방법으로 분리 가능함. H 채널을 그레이스케일로 표시한 값은 밝기가 아니라 색상환 위의 위치임.
h, s, v = cv.split(hsv)
Image.fromarray(v)
밝기와 대비
픽셀 값을 다음 선형식으로 바꾸면 대비와 밝기를 함께 조절할 수 있음.
- : 픽셀 값의 차이를 확대하거나 축소하는 대비 계수
- : 모든 픽셀에 더하는 밝기 보정값
cv.convertScaleAbs()는 계산 결과를 0–255 범위의 uint8 배열로 변환함.
brighter = cv.convertScaleAbs(image, alpha=1.0, beta=40)
show(brighter)
higher_contrast = cv.convertScaleAbs(image, alpha=1.5, beta=0)
show(higher_contrast)
값이 255에 도달한 영역은 더 밝게 만들 수 없으므로 세부 정보가 사라질 수 있음.
히스토그램
히스토그램은 픽셀 값마다 나타난 픽셀 수를 센 분포임. 어두운 이미지는 분포가 왼쪽에, 밝은 이미지는 오른쪽에 치우치는 경향이 있음.
for channel, line_color in enumerate(("b", "g", "r")):
hist = cv.calcHist([image], [channel], None, [256], [0, 256])
plt.plot(hist, color=line_color)
plt.xlim([0, 256])
plt.xlabel("픽셀 값")
plt.ylabel("픽셀 수")
plt.show()

히스토그램 균일화
히스토그램 균일화는 좁은 밝기 범위에 몰린 픽셀 값을 넓게 재배치하여 전체 대비를 높이는 방법임. cv.equalizeHist()는 8비트 1채널 이미지에 적용함.
xray = cv.imread("xray.jpg", cv.IMREAD_GRAYSCALE)
if xray is None:
raise FileNotFoundError("이미지를 읽을 수 없습니다")
equalized = cv.equalizeHist(xray)
Image.fromarray(equalized)


CLAHE
전체 이미지에 한 번만 균일화를 적용하면 일부 영역이 지나치게 어둡거나 밝아지고 잡음이 강조될 수 있음. CLAHE(Contrast Limited Adaptive Histogram Equalization)는 이미지를 작은 타일로 나누어 균일화하고 대비 증가에 제한을 둠.
clahe = cv.createCLAHE(
clipLimit=2.0,
tileGridSize=(8, 8),
)
clahe_result = clahe.apply(xray)
Image.fromarray(clahe_result)
clipLimit: 한 구간의 대비가 지나치게 커지는 것을 제한tileGridSize: 이미지를 나눌 가로·세로 타일 수

컬러 이미지의 밝기 보정
BGR 채널을 따로 균일화하지 않기
B, G, R 채널을 각각 균일화하면 채널 사이의 비율이 달라져 원래 색상이 변할 수 있음. 색상과 채도를 유지하려면 HSV로 변환한 뒤 V 채널만 보정함.

low_contrast = cv.imread("low.jpg")
if low_contrast is None:
raise FileNotFoundError("이미지를 읽을 수 없습니다")
hsv = cv.cvtColor(low_contrast, cv.COLOR_BGR2HSV)
h, s, v = cv.split(hsv)
equalized_v = cv.equalizeHist(v)
corrected_hsv = cv.merge((h, s, equalized_v))
corrected = cv.cvtColor(corrected_hsv, cv.COLOR_HSV2BGR)
show(corrected)

룩업 테이블
룩업 테이블(look-up table, LUT)은 입력값에 대응하는 출력값을 미리 저장한 변환표임. 8비트 이미지의 픽셀값은 0–255이므로 길이가 256인 배열로 모든 변환 결과를 표현 가능함.
입력 픽셀값 x → lut[x] → 출력 픽셀값
다음 LUT는 각 채널의 픽셀값을 0, 32, 64, …, 224의 8단계로 변환함.
lut = (np.arange(256) // 32 * 32).astype(np.uint8)
lut.shape # (256,)
lut[[0, 31, 32, 63, 255]]
array([ 0, 0, 32, 32, 224], dtype=uint8)
cv.LUT()는 이미지의 각 픽셀값을 인덱스로 사용하여 변환표의 값으로 교체함.
quantized = cv.LUT(image, lut)
show(quantized)

감마 보정
선형 보정은 모든 픽셀에 같은 비율과 크기의 변화를 적용함. 감마 보정은 픽셀 값을 비선형으로 변환하여 어두운 영역이나 밝은 영역을 선택적으로 조절함.
- : 어두운 영역을 더 많이 밝힘
- : 밝은 영역을 더 많이 어둡게 함
감마 함수로 0–255의 변환 결과를 LUT에 미리 저장하면 모든 픽셀에서 거듭제곱을 반복하지 않아도 됨.
gamma = 0.5
values = np.arange(256, dtype=np.float32) / 255
lut = np.clip((values ** gamma) * 255, 0, 255).astype(np.uint8)
gamma_corrected = cv.LUT(image, lut)
show(gamma_corrected)
