ํ—ˆํ”„ ๋ณ€ํ™˜ (Hough Transform)

ํ—ˆํ”„ ๋ณ€ํ™˜ (Hough Transform)

๊ฐœ์š”

ํ—ˆํ”„ ๋ณ€ํ™˜์€ ์ด๋ฏธ์ง€์—์„œ ์ง์„ , ์› ๋“ฑ์˜ ๊ธฐํ•˜ํ•™์  ํ˜•ํƒœ๋ฅผ ๊ฒ€์ถœํ•˜๋Š” ์•Œ๊ณ ๋ฆฌ์ฆ˜์ž…๋‹ˆ๋‹ค. ์—ฃ์ง€ ๊ฒ€์ถœ ๊ฒฐ๊ณผ์—์„œ ํŠน์ • ๋ชจ์–‘์„ ์ฐพ๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋ฉฐ, ์ฐจ์„  ๊ฒ€์ถœ, ๋™์ „ ๊ฒ€์ถœ ๋“ฑ ๋‹ค์–‘ํ•œ ์‘์šฉ ๋ถ„์•ผ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.


๋ชฉ์ฐจ

  1. ํ—ˆํ”„ ๋ณ€ํ™˜ ๊ฐœ๋…
  2. ํ—ˆํ”„ ์ง์„  ๋ณ€ํ™˜
  3. ํ™•๋ฅ ์  ํ—ˆํ”„ ์ง์„  ๋ณ€ํ™˜
  4. ํ—ˆํ”„ ์› ๋ณ€ํ™˜
  5. ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ์ „๋žต
  6. ์ฐจ์„  ๊ฒ€์ถœ ๊ธฐ์ดˆ
  7. ์—ฐ์Šต ๋ฌธ์ œ

1. ํ—ˆํ”„ ๋ณ€ํ™˜ ๊ฐœ๋…

ํ—ˆํ”„ ๊ณต๊ฐ„ (Hough Space)

๊ธฐ๋ณธ ์•„์ด๋””์–ด:
์ด๋ฏธ์ง€ ๊ณต๊ฐ„์˜ ์  โ†’ ํ—ˆํ”„ ๊ณต๊ฐ„์˜ ๊ณก์„ 
์ด๋ฏธ์ง€ ๊ณต๊ฐ„์˜ ์ง์„  โ†’ ํ—ˆํ”„ ๊ณต๊ฐ„์˜ ์ 

์ด๋ฏธ์ง€ ๊ณต๊ฐ„ (x, y)              ํ—ˆํ”„ ๊ณต๊ฐ„ (ฯ, ฮธ)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”            โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                 โ”‚            โ”‚                 โ”‚
โ”‚    โ€ข            โ”‚            โ”‚      โ•ฑโ•ฒ         โ”‚
โ”‚      โ•ฒ          โ”‚    โ”€โ”€โ–ถ     โ”‚     โ•ฑ  โ•ฒ        โ”‚
โ”‚        โ•ฒ        โ”‚            โ”‚    โ•ฑ โ€ข  โ•ฒ       โ”‚
โ”‚          โ€ข      โ”‚            โ”‚   โ•ฑ      โ•ฒ      โ”‚
โ”‚                 โ”‚            โ”‚                 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜            โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
์ง์„  ์œ„์˜ ์ ๋“ค                   ์  ํ•˜๋‚˜๋กœ ํ‘œํ˜„

์ง์„ ์˜ ํ‘œํ˜„:
y = mx + b  (๊ธฐ์šธ๊ธฐ, y์ ˆํŽธ) โ†’ ์ˆ˜์ง์„  ํ‘œํ˜„ ๋ถˆ๊ฐ€
ฯ = xยทcos(ฮธ) + yยทsin(ฮธ)    โ†’ ๊ทน์ขŒํ‘œ ํ‘œํ˜„ (์„ ํ˜ธ)

ฯ: ์›์ ์—์„œ ์ง์„ ๊นŒ์ง€์˜ ์ˆ˜์ง ๊ฑฐ๋ฆฌ
ฮธ: ์ˆ˜์ง์„ ๊ณผ x์ถ•์ด ์ด๋ฃจ๋Š” ๊ฐ๋„

ํ—ˆํ”„ ๋ณ€ํ™˜ ๊ณผ์ •

1. ์—ฃ์ง€ ๊ฒ€์ถœ (Canny ๋“ฑ)
         โ”‚
         โ–ผ
2. ๊ฐ ์—ฃ์ง€ ์ ์— ๋Œ€ํ•ด ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ์ง์„  ๊ณ„์‚ฐ
   (ฮธ๋ฅผ 0ยฐ~180ยฐ ๋ณ€ํ™”์‹œํ‚ค๋ฉฐ ฯ ๊ณ„์‚ฐ)
         โ”‚
         โ–ผ
3. ๋ˆ„์  ๋ฐฐ์—ด(Accumulator)์— ํˆฌํ‘œ
         โ”‚
         โ–ผ
4. ์ž„๊ณ„๊ฐ’ ์ด์ƒ์˜ ํˆฌํ‘œ๋ฅผ ๋ฐ›์€ ์  = ์ง์„ 

๋ˆ„์  ๋ฐฐ์—ด ์‹œ๊ฐํ™”:
        ฮธ
      0ยฐ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถ 180ยฐ
    ฯ โ”‚  ยท  ยท  ยท  ยท  ยท  ยท  ยท  ยท
  -maxโ”‚  ยท  ยท  โ˜…  ยท  ยท  ยท  ยท  ยท   โ˜…: ๋งŽ์€ ํˆฌํ‘œ
      โ”‚  ยท  ยท  ยท  ยท  ยท  โ˜…  ยท  ยท      = ์ง์„  ์กด์žฌ
      โ”‚  ยท  ยท  ยท  ยท  ยท  ยท  ยท  ยท
   maxโ”‚  ยท  ยท  ยท  ยท  ยท  ยท  ยท  ยท
      โ–ผ

๊ฐ„๋‹จํ•œ ์˜ˆ์ œ

import cv2
import numpy as np

# ํ—ˆํ”„ ๋ณ€ํ™˜ ์‹œ๊ฐํ™”
def visualize_hough_space(image_path):
    """ํ—ˆํ”„ ๊ณต๊ฐ„ ์‹œ๊ฐํ™”"""
    img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
    edges = cv2.Canny(img, 50, 150)

    # ํ—ˆํ”„ ์ง์„  ๋ณ€ํ™˜ (๋ˆ„์  ๋ฐฐ์—ด ๋ฐ˜ํ™˜)
    lines = cv2.HoughLines(edges, 1, np.pi/180, 100)

    # ์‹œ๊ฐํ™”
    result = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)

    if lines is not None:
        for line in lines:
            rho, theta = line[0]
            a = np.cos(theta)
            b = np.sin(theta)
            x0 = a * rho
            y0 = b * rho

            # ์ง์„  ๊ทธ๋ฆฌ๊ธฐ (์–‘ ๋ฐฉํ–ฅ์œผ๋กœ ๊ธธ๊ฒŒ)
            x1 = int(x0 + 1000 * (-b))
            y1 = int(y0 + 1000 * (a))
            x2 = int(x0 - 1000 * (-b))
            y2 = int(y0 - 1000 * (a))

            cv2.line(result, (x1, y1), (x2, y2), (0, 0, 255), 2)

    cv2.imshow('Edges', edges)
    cv2.imshow('Hough Lines', result)
    cv2.waitKey(0)

