7장. Median 과 Bilateral 필터, 필터 비교
실습 1 — Salt & Pepper 노이즈 만들기
표지판 그림(ch07_sign.png)에 점 비율을 바꿔 가며 Salt & Pepper 노이즈를 더하고, 하얀 점과 검은 점의 개수를 세어 정한 비율과 맞는지 확인합니다. 씨앗이 같으면 같은 점이 찍히는 것도 봅니다.
1단계. 노이즈를 더하는 함수 만들기
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 / 하얀 점: 18692단계. 비율별로 점 세기
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.19983단계. 비율별 그림을 2×2 로 보기
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()
4단계. 씨앗이 같으면 같은 점
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)처럼 씨앗을 고정하면 몇 번을 돌려도 같은 자리에 점이 찍힙니다. 씨앗을 바꾸면 점의 개수는 비슷해도 자리가 달라집니다. 필터끼리 견줄 때는 같은 씨앗의 노이즈를 써야 공정합니다.