5장. 영상 향상과 학습용 전처리
실습 3 — 감마 보정과 LUT
감마 값마다 256칸짜리 조회표를 만들고 cv2.LUT 로 그림을 바꿉니다. 어두운 거리 장면(dark.png)에는 감마를 1 보다 작게, 밝은 장면(bright.png)에는 1 보다 크게 주어 격자로 견줍니다.
1단계. 조회표 만들고 직접 계산과 견주기
import cv2
import numpy as np
def gamma_table(gamma):
"""(i/255)**gamma*255 를 0~255 마다 계산한 256칸짜리 uint8 조회표."""
return np.array([((i / 255) ** gamma) * 255 for i in range(256)]).round().astype(np.uint8)
lut = gamma_table(0.5)
print("조회표 모양:", lut.shape, "/ 자료형:", lut.dtype)
print("입력 16, 64, 128 → ", [int(lut[v]) for v in (16, 64, 128)])
dark = cv2.imread("ch04_exposure/dark.png", cv2.IMREAD_GRAYSCALE)
# cv2.LUT(src, lut): 칸마다 lut[칸의 값] 으로 바꾼다
by_lut = cv2.LUT(dark, lut)
# 직접 계산: 모든 칸에 거듭제곱을 한다(같은 결과가 나와야 한다)
direct = (((dark / 255) ** 0.5) * 255).round().astype(np.uint8)
print("조회표와 직접 계산이 같은가:", np.array_equal(by_lut, direct))조회표 모양: (256,) / 자료형: uint8
입력 16, 64, 128 → [64, 128, 181]
조회표와 직접 계산이 같은가: True2단계. 감마마다 평균과 표준편차 재기
bright = cv2.imread("ch04_exposure/bright.png", cv2.IMREAD_GRAYSCALE)
# 어두운 사진에는 1 보다 작은 감마, 밝은 사진에는 1 보다 큰 감마
plans = {"dark": (dark, [1.0, 0.7, 0.5, 0.4]), "bright": (bright, [1.0, 1.5, 2.0, 2.5])}
results = {}
for name, (g, gammas) in plans.items():
for gm in gammas:
out = cv2.LUT(g, gamma_table(gm))
results[(name, gm)] = out
print(f"{name:6} 감마 {gm}: 평균 {round(float(out.mean()), 1):5}, 표준편차 {round(float(out.std()), 1)}")dark 감마 1.0: 평균 41.9, 표준편차 23.8
dark 감마 0.7: 평균 69.5, 표준편차 29.4
dark 감마 0.5: 평균 98.7, 표준편차 31.1
dark 감마 0.4: 평균 118.2, 표준편차 30.2
bright 감마 1.0: 평균 203.9, 표준편차 30.4
bright 감마 1.5: 평균 183.8, 표준편차 40.8
bright 감마 2.0: 평균 166.6, 표준편차 49.0
bright 감마 2.5: 평균 151.9, 표준편차 55.23단계. 2×4 격자로 견주기
import matplotlib.pyplot as plt
plt.rcParams["font.family"] = "Malgun Gothic"
plt.rcParams["axes.unicode_minus"] = False
fig, axes = plt.subplots(2, 4, figsize=(12, 6.2))
for row, name in enumerate(["dark", "bright"]):
for col, gm in enumerate(plans[name][1]):
out = results[(name, gm)]
axes[row, col].imshow(out, cmap="gray", vmin=0, vmax=255)
axes[row, col].set_title(f"{name} 감마 {gm}\n평균 {round(float(out.mean()), 1)}", fontsize=10)
axes[row, col].axis("off")
plt.tight_layout()
plt.show()
4단계. 감마 곡선 그려 보기
x = np.arange(256)
fig, ax = plt.subplots(figsize=(6, 4.5))
for gm in [0.4, 0.5, 1.0, 2.0, 2.5]:
# 조회표를 그대로 그리면 곡선이 된다: 가로는 입력 밝기, 세로는 바뀐 밝기
ax.plot(x, gamma_table(gm), label=f"감마 {gm}")
ax.set_xlabel("입력 밝기")
ax.set_ylabel("바뀐 밝기")
ax.set_xlim(0, 255)
ax.set_ylim(0, 255)
ax.legend(loc="lower right")
ax.set_title("감마 조회표의 모양")
plt.tight_layout()
plt.show()
코드 설명
gamma_table은 0~255 마다 거듭제곱을 한 번씩만 계산해 256칸 표를 만듭니다. 그림이 76,800칸이어도 거듭제곱은 256번만 합니다.- 조회표는 (256,) 모양의
uint8이고, 감마 0.5 에서 16, 64, 128 이 64, 128, 181 로 바뀝니다.cv2.LUT결과와 모든 칸에 거듭제곱을 한 결과가 같습니다(True). - 어두운 장면은 감마를 0.7, 0.5, 0.4 로 줄일수록 평균이 69.5, 98.7, 118.2 로 오릅니다. 밝은 장면은 감마를 1.5, 2.0, 2.5 로 키울수록 평균이 183.8, 166.6, 151.9 로 내려가고 표준편차는 30.4 에서 55.2 까지 커집니다. 다만 어두운 장면의 감마 0.5 결과는 표준편차가 31.1 이라 4장 기준으로는 아직 "대비 낮음"입니다. 감마는 막대를 옮기는 일이 주이고 넓히는 힘은 약합니다.
- 조회표를 선으로 그리면 감마 곡선이 됩니다. 1 보다 작은 감마는 위로 볼록하고, 1 보다 큰 감마는 아래로 처집니다.