visualize_hough_space('lines.jpg')

2. ํ—ˆํ”„ ์ง์„  ๋ณ€ํ™˜

cv2.HoughLines() ํ•จ์ˆ˜

lines = cv2.HoughLines(image, rho, theta, threshold)
ํŒŒ๋ผ๋ฏธํ„ฐ ์„ค๋ช…
image ์ž…๋ ฅ ์ด๋ฏธ์ง€ (8๋น„ํŠธ, ๋‹จ์ผ ์ฑ„๋„, ์ด์ง„ํ™”๋œ ์—ฃ์ง€ ์ด๋ฏธ์ง€)
rho ฯ ํ•ด์ƒ๋„ (ํ”ฝ์…€ ๋‹จ์œ„, ๋ณดํ†ต 1)
theta ฮธ ํ•ด์ƒ๋„ (๋ผ๋””์•ˆ ๋‹จ์œ„, ๋ณดํ†ต np.pi/180)
threshold ์ง์„ ์œผ๋กœ ์ธ์ •ํ•  ์ตœ์†Œ ํˆฌํ‘œ ์ˆ˜
lines ๊ฒ€์ถœ๋œ ์ง์„  [(ฯ, ฮธ), ...]

๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•

import cv2
import numpy as np

def hough_lines_example(image_path):
    """ํ‘œ์ค€ ํ—ˆํ”„ ์ง์„  ๊ฒ€์ถœ"""
    img = cv2.imread(image_path)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # ์—ฃ์ง€ ๊ฒ€์ถœ
    edges = cv2.Canny(gray, 50, 150, apertureSize=3)

    # ํ—ˆํ”„ ์ง์„  ๋ณ€ํ™˜
    lines = cv2.HoughLines(
        edges,
        rho=1,              # ฯ ํ•ด์ƒ๋„: 1 ํ”ฝ์…€
        theta=np.pi/180,    # ฮธ ํ•ด์ƒ๋„: 1๋„
        threshold=100       # ์ตœ์†Œ ํˆฌํ‘œ ์ˆ˜
    )

    result = img.copy()

    if lines is not None:
        print(f"๊ฒ€์ถœ๋œ ์ง์„  ์ˆ˜: {len(lines)}")

        for line in lines:
            rho, theta = line[0]

            # ๊ทน์ขŒํ‘œ โ†’ ์ง๊ต์ขŒํ‘œ ๋ณ€ํ™˜
            a = np.cos(theta)
            b = np.sin(theta)
            x0 = a * rho
            y0 = b * rho

            # ์ง์„  ๊ทธ๋ฆฌ๊ธฐ (๋ฌดํ•œ ์ง์„ )
            x1 = int(x0 + 1000 * (-b))
            y1 = int(y0 + 1000 * (a))
            x2 = int(x0 - 1000 * (-b))
            y2 = int(y0 - 1000 * (a))

            cv2.line(result, (x1, y1), (x2, y2), (0, 0, 255), 2)

    cv2.imshow('Original', img)
    cv2.imshow('Edges', edges)
    cv2.imshow('Hough Lines', result)
    cv2.waitKey(0)

hough_lines_example('building.jpg')

์ˆ˜ํ‰์„ /์ˆ˜์ง์„ ๋งŒ ๊ฒ€์ถœ

import cv2
import numpy as np

def detect_horizontal_vertical_lines(image_path):
    """์ˆ˜ํ‰์„ ๊ณผ ์ˆ˜์ง์„ ๋งŒ ๊ฒ€์ถœ"""
    img = cv2.imread(image_path)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    edges = cv2.Canny(gray, 50, 150)

    lines = cv2.HoughLines(edges, 1, np.pi/180, 100)

    result = img.copy()
    horizontal = []
    vertical = []

    if lines is not None:
        for line in lines:
            rho, theta = line[0]

            # ๊ฐ๋„๋กœ ๋ถ„๋ฅ˜ (ํ—ˆ์šฉ ์˜ค์ฐจ 5๋„)
            angle_deg = np.degrees(theta)

            if 85 < angle_deg < 95:  # ์ˆ˜์ง์„  (ฮธ โ‰ˆ 90ยฐ)
                vertical.append((rho, theta))
                color = (255, 0, 0)  # ํŒŒ๋ž‘
            elif angle_deg < 5 or angle_deg > 175:  # ์ˆ˜ํ‰์„  (ฮธ โ‰ˆ 0ยฐ ๋˜๋Š” 180ยฐ)
                horizontal.append((rho, theta))
                color = (0, 255, 0)  # ๋…น์ƒ‰
            else:
                continue

            # ์ง์„  ๊ทธ๋ฆฌ๊ธฐ
            a = np.cos(theta)
            b = np.sin(theta)
            x0, y0 = a * rho, b * rho
            x1, y1 = int(x0 + 1000 * (-b)), int(y0 + 1000 * (a))
            x2, y2 = int(x0 - 1000 * (-b)), int(y0 - 1000 * (a))
            cv2.line(result, (x1, y1), (x2, y2), color, 2)

    print(f"์ˆ˜ํ‰์„ : {len(horizontal)}๊ฐœ")
    print(f"์ˆ˜์ง์„ : {len(vertical)}๊ฐœ")

    cv2.imshow('H/V Lines', result)
    cv2.waitKey(0)

detect_horizontal_vertical_lines('grid.jpg')

3. ํ™•๋ฅ ์  ํ—ˆํ”„ ์ง์„  ๋ณ€ํ™˜

cv2.HoughLinesP() ํ•จ์ˆ˜

ํ‘œ์ค€ ํ—ˆํ”„ vs ํ™•๋ฅ ์  ํ—ˆํ”„:

ํ‘œ์ค€ ํ—ˆํ”„ (HoughLines):
- ๋ฌดํ•œ ์ง์„  ๋ฐ˜ํ™˜ (ฯ, ฮธ)
- ๋ชจ๋“  ์  ๊ฒ€์‚ฌ
- ๋А๋ฆผ, ์ •ํ™•

