본문 바로가기
강의 (Lecture)/OpenCV 마스터 with Python (초급)

OpenCV + Python 엣지(Edge) 검출과 허프(Hough) 변환

by codingwalks 2024. 10. 21.
728x90
반응형

안녕하세요. 코딩산책입니다.

객체의 경계를 정확히 검출하는 작업은 다양한 응용 분야에서 활용되며, 이미지 이해의 핵심 과정 중 하나입니다. 이때 경계를 감지하는 기술 중 대표적인 방법이 엣지 검출이며, 이후 감지된 경계를 통해 기하학적 모양을 찾는 데 사용되는 기법이 허프 변환입니다. 엣지 검출은 이미지의 픽셀 강도의 급격한 변화, 즉 객체와 배경 사이의 경계나 내부 구조의 변화를 찾아내는 과정입니다. Sobel, Laplacian, Canny와 같은 다양한 엣지 검출 기법들이 사용되며, 각각의 방법은 이미지의 특성에 따라 적합한 결과를 제공합니다. 엣지 검출을 통해 검출된 경계는 이후의 분석을 위한 중요한 기반이 됩니다. 허프 변환(Hough Transform)은 이러한 엣지 정보를 바탕으로 직선이나 원과 같은 특정 기하학적 모양을 찾는 기법으로, 이미지에서 부분적으로 끊어진 직선이나 원형 경계도 robust하게 검출할 수 있습니다. 이 글에서는 엣지 검출과 허프 변환을 연계하여 이미지에서 선과 원 같은 모양을 검출하는 과정을 살펴보고, 그 원리와 수학적 기초를 설명합니다.

 

1. 엣지(Edge) 검출

1.1. 간단한 엣지 검출 커널

엣지 검출은 특정 커널을 사용한 합성곱을 통해 이루어집니다. 기본적인 엣지 검출을 위해 수평과 수직 커널을 적용할 수 있습니다.

수평 엣지 검출 커널: 이 커널은 인접한 행 간의 강도 변화(그라디언트)를 강조하여 수평 엣지를 감지합니다.

\[K_x = \begin{bmatrix}
-1 & -2 & -1 \\
0 & 0 & 0 \\
1 & 2 & 1
\end{bmatrix}\]

수직 엣지 검출 커널: 이 커널은 수직 방향의 변화를 감지하는 데 사용됩니다.

\[K_y = \begin{bmatrix}
-1 & 0 & 1 \\
-2 & 0 & 2 \\
-1 & 0 & 1
\end{bmatrix}\]

1.2. Sobel 연산자

Sobel 연산자는 수평 및 수직 방향에서 이미지의 미분을 근사하여 두 개의 3x3 필터를 사용합니다. Sobel 연산자는 이미지에서 노이즈에 대한 민감도를 줄이기 위해 부드럽게 처리하는 단계도 포함합니다.

Sobel 연산자의 미분 커널:

\(G_x = \begin{bmatrix}
-1 & 0 & 1 \\
-2 & 0 & 2 \\
-1 & 0 & 1
\end{bmatrix}\)  와 
\(G_y = \begin{bmatrix}
-1 & -2 & -1 \\
0 & 0 & 0 \\
1 & 2 & 1
\end{bmatrix}\)

그라디언트의 크기는 다음과 같이 계산할 수 있습니다:

\[G = \sqrt{G_x^2 + G_y^2}\]

이는 변화의 크기를 나타내며, 엣지의 방향은 다음과 같습니다:

\[\theta = \text{atan2}(G_y, G_x)\]

1.3. Laplacian 연산자

Laplacian 연산자는 이미지 내의 급격한 밝기 변화를 감지하여 에지를 검출하는 데 사용됩니다. Laplacian 연산자는 이미지의 모든 방향에서 동시에 엣지를 검출하기 위해 두 번째 미분을 계산하는 (즉, 이차 미분을 근사하는) 연산자입니다. OpenCV의 cv2.Laplacian() 함수는 이러한 Laplacian 연산을 수행하여 에지 이미지를 생성합니다.

\[\Delta I = \begin{bmatrix}
0 & 1 & 0 \\
1 & -4 & 1 \\
0 & 1 & 0
\end{bmatrix}\]

