7장. Median 과 Bilateral 필터, 필터 비교
Salt & Pepper 노이즈
갓 지은 흰 쌀밥 위에 검은깨 몇 알과 굵은소금 몇 톨이 떨어진 모습을 떠올려 봅시다. 밥알 대부분은 그대로인데, 군데군데 아주 검은 점과 아주 하얀 점이 박혀 있습니다. Salt & Pepper 노이즈가 바로 이렇게 생겼습니다. 대부분의 칸은 멀쩡하고, 몇몇 칸만 0(검정)이나 255(흰색)로 튑니다.
왼쪽이 깨끗한 원본, 가운데가 칸의 5%, 오른쪽이 20% 를 점으로 바꾼 그림입니다. 점의 절반은 하얀 점(소금), 절반은 검은 점(후추)입니다. 20% 면 다섯 칸에 한 칸이 점이라 글자가 거칠어 보입니다. 여기서 흰 쌀밥이 깨끗한 그림, 소금과 깨가 255 와 0 으로 튄 칸입니다. 6장의 가우시안 노이즈는 모든 칸이 조금씩 흔들리지만, 이 노이즈는 몇 칸만 크게 튄다는 점이 다릅니다.
이런 점은 그림을 주고받다 숫자가 깨지거나, 카메라 센서의 칸 하나가 고장 나 늘 켜지거나 꺼져 있거나, 빛을 숫자로 바꾸는 과정에서 가끔 값이 튈 때 생깁니다. 오래된 스캔 문서의 먼지 자국도 비슷한 모양으로 보입니다.
두 노이즈 견주기
| 견줄 것 | 가우시안 노이즈 (6차시) | Salt & Pepper 노이즈 |
|---|---|---|
| 바뀌는 칸 | 모든 칸 | 일부 칸(비율로 정한다) |
| 바뀌는 크기 | 조금씩(플러스·마이너스 몇십) | 끝값까지(0 이나 255) |
| 보이는 모습 | 자글자글한 모래알 | 하얀 점과 검은 점 |
| 정하는 값 | 표준편차(흔들림의 크기) | 점 비율(몇 칸을 바꿀까) |
짧은 예시
NumPy 로 0~1 사이 난수를 칸마다 하나씩 뽑고, 아주 작은 난수가 나온 칸은 검게, 아주 큰 난수가 나온 칸은 하얗게 바꿉니다.
import cv2
import numpy as np
# 표지판 그림을 흑백으로 읽는다. 연습용으로 도형을 그려 만든 가상의 그림이다
gray = cv2.imread("ch07_sign.png", cv2.IMREAD_GRAYSCALE)
# default_rng: 씨앗을 고정한 난수 생성기. 같은 씨앗이면 늘 같은 점이 찍힌다
rng = np.random.default_rng(711)
# random: 그림과 같은 모양으로 0 이상 1 미만의 난수를 칸마다 하나씩 뽑는다
r = rng.random(gray.shape)
amount = 0.05 # 점 비율 5%: 절반은 검은 점, 절반은 하얀 점
noisy = gray.copy() # 원본을 지키려고 복사본에 찍는다
noisy[r < amount / 2] = 0 # 난수가 0.025 보다 작은 칸 → 후추(검정)
noisy[r > 1 - amount / 2] = 255 # 난수가 0.975 보다 큰 칸 → 소금(흰색)
# 원본에는 0 과 255 가 하나도 없어서, 0 과 255 인 칸을 세면 곧 점의 수다
print(int((noisy == 0).sum()), int((noisy == 255).sum())) # 결과: 1911 186976,800칸의 2.5% 는 1,920칸이므로, 검은 점 1,911개와 하얀 점 1,869개는 각각 그 근처입니다. 난수라서 딱 맞게 떨어지지는 않습니다. 원본 표지판의 밝기는 가장 어두운 곳이 30, 가장 밝은 곳이 250 이라 0 과 255 인 칸이 하나도 없습니다. 그래서 0 과 255 를 세면 점의 수가 됩니다.
주의할 점
gray.copy()없이noisy = gray로 쓰면 이름만 하나 더 붙인 것이라 원본에도 점이 찍힙니다.- 씨앗을 고정하지 않으면 실행할 때마다 점의 자리가 바뀌어, 필터의 결과를 견줄 수 없습니다.
- 실제 사진에는 원래 0 이나 255 인 칸(그림자, 하늘)이 있을 수 있어서, "0 과 255 를 세면 점의 수"라는 방법은 이 연습용 그림에서만 맞습니다.