ํ™•๋ฅ ์  ํ—ˆํ”„ (HoughLinesP):
- ์„ ๋ถ„ ๋ฐ˜ํ™˜ (x1, y1, x2, y2)
- ๋ฌด์ž‘์œ„ ์  ์ƒ˜ํ”Œ๋ง
- ๋น ๋ฆ„, ์‹ค์šฉ์ 
lines = cv2.HoughLinesP(image, rho, theta, threshold, minLineLength, maxLineGap)
ํŒŒ๋ผ๋ฏธํ„ฐ ์„ค๋ช…
image ์ž…๋ ฅ ์—ฃ์ง€ ์ด๋ฏธ์ง€
rho ฯ ํ•ด์ƒ๋„
theta ฮธ ํ•ด์ƒ๋„
threshold ์ตœ์†Œ ํˆฌํ‘œ ์ˆ˜
minLineLength ์ตœ์†Œ ์„ ๋ถ„ ๊ธธ์ด
maxLineGap ์„ ๋ถ„ ์‚ฌ์ด ์ตœ๋Œ€ ํ—ˆ์šฉ ๊ฐ„๊ฒฉ
lines ๊ฒ€์ถœ๋œ ์„ ๋ถ„ [(x1, y1, x2, y2), ...]

๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•

import cv2
import numpy as np

def hough_lines_p_example(image_path):
    """ํ™•๋ฅ ์  ํ—ˆํ”„ ์ง์„  ๊ฒ€์ถœ"""
    img = cv2.imread(image_path)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    edges = cv2.Canny(gray, 50, 150)

    # ํ™•๋ฅ ์  ํ—ˆํ”„ ๋ณ€ํ™˜
    lines = cv2.HoughLinesP(
        edges,
        rho=1,
        theta=np.pi/180,
        threshold=50,
        minLineLength=50,    # ์ตœ์†Œ 50ํ”ฝ์…€ ์ด์ƒ
        maxLineGap=10        # 10ํ”ฝ์…€ ์ด๋‚ด ๊ฐ„๊ฒฉ์€ ์—ฐ๊ฒฐ
    )

    result = img.copy()

    if lines is not None:
        print(f"๊ฒ€์ถœ๋œ ์„ ๋ถ„ ์ˆ˜: {len(lines)}")

        for line in lines:
            x1, y1, x2, y2 = line[0]
            cv2.line(result, (x1, y1), (x2, y2), (0, 255, 0), 2)

            # ์„ ๋ถ„ ๋์  ํ‘œ์‹œ
            cv2.circle(result, (x1, y1), 5, (255, 0, 0), -1)
            cv2.circle(result, (x2, y2), 5, (0, 0, 255), -1)

    cv2.imshow('HoughLinesP', result)
    cv2.waitKey(0)

hough_lines_p_example('document.jpg')

์„ ๋ถ„ ํ•„ํ„ฐ๋ง

import cv2
import numpy as np

def filter_lines(image_path, angle_threshold=30):
    """๊ฐ๋„์™€ ๊ธธ์ด๋กœ ์„ ๋ถ„ ํ•„ํ„ฐ๋ง"""
    img = cv2.imread(image_path)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    edges = cv2.Canny(gray, 50, 150)

    lines = cv2.HoughLinesP(edges, 1, np.pi/180, 50, minLineLength=30, maxLineGap=10)

    result = img.copy()

    if lines is None:
        return result

    for line in lines:
        x1, y1, x2, y2 = line[0]

        # ์„ ๋ถ„ ๊ธธ์ด ๊ณ„์‚ฐ
        length = np.sqrt((x2 - x1)**2 + (y2 - y1)**2)

        # ๊ฐ๋„ ๊ณ„์‚ฐ (์ˆ˜ํ‰ ๊ธฐ์ค€)
        if x2 - x1 != 0:
            angle = np.degrees(np.arctan(abs(y2 - y1) / abs(x2 - x1)))
        else:
            angle = 90

        # ํ•„ํ„ฐ๋ง: ํŠน์ • ๊ฐ๋„ ์ดํ•˜๋งŒ
        if angle < angle_threshold:
            color = (0, 255, 0)  # ๊ฑฐ์˜ ์ˆ˜ํ‰
        elif angle > 90 - angle_threshold:
            color = (255, 0, 0)  # ๊ฑฐ์˜ ์ˆ˜์ง
        else:
            continue  # ๋Œ€๊ฐ์„ ์€ ๋ฌด์‹œ

        cv2.line(result, (x1, y1), (x2, y2), color, 2)

    cv2.imshow('Filtered Lines', result)
    cv2.waitKey(0)

    return result

filter_lines('building.jpg', angle_threshold=20)

์„ ๋ถ„ ๋ณ‘ํ•ฉ

import cv2
import numpy as np
from collections import defaultdict

def merge_lines(lines, angle_threshold=10, distance_threshold=20):
    """์œ ์‚ฌํ•œ ์„ ๋ถ„๋“ค ๋ณ‘ํ•ฉ"""
    if lines is None or len(lines) == 0:
        return []

    # ์„ ๋ถ„์„ ๊ฐ๋„๋ณ„๋กœ ๊ทธ๋ฃนํ™”
    groups = defaultdict(list)

    for line in lines:
        x1, y1, x2, y2 = line[0]

        # ๊ฐ๋„ ๊ณ„์‚ฐ
        angle = np.degrees(np.arctan2(y2 - y1, x2 - x1)) % 180

        # ๊ฐ๋„ ๊ทธ๋ฃน (angle_threshold ๋‹จ์œ„๋กœ ์–‘์žํ™”)
        angle_group = round(angle / angle_threshold) * angle_threshold
        groups[angle_group].append(line[0])

    merged = []

    for angle, group_lines in groups.items():
        if len(group_lines) == 1:
            merged.append(group_lines[0])
            continue

        # ๊ฐ™์€ ๊ทธ๋ฃน ๋‚ด์—์„œ ๊ฐ€๊นŒ์šด ์„ ๋ถ„๋“ค ๋ณ‘ํ•ฉ
        # ๊ฐ„๋‹จํžˆ: ์ „์ฒด ์ ๋“ค์˜ ์ตœ์†Œ/์ตœ๋Œ€ ์ขŒํ‘œ๋กœ ํ•˜๋‚˜์˜ ์„ ๋ถ„ ์ƒ์„ฑ
        all_points = []
        for x1, y1, x2, y2 in group_lines:
            all_points.extend([(x1, y1), (x2, y2)])

        all_points = np.array(all_points)

        # ์ฃผ ๋ฐฉํ–ฅ์œผ๋กœ ์ •๋ ฌํ•˜์—ฌ ์–‘ ๋์  ์„ ํƒ
        if abs(np.cos(np.radians(angle))) > 0.5:
            # ์ˆ˜ํ‰์— ๊ฐ€๊นŒ์›€: x๋กœ ์ •๋ ฌ
            sorted_pts = sorted(all_points, key=lambda p: p[0])
        else:
            # ์ˆ˜์ง์— ๊ฐ€๊นŒ์›€: y๋กœ ์ •๋ ฌ
            sorted_pts = sorted(all_points, key=lambda p: p[1])

        start = sorted_pts[0]
        end = sorted_pts[-1]
        merged.append([start[0], start[1], end[0], end[1]])

    return merged

4. ํ—ˆํ”„ ์› ๋ณ€ํ™˜

cv2.HoughCircles() ํ•จ์ˆ˜

ํ—ˆํ”„ ์› ๋ณ€ํ™˜:
์ด๋ฏธ์ง€์—์„œ ์› ๊ฒ€์ถœ

