์ฃ์ง ๊ฒ์ถ (Edge Detection)
์ฃ์ง ๊ฒ์ถ (Edge Detection)¶
๊ฐ์¶
์ฃ์ง(Edge)๋ ์ด๋ฏธ์ง์์ ๋ฐ๊ธฐ๊ฐ ๊ธ๊ฒฉํ๊ฒ ๋ณํ๋ ์์ญ์ผ๋ก, ๊ฐ์ฒด์ ๊ฒฝ๊ณ๋ ๊ตฌ์กฐ๋ฅผ ๋ํ๋ ๋๋ค. ์ด ๋ ์จ์์๋ ์ด๋ฏธ์ง ๊ทธ๋๋์ธํธ ๊ฐ๋ ๊ณผ Sobel, Scharr, Laplacian, Canny ๋ฑ ๋ค์ํ ์ฃ์ง ๊ฒ์ถ ๊ธฐ๋ฒ์ ํ์ตํฉ๋๋ค.
๋ชฉ์ฐจ¶
- ์ด๋ฏธ์ง ๊ทธ๋๋์ธํธ ๊ฐ๋
- Sobel ์ฐ์ฐ์
- Scharr ์ฐ์ฐ์
- Laplacian ์ฐ์ฐ์
- Canny ์ฃ์ง ๊ฒ์ถ
- ๊ทธ๋๋์ธํธ ํฌ๊ธฐ์ ๋ฐฉํฅ
- ์ฐ์ต ๋ฌธ์
1. ์ด๋ฏธ์ง ๊ทธ๋๋์ธํธ ๊ฐ๋ ¶
๊ทธ๋๋์ธํธ๋?¶
๊ทธ๋๋์ธํธ(Gradient): ์ด๋ฏธ์ง ๋ฐ๊ธฐ์ ๋ณํ์จ
์ํ์ ์ ์:
โf = (โf/โx, โf/โy)
- โf/โx: x ๋ฐฉํฅ(์ํ) ๋ฐ๊ธฐ ๋ณํ์จ
- โf/โy: y ๋ฐฉํฅ(์์ง) ๋ฐ๊ธฐ ๋ณํ์จ
๊ทธ๋๋์ธํธ ํฌ๊ธฐ (Magnitude):
|โf| = โ((โf/โx)ยฒ + (โf/โy)ยฒ)
๊ทธ๋๋์ธํธ ๋ฐฉํฅ (Direction):
ฮธ = arctan(โf/โy / โf/โx)
์ฃ์ง์ ์ข ๋ฅ¶
1. ์คํ
์ฃ์ง (Step Edge)
๋ฐ๊ธฐ โโโ
โ
โโโ ๋ฐ๊ธฐ
โ ์ด์์ ์ธ ์ฃ์ง, ๊ธ๊ฒฉํ ๋ณํ
2. ๋จํ ์ฃ์ง (Ramp Edge)
๋ฐ๊ธฐ โโโฒ
โฒ
โฒโโ ๋ฐ๊ธฐ
โ ์ ์ง์ ์ธ ๋ณํ, ํ๋ฆฟํ ๊ฒฝ๊ณ
3. ์ง๋ถ ์ฃ์ง (Roof Edge)
๋ฐ๊ธฐ โโโฑโฒ
โฑ โฒ
โฑ โฒโโ ๋ฐ๊ธฐ
โ ์ (line) ๊ตฌ์กฐ
4. ๋ฆฌ์ง ์ฃ์ง (Ridge Edge)
โฑโฒ
โฑ โฒ
โโโฑ โฒโโ
โ ์์ ์ ๊ตฌ์กฐ
์ฃ์ง ๊ฒ์ถ ํ์ดํ๋ผ์ธ¶
โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ
โ ์
๋ ฅ โ โ ๋
ธ์ด์ฆ โ โ ๊ทธ๋๋์ธํธ โ โ ์ฃ์ง โ
โ ์ด๋ฏธ์ง โ โโโถ โ ์ ๊ฑฐ โ โโโถ โ ๊ณ์ฐ โ โโโถ โ ์ถ์ถ โ
โ โ โ (Gaussian) โ โ (Sobel ๋ฑ) โ โ (์๊ณ๊ฐ) โ
โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ
2. Sobel ์ฐ์ฐ์¶
๊ฐ๋ ¶
Sobel ์ฐ์ฐ์: 1์ฐจ ๋ฏธ๋ถ ๊ธฐ๋ฐ ์ฃ์ง ๊ฒ์ถ
โ x, y ๋ฐฉํฅ์ ๊ทธ๋๋์ธํธ๋ฅผ ๊ฐ๊ฐ ๊ณ์ฐ
3x3 Sobel ์ปค๋:
Gx (์ํ ์ฃ์ง ๊ฒ์ถ): Gy (์์ง ์ฃ์ง ๊ฒ์ถ):
โโโโโโฌโโโโโฌโโโโโ โโโโโโฌโโโโโฌโโโโโ
โ -1 โ 0 โ +1 โ โ -1 โ -2 โ -1 โ
โโโโโโผโโโโโผโโโโโค โโโโโโผโโโโโผโโโโโค
โ -2 โ 0 โ +2 โ โ 0 โ 0 โ 0 โ
โโโโโโผโโโโโผโโโโโค โโโโโโผโโโโโผโโโโโค
โ -1 โ 0 โ +1 โ โ +1 โ +2 โ +1 โ
โโโโโโดโโโโโดโโโโโ โโโโโโดโโโโโดโโโโโ
โ Gx: ์ธ๋ก ์ฃ์ง ๊ฒ์ถ (์ข์ฐ ๋ฐ๊ธฐ ์ฐจ์ด)
โ Gy: ๊ฐ๋ก ์ฃ์ง ๊ฒ์ถ (์ํ ๋ฐ๊ธฐ ์ฐจ์ด)
cv2.Sobel() ํจ์¶
cv2.Sobel(src, ddepth, dx, dy, ksize=3, scale=1, delta=0)
| ํ๋ผ๋ฏธํฐ | ์ค๋ช |
|---|---|
| src | ์ ๋ ฅ ์ด๋ฏธ์ง |
| ddepth | ์ถ๋ ฅ ์ด๋ฏธ์ง ๊น์ด (cv2.CV_64F ๊ถ์ฅ) |
| dx | x ๋ฐฉํฅ ๋ฏธ๋ถ ์ฐจ์ (0 ๋๋ 1) |
| dy | y ๋ฐฉํฅ ๋ฏธ๋ถ ์ฐจ์ (0 ๋๋ 1) |
| ksize | ์ปค๋ ํฌ๊ธฐ (1, 3, 5, 7) |
| scale | ์ค์ผ์ผ ํฉํฐ |
| delta | ๊ฒฐ๊ณผ์ ๋ํ ๊ฐ |
๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ¶
import cv2
import numpy as np
# ์ด๋ฏธ์ง ์ฝ๊ธฐ
img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)
# Sobel ์ฐ์ฐ
# ddepth๋ฅผ CV_64F๋ก ์ค์ ํ์ฌ ์์ ๊ฐ๋ ์ฒ๋ฆฌ
sobel_x = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3) # x ๋ฐฉํฅ
sobel_y = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3) # y ๋ฐฉํฅ
# ์ ๋๊ฐ ๋ณํ ํ 8๋นํธ๋ก ๋ณํ
sobel_x = cv2.convertScaleAbs(sobel_x)
sobel_y = cv2.convertScaleAbs(sobel_y)
# x, y ๊ทธ๋๋์ธํธ ํฉ์ฑ
sobel_combined = cv2.addWeighted(sobel_x, 0.5, sobel_y, 0.5, 0)
# ๊ฒฐ๊ณผ ํ์
cv2.imshow('Original', img)
cv2.imshow('Sobel X', sobel_x)
cv2.imshow('Sobel Y', sobel_y)
cv2.imshow('Sobel Combined', sobel_combined)
cv2.waitKey(0)
๊ทธ๋๋์ธํธ ํฌ๊ธฐ ๊ณ์ฐ¶
import cv2
import numpy as np
def sobel_magnitude(image):
"""Sobel ๊ทธ๋๋์ธํธ ํฌ๊ธฐ ๊ณ์ฐ"""
# ๊ทธ๋ ์ด์ค์ผ์ผ ๋ณํ
if len(image.shape) == 3:
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
else:
gray = image
# ๋
ธ์ด์ฆ ์ ๊ฑฐ
blurred = cv2.GaussianBlur(gray, (3, 3), 0)
# Sobel ์ฐ์ฐ (float64๋ก ๊ณ์ฐ)
sobel_x = cv2.Sobel(blurred, cv2.CV_64F, 1, 0, ksize=3)
sobel_y = cv2.Sobel(blurred, cv2.CV_64F, 0, 1, ksize=3)
# ๊ทธ๋๋์ธํธ ํฌ๊ธฐ: sqrt(Gxยฒ + Gyยฒ)
magnitude = np.sqrt(sobel_x**2 + sobel_y**2)
# 0-255 ๋ฒ์๋ก ์ ๊ทํ
magnitude = np.clip(magnitude, 0, 255).astype(np.uint8)
return magnitude
# ์ฌ์ฉ ์
img = cv2.imread('image.jpg')
edges = sobel_magnitude(img)
cv2.imshow('Sobel Magnitude', edges)
cv2.waitKey(0)
์ปค๋ ํฌ๊ธฐ์ ๋ฐ๋ฅธ ์ฐจ์ด¶
import cv2
import numpy as np
import matplotlib.pyplot as plt
def compare_sobel_ksize(image_path):
"""Sobel ์ปค๋ ํฌ๊ธฐ ๋น๊ต"""
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
fig, axes = plt.subplots(2, 2, figsize=(12, 12))
ksizes = [1, 3, 5, 7]
for ax, ksize in zip(axes.flatten(), ksizes):
# ksize=1์ผ ๋๋ 3x1 ๋๋ 1x3 ํํฐ ์ฌ์ฉ
if ksize == 1:
sobel_x = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=1)
sobel_y = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=1)
else:
sobel_x = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=ksize)
sobel_y = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=ksize)
magnitude = np.sqrt(sobel_x**2 + sobel_y**2)
magnitude = np.clip(magnitude, 0, 255).astype(np.uint8)
ax.imshow(magnitude, cmap='gray')
ax.set_title(f'Sobel ksize={ksize}')
ax.axis('off')
plt.tight_layout()
plt.show()
# ksize ๋น๊ต:
# - ksize=1: ๊ฐ์ฅ ๋ฏผ๊ฐ, ๋
ธ์ด์ฆ์ ์ทจ์ฝ
# - ksize=3: ํ์ค, ๊ท ํ ์กํ ๊ฒฐ๊ณผ
# - ksize=5, 7: ๋ ๋ถ๋๋ฌ์ด ์ฃ์ง, ๋
ธ์ด์ฆ์ ๊ฐํจ
3. Scharr ์ฐ์ฐ์¶
๊ฐ๋ ¶
Scharr ์ฐ์ฐ์: Sobel๋ณด๋ค ๋ ์ ํํ 3x3 ์ปค๋
โ ํ์ ๋์นญ์ฑ์ด ๋ ์ข์
Scharr ์ปค๋:
Gx: Gy:
โโโโโโฌโโโโโฌโโโโโ โโโโโโฌโโโโโฌโโโโโ
โ -3 โ 0 โ +3 โ โ -3 โ-10 โ -3 โ
โโโโโโผโโโโโผโโโโโค โโโโโโผโโโโโผโโโโโค
โ-10 โ 0 โ+10 โ โ 0 โ 0 โ 0 โ
โโโโโโผโโโโโผโโโโโค โโโโโโผโโโโโผโโโโโค
โ -3 โ 0 โ +3 โ โ +3 โ+10 โ +3 โ
โโโโโโดโโโโโดโโโโโ โโโโโโดโโโโโดโโโโโ
Sobel vs Scharr:
- Sobel: [-1, 0, 1] ร [-1, -2, -1]แต
- Scharr: [-3, 0, 3] ร [-3, -10, -3]แต
โ Scharr๊ฐ ๋๊ฐ์ ๋ฐฉํฅ์์ ๋ ์ ํ
cv2.Scharr() ํจ์¶
cv2.Scharr(src, ddepth, dx, dy, scale=1, delta=0)
import cv2
import numpy as np
def compare_sobel_scharr(image):
"""Sobel๊ณผ Scharr ๋น๊ต"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Sobel (ksize=3)
sobel_x = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
sobel_y = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
sobel_mag = np.sqrt(sobel_x**2 + sobel_y**2)
# Scharr (3x3 ๊ณ ์ )
scharr_x = cv2.Scharr(gray, cv2.CV_64F, 1, 0)
scharr_y = cv2.Scharr(gray, cv2.CV_64F, 0, 1)
scharr_mag = np.sqrt(scharr_x**2 + scharr_y**2)
# ์ ๊ทํ
sobel_mag = np.clip(sobel_mag, 0, 255).astype(np.uint8)
scharr_mag = np.clip(scharr_mag, 0, 255).astype(np.uint8)
return sobel_mag, scharr_mag
# Scharr ์ฌ์ฉ ์
img = cv2.imread('image.jpg')
sobel, scharr = compare_sobel_scharr(img)
cv2.imshow('Sobel', sobel)
cv2.imshow('Scharr', scharr)
cv2.waitKey(0)
Sobel์์ Scharr ์ฌ์ฉํ๊ธฐ¶
# cv2.Sobel()์์ ksize=-1 ๋๋ ksize=cv2.FILTER_SCHARR ์ฌ์ฉ
scharr_x = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=-1) # Scharr ์ปค๋ ์ฌ์ฉ
scharr_y = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=-1)
# ์ ์ฝ๋๋ ์๋์ ๋์ผ
scharr_x = cv2.Scharr(gray, cv2.CV_64F, 1, 0)
scharr_y = cv2.Scharr(gray, cv2.CV_64F, 0, 1)
4. Laplacian ์ฐ์ฐ์¶
๊ฐ๋ ¶
Laplacian ์ฐ์ฐ์: 2์ฐจ ๋ฏธ๋ถ ๊ธฐ๋ฐ ์ฃ์ง ๊ฒ์ถ
โ ๋ฐ๊ธฐ๊ฐ ๊ธ๊ฒฉํ ๋ณํ๋ ์ง์ ์์ 0์ ๊ต์ฐจ
์ํ์ ์ ์:
โยฒf = โยฒf/โxยฒ + โยฒf/โyยฒ
Laplacian ์ปค๋:
4-์ฐ๊ฒฐ์ฑ: 8-์ฐ๊ฒฐ์ฑ:
โโโโโโฌโโโโโฌโโโโโ โโโโโโฌโโโโโฌโโโโโ
โ 0 โ 1 โ 0 โ โ 1 โ 1 โ 1 โ
โโโโโโผโโโโโผโโโโโค โโโโโโผโโโโโผโโโโโค
โ 1 โ -4 โ 1 โ โ 1 โ -8 โ 1 โ
โโโโโโผโโโโโผโโโโโค โโโโโโผโโโโโผโโโโโค
โ 0 โ 1 โ 0 โ โ 1 โ 1 โ 1 โ
โโโโโโดโโโโโดโโโโโ โโโโโโดโโโโโดโโโโโ
ํน์ง:
- ๋ฐฉํฅ์ ๋ฌด๊ดํ๊ฒ ์ฃ์ง ๊ฒ์ถ
- ๋
ธ์ด์ฆ์ ๋งค์ฐ ๋ฏผ๊ฐ (2์ฐจ ๋ฏธ๋ถ)
- Zero-crossing ์ง์ ์ด ์ฃ์ง
1์ฐจ ๋ฏธ๋ถ vs 2์ฐจ ๋ฏธ๋ถ¶
์๋ณธ ์ ํธ (์คํ
์ฃ์ง):
โโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโ
1์ฐจ ๋ฏธ๋ถ (Sobel):
โฑโฒ
โฑ โฒ
โโโโโโโโโโฑ โฒโโโโโโโโโ
โ ํผํฌ ์ง์ ์ด ์ฃ์ง
2์ฐจ ๋ฏธ๋ถ (Laplacian):
โฑโฒ
โฑ โฒ
โโโโฑ โฒโโโ
โฑ โฒ
โฑ โฒ
โ Zero-crossing ์ง์ ์ด ์ฃ์ง
cv2.Laplacian() ํจ์¶
cv2.Laplacian(src, ddepth, ksize=1, scale=1, delta=0)
| ํ๋ผ๋ฏธํฐ | ์ค๋ช |
|---|---|
| src | ์ ๋ ฅ ์ด๋ฏธ์ง |
| ddepth | ์ถ๋ ฅ ์ด๋ฏธ์ง ๊น์ด |
| ksize | ์ปค๋ ํฌ๊ธฐ (1, 3, 5, 7) |
| scale | ์ค์ผ์ผ ํฉํฐ |
| delta | ๊ฒฐ๊ณผ์ ๋ํ ๊ฐ |
๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ¶
import cv2
import numpy as np
def laplacian_edge(image):
"""Laplacian ์ฃ์ง ๊ฒ์ถ"""
# ๊ทธ๋ ์ด์ค์ผ์ผ ๋ณํ
if len(image.shape) == 3:
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
else:
gray = image
# ๋
ธ์ด์ฆ ์ ๊ฑฐ (Laplacian์ ๋
ธ์ด์ฆ์ ๋ฏผ๊ฐ)
blurred = cv2.GaussianBlur(gray, (3, 3), 0)
# Laplacian ์ฐ์ฐ
laplacian = cv2.Laplacian(blurred, cv2.CV_64F, ksize=3)
# ์ ๋๊ฐ ๋ณํ
laplacian = cv2.convertScaleAbs(laplacian)
return laplacian
# ์ฌ์ฉ ์
img = cv2.imread('image.jpg')
edges = laplacian_edge(img)
cv2.imshow('Laplacian', edges)
cv2.waitKey(0)
LoG (Laplacian of Gaussian)¶
import cv2
import numpy as np
def log_edge_detection(image, sigma=1.0):
"""
LoG (Laplacian of Gaussian) ์ฃ์ง ๊ฒ์ถ
1. Gaussian ๋ธ๋ฌ๋ก ๋
ธ์ด์ฆ ์ ๊ฑฐ
2. Laplacian์ผ๋ก ์ฃ์ง ๊ฒ์ถ
"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Gaussian ๋ธ๋ฌ (sigma์ ๋ฐ๋ฅธ ์ปค๋ ํฌ๊ธฐ)
ksize = int(6 * sigma + 1)
if ksize % 2 == 0:
ksize += 1
blurred = cv2.GaussianBlur(gray, (ksize, ksize), sigma)
# Laplacian
log = cv2.Laplacian(blurred, cv2.CV_64F, ksize=3)
# ์ ๋๊ฐ
log = cv2.convertScaleAbs(log)
return log
# LoG ์ฌ์ฉ
img = cv2.imread('image.jpg')
edges = log_edge_detection(img, sigma=1.5)
cv2.imshow('LoG', edges)
cv2.waitKey(0)
5. Canny ์ฃ์ง ๊ฒ์ถ¶
๊ฐ๋ ¶
Canny ์ฃ์ง ๊ฒ์ถ: ๋ค๋จ๊ณ ์ฃ์ง ๊ฒ์ถ ์๊ณ ๋ฆฌ์ฆ
โ ๊ฐ์ฅ ๋๋ฆฌ ์ฌ์ฉ๋๋ ์ฃ์ง ๊ฒ์ถ ๋ฐฉ๋ฒ
Canny์ 3๊ฐ์ง ๋ชฉํ:
1. ๋ฎ์ ์ค๋ฅ์จ: ์ค์ ์ฃ์ง๋ง ๊ฒ์ถ
2. ์ ํํ ์์น: ์ฃ์ง๊ฐ ์ ํํ ์์น์
3. ๋จ์ผ ์๋ต: ํ๋์ ์ฃ์ง์ ํ๋์ ์
4๋จ๊ณ ์ฒ๋ฆฌ:
โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ
โ Gaussian โ โ Sobel โ โ ๋น์ต๋ โ โ ์ด๋ ฅ โ
โ Blur โ โโโถ โ Gradient โ โโโถ โ ์ต์ โ โโโถ โ ์๊ณ๊ฐ โ
โ โ โ โ โ (NMS) โ โ โ
โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ
Canny ์๊ณ ๋ฆฌ์ฆ ์์ธ¶
๋จ๊ณ 1: ๋
ธ์ด์ฆ ์ ๊ฑฐ (Gaussian Blur)
- 5x5 Gaussian ํํฐ ์ ์ฉ
- ๊ณ ์ฃผํ ๋
ธ์ด์ฆ ์ ๊ฑฐ
๋จ๊ณ 2: ๊ทธ๋๋์ธํธ ๊ณ์ฐ
- Sobel ์ฐ์ฐ์ผ๋ก Gx, Gy ๊ณ์ฐ
- ํฌ๊ธฐ: G = โ(Gxยฒ + Gyยฒ)
- ๋ฐฉํฅ: ฮธ = arctan(Gy/Gx)
๋จ๊ณ 3: ๋น์ต๋ ์ต์ (Non-Maximum Suppression)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๊ทธ๋๋์ธํธ ๋ฐฉํฅ์ ๋ฐ๋ผ ์ต๋๊ฐ๋ง ์ ์ง โ
โ โ ์ฃ์ง๋ฅผ 1ํฝ์
๋๊ป๋ก ์๊ฒ ๋ง๋ฆ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๋ฐฉํฅ ์์ํ (4๋ฐฉํฅ):
90ยฐ
โ
135ยฐ โโโผโโ 45ยฐ
โ
0ยฐ (180ยฐ)
์์:
๋ฐฉํฅ ฮธ = 45ยฐ์ผ ๋, ๋๊ฐ์ ๋ฐฉํฅ์ผ๋ก ๋น๊ต
โโโโโฌโโโโฌโโโโ
โ โ q โ โ
โโโโโผโโโโผโโโโค
โ โ p โ โ p๊ฐ q, r๋ณด๋ค ํฌ๋ฉด ์ ์ง
โโโโโผโโโโผโโโโค
โ โ r โ โ
โโโโโดโโโโดโโโโ
๋จ๊ณ 4: ์ด๋ ฅ ์๊ณ๊ฐ (Hysteresis Thresholding)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ high_threshold: ๊ฐํ ์ฃ์ง โ
โ low_threshold: ์ฝํ ์ฃ์ง โ
โ โ
โ ๊ฐํ ์ฃ์ง: ๋ฌด์กฐ๊ฑด ํฌํจ โ
โ ์ฝํ ์ฃ์ง: ๊ฐํ ์ฃ์ง์ ์ฐ๊ฒฐ๋๋ฉด ํฌํจ โ
โ ๋๋จธ์ง: ์ ๊ฑฐ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
์์:
high = 100, low = 50
ํฝ์
๊ฐ 120 โ ๊ฐํ ์ฃ์ง (ํฌํจ)
ํฝ์
๊ฐ 70 โ ์ฝํ ์ฃ์ง (์ฐ๊ฒฐ ํ์ธ)
ํฝ์
๊ฐ 30 โ ์ ๊ฑฐ
cv2.Canny() ํจ์¶
cv2.Canny(image, threshold1, threshold2, apertureSize=3, L2gradient=False)
| ํ๋ผ๋ฏธํฐ | ์ค๋ช |
|---|---|
| image | ์ ๋ ฅ ์ด๋ฏธ์ง (๊ทธ๋ ์ด์ค์ผ์ผ) |
| threshold1 | ๋ฎ์ ์๊ณ๊ฐ (low) |
| threshold2 | ๋์ ์๊ณ๊ฐ (high) |
| apertureSize | Sobel ์ปค๋ ํฌ๊ธฐ (3, 5, 7) |
| L2gradient | True: L2 norm, False: L1 norm |
๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ¶
import cv2
def canny_edge(image, low=50, high=150):
"""Canny ์ฃ์ง ๊ฒ์ถ"""
# ๊ทธ๋ ์ด์ค์ผ์ผ ๋ณํ
if len(image.shape) == 3:
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
else:
gray = image
# ๋
ธ์ด์ฆ ์ ๊ฑฐ (์ ํ์ - Canny ๋ด๋ถ์์๋ ์ํ)
blurred = cv2.GaussianBlur(gray, (5, 5), 1.4)
# Canny ์ฃ์ง ๊ฒ์ถ
edges = cv2.Canny(blurred, low, high)
return edges
# ์ฌ์ฉ ์
img = cv2.imread('image.jpg')
edges = canny_edge(img, 50, 150)
cv2.imshow('Original', img)
cv2.imshow('Canny Edges', edges)
cv2.waitKey(0)
์๊ณ๊ฐ ํ๋¶
import cv2
import numpy as np
def canny_with_trackbar(image_path):
"""ํธ๋๋ฐ๋ก Canny ์๊ณ๊ฐ ์กฐ์ """
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 1.4)
cv2.namedWindow('Canny')
def nothing(x):
pass
cv2.createTrackbar('Low', 'Canny', 50, 255, nothing)
cv2.createTrackbar('High', 'Canny', 150, 255, nothing)
while True:
low = cv2.getTrackbarPos('Low', 'Canny')
high = cv2.getTrackbarPos('High', 'Canny')
# low๊ฐ high๋ณด๋ค ํฌ์ง ์๋๋ก
if low >= high:
low = high - 1
edges = cv2.Canny(blurred, low, high)
cv2.imshow('Canny', edges)
if cv2.waitKey(1) & 0xFF == 27: # ESC
break
cv2.destroyAllWindows()
# ์คํ
canny_with_trackbar('image.jpg')
์๋ ์๊ณ๊ฐ ์ค์ ¶
import cv2
import numpy as np
def auto_canny(image, sigma=0.33):
"""
์๋ ์๊ณ๊ฐ Canny
์ค๊ฐ๊ฐ ๊ธฐ์ค์ผ๋ก low, high ๊ณ์ฐ
"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 1.4)
# ์ค๊ฐ๊ฐ ๊ณ์ฐ
median = np.median(blurred)
# ์๊ณ๊ฐ ๊ณ์ฐ
low = int(max(0, (1.0 - sigma) * median))
high = int(min(255, (1.0 + sigma) * median))
print(f"Auto threshold: low={low}, high={high}")
edges = cv2.Canny(blurred, low, high)
return edges
# ์ฌ์ฉ ์
img = cv2.imread('image.jpg')
edges = auto_canny(img)
cv2.imshow('Auto Canny', edges)
cv2.waitKey(0)
์ปฌ๋ฌ ์ด๋ฏธ์ง์์ Canny¶
import cv2
import numpy as np
def canny_color(image, low=50, high=150):
"""
์ปฌ๋ฌ ์ด๋ฏธ์ง์์ Canny ์ฃ์ง ๊ฒ์ถ
๊ฐ ์ฑ๋๋ณ๋ก ์ฃ์ง ๊ฒ์ถ ํ ํฉ์ฑ
"""
# ๋ฐฉ๋ฒ 1: ๊ทธ๋ ์ด์ค์ผ์ผ ๋ณํ ํ ์ฒ๋ฆฌ
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
edges_gray = cv2.Canny(gray, low, high)
# ๋ฐฉ๋ฒ 2: ๊ฐ ์ฑ๋๋ณ ์ฒ๋ฆฌ ํ ํฉ์ฑ
b, g, r = cv2.split(image)
edges_b = cv2.Canny(b, low, high)
edges_g = cv2.Canny(g, low, high)
edges_r = cv2.Canny(r, low, high)
# OR ์ฐ์ฐ์ผ๋ก ํฉ์ฑ
edges_color = cv2.bitwise_or(edges_b, edges_g)
edges_color = cv2.bitwise_or(edges_color, edges_r)
return edges_gray, edges_color
# ์ฌ์ฉ ์
img = cv2.imread('image.jpg')
edges_gray, edges_color = canny_color(img)
cv2.imshow('Edges (Gray)', edges_gray)
cv2.imshow('Edges (Color)', edges_color)
cv2.waitKey(0)
6. ๊ทธ๋๋์ธํธ ํฌ๊ธฐ์ ๋ฐฉํฅ¶
๊ทธ๋๋์ธํธ ํฌ๊ธฐ ๊ณ์ฐ¶
import cv2
import numpy as np
def gradient_magnitude_direction(image):
"""๊ทธ๋๋์ธํธ ํฌ๊ธฐ์ ๋ฐฉํฅ ๊ณ์ฐ"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (3, 3), 0)
# Sobel ๊ทธ๋๋์ธํธ
gx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
gy = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
# ํฌ๊ธฐ (Magnitude)
magnitude = np.sqrt(gx**2 + gy**2)
# ๋ฐฉํฅ (Direction) - ๋ผ๋์
direction = np.arctan2(gy, gx)
# ๋ฐฉํฅ์ ๋(degree)๋ก ๋ณํ (0-180)
direction_deg = np.degrees(direction) % 180
return magnitude, direction_deg
# ์ฌ์ฉ ์
img = cv2.imread('image.jpg')
mag, dir = gradient_magnitude_direction(img)
# ์ ๊ทํํ์ฌ ํ์
mag_display = cv2.normalize(mag, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
dir_display = (dir / 180 * 255).astype(np.uint8)
cv2.imshow('Magnitude', mag_display)
cv2.imshow('Direction', dir_display)
cv2.waitKey(0)
๊ทธ๋๋์ธํธ ๋ฐฉํฅ ์๊ฐํ¶
import cv2
import numpy as np
import matplotlib.pyplot as plt
def visualize_gradient_direction(image, step=20):
"""
๊ทธ๋๋์ธํธ ๋ฐฉํฅ์ ํ์ดํ๋ก ์๊ฐํ
step: ์ํ๋ง ๊ฐ๊ฒฉ
"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (5, 5), 0)
gx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
gy = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
magnitude = np.sqrt(gx**2 + gy**2)
# ํ์ดํ ๊ทธ๋ฆฌ๊ธฐ
result = image.copy()
h, w = gray.shape
for y in range(step, h - step, step):
for x in range(step, w - step, step):
if magnitude[y, x] > 50: # ์ผ์ ํฌ๊ธฐ ์ด์๋ง ํ์
# ๋ฐฉํฅ ๋ฒกํฐ ์ ๊ทํ
dx = gx[y, x]
dy = gy[y, x]
length = np.sqrt(dx**2 + dy**2)
if length > 0:
dx = int(dx / length * 10)
dy = int(dy / length * 10)
cv2.arrowedLine(
result,
(x, y),
(x + dx, y + dy),
(0, 255, 0),
1,
tipLength=0.3
)
return result
# ์ฌ์ฉ ์
img = cv2.imread('image.jpg')
vis = visualize_gradient_direction(img, step=15)
cv2.imshow('Gradient Direction', vis)
cv2.waitKey(0)
์ฃ์ง ๊ฒ์ถ ์๊ณ ๋ฆฌ์ฆ ๋น๊ต¶
import cv2
import numpy as np
import matplotlib.pyplot as plt
def compare_edge_detectors(image_path):
"""๋ค์ํ ์ฃ์ง ๊ฒ์ถ ์๊ณ ๋ฆฌ์ฆ ๋น๊ต"""
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 1.4)
# 1. Sobel
sobel_x = cv2.Sobel(blurred, cv2.CV_64F, 1, 0, ksize=3)
sobel_y = cv2.Sobel(blurred, cv2.CV_64F, 0, 1, ksize=3)
sobel = np.sqrt(sobel_x**2 + sobel_y**2)
sobel = np.clip(sobel, 0, 255).astype(np.uint8)
# 2. Scharr
scharr_x = cv2.Scharr(blurred, cv2.CV_64F, 1, 0)
scharr_y = cv2.Scharr(blurred, cv2.CV_64F, 0, 1)
scharr = np.sqrt(scharr_x**2 + scharr_y**2)
scharr = np.clip(scharr, 0, 255).astype(np.uint8)
# 3. Laplacian
laplacian = cv2.Laplacian(blurred, cv2.CV_64F, ksize=3)
laplacian = cv2.convertScaleAbs(laplacian)
# 4. Canny
canny = cv2.Canny(blurred, 50, 150)
# ์๊ฐํ
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
axes[0, 0].imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
axes[0, 0].set_title('Original')
axes[0, 1].imshow(sobel, cmap='gray')
axes[0, 1].set_title('Sobel')
axes[0, 2].imshow(scharr, cmap='gray')
axes[0, 2].set_title('Scharr')
axes[1, 0].imshow(laplacian, cmap='gray')
axes[1, 0].set_title('Laplacian')
axes[1, 1].imshow(canny, cmap='gray')
axes[1, 1].set_title('Canny')
axes[1, 2].axis('off')
for ax in axes.flatten():
ax.axis('off')
plt.tight_layout()
plt.show()
# ๋น๊ต ์คํ
compare_edge_detectors('image.jpg')
7. ์ฐ์ต ๋ฌธ์ ¶
๋ฌธ์ 1: ์ ์ํ Canny ๊ตฌํ¶
์ด๋ฏธ์ง์ ๋ฐ๊ธฐ ๋ถํฌ์ ๋ฐ๋ผ ์๋์ผ๋ก ์๊ณ๊ฐ์ ์กฐ์ ํ๋ Canny ํจ์๋ฅผ ๊ตฌํํ์ธ์.
ํํธ
์ด๋ฏธ์ง์ ์ค๊ฐ๊ฐ(median)์ ๊ธฐ์ค์ผ๋ก ๋ฎ์ ์๊ณ๊ฐ๊ณผ ๋์ ์๊ณ๊ฐ์ ๊ณ์ฐํฉ๋๋ค.์ ๋ต ์ฝ๋
import cv2
import numpy as np
def adaptive_canny(image, sigma=0.33):
"""
์ ์ํ Canny ์ฃ์ง ๊ฒ์ถ
์ด๋ฏธ์ง ๋ฐ๊ธฐ์ ์ค๊ฐ๊ฐ์ ๊ธฐ์ค์ผ๋ก ์๊ณ๊ฐ ์๋ ์ค์
"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
# ์ค๊ฐ๊ฐ ๊ณ์ฐ
median = np.median(blurred)
# ์๊ณ๊ฐ ๊ณ์ฐ (sigma๋ก ๋ฒ์ ์กฐ์ )
low = int(max(0, (1.0 - sigma) * median))
high = int(min(255, (1.0 + sigma) * median))
edges = cv2.Canny(blurred, low, high)
return edges, low, high
# ํ
์คํธ
img = cv2.imread('image.jpg')
edges, low, high = adaptive_canny(img)
print(f"Adaptive thresholds: low={low}, high={high}")
cv2.imshow('Adaptive Canny', edges)
cv2.waitKey(0)
๋ฌธ์ 2: ๋ฐฉํฅ๋ณ ์ฃ์ง ๋ถ๋ฆฌ¶
์ํ ์ฃ์ง์ ์์ง ์ฃ์ง๋ฅผ ๋ถ๋ฆฌํ์ฌ ํ์ํ๋ ํจ์๋ฅผ ๊ตฌํํ์ธ์.
ํํธ
๊ทธ๋๋์ธํธ ๋ฐฉํฅ์ ๊ณ์ฐํ๊ณ , ๊ฐ๋์ ๋ฐ๋ผ ์ํ(0๋ ๊ทผ์ฒ)๊ณผ ์์ง(90๋ ๊ทผ์ฒ)์ ๋ถ๋ฅํฉ๋๋ค.์ ๋ต ์ฝ๋
import cv2
import numpy as np
def separate_edges_by_direction(image, angle_threshold=30):
"""
์ํ/์์ง ์ฃ์ง ๋ถ๋ฆฌ
angle_threshold: ํ์ฉ ๊ฐ๋ ๋ฒ์
"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
# Sobel ๊ทธ๋๋์ธํธ
gx = cv2.Sobel(blurred, cv2.CV_64F, 1, 0, ksize=3)
gy = cv2.Sobel(blurred, cv2.CV_64F, 0, 1, ksize=3)
# ํฌ๊ธฐ์ ๋ฐฉํฅ
magnitude = np.sqrt(gx**2 + gy**2)
direction = np.degrees(np.arctan2(gy, gx)) % 180
# ์๊ณ๊ฐ ์ ์ฉ
_, edges = cv2.threshold(magnitude.astype(np.uint8), 50, 255, cv2.THRESH_BINARY)
# ์ํ ์ฃ์ง (๋ฐฉํฅ์ด 0 ๋๋ 180๋ ๊ทผ์ฒ)
# Sobel gy๊ฐ ๊ฐํ๋ฉด ์ํ ์ฃ์ง
horizontal_mask = ((direction < angle_threshold) |
(direction > 180 - angle_threshold))
horizontal_edges = np.zeros_like(edges)
horizontal_edges[horizontal_mask & (edges > 0)] = 255
# ์์ง ์ฃ์ง (๋ฐฉํฅ์ด 90๋ ๊ทผ์ฒ)
vertical_mask = ((direction > 90 - angle_threshold) &
(direction < 90 + angle_threshold))
vertical_edges = np.zeros_like(edges)
vertical_edges[vertical_mask & (edges > 0)] = 255
return horizontal_edges, vertical_edges
# ํ
์คํธ
img = cv2.imread('image.jpg')
h_edges, v_edges = separate_edges_by_direction(img)
cv2.imshow('Horizontal Edges', h_edges)
cv2.imshow('Vertical Edges', v_edges)
cv2.waitKey(0)
๋ฌธ์ 3: ๋ค์ค ์ค์ผ์ผ ์ฃ์ง ๊ฒ์ถ¶
์ฌ๋ฌ ์ค์ผ์ผ์์ ์ฃ์ง๋ฅผ ๊ฒ์ถํ๊ณ ํฉ์ฑํ๋ ํจ์๋ฅผ ๊ตฌํํ์ธ์.
ํํธ
๋ค์ํ sigma ๊ฐ์ผ๋ก Gaussian blur๋ฅผ ์ ์ฉํ ํ Canny๋ฅผ ์ ์ฉํ๊ณ , ๊ฒฐ๊ณผ๋ฅผ ํฉ์ฑํฉ๋๋ค.์ ๋ต ์ฝ๋
import cv2
import numpy as np
def multi_scale_canny(image, scales=[1.0, 2.0, 4.0], low=50, high=150):
"""
๋ค์ค ์ค์ผ์ผ Canny ์ฃ์ง ๊ฒ์ถ
scales: Gaussian blur sigma ๊ฐ๋ค
"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
combined_edges = np.zeros(gray.shape, dtype=np.uint8)
for sigma in scales:
# ์ค์ผ์ผ์ ๋ฐ๋ฅธ ์ปค๋ ํฌ๊ธฐ
ksize = int(6 * sigma + 1)
if ksize % 2 == 0:
ksize += 1
# Gaussian blur ์ ์ฉ
blurred = cv2.GaussianBlur(gray, (ksize, ksize), sigma)
# Canny ์ฃ์ง ๊ฒ์ถ
edges = cv2.Canny(blurred, low, high)
# ํฉ์ฑ (OR ์ฐ์ฐ)
combined_edges = cv2.bitwise_or(combined_edges, edges)
return combined_edges
# ํ
์คํธ
img = cv2.imread('image.jpg')
edges = multi_scale_canny(img, scales=[1.0, 2.0, 3.0])
cv2.imshow('Multi-scale Canny', edges)
cv2.waitKey(0)
์ถ์ฒ ๋ฌธ์ ¶
| ๋์ด๋ | ์ฃผ์ | ์ค๋ช |
|---|---|---|
| โญ | ๊ธฐ๋ณธ Canny | ๋ค์ํ ์ด๋ฏธ์ง์ Canny ์ ์ฉ |
| โญโญ | ์๊ณ๊ฐ ์คํ | ํธ๋๋ฐ๋ก ์ต์ ์๊ณ๊ฐ ์ฐพ๊ธฐ |
| โญโญ | ์ ์ฒ๋ฆฌ ๋น๊ต | blur ์ข ๋ฅ์ ๋ฐ๋ฅธ ์ฃ์ง ํ์ง ๋น๊ต |
| โญโญโญ | ๋ฌธ์ ์ค์บ | ๋ฌธ์ ์ค๊ณฝ์ ๊ฒ์ถ |
| โญโญโญ | ๋์ ๊ฒ์ถ | ์ฃ์ง๋ก ๋์ ๊ฒฝ๊ณ ์ฐพ๊ธฐ |
๋ค์ ๋จ๊ณ¶
- 09_Contours.md - findContours, drawContours, ๊ณ์ธต ๊ตฌ์กฐ