이 연산자는 수평 및 수직 방향에서 급격한 강도 변화를 동시에 감지합니다. Sobel 연산자와 달리, Laplacian 연산자는 엣지의 방향 정보를 제공하지 않습니다.

1.4. Canny 엣지 검출

Canny Edge Detection은 이미지 처리에서 가장 많이 사용되는 에지 검출 알고리즘 중 하나입니다. OpenCV의 cv2.Canny() 함수는 이 알고리즘을 간단하게 사용할 수 있도록 제공하지만, 직접 구현해보면 알고리즘의 내부 동작을 더 잘 이해할 수 있습니다.

Canny Edge Detection의 과정:

  1. 노이즈 제거(Gaussian Blur): 노이즈를 제거하기 위해 가우시안 필터를 적용합니다.
  2. 그라디언트(Gradient) 계산: Sobel 연산자를 이용하여 x, y 방향의 gradient를 계산하고, gradient의 크기 ((G)) 와 방향 \(\theta\) 를 구합니다.
  3. 비최대 억제(NMS, Non-maximum suppression): gradient의 크기가 가장 큰 방향으로 이웃 픽셀과 비교하여 local maximum만 남깁니다.
  4. 이중 임계값(Double thresholding): 높은 임계값과 낮은 임계값을 이용하여 강한 에지, 약한 에지와 엣지가 아님을 분류합니다
    \[\text{Edge} =
    \begin{cases}
    \text{강한 엣지} & \text{if } G > T_{high} \\
    \text{약한 엣지} & \text{if } T_{low} < G \leq T_{high} \\
    \text{엣지가 아님} & \text{if } G \leq T_{low}
    \end{cases}\]
  5. 히스테리시스에 의한 엣지 추적(Edge tracking by hysteresis): 강한 에지를 시작점으로 하여 약한 에지를 연결하여 완전한 에지를 생성합니다.

 

OpenCV 엣지 검출을 활용한 예제코드:

import cv2
import numpy as np
import matplotlib.pyplot as plt

image = cv2.imread('resources/lena.bmp', cv2.IMREAD_GRAYSCALE)

# 1.1. Simple edge detection kernel
# Defining horizontal and vertical kernels
horizontal_kernel = np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]])
vertical_kernel = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]])

# Apply convolution
horizontal_edges = cv2.filter2D(image, -1, horizontal_kernel)
vertical_edges = cv2.filter2D(image, -1, vertical_kernel)

# 1.2. Sobel operator
sobel_x = cv2.Sobel(image, cv2.CV_64F, 1, 0, ksize=3)
sobel_y = cv2.Sobel(image, cv2.CV_64F, 0, 1, ksize=3)
sobel_magnitude = np.sqrt(sobel_x**2 + sobel_y**2)

# 1.3. Laplacian operator
laplacian = cv2.Laplacian(image, cv2.CV_64F)

# 1.4. Canny edge detection
canny_edges = cv2.Canny(image, 100, 200)

titles = ['Original', 'Horizontal Edges', 'Vertical Edges', 
          'Sobel Operator', 'Laplacian Operator', 'Canny Edges']
images = [image, horizontal_edges, vertical_edges, sobel_magnitude, laplacian, canny_edges]

