OpenCV 비전 실무10장 · Sobel 과 Laplacian 경계 검출18 / 21← 이전목차다음 →MetaContents

10장. Sobel 과 Laplacian 경계 검출

실습 5 — 노이즈 낀 그림에서 블러 먼저 하기

도형 그림에 가우시안 노이즈를 더한 뒤, 블러 없이와 Gaussian 블러 뒤에 Sobel 크기와 Laplacian 을 구해 고른 바탕에서 노이즈를 경계로 잘못 찾은 칸을 셉니다. 블러 크기를 바꿔 가며 표로 견줍니다.

1단계. 노이즈 더하기

Python
import cv2
import numpy as np

img = cv2.imread("ch10_shapes.png", cv2.IMREAD_GRAYSCALE)

# 6차시에서 본 것과 같은 꼴의 가우시안 노이즈: 씨앗 1010, 평균 0, 표준편차 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)

# 도형 사이의 고른 바탕: 세로 125~144, 가로 150~309
bg = (slice(125, 145), slice(150, 310))
print("바탕의 밝기 표준편차: 원본", round(float(img[bg].std()), 1), "/ 노이즈", round(float(noisy[bg].std()), 1))
print("바탕 다섯 칸:", [int(v) for v in noisy[130, 150:155]])
출력
바탕의 밝기 표준편차: 원본 0.0 / 노이즈 20.1
바탕 다섯 칸: [96, 24, 72, 61, 49]

2단계. 블러 없이와 블러 먼저 견주기

Python
def false_ratio(src, t=100):
    """고른 바탕에서 Sobel 크기와 |Laplacian| 이 기준 t 를 넘는 칸의 비율을 돌려준다."""
    mag = cv2.magnitude(cv2.Sobel(src, cv2.CV_64F, 1, 0), cv2.Sobel(src, cv2.CV_64F, 0, 1))
    lap = np.abs(cv2.Laplacian(src, cv2.CV_64F, ksize=3))
    return round(float((mag[bg] > t).mean()), 3), round(float((lap[bg] > t).mean()), 3)

blur5 = cv2.GaussianBlur(noisy, (5, 5), 0)
for name, src in [("원본", img), ("노이즈", noisy), ("노이즈 → 블러 5", blur5)]:
    s, l = false_ratio(src)
    print(f"{name}: Sobel 잘못 찾은 비율 {s}, Laplacian 잘못 찾은 비율 {l}")
출력
원본: Sobel 잘못 찾은 비율 0.0, Laplacian 잘못 찾은 비율 0.0
노이즈: Sobel 잘못 찾은 비율 0.372, Laplacian 잘못 찾은 비율 0.591
노이즈 → 블러 5: Sobel 잘못 찾은 비율 0.0, Laplacian 잘못 찾은 비율 0.0

3단계. 블러 크기별 표 만들기

Python
import pandas as pd

rows = []
for k in (0, 3, 5, 9):
    # k=0 은 블러 없이. GaussianBlur(그림, (k, k), 0): 시그마는 크기에 맞춰 자동
    src = noisy if k == 0 else cv2.GaussianBlur(noisy, (k, k), 0)
    s, l = false_ratio(src)
    # 진짜 경계가 얼마나 약해지나: 네모의 왼쪽 변(가로줄 80, 가로 50~70)에서 가장 큰 크기
    mag = cv2.magnitude(cv2.Sobel(src, cv2.CV_64F, 1, 0), cv2.Sobel(src, cv2.CV_64F, 0, 1))
    rows.append({"블러": "없음" if k == 0 else f"{k}×{k}", "바탕 표준편차": round(float(src[bg].std()), 1),
                 "Sobel 오검출": s, "Laplacian 오검출": l, "왼쪽 변 최대 크기": round(float(mag[80, 50:71].max()))})
table = pd.DataFrame(rows)
print(table.to_string(index=False))
출력
 블러  바탕 표준편차  Sobel 오검출  Laplacian 오검출  왼쪽 변 최대 크기
 없음     20.1      0.372          0.591         639
3×3      7.6      0.010          0.014         452
5×5      5.5      0.000          0.000         372
9×9      3.4      0.000          0.000         257

4단계. 2행 3열로 견주어 보기

Python
import matplotlib.pyplot as plt

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

fig, axes = plt.subplots(2, 3, figsize=(12, 6))
for i, (name, src) in enumerate([("블러 없이", noisy), ("블러 5×5 뒤", blur5)]):
    mag = cv2.magnitude(cv2.Sobel(src, cv2.CV_64F, 1, 0), cv2.Sobel(src, cv2.CV_64F, 0, 1))
    lap = cv2.Laplacian(src, cv2.CV_64F, ksize=3)
    # 경계 지도는 기준 100 으로 만든다
    panels = [(f"{name}: 입력", src),
              (f"{name}: Sobel 크기 > 100", np.where(mag > 100, 255, 0).astype(np.uint8)),
              (f"{name}: |Laplacian| > 100", np.where(np.abs(lap) > 100, 255, 0).astype(np.uint8))]
    for ax, (title, arr) in zip(axes[i], panels):
        ax.imshow(arr, cmap="gray", vmin=0, vmax=255)
        ax.set_title(title)
        ax.axis("off")
plt.tight_layout()
plt.show()
노이즈 낀 입력과 Sobel, Laplacian 경계 지도를 블러 없이와 블러 5×5 뒤로 나눠 2행 3열로 놓은 결과

코드 설명

  • 표준편차 20 의 노이즈를 더하자 고른 바탕의 밝기 표준편차가 0.0 에서 20.1 로 커졌습니다. 바탕 다섯 칸이 96, 24, 72, 61, 49 처럼 원래 값 50 둘레로 크게 흔들립니다.
  • 블러 없이 기준 100 을 걸면 고른 바탕의 0.372 를 Sobel 이, 0.591 을 Laplacian 이 경계로 잘못 찾습니다. 두 번 빼는 Laplacian 이 노이즈에 더 민감합니다. Gaussian 5×5 를 먼저 걸면 둘 다 0.0 이 됩니다.
  • 블러 크기별 표에서 3×3 만으로도 잘못 찾은 비율이 0.010, 0.014 로 크게 줄고, 5×5 부터는 0 입니다. 그 대신 네모 왼쪽 변의 가장 큰 크기가 639(블러 없이) → 452 → 372 → 257(9×9)로 작아집니다. 블러가 셀수록 진짜 경계도 약해지므로, 기준을 넘는 가장 작은 블러를 고릅니다.
  • 그림의 위 줄은 바탕 전체가 흰 점으로 덮였고, 아래 줄의 Sobel 은 테두리만 깨끗하게 남았습니다. 아래 줄의 Laplacian 은 테두리가 군데군데 끊겼습니다. 블러 뒤의 Laplacian 값은 작아지므로, Sobel 에 맞춘 기준 100 이 Laplacian 에는 높았던 것입니다. 방법을 바꾸면 기준도 그 방법에 맞춰 다시 고릅니다.
← 이전 절10장 · 실습 4 — Laplacian 으로 경계 찾기다음 절 →10장 · 프로젝트 1 — 도로 그림에서 차선과 정지선 찾기