히스토그램
히스토그램 계산부터 정규화, 평탄화, CLAHE까지 모든 과정을 다룹니다.
히스토그램 기본 이론
히스토그램 개념
히스토그램은 도수 분포표를 그래프로 나타낸 것입니다. 이미지에서 히스토그램은 각 픽셀 값이 몇 개씩 있는지를 시각적으로 보여줍니다. 예를 들어, 픽셀 값 0부터 255까지 각각 몇 개씩 존재하는지 그래프로 표현한 것입니다.
이미지 히스토그램을 통해 다음을 파악할 수 있습니다:
-
픽셀들의 색상 분포
-
명암의 분포 특성
-
이미지의 전체적인 밝기 특성
cv2.calcHist() 함수
OpenCV에서는 cv2.calcHist() 함수로 히스토그램을 계산합니다.
hist = cv2.calcHist(images, channels, mask, histSize, ranges)
매개변수 상세 설명
-
images: 입력 이미지, 리스트 형태로 전달 (예:[img]) -
channels: 분석할 채널 지정, 리스트 형태
* 1채널: [0]
* 2채널: [0, 1]
* 3채널: [0, 1, 2]
-
mask: 특정 영역만 계산할 마스크, 전체 영역이면None -
histSize: 히스토그램 구간(bin) 개수, 채널 수에 맞는 리스트
* 1채널: [256]
* 3채널: [256, 256, 256]
-
ranges: 픽셀 값 범위, RGB의 경우[0, 256]
히스토그램 계산 실습
회색조 이미지 히스토그램
가장 기본적인 회색조 이미지의 히스토그램을 계산하고 시각화하는 예제입니다.
# 회색조 1채널 히스토그램
import cv2
import numpy as np
import matplotlib.pylab as plt
# 이미지를 그레이스케일로 읽기
img = cv2.imread('../img/mountain.jpg', cv2.IMREAD_GRAYSCALE)
cv2.imshow('img', img)
# 히스토그램 계산
hist = cv2.calcHist([img], [0], None, [256], [0, 256])
# 히스토그램 그리기
plt.plot(hist)
print("hist.shape:", hist.shape) # (256, 1)
print("hist.sum():", hist.sum(), "img.shape:", img.shape)
plt.show()
핵심 포인트
-
cv2.calcHist()의 반환값은 shape이(256, 1)인 배열 -
히스토그램의 총합은 이미지의 전체 픽셀 수와 같음
-
plt.plot(hist)로 간단하게 그래프 표시 가능
컬러 이미지 히스토그램
RGB 3채널 이미지의 각 채널별 히스토그램을 계산하는 방법입니다.
# 색상 이미지 히스토그램
import cv2
import numpy as np
import matplotlib.pylab as plt
# 컬러 이미지 읽기
img = cv2.imread('../img/mountain.jpg')
cv2.imshow('img', img)
# 채널별로 분리하여 히스토그램 계산
channels = cv2.split(img)
colors = ('b', 'g', 'r')
for (ch, color) in zip(channels, colors):
hist = cv2.calcHist([ch], [0], None, [256], [0, 256])
plt.plot(hist, color=color)
plt.show()
핵심 포인트
-
cv2.split(img)로 BGR 각 채널을 분리 -
각 채널별로 히스토그램을 계산하여 색상별 분포 확인
-
OpenCV는 BGR 순서를 사용함에 주의
정규화 (Normalization)
이미지의 픽셀 값 분포가 특정 영역에 몰려있을 때, 전체 범위로 확장하여 화질을 개선하는 기법입니다.
cv2.normalize() 함수
dst = cv2.normalize(src, dst, alpha, beta, type_flag)
매개변수 상세 설명
-
src: 입력 이미지 -
dst: 출력 이미지 -
alpha: 정규화 구간의 시작값 -
beta: 정규화 구간의 끝값 -
type_flag: 정규화 방식 선택
* cv2.NORM_MINMAX: 최솟값-최댓값 구간 정규화
* cv2.NORM_L1: 전체 합으로 나누기
* cv2.NORM_L2: 단위 벡터로 정규화
* cv2.NORM_INF: 최댓값으로 나누기
정규화 적용 예제
# 히스토그램 정규화
import cv2
import numpy as np
import matplotlib.pylab as plt
# 어두운 이미지 읽기
img = cv2.imread('../img/abnormal.jpg', cv2.IMREAD_GRAYSCALE)
# 수동 정규화 계산
img_f = img.astype(np.float32)
img_norm = ((img_f - img_f.min()) * 255 / (img_f.max() - img_f.min()))
img_norm = img_norm.astype(np.uint8)
# OpenCV API를 이용한 정규화
img_norm2 = cv2.normalize(img, None, 0, 255, cv2.NORM_MINMAX)
# 결과 비교
cv2.imshow('Before', img)
cv2.imshow('Manual', img_norm)
cv2.imshow('cv2.normalize()', img_norm2)
# 히스토그램 비교
hist = cv2.calcHist([img], [0], None, [256], [0, 255])
hist_norm = cv2.calcHist([img_norm], [0], None, [256], [0, 255])
hist_norm2 = cv2.calcHist([img_norm2], [0], None, [256], [0, 255])
hists = {'Before': hist, 'Manual': hist_norm, 'cv2.normalize()': hist_norm2}
for i, (k, v) in enumerate(hists.items()):
plt.subplot(1, 3, i+1)
plt.title(k)
plt.plot(v)
plt.show()
정규화 효과
-
중앙에 몰린 픽셀 값들이 전체 범위로 확산
-
명암 대비 향상으로 이미지가 더 선명해짐
평탄화 (Histogram Equalization)
히스토그램을 균등하게 분포시켜 명암 대비를 극대화하는 기법입니다. 정규화보다 더 강력한 대비 개선 효과가 있습니다.
평탄화의 원리
히스토그램 평탄화는 누적 분포 함수(CDF)를 이용하여 픽셀 값을 재분배합니다. 이를 통해 모든 픽셀 값이 균등하게 분포하도록 만듭니다.
cv2.equalizeHist() 함수
dst = cv2.equalizeHist(src)
매개변수 설명
-
src: 입력 이미지 (8비트 1채널) -
dst: 평탄화된 출력 이미지
회색조 이미지 평탄화
# 회색조 이미지 평탄화
import cv2
import numpy as np
import matplotlib.pylab as plt
# 어두운 이미지 읽기
img = cv2.imread('../img/yate.jpg', cv2.IMREAD_GRAYSCALE)
rows, cols = img.shape[:2]
# 수동 평탄화 구현
hist = cv2.calcHist([img], [0], None, [256], [0, 256])
cdf = hist.cumsum() # 누적 히스토그램
cdf_m = np.ma.masked_equal(cdf, 0) # 0값을 NaN으로 마스킹
cdf_m = (cdf_m - cdf_m.min()) / (rows * cols) * 255 # 평탄화 계산
cdf = np.ma.filled(cdf_m, 0).astype('uint8') # NaN을 0으로 복원
img2 = cdf[img] # 히스토그램을 이용한 픽셀 매핑
# OpenCV API를 이용한 평탄화
img3 = cv2.equalizeHist(img)
# 결과 비교
cv2.imshow('Before', img)
cv2.imshow('Manual', img2)
cv2.imshow('cv2.equalizeHist()', img3)
# 히스토그램 비교
hist2 = cv2.calcHist([img2], [0], None, [256], [0, 256])
hist3 = cv2.calcHist([img3], [0], None, [256], [0, 256])
hists = {'Before': hist, 'Manual': hist2, 'cv2.equalizeHist()': hist3}
for i, (k, v) in enumerate(hists.items()):
plt.subplot(1, 3, i+1)
plt.title(k)
plt.plot(v)
plt.show()
컬러 이미지 평탄화
컬러 이미지에 평탄화를 적용할 때는 YUV 색공간을 활용하여 밝기 채널(Y)만 처리하는 것이 효과적입니다.
# 색상 이미지 평탄화
import numpy as np, cv2
# 컬러 이미지 읽기
img = cv2.imread('../img/yate.jpg')
# BGR을 YUV로 변환
img_yuv = cv2.cvtColor(img, cv2.COLOR_BGR2YUV)
# Y 채널(밝기)에 평탄화 적용
img_yuv[:, :, 0] = cv2.equalizeHist(img_yuv[:, :, 0])
# YUV를 BGR로 변환
img2 = cv2.cvtColor(img_yuv, cv2.COLOR_YUV2BGR)
cv2.imshow('Before', img)
cv2.imshow('After', img2)
cv2.waitKey()
cv2.destroyAllWindows()
핵심 포인트
-
YUV 색공간에서 Y는 밝기, U와 V는 색상 정보
-
밝기 채널만 평탄화하면 색상 왜곡 없이 대비 개선 가능
-
BGR 각 채널을 개별적으로 평탄화하면 색상이 왜곡됨
CLAHE (Contrast Limited Adaptive Histogram Equalization)
일반적인 평탄화의 문제점을 해결한 고급 기법입니다. 지역적 적응형 평탄화로 과도한 밝기 날림 현상을 방지합니다.
CLAHE의 특징
-
적응형: 이미지를 작은 영역으로 나누어 각각 평탄화
-
대비 제한: 지정된 임계값을 넘는 픽셀은 균등 배분
-
노이즈 감소: 극단적인 값으로 인한 노이즈 방지
cv2.createCLAHE() 함수
clahe = cv2.createCLAHE(clipLimit, tileGridSize)
result = clahe.apply(src)
매개변수 설명
-
clipLimit: 대비 제한 임계값 (기본값: 40.0) -
tileGridSize: 영역 분할 크기 (기본값: 8x8)
CLAHE 적용 예제
# CLAHE 적용
import cv2
import numpy as np
# 밝은 부분이 있는 이미지 읽기
img = cv2.imread('../img/bright.jpg')
img_yuv = cv2.cvtColor(img, cv2.COLOR_BGR2YUV)
# 일반 평탄화 적용
img_eq = img_yuv.copy()
img_eq[:, :, 0] = cv2.equalizeHist(img_eq[:, :, 0])
img_eq = cv2.cvtColor(img_eq, cv2.COLOR_YUV2BGR)
# CLAHE 적용
img_clahe = img_yuv.copy()
clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))
img_clahe[:, :, 0] = clahe.apply(img_clahe[:, :, 0])
img_clahe = cv2.cvtColor(img_clahe, cv2.COLOR_YUV2BGR)
# 결과 비교
cv2.imshow('Before', img)
cv2.imshow('CLAHE', img_clahe)
cv2.imshow('equalizeHist', img_eq)
cv2.waitKey()
cv2.destroyAllWindows()
CLAHE 핵심 사용법
# CLAHE 기본 사용 패턴
clahe = cv2.createCLAHE() # 기본 설정으로 CLAHE 객체 생성
result = clahe.apply(img) # 이미지에 CLAHE 적용
매개변수 조정 가이드
-
clipLimit을 낮추면 (예: 2.0) 더 자연스러운 결과 -
clipLimit을 높이면 (예: 8.0) 더 강한 대비 효과 -
tileGridSize를 작게 하면 더 세밀한 지역 적응 -
tileGridSize를 크게 하면 더 부드러운 전환
기법별 비교 및 활용 가이드
사용 시기별 권장사항
정규화 사용 권장 상황
-
픽셀 값이 좁은 범위에 집중된 경우
-
전체적으로 어둡거나 밝은 이미지
-
간단한 대비 개선이 필요한 경우
평탄화 사용:
-
강한 명암 대비 개선이 필요한 경우
-
히스토그램이 불균등하게 분포된 경우
-
흑백 이미지 처리
CLAHE 사용:
-
밝은 부분과 어두운 부분이 모두 중요한 경우
-
일반 평탄화로 세부사항이 손실되는 경우
-
의료 영상, 보안 카메라 영상 등 디테일이 중요한 분야
처리 결과 특성
|
기법 |
장점 |
단점 |
적용 분야 |
|---|---|---|---|
|
정규화 |
간단하고 빠름, 자연스러운 결과 |
제한적인 개선 효과 |
일반적인 이미지 보정 |
|
평탄화 |
강력한 대비 개선 |
밝은 부분 날림, 색상 왜곡 |
어두운 이미지, 흑백 사진 |
|
CLAHE |
균형잡힌 결과, 세부사항 보존 |
복잡한 설정, 처리 시간 |
전문적인 이미지 분석 |