OpenCV 비전 실무5장 · 영상 향상과 학습용 전처리15 / 21← 이전목차다음 →MetaContents

5장. 영상 향상과 학습용 전처리

실습 2 — alpha·beta 대비 조정과 정규화

4장의 대비 낮은 거리 장면(ch04_exposure/low_contrast.png)을 cv2.convertScaleAbs 로 alpha 를 바꿔 가며 늘여 보고, cv2.normalize 의 NORM_MINMAX 로 가장 어두운 값과 가장 밝은 값을 0 과 255 에 맞춥니다.

1단계. 범위 재고 식을 한 칸에 넣어 보기

Python
import cv2
import numpy as np

gray = cv2.imread("ch04_exposure/low_contrast.png", cv2.IMREAD_GRAYSCALE)
lo, hi = int(gray.min()), int(gray.max())
print(f"가장 어두운 값 {lo}, 가장 밝은 값 {hi}, 표준편차 {round(float(gray.std()), 1)}")

# 대비 조정 식 g = alpha × f + beta. 가운데(128)는 그대로 두려고 beta = 128 - alpha × 128 로 정한다
alpha = 2.0
beta = 128 - alpha * 128            # 128 - 256 = -128
f = int(gray[100, 150])             # 가운데 건물 벽 칸 하나(세로 100, 가로 150)
print(f"손계산: {alpha} × {f} + ({beta}) = {alpha * f + beta}")
# convertScaleAbs(src, alpha, beta): 곱하고 더한 뒤 절댓값을 내어 0~255 로 맞춘다
out = cv2.convertScaleAbs(gray, alpha=alpha, beta=beta)
print("convertScaleAbs 결과:", int(out[100, 150]))
출력
가장 어두운 값 97, 가장 밝은 값 164, 표준편차 20.3
손계산: 2.0 × 119 + (-128.0) = 110.0
convertScaleAbs 결과: 110

2단계. alpha 를 바꿔 가며 견주기

Python
import pandas as pd

rows = []
for alpha in [1.0, 2.0, 3.0, 4.0]:
    beta = 128 - alpha * 128
    # 음수가 절댓값으로 바뀌지 않게, 소수로 계산해 np.clip 으로 0~255 를 자른다
    g = np.clip(alpha * gray.astype(np.float32) + beta, 0, 255).astype(np.uint8)
    rows.append({"alpha": alpha, "beta": beta,
                 "평균": round(float(g.mean()), 1),
                 "표준편차": round(float(g.std()), 1),
                 "0인칸": round(float((g == 0).mean()), 3),        # 끝에 몰린 칸의 비율
                 "255인칸": round(float((g == 255).mean()), 3)})
print(pd.DataFrame(rows).to_string(index=False))
출력
 alpha   beta    평균  표준편차  0인칸  255인칸
   1.0    0.0 125.2  20.3  0.0  0.000
   2.0 -128.0 122.5  40.7  0.0  0.000
   3.0 -256.0 119.7  61.0  0.0  0.000
   4.0 -384.0 116.5  80.6  0.0  0.025

3단계. 정규화로 양 끝 맞추기

Python
# cv2.normalize(src, dst, alpha, beta, norm_type): NORM_MINMAX 면 최솟값→alpha, 최댓값→beta
norm = cv2.normalize(gray,              # 바꿀 그림
                     None,              # 결과를 새로 만든다
                     0,                 # 가장 어두운 값이 갈 자리
                     255,               # 가장 밝은 값이 갈 자리
                     cv2.NORM_MINMAX)   # 최솟값·최댓값 방식
print("정규화 뒤 범위:", int(norm.min()), "~", int(norm.max()))
print("정규화 뒤 평균·표준편차:", round(float(norm.mean()), 1), round(float(norm.std()), 1))

# 손계산과 견준다: (값 - 가장 어두운 값) ÷ (범위) × 255
f = int(gray[100, 150])
print(f"손계산: ({f} - {lo}) ÷ ({hi} - {lo}) × 255 = {round((f - lo) / (hi - lo) * 255, 1)}")
print("normalize 결과:", int(norm[100, 150]))
출력
정규화 뒤 범위: 0 ~ 255
정규화 뒤 평균·표준편차: 107.4 77.3
손계산: (119 - 97) ÷ (164 - 97) × 255 = 83.7
normalize 결과: 84

4단계. 그림과 히스토그램으로 견주기

Python
import matplotlib.pyplot as plt

plt.rcParams["font.family"] = "Malgun Gothic"
plt.rcParams["axes.unicode_minus"] = False

alpha2 = np.clip(2.0 * gray.astype(np.float32) - 128, 0, 255).astype(np.uint8)
panels = [("원본 (대비 낮음)", gray), ("alpha 2, beta -128", alpha2), ("정규화 (NORM_MINMAX)", norm)]
fig, axes = plt.subplots(2, 3, figsize=(11, 5.4))
for k, (title, g) in enumerate(panels):
    axes[0, k].imshow(g, cmap="gray", vmin=0, vmax=255)
    axes[0, k].set_title(title)
    axes[0, k].axis("off")
    hist = cv2.calcHist([g], [0], None, [32], [0, 256]).ravel()
    axes[1, k].bar(np.arange(32) * 8, hist, width=8, align="edge")
    axes[1, k].set_xlim(0, 256)
    axes[1, k].set_title(f"표준편차 {round(float(g.std()), 1)}", fontsize=10)
plt.tight_layout()
plt.show()
대비 낮은 원본, alpha 2 로 늘인 그림, 정규화한 그림과 각각의 히스토그램

코드 설명

  • beta = 128 - alpha * 128 은 밝기 128 인 칸이 그대로 128 에 남게 하는 beta 입니다. 가운데를 축으로 양쪽으로 당기는 셈입니다.
  • 가운데 건물 벽 칸 119 는 손계산 2.0 × 119 - 128 = 110 과 convertScaleAbs 결과 110 이 같습니다. alpha 를 1, 2, 3, 4 로 올리면 표준편차가 20.3, 40.7, 61.0, 80.6 으로 거의 alpha 배만큼 커집니다. alpha 4 에서는 0.025 의 칸이 255 에 몰리기 시작합니다.
  • 정규화는 가장 어두운 값 97 을 0, 가장 밝은 값 164 를 255 로 늘였고, 표준편차가 77.3 이 됐습니다. 벽 칸 119 는 손계산 (119 - 97) ÷ 67 × 255 = 83.7 을 반올림한 84 가 나옵니다. 정규화 뒤 평균이 107.4 로 원본(125.2)보다 내려간 까닭은, 이 그림에서 어두운 칸이 0 쪽으로 더 많이 당겨졌기 때문입니다.
  • cv2.convertScaleAbs 대신 np.clip 으로 계산한 까닭은, 음수가 된 칸이 절댓값으로 바뀌어 밝게 나오는 일을 막기 위해서입니다.
← 이전 절5장 · 실습 1 — 밝기 더하고 빼기다음 절 →5장 · 실습 3 — 감마 보정과 LUT