Perception/OpenCV

[OpenCV] 허프 변환 (Hough Transform) - 원 검출

고집호랑이 2023. 2. 18. 07:00

개요 

이전 포스팅에서는 영상에서 허프 변환으로 직선을 검출하는 원리와 OpenCV에서 제공하는 함수를 알아봤습니다.

 

허프 변환을 이용하면 영상 내의 원 또한 검출할 수 있는데, 이번에는 원을 검출하는 원리와 OpenCV 함수에 대해서 알아보겠습니다.

 

허프 변환 (원 검출) 원리

직선 검출의 핵심은 xy 공간에서 에지로 판별된 점들을 ρθ 공간으로 변환하여 생성된 곡선들이, 임계값 이상으로 교차되는 점들을 찾음으로써 직선을 검출한다는 것이었습니다.

 

원 또한 이 방식으로 검출할 수 있습니다. 일반적으로 중점이 (a, b)이고 반지름이 r인 원의 방정식은 다음과 같이 표현합니다.

 

xy 공간에서의 원의 방정식

이 원의 방정식은 세 개의 파라미터를 가지고 있으므로 허프 변환을 그대로 사용하려면 a, b, r의 3차원 파라미터 공간으로 변환시켜야 합니다. 

 

위 원의 방정식에서 <x, y>를 상수로, <a, b, r>을 변수로 판단하면 식은 다음과 같이 바뀝니다.

 

3차원 파라미터 공간에서의 원의 방정식

r에 상수를 하나씩 대입시키면서 생각해보면 아래 그림과 같이 xy 공간에서 원 위의 한 점은 a, b, r의 3차원 파라미터 공간에서 원뿔의 표면으로 표현됩니다.

 

원뿔의 표면이 만나는 한 점

 

따라서 영상의 에지 픽셀들을 허프 변환시켜 만들어진, 원뿔들의 표면이 많이 교차하는 점을 찾으면 원을 검출할 수 있습니다.

 

하지만 이 방법은 많은 메모리와 연산량을 필요로 하기 때문에 비효율적입니다. 그래서 OpenCV에서는 일반적인 허프 변환 대신 허프 그래디언트 방법(Hough Gradient Method)을 사용하여 원을 검출합니다.

 

이름에서 알 수 있듯이 이 방법은 그래디언트를 사용합니다. 그래디언트를 이용해 영상에 존재하는 모든 원의 중심 좌표를 찾고, 이후 찾아낸 원의 중심으로부터 원에 적합한 반지름을 구합니다.

 

먼저 그래디언트로 어떻게 원의 중심 좌표를 찾아내는지부터 알아봅시다. 

 

그래디언트는 이전에 배운 Sobel 마스크를 통한 x, y 방향으로의 편미분 결과를 합쳐서 계산하였습니다. 식으로 표현하면 다음과 같죠.

그래디언트 계산 식

벡터인 그래디언트의 크기는 변화율의 세기를 나타내고, 방향은 변화 정도가 가장 큰 방향을 나타냅니다. 이 그래디언트 벡터를 실제 영상에 표현해보면 다음 그림과 같습니다. 

 

그레이디언트와 에지의 관계
출처 - 책 OpenCV 4로 배우는 컴퓨터 비전과 머신 러닝

 

빨간색 화살표가 그래디언트 벡터를, 노란색 화살표는 에지의 방향을 나타내는데 에지 방향과 그래디언트 방향은 항상 수직한다는 것을 확인할 수 있습니다.

 

그렇다면 원의 에지 픽셀들의 그래디언트 방향은 아래 그림과 같이 모두 원의 중심을 향한다는 것을 알 수 있습니다. 

 

원에서의 그래디언트

 

따라서 에지 픽셀들의 그래디언트를 구하고, 그래디언트 방향을 따르는 직선들이 임계값 이상으로 만나는 점을 원의 중심으로 판단할 수 있습니다. 

 

