OpenCV 비전 실무11장 · Canny 와 Edge 비교16 / 21← 이전목차다음 →MetaContents

11장. Canny 와 Edge 비교

실습 3 — 이력 임계값의 효과

이력 임계값을 작은 격자에서 직접 해 보고, 옅어지는 띠 그림(ch11_set/fade.png)에서 low 만 바꿀 때와 high 만 바꿀 때를 견줍니다. 이 그림의 띠는 왼쪽(밝기 60)에서 오른쪽(176)으로 갈수록 바탕(180)과 비슷해져 경계가 점점 약해지고, 오른쪽 아래의 원은 바탕과 밝기 차이가 25 뿐입니다. 이어진 흰 덩어리의 개수(연결 요소)도 셉니다.

1단계. 작은 격자에서 이력 임계값 해 보기

Python
import cv2
import numpy as np

grid = np.array([[0, 120, 80, 60, 0, 0],
                 [0,   0,  0, 70, 0, 0],
                 [0,   0,  0,  0, 0, 0],
                 [0,  60, 70,  0, 0, 90],
                 [0,   0,  0,  0, 30, 0]])
low, high = 50, 100

# low 이상인 칸을 모두 흰색으로 만든 판
candidate = np.where(grid >= low, 255, 0).astype(np.uint8)
# cv2.connectedComponents: 이어진 흰 덩어리마다 1, 2, 3, ... 번호를 매긴다. 0 은 바탕
count, labels = cv2.connectedComponents(candidate,
                                        connectivity=8)   # 대각선 이웃까지 이어진 것으로 본다
print("덩어리 수(바탕 포함):", count)
print(labels)

# 강한 경계(high 이상)가 하나라도 든 덩어리 번호만 남긴다
strong_labels = set(labels[grid >= high].tolist())
keep = np.isin(labels, list(strong_labels))
print("강한 경계가 든 덩어리:", sorted(strong_labels))
print("남은 칸의 값:", grid[keep].tolist())
출력
덩어리 수(바탕 포함): 4
[[0 1 1 1 0 0]
 [0 0 0 1 0 0]
 [0 0 0 0 0 0]
 [0 2 2 0 0 3]
 [0 0 0 0 0 0]]
강한 경계가 든 덩어리: [1]
남은 칸의 값: [120, 80, 60, 70]

2단계. high 를 두고 low 만 바꾸기

Python
# 옅어지는 띠 그림과 정답 경계. 연습용으로 그려 만든 가상의 그림이다
gray = cv2.imread("ch11_set/fade.png", cv2.IMREAD_GRAYSCALE)
answer = cv2.imread("ch11_set/fade_edge.png", cv2.IMREAD_GRAYSCALE)
kernel = np.ones((3, 3), np.uint8)

def recall(edges):
    """정답 경계 가운데, 찾은 경계에서 한 칸 안에 든 칸의 비율"""
    near = cv2.dilate(edges, kernel)       # 9차시에서 본 팽창: 흰 덩어리를 한 겹 부풀린다
    return round(float(((answer > 0) & (near > 0)).sum() / (answer > 0).sum()), 3)

def pieces(edges):
    """이어진 흰 덩어리의 개수(바탕 빼고). 많을수록 선이 잘게 끊겼다"""
    return cv2.connectedComponents(edges, connectivity=8)[0] - 1

low_results = {}
for low in [10, 50, 100, 150]:
    e = cv2.Canny(gray, low, 150)
    low_results[low] = e
    # 띠의 위쪽 테두리(세로 48~52)가 가로 몇 번째 칸까지 이어졌나
    right_end = int(np.nonzero(e[48:53].any(axis=0))[0].max())
    print(f"low {low:3}, high 150: 재현율 {recall(e)}, 덩어리 {pieces(e)}개, 위 테두리 끝 {right_end}")
출력
low  10, high 150: 재현율 0.739, 덩어리 1개, 위 테두리 끝 300
low  50, high 150: 재현율 0.67, 덩어리 1개, 위 테두리 끝 297
low 100, high 150: 재현율 0.596, 덩어리 1개, 위 테두리 끝 263
low 150, high 150: 재현율 0.523, 덩어리 1개, 위 테두리 끝 229

3단계. low 를 두고 high 만 바꾸기

Python
high_results = {}
for high in [80, 150, 400, 700]:
    e = cv2.Canny(gray, 50, high)
    high_results[high] = e
    print(f"low 50, high {high:3}: 재현율 {recall(e)}, 덩어리 {pieces(e)}개, "
          f"경계 칸 {int((e > 0).sum())}개")
출력
low 50, high  80: 재현율 0.931, 덩어리 2개, 경계 칸 847개
low 50, high 150: 재현율 0.67, 덩어리 1개, 경계 칸 615개
low 50, high 400: 재현율 0.67, 덩어리 1개, 경계 칸 615개
low 50, high 700: 재현율 0.0, 덩어리 0개, 경계 칸 0개

4단계. 결과를 두 줄로 견주기

Python
import matplotlib.pyplot as plt

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

fig, axes = plt.subplots(2, 4, figsize=(13, 5.4))
for ax, (low, e) in zip(axes[0], low_results.items()):
    ax.imshow(e, cmap="gray", vmin=0, vmax=255)
    ax.set_title(f"low {low}, high 150")
    ax.axis("off")
for ax, (high, e) in zip(axes[1], high_results.items()):
    ax.imshow(e, cmap="gray", vmin=0, vmax=255)
    ax.set_title(f"low 50, high {high}")
    ax.axis("off")
plt.tight_layout()
plt.show()
옅어지는 띠 그림에서 위 줄은 high 150 에 low 를 10·50·100·150 으로, 아래 줄은 low 50 에 high 를 80·150·400·700 으로 바꾼 Canny 결과

코드 설명

  • cv2.connectedComponents(candidate, connectivity=8) 은 low 이상인 칸을 이어진 덩어리로 묶어, 바탕(0)과 덩어리 1·2·3 으로 모두 4개의 번호를 매깁니다. 강한 경계 120 이 든 덩어리는 1번뿐이라, 본문 손계산처럼 120, 80, 60, 70 만 남습니다.
  • high 를 150 에 두고 low 를 150, 100, 50, 10 으로 낮추면 띠의 위 테두리가 가로 229, 263, 297, 300 번째 칸까지 이어지고, 재현율이 0.523, 0.596, 0.67, 0.739 로 오릅니다. 왼쪽 끝의 강한 경계에서 출발해, 오른쪽으로 갈수록 약해지는 테두리를 따라가기 때문입니다. low 와 high 가 같은 150 이면 기준 하나로 자른 것과 같습니다.
  • low 를 50 에 두고 high 를 바꾸면 80 에서만 원이 나타나 덩어리가 2개, 재현율이 0.931 이 됩니다. 원은 바탕과 차이가 25 뿐이라 기울기가 150 에 못 미치고, 강한 경계가 하나도 없으니 high 150 이상에서는 통째로 사라집니다.
  • high 400 은 150 과 결과가 같습니다(경계 칸 615개). 띠의 왼쪽 끝은 아주 진해서 400 도 넘기 때문입니다. high 700 이면 넘는 칸이 하나도 없어 경계도 0개입니다. 강한 경계가 없으면 약한 경계가 이어질 곳도 없습니다.
← 이전 절11장 · 실습 2 — Canny 의 단계 따라가기다음 절 →11장 · 실습 4 — 노이즈에서 블러 크기, apertureSize, L2gradient 견주기