11장. Canny 와 Edge 비교
실습 1 — cv2.Canny 기본과 두 기준값 격자
도형 그림(ch11_set/shapes.png)에 cv2.Canny 를 걸어 결과의 꼴을 보고, 1장의 과일 진열대(fruit_stand.png)에서 두 기준값을 여러 개로 바꿔 경계 칸 비율을 표와 격자 그림으로 견줍니다. 마지막에 찾은 경계를 컬러 그림 위에 겹쳐 봅니다.
1단계. 그림 읽고 Canny 걸기
import cv2
import numpy as np
# 도형 그림과 정답 경계를 흑백으로 읽는다. 연습용으로 그려 만든 가상의 그림이다
gray = cv2.imread("ch11_set/shapes.png", cv2.IMREAD_GRAYSCALE)
answer = cv2.imread("ch11_set/shapes_edge.png", cv2.IMREAD_GRAYSCALE)
print("모양:", gray.shape, "/ 자료형:", gray.dtype)
# cv2.Canny(그림, threshold1, threshold2): 작은 쪽이 low, 큰 쪽이 high
edges = cv2.Canny(gray, # image: 8비트 흑백 그림
50, # threshold1: low. 이보다 작은 칸은 버린다
150) # threshold2: high. 이 이상인 칸은 강한 경계
# 결과: 0 과 255 두 값만 있는 이진 그림
print("결과에 있는 값:", [int(v) for v in np.unique(edges)])
print("Canny 경계 칸 비율:", round(float((edges > 0).mean()), 3))
print("정답 경계 칸 비율:", round(float((answer > 0).mean()), 3))모양: (240, 320) / 자료형: uint8
결과에 있는 값: [0, 255]
Canny 경계 칸 비율: 0.019
정답 경계 칸 비율: 0.022단계. 과일 진열대에서 두 기준값을 바꿔 가며 표 만들기
import pandas as pd
# 1차시 실습 5에서 맛보기로 Canny 를 걸었던 가상의 과일 진열대 그림
fruit = cv2.imread("fruit_stand.png")
fruit_gray = cv2.cvtColor(fruit, cv2.COLOR_BGR2GRAY)
lows = [25, 50, 100]
highs = [100, 200, 400]
rows, results = [], {}
for low in lows:
for high in highs:
e = cv2.Canny(fruit_gray, low, high)
results[(low, high)] = e
rows.append({"low": low, "high": high, "경계 칸 비율": round(float((e > 0).mean()), 4)})
table = pd.DataFrame(rows)
# pivot: low 를 줄, high 를 칸으로 펼친 표로 바꾼다
print(table.pivot(index="low", columns="high", values="경계 칸 비율"))high 100 200 400
low
25 0.0324 0.0245 0.0187
50 0.0323 0.0244 0.0187
100 0.0233 0.0195 0.01443단계. 격자 그림으로 견주기
import matplotlib.pyplot as plt
# 그래프의 한글이 깨지지 않게 맑은 고딕을 쓰고, 빼기 기호가 네모로 나오지 않게 한다
plt.rcParams["font.family"] = "Malgun Gothic"
plt.rcParams["axes.unicode_minus"] = False
fig, axes = plt.subplots(3, 3, figsize=(10, 7.6))
for i, low in enumerate(lows):
for j, high in enumerate(highs):
axes[i, j].imshow(results[(low, high)], cmap="gray", vmin=0, vmax=255)
axes[i, j].set_title(f"low {low}, high {high}")
axes[i, j].axis("off")
plt.tight_layout()
plt.show()
4단계. 찾은 경계를 컬러 그림 위에 겹쳐 보기
# 1차시와 같은 기준값 50, 150 으로 다시 건다
edges = cv2.Canny(fruit_gray, 50, 150)
# 원본을 복사해 경계 칸만 빨간색(B, G, R = 0, 0, 255)으로 칠한다
overlay = fruit.copy()
overlay[edges > 0] = (0, 0, 255)
print("경계 칸 비율:", round(float((edges > 0).mean()), 3))
fig, axes = plt.subplots(1, 2, figsize=(10, 3.8))
axes[0].imshow(edges, cmap="gray", vmin=0, vmax=255)
axes[0].set_title("Canny (50, 150)")
axes[1].imshow(cv2.cvtColor(overlay, cv2.COLOR_BGR2RGB))
axes[1].set_title("원본 위에 겹친 경계")
for ax in axes:
ax.axis("off")
plt.tight_layout()
plt.show()경계 칸 비율: 0.028
코드 설명
cv2.Canny(gray, 50, 150)은 0 과 255 두 값만 있는 경계 그림을 돌려줍니다. 깨끗한 도형 그림에서 경계 칸 비율은 0.019 로 정답 경계(0.02)와 거의 같습니다.- 표를 가로로 읽으면 high 의 효과입니다. low 25 에서 high 를 100, 200, 400 으로 올리면 경계 칸 비율이 0.0324, 0.0245, 0.0187 로 줄어듭니다. high 를 넘는 강한 경계가 줄면 거기에 이어질 약한 선도 함께 사라지기 때문입니다. 격자의 오른쪽 줄(high 400)에서는 가운데 귤의 위쪽 테두리와 무늬가 사라졌습니다.
- 표를 세로로 읽으면 low 의 효과입니다. low 25 와 50 은 0.0324 와 0.0323 으로 거의 같고, 100 이면 0.0233 으로 줄어듭니다. 격자의 맨 아래 줄(low 100)에서는 진열대 판자의 옅은 가로 줄무늬가 사라집니다.
overlay[edges > 0] = (0, 0, 255)는 경계 칸만 빨간색으로 칠합니다. OpenCV 는 B, G, R 순서라 (0, 0, 255) 가 빨강입니다. 경계 칸 비율 0.028 은 1장 실습 5에서 본 값과 같습니다.