์›์˜ ๋ฐฉ์ •์‹: (x - a)ยฒ + (y - b)ยฒ = rยฒ
ํŒŒ๋ผ๋ฏธํ„ฐ: ์ค‘์‹ฌ (a, b), ๋ฐ˜์ง€๋ฆ„ r

3์ฐจ์› ๋ˆ„์  ๋ฐฐ์—ด ํ•„์š” โ†’ ๋น„ํšจ์œจ์ 
โ†’ ๊ทธ๋ž˜๋””์–ธํŠธ ๊ธฐ๋ฐ˜ ๋ฐฉ๋ฒ• ์‚ฌ์šฉ (cv2.HOUGH_GRADIENT)

cv2.HOUGH_GRADIENT ๋™์ž‘:
1. ์—ฃ์ง€ ๊ฒ€์ถœ
2. ๊ฐ ์—ฃ์ง€ ์ ์—์„œ ๊ทธ๋ž˜๋””์–ธํŠธ ๋ฐฉํ–ฅ์œผ๋กœ ํˆฌํ‘œ
3. ์ค‘์‹ฌ ํ›„๋ณด ์„ ์ •
4. ๋ฐ˜์ง€๋ฆ„ ์ถ”์ •
circles = cv2.HoughCircles(image, method, dp, minDist, param1, param2, minRadius, maxRadius)
ํŒŒ๋ผ๋ฏธํ„ฐ ์„ค๋ช…
image ์ž…๋ ฅ ๊ทธ๋ ˆ์ด์Šค์ผ€์ผ ์ด๋ฏธ์ง€
method ๊ฒ€์ถœ ๋ฐฉ๋ฒ• (cv2.HOUGH_GRADIENT ๋˜๋Š” cv2.HOUGH_GRADIENT_ALT)
dp ๋ˆ„์  ๋ฐฐ์—ด ํ•ด์ƒ๋„ ๋น„์œจ (1 = ์›๋ณธ๊ณผ ๋™์ผ)
minDist ๊ฒ€์ถœ๋œ ์› ์ค‘์‹ฌ ๊ฐ„ ์ตœ์†Œ ๊ฑฐ๋ฆฌ
param1 Canny ์—ฃ์ง€์˜ ์ƒ์œ„ ์ž„๊ณ„๊ฐ’
param2 ์› ๊ฒ€์ถœ ์ž„๊ณ„๊ฐ’ (๋‚ฎ์„์ˆ˜๋ก ๋งŽ์ด ๊ฒ€์ถœ)
minRadius ์ตœ์†Œ ๋ฐ˜์ง€๋ฆ„ (0 = ๋ฌด์ œํ•œ)
maxRadius ์ตœ๋Œ€ ๋ฐ˜์ง€๋ฆ„ (0 = ๋ฌด์ œํ•œ)

๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•

import cv2
import numpy as np

def hough_circles_example(image_path):
    """ํ—ˆํ”„ ์› ๊ฒ€์ถœ"""
    img = cv2.imread(image_path)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # ๋…ธ์ด์ฆˆ ์ œ๊ฑฐ (์› ๊ฒ€์ถœ์— ์ค‘์š”)
    blurred = cv2.GaussianBlur(gray, (9, 9), 2)

    # ํ—ˆํ”„ ์› ๋ณ€ํ™˜
    circles = cv2.HoughCircles(
        blurred,
        cv2.HOUGH_GRADIENT,
        dp=1,              # ์›๋ณธ ํ•ด์ƒ๋„
        minDist=50,        # ์› ์ค‘์‹ฌ ๊ฐ„ ์ตœ์†Œ ๊ฑฐ๋ฆฌ
        param1=100,        # Canny ์ƒ์œ„ ์ž„๊ณ„๊ฐ’
        param2=30,         # ์› ๊ฒ€์ถœ ์ž„๊ณ„๊ฐ’
        minRadius=10,      # ์ตœ์†Œ ๋ฐ˜์ง€๋ฆ„
        maxRadius=100      # ์ตœ๋Œ€ ๋ฐ˜์ง€๋ฆ„
    )

    result = img.copy()

    if circles is not None:
        circles = np.uint16(np.around(circles))

        for circle in circles[0, :]:
            cx, cy, r = circle

            # ์› ๊ทธ๋ฆฌ๊ธฐ
            cv2.circle(result, (cx, cy), r, (0, 255, 0), 2)

            # ์ค‘์‹ฌ์ 
            cv2.circle(result, (cx, cy), 3, (0, 0, 255), -1)

            print(f"์›: ์ค‘์‹ฌ({cx}, {cy}), ๋ฐ˜์ง€๋ฆ„={r}")

        print(f"๊ฒ€์ถœ๋œ ์› ์ˆ˜: {len(circles[0])}")

    cv2.imshow('Circles', result)
    cv2.waitKey(0)

hough_circles_example('coins.jpg')

๋™์ „ ๊ฒ€์ถœ

import cv2
import numpy as np

def detect_coins(image_path):
    """๋™์ „ ๊ฒ€์ถœ ๋ฐ ๋ถ„๋ฅ˜"""
    img = cv2.imread(image_path)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    blurred = cv2.GaussianBlur(gray, (11, 11), 0)

    # ํ—ˆํ”„ ์› ๋ณ€ํ™˜
    circles = cv2.HoughCircles(
        blurred,
        cv2.HOUGH_GRADIENT,
        dp=1.2,
        minDist=80,
        param1=100,
        param2=35,
        minRadius=30,
        maxRadius=80
    )

    result = img.copy()
    coin_count = 0
    total_value = 0

    if circles is not None:
        circles = np.uint16(np.around(circles))

        for circle in circles[0, :]:
            cx, cy, r = circle
            coin_count += 1

            # ํฌ๊ธฐ๋กœ ๋™์ „ ์ข…๋ฅ˜ ์ถ”์ • (์˜ˆ์‹œ)
            if r < 40:
                value = 10
                color = (255, 0, 0)    # ํŒŒ๋ž‘
            elif r < 55:
                value = 50
                color = (0, 255, 0)    # ๋…น์ƒ‰
            else:
                value = 100
                color = (0, 0, 255)    # ๋นจ๊ฐ•

            total_value += value

            # ๊ทธ๋ฆฌ๊ธฐ
            cv2.circle(result, (cx, cy), r, color, 2)
            cv2.circle(result, (cx, cy), 3, (0, 0, 0), -1)
            cv2.putText(result, f'{value}', (cx - 15, cy + 5),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)

    print(f"๋™์ „ ๊ฐœ์ˆ˜: {coin_count}")
    print(f"์ด์•ก: {total_value}์›")

    cv2.imshow('Coins', result)
    cv2.waitKey(0)

    return coin_count, total_value

detect_coins('coins.jpg')

HOUGH_GRADIENT_ALT (OpenCV 4.3+)

import cv2
import numpy as np

