10장. Sobel 과 Laplacian 경계 검출
노이즈와 블러 먼저 하기
시끄러운 카페에서 친구 목소리를 들으려고 귀를 바짝 세우면, 옆 테이블의 그릇 소리까지 크게 들립니다. "소리가 바뀌는 순간"에 민감해질수록 잡음에도 민감해집니다. 경계 검출이 그렇습니다. 옆 칸끼리 빼면 진짜 경계뿐 아니라 노이즈로 생긴 작은 들쭉날쭉까지 변화량으로 잡힙니다. 두 번 빼는 Laplacian 은 더 민감합니다. 그래서 6장에서 본 Gaussian 블러로 노이즈를 먼저 누그러뜨린 뒤 경계를 찾습니다.
그림의 네 단계가 노이즈 낀 그림에서 경계를 찾는 순서입니다. 둘째 단계의 블러가 핵심입니다. 블러 뒤에 Laplacian 을 거는 짝은 따로 LoG(Laplacian of Gaussian)라는 이름이 있을 만큼 흔합니다. 여기서 옆 테이블의 그릇 소리가 노이즈이고, 귀를 바짝 세우는 일이 빼기입니다.
위 줄이 블러 없이, 아래 줄이 Gaussian 5×5 블러 뒤에 구한 결과입니다. 노이즈는 6장에서 본 것과 같은 꼴(표준편차 20)이고 씨앗은 1010 입니다. 위 줄의 Laplacian(오른쪽 위)은 바탕 전체가 자글자글해 도형의 테두리를 알아보기 어렵습니다. Sobel 크기(가운데 위)도 바탕이 지글거리지만 Laplacian 보다는 덜합니다. 블러를 먼저 건 아래 줄은 바탕이 거의 검고 테두리만 남았습니다.
블러 없이와 블러 먼저 견주기
도형 사이의 고른 바탕(세로 125~144, 가로 150~309)에서 크기가 100 을 넘는 칸, 곧 "노이즈를 경계로 잘못 찾은 칸"의 비율입니다.
| 입력 | 바탕의 밝기 표준편차 | Sobel 크기 > 100 | |Laplacian(ksize 3)| > 100 |
|---|---|---|---|
| 노이즈 없는 원본 | 0.0 | 0.000 | 0.000 |
| 노이즈 (표준편차 20) | 20.1 | 0.372 | 0.591 |
| 노이즈 → Gaussian 5×5 | 5.5 | 0.000 | 0.000 |
노이즈만 더해도 바탕의 37% 넘는 칸을 Sobel 이 경계로 잘못 찾고, Laplacian 은 59% 넘게 잘못 찾습니다. 블러를 먼저 걸면 바탕의 흔들림이 20.1 에서 5.5 로 줄어 잘못 찾는 칸이 없어집니다.
짧은 예시
import cv2
import numpy as np
img = cv2.imread("ch10_shapes.png", cv2.IMREAD_GRAYSCALE)
# 6차시에서 본 것과 같은 꼴의 가우시안 노이즈. 씨앗 1010, 표준편차 20
rng = np.random.default_rng(1010)
noisy = np.clip(img.astype(np.float64) + rng.normal(0, 20, img.shape), 0, 255).astype(np.uint8)
for name, src in [("블러 없이", noisy), ("블러 먼저", cv2.GaussianBlur(noisy, (5, 5), 0))]:
mag = cv2.magnitude(cv2.Sobel(src, cv2.CV_64F, 1, 0), cv2.Sobel(src, cv2.CV_64F, 0, 1))
# 고른 바탕에서 크기가 100 을 넘는 칸의 비율
print(name, round(float((mag[125:145, 150:310] > 100).mean()), 3))
# 결과: 블러 없이 0.372 / 블러 먼저 0.0주의할 점
- 블러를 너무 세게 걸면 진짜 경계도 흐려져 약해지고 굵어집니다. 5×5 쯤에서 시작해 결과를 보며 고릅니다.
- 블러 크기를 바꾸면 기울기 값의 크기도 바뀌므로, 임계값도 다시 봅니다.