plt.figure(figsize=(10, 10))
for i in range(6):
    plt.subplot(2, 3, i+1), plt.imshow(images[i], cmap='gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])

plt.show()

edge detection 결과 이미지

 

2. 허프(Hough) 변환

허프 변환(Hough Transformation)은 이미지 내에서 직선, 원과 같은 기하학적 형상을 검출하는 기법입니다. 기본적으로 노이즈가 많은 환경에서도 선형 또는 원형 패턴을 찾아내는 데 강력한 성능을 보이며, 특히 컴퓨터 비전에서 직선 검출이나 원 검출에 자주 사용됩니다. 허프 변환의 핵심 개념은 “특징 공간(feature space)“에서 패턴을 찾는 것입니다.

허프 변환을 알기 위해서는 직선의 방정식을 먼저 이해해야 합니다. 직선 방정식은 우리가 익히 알고 있는 두 가지 방식으로 나타낼 수 있습니다.

2.1. 허프 선 검출 (Hough Line Transform)

 

직선을 표현하는 가장 일반적인 방법은 다음과 같은 방정식입니다.

\[y = mx + b\]

여기서  m 은 기울기,  b 는 y-절편입니다. 하지만 이 방정식은 수직선의 경우 기울기  m 이 무한대가 되어 계산에 어려움이 생깁니다. 이를 해결하기 위해 허프 변환에서는 극좌표계를 사용하여 직선을 다음과 같이 표현합니다:

\[\rho = x \cdot \cos\theta + y \cdot \sin\theta\]

여기서:

  • \(\rho\) 는 원점에서 직선까지의 수직 거리입니다.
  • \(\theta\) 는 직선과 x축 사이의 각도입니다.

허프 변환은 이미지 공간에서 각 에지 픽셀에 대해 여러 값의  \(\theta\) 에 대해  \(\rho\) 를 계산하고, 이를 투표하는 누적 히스토그램을 구성합니다. 누적 히스토그램에서 높은 투표 수를 얻는  (\(\rho\), \(\theta\))  값은 해당 직선이 이미지에서 많이 교차하는 지점이므로, 이는 이미지에서 검출된 직선입니다.

허프 선 검출 알고리즘은 다음과 같이 작동합니다:

  1. 엣지 검출: 우선, Sobel 또는 Canny 엣지 검출기를 사용하여 이미지에서 엣지를 추출합니다.
  2. 누적 공간 투표: 각 엣지 픽셀에 대해 (\(\rho\), \(\theta\)) 값을 계산하고, 누적 히스토그램에 투표합니다.
  3. 최댓값 검출: 누적 공간에서 가장 많은 투표를 받은  (\(\rho\), \(\theta\))  값들을 직선으로 변환하여 직선을 검출합니다.

 

cv2.HoughLines() 를 활용한 예제코드

이 함수는 이미지를 입력받아 극좌표 형태의 직선을 검출합니다. 검출된 직선은  \(\rho\) 와  \(\theta\)  값으로 반환됩니다.

import cv2
import numpy as np

image = cv2.imread('resources/building.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(img, 50, 150)

# Line detection using Hough transform
lines = cv2.HoughLines(edges, 1, np.pi / 180, 270)

# Draw the detected straight line
results = cv2.cvtColor(edges, cv2.COLOR_GRAY2BGR)
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(results, (x1, y1), (x2, y2), (0, 0, 255), 2)

cv2.imshow('Hough Lines', results)
cv2.waitKey(0)
cv2.destroyAllWindows()

cv2.HoughLines() 결과 이미지

 

2.2. 확률적 허프 선 검출 (Probabilistic Hough Line Transform)

기본 허프 변환은 이미지의 모든 엣지 픽셀에 대해 계산하므로 매우 많은 연산을 필요로 합니다. 이를 개선하기 위해 확률적 허프 선 검출이 도입되었습니다. 이 방법은 전체 엣지 픽셀 중 일부만 무작위로 샘플링하여 선을 검출하기 때문에 계산량을 크게 줄일 수 있습니다.

확률적 허프 선 검출 알고리즘은 다음과 같이 작동합니다:

  1. 엣지 검출: 기본 허프 변환과 동일하게 엣지 검출을 먼저 수행합니다.
  2. 선 추출: 이미지에서 무작위로 선택된 에지 픽셀들에 대해 선 검출을 수행합니다.
  3. 누적 공간 분석: 누적 히스토그램에서 충분히 많은 투표를 받은 선만을 추출합니다.
  4. 선 분할: 직선이 전체적으로 검출되지 않고, 시작점과 끝점을 기준으로 선을 분할하여 표현합니다.

 

cv2.HoughLinesP() 를 활용한 예제코드

이 함수는 cv2.HoughLines()와 유사하지만, 확률적 허프 변환을 사용하여 더 효율적으로 직선을 검출합니다. 여기서는 선의 시작점 (x1, y1)과 끝점 (x2, y2)을 직접 반환합니다.

import cv2
import numpy as np
import matplotlib.pyplot as plt

img = cv2.imread('resources/building.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 50, 150)

# Probabilistic Hough Line Detection
linesP = cv2.HoughLinesP(edges, 1, np.pi / 180, 160, None, 50, 5)

# Draw a straight line
results = cv2.cvtColor(edges, cv2.COLOR_GRAY2BGR)
if linesP is not None:
    for line in linesP:
        x1, y1, x2, y2 = line[0]
        cv2.line(results, (x1, y1), (x2, y2), (0, 0, 255), 2)

cv2.imshow('Probabilistic Hough Lines', results)
cv2.waitKey(0)
cv2.destroyAllWindows()

cv2.HoughLinesP() 결과 이미지

 

2.3. 허프 원 검출 (Hough Circle Transform)

허프 변환을 이용하여 원을 검출하는 방법도 있습니다. 원은 3개의 파라미터로 정의됩니다:

  • (\(x_c\), \(y_c\)) : 원의 중심 좌표
  • \(r\) : 반지름

이 방법은 이미지의 각 엣지 픽셀에서 가능한 중심 좌표와 반지름에 대해 누적 히스토그램을 생성하고, 그 중 가장 많은 투표를 받은 좌표와 반지름을 원으로 인식합니다.

원의 방정식은 다음과 같습니다:

\[(x - x_c)^2 + (y - y_c)^2 = r^2\]

허프 원 검출은 이 방정식을 사용하여 각 엣지 픽셀에 대해 중심과 반지름을 추정합니다.

허프 원 검출 알고리즘은 다음과 같이 작동합니다:

  1. 엣지 검출: 원을 검출할 이미지에서 엣지 검출을 수행합니다.
  2. 누적 공간 계산: 각 엣지 픽셀에 대해 가능한 중심 좌표와 반지름에 대해 투표합니다.
  3. 원 검출: 누적 히스토그램에서 가장 많은 투표를 받은  (\(x_c\), \(y_c\), \(r\))  값을 원으로 검출합니다.

 

cv2.HoughCircles() 를 활용한 예제코드

이 함수는 이미지에서 원을 검출하는 데 사용됩니다. 허프 변환을 사용하여 원의 중심과 반지름을 계산합니다.

import cv2
import numpy as np

img = cv2.imread('resources/circle.jpg')

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (0,0), 1)

# Circle detection using Hough transform
circles = cv2.HoughCircles(blur, cv2.HOUGH_GRADIENT, 1, 100, None, 150, 40, 20, 80)

circles = np.uint16(np.around(circles))
for i in circles[0, :]:
	# Draw outer circle
    cv2.circle(img, (i[0], i[1]), i[2], (0, 255, 0), 2)
    # Draw center of the circle
    cv2.circle(img, (i[0], i[1]), 2, (0, 0, 255), 3)

cv2.imshow('Hough Circles', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

cv2.HoughCircles() 결과 이미지

 

3. 결론

엣지 검출과 허프 변환은 컴퓨터 비전에서 매우 중요한 기술로, 이미지의 구조적 특성을 분석하고 객체를 인식하는 데 널리 사용됩니다. 엣지 검출은 이미지에서 픽셀 강도의 변화를 감지하여 경계를 찾고, 허프 변환은 이 경계 정보를 바탕으로 직선이나 원과 같은 기하학적 모양을 검출하는 방법입니다. 허프 변환은 직선 검출에서부터 원 검출에 이르기까지 다양한 형태의 객체를 효과적으로 탐지할 수 있으며, 특히 노이즈가 많은 환경에서도 강건한 성능을 제공합니다.

이러한 기법들은 이미지 처리 및 분석의 기초적인 단계로, 더 복잡한 객체 인식과 추적, 3D 복원, 컴퓨터 비전 기반의 자동화 시스템 등 다양한 분야에서 중요한 역할을 합니다. 엣지 검출과 허프 변환을 적절히 활용하면 이미지에서 중요한 정보를 추출하고 더 높은 수준의 비전 시스템을 구축할 수 있습니다.

 

해당 포스트가 유용하셨다면 하단의 좋아요와 구독하기 부탁드립니다. ^^

Buy me a coffee

 

[Codingwalks]에게 송금하기 - AQR

[Codingwalks]에게 송금하기 - AQR

aq.gy

★ 모든 내용은 아래의 링크를 참조하였습니다. ★

 

OpenCV: OpenCV-Python Tutorials

Core Operations In this section you will learn basic operations on image like pixel editing, geometric transformations, code optimization, some mathematical tools etc.

docs.opencv.org

728x90
반응형