def hough_circles_alt(image_path):
    """HOUGH_GRADIENT_ALT ์‚ฌ์šฉ (๋” ์ •ํ™•)"""
    img = cv2.imread(image_path)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    blurred = cv2.GaussianBlur(gray, (9, 9), 2)

    # HOUGH_GRADIENT_ALT: ๋” ์ •ํ™•ํ•˜์ง€๋งŒ ๋А๋ฆผ
    circles = cv2.HoughCircles(
        blurred,
        cv2.HOUGH_GRADIENT_ALT,  # ๋Œ€์ฒด ์•Œ๊ณ ๋ฆฌ์ฆ˜
        dp=1.5,
        minDist=50,
        param1=300,    # ์—ฃ์ง€ ๊ทธ๋ž˜๋””์–ธํŠธ ์ž„๊ณ„๊ฐ’
        param2=0.9,    # ์›ํ˜•๋„ ์ž„๊ณ„๊ฐ’ (0-1, ๋†’์„์ˆ˜๋ก ์—„๊ฒฉ)
        minRadius=20,
        maxRadius=100
    )

    result = img.copy()

    if circles is not None:
        circles = np.uint16(np.around(circles))
        for cx, cy, r in circles[0, :]:
            cv2.circle(result, (cx, cy), r, (0, 255, 0), 2)
            cv2.circle(result, (cx, cy), 3, (0, 0, 255), -1)

    cv2.imshow('HOUGH_GRADIENT_ALT', result)
    cv2.waitKey(0)

hough_circles_alt('circles.jpg')

5. ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ์ „๋žต

์ง์„  ๊ฒ€์ถœ ํŒŒ๋ผ๋ฏธํ„ฐ

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    HoughLines ํŒŒ๋ผ๋ฏธํ„ฐ                          โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ rho (ฯ ํ•ด์ƒ๋„)                                                  โ”‚
โ”‚ - ์ž‘์„์ˆ˜๋ก: ๋” ์ •๋ฐ€, ๋” ๋งŽ์€ ๋ฉ”๋ชจ๋ฆฌ, ๋” ๋А๋ฆผ                     โ”‚
โ”‚ - ๊ถŒ์žฅ: 1 (1ํ”ฝ์…€)                                               โ”‚
โ”‚                                                                โ”‚
โ”‚ theta (ฮธ ํ•ด์ƒ๋„)                                                โ”‚
โ”‚ - ์ž‘์„์ˆ˜๋ก: ๋” ์ •๋ฐ€ํ•œ ๊ฐ๋„                                       โ”‚
โ”‚ - ๊ถŒ์žฅ: np.pi/180 (1๋„)                                         โ”‚
โ”‚                                                                โ”‚
โ”‚ threshold (์ตœ์†Œ ํˆฌํ‘œ ์ˆ˜)                                        โ”‚
โ”‚ - ๋†’์„์ˆ˜๋ก: ๋” ๊ฐ•ํ•œ(๊ธด) ์ง์„ ๋งŒ ๊ฒ€์ถœ                             โ”‚
โ”‚ - ๋‚ฎ์„์ˆ˜๋ก: ์•ฝํ•œ(์งง์€) ์ง์„ ๋„ ๊ฒ€์ถœ, ๋…ธ์ด์ฆˆ ์ฆ๊ฐ€                 โ”‚
โ”‚ - ํŠœ๋‹ ๋ฐฉ๋ฒ•: ์ด๋ฏธ์ง€ ํฌ๊ธฐ์™€ ์˜ˆ์ƒ ์ง์„  ๊ธธ์ด์— ๋”ฐ๋ผ ์กฐ์ •           โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                   HoughLinesP ํŒŒ๋ผ๋ฏธํ„ฐ                          โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ minLineLength (์ตœ์†Œ ์„ ๋ถ„ ๊ธธ์ด)                                  โ”‚
โ”‚ - ๋†’์„์ˆ˜๋ก: ๊ธด ์„ ๋ถ„๋งŒ ๊ฒ€์ถœ                                      โ”‚
โ”‚ - ๋…ธ์ด์ฆˆ ๊ฐ์†Œ์— ํšจ๊ณผ์                                           โ”‚
โ”‚                                                                โ”‚
โ”‚ maxLineGap (์ตœ๋Œ€ ๊ฐ„๊ฒฉ)                                         โ”‚
โ”‚ - ๋†’์„์ˆ˜๋ก: ๋Š์–ด์ง„ ์„ ๋ถ„๋„ ํ•˜๋‚˜๋กœ ์—ฐ๊ฒฐ                           โ”‚
โ”‚ - ์ ์„  ๊ฒ€์ถœ ์‹œ ์œ ์šฉ                                             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

์› ๊ฒ€์ถœ ํŒŒ๋ผ๋ฏธํ„ฐ

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                   HoughCircles ํŒŒ๋ผ๋ฏธํ„ฐ                         โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ dp (ํ•ด์ƒ๋„ ๋น„์œจ)                                                โ”‚
โ”‚ - 1: ์›๋ณธ ํ•ด์ƒ๋„ โ†’ ์ •ํ™•ํ•˜์ง€๋งŒ ๋А๋ฆผ                              โ”‚
โ”‚ - 2: 1/2 ํ•ด์ƒ๋„ โ†’ ๋น ๋ฅด์ง€๋งŒ ๋œ ์ •ํ™•                              โ”‚
โ”‚ - ๊ถŒ์žฅ: 1 ~ 1.5                                                 โ”‚
โ”‚                                                                โ”‚
โ”‚ minDist (์ตœ์†Œ ์ค‘์‹ฌ ๊ฑฐ๋ฆฌ)                                        โ”‚
โ”‚ - ๋„ˆ๋ฌด ์ž‘์œผ๋ฉด: ๊ฐ™์€ ์›์„ ์—ฌ๋Ÿฌ ๋ฒˆ ๊ฒ€์ถœ                           โ”‚
โ”‚ - ๋„ˆ๋ฌด ํฌ๋ฉด: ๊ฐ€๊นŒ์šด ์› ๋†“์นจ                                     โ”‚
โ”‚ - ๊ถŒ์žฅ: ์˜ˆ์ƒ ์› ๋ฐ˜์ง€๋ฆ„ * 2 ์ด์ƒ                                 โ”‚
โ”‚                                                                โ”‚
โ”‚ param1 (Canny ์ƒ์œ„ ์ž„๊ณ„๊ฐ’)                                      โ”‚
โ”‚ - ๋†’์„์ˆ˜๋ก: ๊ฐ•ํ•œ ์—ฃ์ง€๋งŒ ์‚ฌ์šฉ                                    โ”‚
โ”‚ - ๊ถŒ์žฅ: 100 ~ 200                                               โ”‚
โ”‚                                                                โ”‚
โ”‚ param2 (๋ˆ„์  ์ž„๊ณ„๊ฐ’)                                            โ”‚
โ”‚ - ๋†’์„์ˆ˜๋ก: ํ™•์‹คํ•œ ์›๋งŒ ๊ฒ€์ถœ                                    โ”‚
โ”‚ - ๋‚ฎ์„์ˆ˜๋ก: ๋ถˆ์™„์ „ํ•œ ์›๋„ ๊ฒ€์ถœ                                  โ”‚
โ”‚ - ๊ถŒ์žฅ: 20 ~ 50                                                 โ”‚
โ”‚                                                                โ”‚
โ”‚ minRadius, maxRadius                                            โ”‚
โ”‚ - ์˜ˆ์ƒ ์› ํฌ๊ธฐ ๋ฒ”์œ„ ์ง€์ •                                        โ”‚
โ”‚ - ์ž˜๋ชป ์„ค์ •ํ•˜๋ฉด ๊ฒ€์ถœ ์‹คํŒจ                                       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

