OpenCV 비전 실무7장 · Median 과 Bilateral 필터, 필터 비교14 / 21← 이전목차다음 →MetaContents

7장. Median 과 Bilateral 필터, 필터 비교

실습 1 — Salt & Pepper 노이즈 만들기

표지판 그림(ch07_sign.png)에 점 비율을 바꿔 가며 Salt & Pepper 노이즈를 더하고, 하얀 점과 검은 점의 개수를 세어 정한 비율과 맞는지 확인합니다. 씨앗이 같으면 같은 점이 찍히는 것도 봅니다.

1단계. 노이즈를 더하는 함수 만들기

Python
import cv2
import numpy as np

# 표지판 그림을 흑백으로 읽는다. 연습용으로 도형을 그려 만든 가상의 그림이다
gray = cv2.imread("ch07_sign.png", cv2.IMREAD_GRAYSCALE)
print("모양:", gray.shape, "/ 가장 어두운 값:", int(gray.min()), "/ 가장 밝은 값:", int(gray.max()))

def add_salt_pepper(img, amount, seed):
    """칸의 amount 비율을 점으로 바꾼다. 절반은 검은 점(0), 절반은 하얀 점(255)."""
    # default_rng(seed): 씨앗을 고정한 난수 생성기
    rng = np.random.default_rng(seed)
    # random(모양): 칸마다 0 이상 1 미만의 난수 하나
    r = rng.random(img.shape)
    noisy = img.copy()                       # 원본은 그대로 두고 복사본에 찍는다
    noisy[r < amount / 2] = 0                # 아주 작은 난수 → 후추
    noisy[r > 1 - amount / 2] = 255          # 아주 큰 난수 → 소금
    return noisy

noisy = add_salt_pepper(gray, 0.05, 711)
# 원본에 0 과 255 가 없으므로 0 과 255 인 칸의 수가 곧 점의 수다
print("검은 점:", int((noisy == 0).sum()), "/ 하얀 점:", int((noisy == 255).sum()))
출력
모양: (240, 320) / 가장 어두운 값: 30 / 가장 밝은 값: 250
검은 점: 1911 / 하얀 점: 1869

2단계. 비율별로 점 세기

Python
import pandas as pd

rows = []
for amount in [0.02, 0.05, 0.10, 0.20]:
    noisy = add_salt_pepper(gray, amount, 711)
    pepper = int((noisy == 0).sum())
    salt = int((noisy == 255).sum())
    rows.append({
        "정한 비율": amount,
        "검은 점": pepper,
        "하얀 점": salt,
        # 실제 비율 = 점의 수 ÷ 전체 칸 수(76,800)
        "실제 비율": round((pepper + salt) / gray.size, 4),
    })
print(pd.DataFrame(rows).to_string(index=False))
출력
 정한 비율  검은 점  하얀 점  실제 비율
  0.02   755   769 0.0198
  0.05  1911  1869 0.0492
  0.10  3803  3754 0.0984
  0.20  7663  7680 0.1998

3단계. 비율별 그림을 2×2 로 보기

Python
import matplotlib.pyplot as plt

# 그래프의 한글이 깨지지 않게 맑은 고딕을 쓰고, 빼기 기호가 네모로 나오지 않게 한다
plt.rcParams["font.family"] = "Malgun Gothic"
plt.rcParams["axes.unicode_minus"] = False

fig, axes = plt.subplots(2, 2, figsize=(8, 6))
for ax, amount in zip(axes.flat, [0.02, 0.05, 0.10, 0.20]):
    # 흑백은 cmap="gray", 밝기 기준을 0~255 로 고정한다
    ax.imshow(add_salt_pepper(gray, amount, 711), cmap="gray", vmin=0, vmax=255)
    ax.set_title(f"점 비율 {amount:.0%}")    # :.0% 는 0.05 를 5% 로 적는다
    ax.axis("off")
plt.tight_layout()
plt.show()
점 비율 2%, 5%, 10%, 20% 의 Salt & Pepper 노이즈를 더한 표지판을 2행 2열로 놓은 결과

4단계. 씨앗이 같으면 같은 점

Python
a = add_salt_pepper(gray, 0.10, 711)
b = add_salt_pepper(gray, 0.10, 711)      # 같은 씨앗
c = add_salt_pepper(gray, 0.10, 799)      # 다른 씨앗

# array_equal: 모양과 모든 숫자가 같으면 True
print("씨앗 711 과 711 이 같은가:", np.array_equal(a, b))
print("씨앗 711 과 799 가 같은가:", np.array_equal(a, c))
# 두 그림에서 점의 자리가 다른 칸 수
print("점 자리가 다른 칸:", int((a != c).sum()))
# 원본은 바뀌지 않았다: 여전히 0 과 255 가 없다
print("원본의 0·255 칸 수:", int((gray == 0).sum() + (gray == 255).sum()))
출력
씨앗 711 과 711 이 같은가: True
씨앗 711 과 799 가 같은가: False
점 자리가 다른 칸: 14114
원본의 0·255 칸 수: 0

코드 설명

  • rng.random(img.shape) 는 그림과 같은 모양으로 0~1 사이 난수를 칸마다 하나씩 뽑습니다. 난수가 amount / 2 보다 작은 칸은 0, 1 - amount / 2 보다 큰 칸은 255 로 바꾸므로, 검은 점과 하얀 점이 거의 반씩 찍힙니다.
  • 표지판 그림은 가장 어두운 값이 30, 가장 밝은 값이 250 이라 원본에는 0 과 255 가 없습니다. 그래서 (noisy == 0).sum() 이 곧 검은 점의 수입니다.
  • 2단계 표를 보면 정한 비율 0.02, 0.05, 0.10, 0.20 에서 실제 비율이 0.0198, 0.0492, 0.0984, 0.1998 로 거의 같습니다. 검은 점과 하얀 점도 755개와 769개, 7,663개와 7,680개처럼 거의 반씩입니다.
  • default_rng(711) 처럼 씨앗을 고정하면 몇 번을 돌려도 같은 자리에 점이 찍힙니다. 씨앗을 바꾸면 점의 개수는 비슷해도 자리가 달라집니다. 필터끼리 견줄 때는 같은 씨앗의 노이즈를 써야 공정합니다.
← 이전 절7장 · 실습 지도다음 절 →7장 · 실습 2 — 평균 필터와 Median 필터: 점 노이즈에서 견주기