logo

색 공간과 이미지 보정

실습 준비

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는 색의 종류와 밝기를 분리하므로 색상 기반 처리에 편리함.

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)

BGR 채널을 각각 밝기로 표현한 이미지

BGR 채널을 각각의 색으로 표현한 이미지

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()

BGR 채널별 히스토그램

히스토그램 균일화

히스토그램 균일화는 좁은 밝기 범위에 몰린 픽셀 값을 넓게 재배치하여 전체 대비를 높이는 방법임. 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: 이미지를 나눌 가로·세로 타일 수

CLAHE 적용 결과

컬러 이미지의 밝기 보정

BGR 채널을 따로 균일화하지 않기

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

BGR 채널을 각각 균일화하여 색이 변한 결과

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)

HSV의 V 채널만 균일화한 결과

룩업 테이블

룩업 테이블(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)

각 채널의 픽셀값을 8단계로 변환한 이미지

감마 보정

선형 보정은 모든 픽셀에 같은 비율과 크기의 변화를 적용함. 감마 보정은 픽셀 값을 비선형으로 변환하여 어두운 영역이나 밝은 영역을 선택적으로 조절함.

  • : 어두운 영역을 더 많이 밝힘
  • : 밝은 영역을 더 많이 어둡게 함

감마 함수로 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)

감마 값에 따른 밝기 변환 곡선

Previous
OpenCV와 이미지·영상 입출력