ํŠธ๋ž™๋ฐ”๋กœ ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹

import cv2
import numpy as np

def tune_hough_circles(image_path):
    """ํŠธ๋ž™๋ฐ”๋กœ HoughCircles ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹"""
    img = cv2.imread(image_path)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    blurred = cv2.GaussianBlur(gray, (9, 9), 2)

    cv2.namedWindow('Circles')

    def nothing(x):
        pass

    cv2.createTrackbar('minDist', 'Circles', 50, 200, nothing)
    cv2.createTrackbar('param1', 'Circles', 100, 300, nothing)
    cv2.createTrackbar('param2', 'Circles', 30, 100, nothing)
    cv2.createTrackbar('minRadius', 'Circles', 10, 100, nothing)
    cv2.createTrackbar('maxRadius', 'Circles', 100, 200, nothing)

    while True:
        minDist = cv2.getTrackbarPos('minDist', 'Circles')
        param1 = cv2.getTrackbarPos('param1', 'Circles')
        param2 = cv2.getTrackbarPos('param2', 'Circles')
        minRadius = cv2.getTrackbarPos('minRadius', 'Circles')
        maxRadius = cv2.getTrackbarPos('maxRadius', 'Circles')

        # ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
        if minDist < 1:
            minDist = 1
        if param2 < 1:
            param2 = 1

        circles = cv2.HoughCircles(
            blurred,
            cv2.HOUGH_GRADIENT,
            dp=1,
            minDist=minDist,
            param1=param1,
            param2=param2,
            minRadius=minRadius,
            maxRadius=maxRadius
        )

        result = img.copy()

        if circles is not None:
            circles = np.uint16(np.around(circles))
            for cx, cy, r in circles[0, :]:
                cv2.circle(result, (cx, cy), r, (0, 255, 0), 2)
                cv2.circle(result, (cx, cy), 3, (0, 0, 255), -1)

            # ๊ฒ€์ถœ๋œ ์› ์ˆ˜ ํ‘œ์‹œ
            cv2.putText(result, f'Circles: {len(circles[0])}', (10, 30),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 0, 0), 2)

        cv2.imshow('Circles', result)

        if cv2.waitKey(1) & 0xFF == 27:
            break

    cv2.destroyAllWindows()

tune_hough_circles('coins.jpg')

6. ์ฐจ์„  ๊ฒ€์ถœ ๊ธฐ์ดˆ

์ฐจ์„  ๊ฒ€์ถœ ํŒŒ์ดํ”„๋ผ์ธ

1. ๊ด€์‹ฌ ์˜์—ญ(ROI) ์„ค์ •
         โ”‚
         โ–ผ
2. ๊ทธ๋ ˆ์ด์Šค์ผ€์ผ ๋ณ€ํ™˜
         โ”‚
         โ–ผ
3. ๊ฐ€์šฐ์‹œ์•ˆ ๋ธ”๋Ÿฌ
         โ”‚
         โ–ผ
4. Canny ์—ฃ์ง€ ๊ฒ€์ถœ
         โ”‚
         โ–ผ
5. ๊ด€์‹ฌ ์˜์—ญ ๋งˆ์Šคํ‚น
         โ”‚
         โ–ผ
6. ํ—ˆํ”„ ์ง์„  ๋ณ€ํ™˜
         โ”‚
         โ–ผ
7. ์„ ๋ถ„ ํ•„ํ„ฐ๋ง ๋ฐ ํ‰๊ท ํ™”
         โ”‚
         โ–ผ
8. ๊ฒฐ๊ณผ ํ•ฉ์„ฑ

๊ธฐ๋ณธ ์ฐจ์„  ๊ฒ€์ถœ

import cv2
import numpy as np

def detect_lane_lines(image):
    """๊ธฐ๋ณธ ์ฐจ์„  ๊ฒ€์ถœ"""
    height, width = image.shape[:2]

    # ๊ทธ๋ ˆ์ด์Šค์ผ€์ผ
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    # ๊ฐ€์šฐ์‹œ์•ˆ ๋ธ”๋Ÿฌ
    blurred = cv2.GaussianBlur(gray, (5, 5), 0)

    # Canny ์—ฃ์ง€
    edges = cv2.Canny(blurred, 50, 150)

    # ๊ด€์‹ฌ ์˜์—ญ (์‚ฌ๋‹ค๋ฆฌ๊ผด)
    mask = np.zeros_like(edges)
    vertices = np.array([[
        (0, height),
        (width * 0.45, height * 0.6),
        (width * 0.55, height * 0.6),
        (width, height)
    ]], dtype=np.int32)
    cv2.fillPoly(mask, vertices, 255)
    masked_edges = cv2.bitwise_and(edges, mask)

    # ํ—ˆํ”„ ์ง์„  ๋ณ€ํ™˜
    lines = cv2.HoughLinesP(
        masked_edges,
        rho=1,
        theta=np.pi/180,
        threshold=50,
        minLineLength=50,
        maxLineGap=150
    )

    # ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€
    line_image = np.zeros_like(image)

    if lines is not None:
        for line in lines:
            x1, y1, x2, y2 = line[0]
            cv2.line(line_image, (x1, y1), (x2, y2), (0, 255, 0), 3)

    # ์›๋ณธ๊ณผ ํ•ฉ์„ฑ
    result = cv2.addWeighted(image, 0.8, line_image, 1, 0)

    return result

# ์‚ฌ์šฉ ์˜ˆ
img = cv2.imread('road.jpg')
result = detect_lane_lines(img)
cv2.imshow('Lane Detection', result)
cv2.waitKey(0)

์ขŒ/์šฐ ์ฐจ์„  ๋ถ„๋ฆฌ

import cv2
import numpy as np

