5장. 영상 향상과 학습용 전처리
감마 보정
어두운 방에서 스탠드를 켜면, 어둡던 구석은 확 밝아지지만 원래 밝던 창가는 조금밖에 달라지지 않습니다. 감마 보정이 이렇습니다. 밝기를 모든 칸에 똑같이 더하는 대신, 어두운 칸은 많이, 밝은 칸은 조금 올립니다. 반대로 밝은 칸을 많이 내리는 쪽으로도 쓸 수 있습니다.
그림의 세 상자는 감마 값에 따른 효과입니다. 감마가 1 보다 작으면 어두운 쪽을 크게 올리고, 1 이면 그대로, 1 보다 크면 밝은 쪽을 크게 내립니다. 여기서 스탠드를 켜는 일이 감마 0.5 같은 1 보다 작은 감마이고, 커튼을 쳐서 눈부신 창가를 가리는 일이 1 보다 큰 감마입니다. 비유와 다른 점은, 감마 보정은 0 인 칸과 255 인 칸을 언제나 그대로 둔다는 것입니다.
가로축이 원래 밝기, 세로축이 바뀐 밝기입니다. 초록 직선이 감마 1 입니다. 산호색 곡선(감마 0.5)은 왼쪽에서 가파르게 올라가므로 어두운 값이 많이 오릅니다. 16 은 64 가 되어 48 이 오르고, 225 는 240 이 되어 15 만 오릅니다. 보라와 갈색 곡선(감마 2, 2.5)은 반대로 아래로 처집니다.
손으로 계산해 보기
감마 보정의 식은 "새 밝기 = (밝기 ÷ 255) 를 감마만큼 거듭제곱 × 255" 입니다. 0.5제곱은 제곱근과 같습니다(4 의 0.5제곱 = 2). 감마 0.5 로 밝기 64 를 바꿔 봅니다.
| 단계 | 계산 | 결과 |
|---|---|---|
| ① 0~1 로 바꾸기 | 64 ÷ 255 | 약 0.251 |
| ② 0.5제곱(제곱근) | 0.251 의 제곱근 | 약 0.501 |
| ③ 255 곱하기 | 0.501 × 255 | 약 128 |
같은 식으로 감마 2 를 넣으면 128 은 (128 ÷ 255) × (128 ÷ 255) × 255 = 약 64 로 내려갑니다. 0~1 사이의 수는 제곱근을 하면 커지고, 제곱하면 작아지기 때문입니다. 0 과 1 은 몇 제곱을 해도 0 과 1 이므로, 0 과 255 는 그대로 남습니다.
조회표로 한 번에 바꾸기
76,800칸마다 거듭제곱을 계산하면 같은 계산을 여러 번 하게 됩니다. 밝기는 0~255 의 256가지뿐이므로, 256칸에 답을 미리 적어 두고 칸마다 표에서 찾아 바꾸면 됩니다. 이 표가 조회표(LUT)이고, cv2.LUT(그림, 표) 가 표를 보고 바꿉니다.
| 입력 밝기 | 0 | 16 | 64 | 128 | 225 | 255 |
|---|---|---|---|---|---|---|
| 감마 0.5 | 0 | 64 | 128 | 181 | 240 | 255 |
| 감마 2 | 0 | 1 | 16 | 64 | 199 | 255 |
짧은 예시
import cv2
import numpy as np
def gamma_table(gamma):
"""0~255 마다 (i/255)**gamma*255 를 계산해 256칸짜리 조회표를 만든다."""
values = [((i / 255) ** gamma) * 255 for i in range(256)]
return np.array(values).round().astype(np.uint8) # 반올림해서 0~255 정수로
lut = gamma_table(0.5)
print([int(lut[v]) for v in (0, 16, 64, 128, 225, 255)]) # 결과: [0, 64, 128, 181, 240, 255]
px = np.array([[16, 64, 225]], dtype=np.uint8)
# cv2.LUT(src, lut): src 의 칸마다 lut[칸의 값] 으로 바꾼다
print(cv2.LUT(px, lut)) # 결과: [[ 64 128 240]]주의할 점
- 조회표는 반드시 256칸,
uint8이어야 합니다. 소수 그대로 넣으면 결과의 자료형이 바뀌어 그림으로 다루기 불편합니다. - 감마를 너무 작게(예: 0.2) 주면 어두운 칸이 모두 밝은 회색이 되어 뿌옇게 보입니다. 감마는 대비를 키우지 않고 옮기기만 하므로, 고친 뒤 표준편차를 다시 봅니다.