์—ฃ์ง€ ๊ฒ€์ถœ (Edge Detection)

์—ฃ์ง€ ๊ฒ€์ถœ (Edge Detection)

๊ฐœ์š”

์—ฃ์ง€(Edge)๋Š” ์ด๋ฏธ์ง€์—์„œ ๋ฐ๊ธฐ๊ฐ€ ๊ธ‰๊ฒฉํ•˜๊ฒŒ ๋ณ€ํ•˜๋Š” ์˜์—ญ์œผ๋กœ, ๊ฐ์ฒด์˜ ๊ฒฝ๊ณ„๋‚˜ ๊ตฌ์กฐ๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. ์ด ๋ ˆ์Šจ์—์„œ๋Š” ์ด๋ฏธ์ง€ ๊ทธ๋ž˜๋””์–ธํŠธ ๊ฐœ๋…๊ณผ Sobel, Scharr, Laplacian, Canny ๋“ฑ ๋‹ค์–‘ํ•œ ์—ฃ์ง€ ๊ฒ€์ถœ ๊ธฐ๋ฒ•์„ ํ•™์Šตํ•ฉ๋‹ˆ๋‹ค.


๋ชฉ์ฐจ

  1. ์ด๋ฏธ์ง€ ๊ทธ๋ž˜๋””์–ธํŠธ ๊ฐœ๋…
  2. Sobel ์—ฐ์‚ฐ์ž
  3. Scharr ์—ฐ์‚ฐ์ž
  4. Laplacian ์—ฐ์‚ฐ์ž
  5. Canny ์—ฃ์ง€ ๊ฒ€์ถœ
  6. ๊ทธ๋ž˜๋””์–ธํŠธ ํฌ๊ธฐ์™€ ๋ฐฉํ–ฅ
  7. ์—ฐ์Šต ๋ฌธ์ œ

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, ๊ณ„์ธต ๊ตฌ์กฐ

์ฐธ๊ณ  ์ž๋ฃŒ

to navigate between lessons