def separate_lanes(image):
    """์ขŒ/์šฐ ์ฐจ์„  ๋ถ„๋ฆฌ ๊ฒ€์ถœ"""
    height, width = image.shape[:2]

    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    blurred = cv2.GaussianBlur(gray, (5, 5), 0)
    edges = cv2.Canny(blurred, 50, 150)

    # ROI ๋งˆ์Šคํฌ
    mask = np.zeros_like(edges)
    vertices = np.array([[
        (50, height),
        (width * 0.45, height * 0.6),
        (width * 0.55, height * 0.6),
        (width - 50, height)
    ]], dtype=np.int32)
    cv2.fillPoly(mask, vertices, 255)
    masked = cv2.bitwise_and(edges, mask)

    lines = cv2.HoughLinesP(masked, 1, np.pi/180, 30,
                             minLineLength=30, maxLineGap=100)

    left_lines = []
    right_lines = []

    if lines is not None:
        for line in lines:
            x1, y1, x2, y2 = line[0]

            # ๊ธฐ์šธ๊ธฐ ๊ณ„์‚ฐ
            if x2 - x1 == 0:
                continue
            slope = (y2 - y1) / (x2 - x1)

            # ๊ธฐ์šธ๊ธฐ๋กœ ์ขŒ/์šฐ ๋ถ„๋ฅ˜
            # ์ด๋ฏธ์ง€ ์ขŒํ‘œ๊ณ„: y์ถ•์ด ์•„๋ž˜๋กœ ์ฆ๊ฐ€
            # ์™ผ์ชฝ ์ฐจ์„ : ์Œ์˜ ๊ธฐ์šธ๊ธฐ (/)
            # ์˜ค๋ฅธ์ชฝ ์ฐจ์„ : ์–‘์˜ ๊ธฐ์šธ๊ธฐ (\)
            if slope < -0.5:
                left_lines.append(line[0])
            elif slope > 0.5:
                right_lines.append(line[0])

    result = image.copy()

    # ์ขŒ/์šฐ ์ฐจ์„  ๊ทธ๋ฆฌ๊ธฐ
    for x1, y1, x2, y2 in left_lines:
        cv2.line(result, (x1, y1), (x2, y2), (255, 0, 0), 3)  # ํŒŒ๋ž‘

    for x1, y1, x2, y2 in right_lines:
        cv2.line(result, (x1, y1), (x2, y2), (0, 0, 255), 3)  # ๋นจ๊ฐ•

    return result, left_lines, right_lines

# ์‚ฌ์šฉ ์˜ˆ
img = cv2.imread('road.jpg')
result, left, right = separate_lanes(img)
print(f"์™ผ์ชฝ ์ฐจ์„ : {len(left)}๊ฐœ")
print(f"์˜ค๋ฅธ์ชฝ ์ฐจ์„ : {len(right)}๊ฐœ")
cv2.imshow('Lanes', result)
cv2.waitKey(0)

์ฐจ์„  ํ‰๊ท ํ™”

import cv2
import numpy as np

def average_lane_lines(lines, height):
    """์„ ๋ถ„๋“ค์„ ํ‰๊ท ํ•˜์—ฌ ํ•˜๋‚˜์˜ ์ง์„ ์œผ๋กœ"""
    if len(lines) == 0:
        return None

    # ๋ชจ๋“  ์  ์ˆ˜์ง‘
    x_coords = []
    y_coords = []

    for x1, y1, x2, y2 in lines:
        x_coords.extend([x1, x2])
        y_coords.extend([y1, y2])

    # 1์ฐจ ๋‹คํ•ญ์‹ ํ”ผํŒ… (์ง์„ )
    poly = np.polyfit(y_coords, x_coords, 1)

    # ์ง์„ ์˜ ์‹œ์ž‘์ ๊ณผ ๋์  ๊ณ„์‚ฐ
    y1 = height
    y2 = int(height * 0.6)
    x1 = int(np.polyval(poly, y1))
    x2 = int(np.polyval(poly, y2))

    return (x1, y1, x2, y2)

def detect_lanes_averaged(image):
    """ํ‰๊ท ํ™”๋œ ์ฐจ์„  ๊ฒ€์ถœ"""
    height, width = image.shape[:2]

    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    blurred = cv2.GaussianBlur(gray, (5, 5), 0)
    edges = cv2.Canny(blurred, 50, 150)

    # ROI
    mask = np.zeros_like(edges)
    vertices = np.array([[
        (50, height),
        (width * 0.45, height * 0.6),
        (width * 0.55, height * 0.6),
        (width - 50, height)
    ]], dtype=np.int32)
    cv2.fillPoly(mask, vertices, 255)
    masked = cv2.bitwise_and(edges, mask)

    lines = cv2.HoughLinesP(masked, 1, np.pi/180, 30,
                             minLineLength=30, maxLineGap=100)

    left_lines = []
    right_lines = []

    if lines is not None:
        for line in lines:
            x1, y1, x2, y2 = line[0]
            if x2 - x1 == 0:
                continue
            slope = (y2 - y1) / (x2 - x1)

            if slope < -0.5:
                left_lines.append(line[0])
            elif slope > 0.5:
                right_lines.append(line[0])

    result = image.copy()

    # ํ‰๊ท ํ™”๋œ ์ฐจ์„  ๊ทธ๋ฆฌ๊ธฐ
    left_avg = average_lane_lines(left_lines, height)
    right_avg = average_lane_lines(right_lines, height)

    if left_avg is not None:
        cv2.line(result, (left_avg[0], left_avg[1]),
                 (left_avg[2], left_avg[3]), (255, 0, 0), 5)

    if right_avg is not None:
        cv2.line(result, (right_avg[0], right_avg[1]),
                 (right_avg[2], right_avg[3]), (0, 0, 255), 5)

    # ์ฐจ์„  ์˜์—ญ ์ฑ„์šฐ๊ธฐ
    if left_avg is not None and right_avg is not None:
        pts = np.array([
            [left_avg[0], left_avg[1]],
            [left_avg[2], left_avg[3]],
            [right_avg[2], right_avg[3]],
            [right_avg[0], right_avg[1]]
        ], np.int32)

        overlay = result.copy()
        cv2.fillPoly(overlay, [pts], (0, 255, 0))
        result = cv2.addWeighted(overlay, 0.3, result, 0.7, 0)

    return result

# ์‚ฌ์šฉ ์˜ˆ
img = cv2.imread('road.jpg')
result = detect_lanes_averaged(img)
cv2.imshow('Averaged Lanes', result)
cv2.waitKey(0)

7. ์—ฐ์Šต ๋ฌธ์ œ

๋ฌธ์ œ 1: ์ฒด์ŠคํŒ ๊ฒ€์ถœ

์ฒด์ŠคํŒ ์ด๋ฏธ์ง€์—์„œ ๋ชจ๋“  ์ง์„ ์„ ๊ฒ€์ถœํ•˜๊ณ  ๊ต์ฐจ์ ์„ ์ฐพ์œผ์„ธ์š”.

์ •๋‹ต ์ฝ”๋“œ
import cv2
import numpy as np