직선들이 많이 만나는 점은 허프 변환 직선 검출 방식과 마찬가지로 축적 배열로 찾습니다.

 

이후 원의 반지름은, 찾은 원의 중심으로부터 반지름을 크게 하면서 원의 원주상에 충분히 많은 에지 픽셀이 존재하는지 확인하면서 구하게 됩니다.

 

허프 변환 원 검출 함수

OpenCV에서는 허프 변환 원 검출을 위해서 HoughCircles() 함수를 제공합니다.

 

HoughCircles() 함수는 다음과 같습니다.

void HoughCircles(InputArray image, OutputArray circles, int method,
                  double dp, double minDist, double param1 = 100,
                  double param2 = 0, int minRadius = 0, 
                  int maxRadius = 0);
  • image: 입력 영상. 에지 영상이 아닌 흑백 영상을 지정
  • circles: 검출된 원 정보(원의 중심과 반지름)를 저장할 출력 벡터
  • method: HOUGH_GRADIENT만 지정 가능
  • dp: 입력 영상과 축적 배열의 크기 비율
  • minDist: 인접한 원 중심의 최소 거리
  • param1: Canny 에지 검출기의 높은 임계값
  • param2: 축적 배열에서 원 검출을 위한 임계값
  • minRadius: 검출할 원의 최소 반지름
  • maxRadius: 검출할 원의 최대 반지름

 

직선을 검출하는 HoughLines()와 HoughLinesP() 함수에는 입력 영상으로 에지 영상을 전달하였습니다.

 

하지만 원을 검출할 때는 그래디언트 결과를 사용하는 허프 그래디언트 방법을 사용하기 때문에 HoughCircles() 함수 내부에서 에지를 검출하면서 그래디언트를 계산하기 위해 입력 영상으로 흑백 영상(Grayscale)을 전달해야 합니다.

 

dp 인자는 사용할 축적 배열의 크기를 결정하는 용도로 사용됩니다. 1로 지정하면 입력 영상과 같은 크기의 축적 배열을 사용하고, 2를 지정하면 입력 영상의 가로와 세로 크기를 2로 나눈 크기의 축적 배열을 사용합니다.

 

minDist 인자에는 인접한 원의 최소 거리를 지정하는데 두 원의 중심점 사이 거리가 이 값보다 작으면 두 원 중 하나는 검출하지 않습니다.

 

param1 인자는 캐니 에지 검출기를 사용할 때 높은 임계값으로 사용되며, 낮은 임계값은 param1의 절반으로 설정합니다.

 

param2는 축적 배열에서 원의 중심을 찾기 위한 임계값, minRadius와 maxRadius는 검출할 원의 최소 반지름과 최대 반지름을 지정해줍니다.

  

자신이 원하는 원들을 출력하기 위해서는 이 인자 값들을 적절하게 조절해줘야 합니다.  

 

이제 이 HoughCircles() 함수를 이용해 아래 영상에서 원을 검출해보도록 하겠습니다.

 

원 검출을 할 예시 사진

 

import cv2

# 행성 영상 불러오기
image = cv2.imread("../planets.jpg")
# 원본 영상을 복사
image2 = image.copy()

# 입력 영상이 흑백이어야 하므로 흑백 영상으로 변환
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# HoughCircles 함수를 이용해 검출한 원의 중심과 반지름을 circles에 저장 
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 50, param1 = 100, param2 = 40, minRadius = 5, maxRadius = 70)

# 원본 영상에 검출한 원을 빨간색으로 표시
for i in circles[0]:
    cv2.circle(image2, (int(i[0]), int(i[1])), int(i[2]), (0,0,255), 2)

# 원 검출 영상 출력
cv2.imshow("circle", image2)
cv2.waitKey(0)

 

image2는 허프 그래디언트 방법으로 검출된 원을 원본 영상에서 초록색으로 표시해줬습니다.

 

출력 결과 아래 그림과 같이 원들이 잘 출력된 것을 확인할 수 있습니다.

 

원 검출 결과
원 검출 결과