def detect_chessboard_lines(image_path):
    """์ฒด์ŠคํŒ ์ง์„ ๊ณผ ๊ต์ฐจ์  ๊ฒ€์ถœ"""
    img = cv2.imread(image_path)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    edges = cv2.Canny(gray, 50, 150)

    lines = cv2.HoughLines(edges, 1, np.pi/180, 100)

    result = img.copy()
    horizontal = []
    vertical = []

    if lines is not None:
        for line in lines:
            rho, theta = line[0]
            angle = np.degrees(theta)

            a = np.cos(theta)
            b = np.sin(theta)

            # ์ˆ˜ํ‰์„ /์ˆ˜์ง์„  ๋ถ„๋ฅ˜
            if 80 < angle < 100:  # ์ˆ˜์ง
                vertical.append((rho, theta))
            elif angle < 10 or angle > 170:  # ์ˆ˜ํ‰
                horizontal.append((rho, theta))

    # ๊ต์ฐจ์  ๊ณ„์‚ฐ
    intersections = []
    for h_rho, h_theta in horizontal:
        for v_rho, v_theta in vertical:
            # ๋‘ ์ง์„ ์˜ ๊ต์ฐจ์ 
            A = np.array([
                [np.cos(h_theta), np.sin(h_theta)],
                [np.cos(v_theta), np.sin(v_theta)]
            ])
            b = np.array([h_rho, v_rho])

            try:
                x, y = np.linalg.solve(A, b)
                if 0 <= x < img.shape[1] and 0 <= y < img.shape[0]:
                    intersections.append((int(x), int(y)))
            except:
                pass

    # ๊ทธ๋ฆฌ๊ธฐ
    for x, y in intersections:
        cv2.circle(result, (x, y), 5, (0, 0, 255), -1)

    print(f"๊ต์ฐจ์  ์ˆ˜: {len(intersections)}")
    cv2.imshow('Chessboard', result)
    cv2.waitKey(0)

detect_chessboard_lines('chessboard.jpg')

๋ฌธ์ œ 2: ์•„์ด๋ฆฌ์Šค ๊ฒ€์ถœ

๋ˆˆ ์ด๋ฏธ์ง€์—์„œ ํ™์ฑ„ ์›์„ ๊ฒ€์ถœํ•˜์„ธ์š”.

์ •๋‹ต ์ฝ”๋“œ
import cv2
import numpy as np

def detect_iris(image_path):
    """๋ˆˆ์—์„œ ํ™์ฑ„ ๊ฒ€์ถœ"""
    img = cv2.imread(image_path)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # ๋ฐ๊ธฐ ๊ท ์ผํ™”
    gray = cv2.equalizeHist(gray)
    blurred = cv2.GaussianBlur(gray, (7, 7), 0)

    # ํ™์ฑ„ ๊ฒ€์ถœ (์–ด๋‘์šด ์›)
    circles = cv2.HoughCircles(
        blurred,
        cv2.HOUGH_GRADIENT,
        dp=1,
        minDist=100,
        param1=100,
        param2=25,
        minRadius=20,
        maxRadius=60
    )

    result = img.copy()

    if circles is not None:
        circles = np.uint16(np.around(circles))

        # ๊ฐ€์žฅ ํฐ ์› ์„ ํƒ (ํ™์ฑ„)
        for cx, cy, r in sorted(circles[0], key=lambda x: -x[2])[:1]:
            # ํ™์ฑ„
            cv2.circle(result, (cx, cy), r, (0, 255, 0), 2)
            cv2.circle(result, (cx, cy), 2, (0, 0, 255), 3)

    cv2.imshow('Iris', result)
    cv2.waitKey(0)

detect_iris('eye.jpg')

๋ฌธ์ œ 3: ์›ํ˜• ๋„๋กœ ํ‘œ์ง€ํŒ ๊ฒ€์ถœ

๋นจ๊ฐ„์ƒ‰ ์›ํ˜• ๊ตํ†ต ํ‘œ์ง€ํŒ์„ ๊ฒ€์ถœํ•˜์„ธ์š”.

์ •๋‹ต ์ฝ”๋“œ
import cv2
import numpy as np

def detect_red_signs(image_path):
    """๋นจ๊ฐ„ ์›ํ˜• ํ‘œ์ง€ํŒ ๊ฒ€์ถœ"""
    img = cv2.imread(image_path)
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

    # ๋นจ๊ฐ„์ƒ‰ ๋งˆ์Šคํฌ (HSV์—์„œ ๋นจ๊ฐ•์€ 0๋„์™€ 180๋„ ๋ถ€๊ทผ)
    lower_red1 = np.array([0, 100, 100])
    upper_red1 = np.array([10, 255, 255])
    lower_red2 = np.array([160, 100, 100])
    upper_red2 = np.array([180, 255, 255])

    mask1 = cv2.inRange(hsv, lower_red1, upper_red1)
    mask2 = cv2.inRange(hsv, lower_red2, upper_red2)
    red_mask = cv2.bitwise_or(mask1, mask2)

    # ๋ชจํด๋กœ์ง€ ์—ฐ์‚ฐ
    kernel = np.ones((5, 5), np.uint8)
    red_mask = cv2.morphologyEx(red_mask, cv2.MORPH_CLOSE, kernel)
    red_mask = cv2.morphologyEx(red_mask, cv2.MORPH_OPEN, kernel)

    # ์› ๊ฒ€์ถœ
    circles = cv2.HoughCircles(
        red_mask,
        cv2.HOUGH_GRADIENT,
        dp=1.2,
        minDist=50,
        param1=50,
        param2=30,
        minRadius=20,
        maxRadius=100
    )

    result = img.copy()

    if circles is not None:
        circles = np.uint16(np.around(circles))
        for cx, cy, r in circles[0]:
            cv2.circle(result, (cx, cy), r, (0, 255, 0), 3)
            cv2.circle(result, (cx, cy), 3, (0, 0, 255), -1)

    cv2.imshow('Red Signs', result)
    cv2.imshow('Mask', red_mask)
    cv2.waitKey(0)

detect_red_signs('traffic_sign.jpg')

์ถ”์ฒœ ๋ฌธ์ œ

๋‚œ์ด๋„ ์ฃผ์ œ ์„ค๋ช…
โญ ์ง์„  ๊ฒ€์ถœ ๊ฑด๋ฌผ ์‚ฌ์ง„์—์„œ ์ˆ˜ํ‰/์ˆ˜์ง์„  ๊ฒ€์ถœ
โญโญ ๋™์ „ ์„ธ๊ธฐ ๋™์ „ ์‚ฌ์ง„์—์„œ ๊ฐœ์ˆ˜์™€ ๊ธˆ์•ก ๊ณ„์‚ฐ
โญโญ ๋ฌธ์„œ ๊ฒ€์ถœ ๋ฌธ์„œ ๊ฒฝ๊ณ„์„  4๊ฐœ ๊ฒ€์ถœ
โญโญโญ ์ฐจ์„  ๊ฒ€์ถœ ๋„๋กœ ์˜์ƒ์—์„œ ์‹ค์‹œ๊ฐ„ ์ฐจ์„  ๊ฒ€์ถœ
โญโญโญ ๊ณ„๊ธฐํŒ ์•„๋‚ ๋กœ๊ทธ ๊ฒŒ์ด์ง€ ๋ˆˆ๊ธˆ ์ฝ๊ธฐ

๋‹ค์Œ ๋‹จ๊ณ„


์ฐธ๊ณ  ์ž๋ฃŒ

to navigate between lessons