이 글에서는 허프 변환(Hough Transform)이라는 기법을 활용해 이미지 속 직선을 감지하는 방법을 알아봅니다. OpenCV와 파이썬만 있으면 누구나 쉽게 구현할 수 있으며, 이론부터 실전 예제까지 단계별로 살펴보겠습니다.
허프 변환이란?
허프 변환은 수학적 형태로 표현할 수 있는 간단한 도형을 이미지에서 추출하는 특징 검출(feature extraction) 기법입니다. 놀랍게도 선이 조금 끊어져 있거나 일부 왜곡되어 있더라도 형태를 인식할 수 있습니다.
여기서 '간단한' 도형이란 적은 수의 매개변수만으로 표현 가능한 도형을 의미합니다. 예를 들어:
- 직선: 기울기(slope)와 절편(intercept) 두 개의 매개변수로 표현
- 원: 중심 좌표(x, y)와 반지름(r), 세 개의 매개변수로 표현
허프 변환으로 이미지에서 직선 감지하기
직선은 다음과 같은 매개변수 형태로 표현할 수 있습니다.
ρ = x·cos(θ) + y·sin(θ)
여기서 ρ(로)는 원점에서 직선까지의 수직 거리를, θ(세타)는 이 수직선과 수평축이 이루는 각도(반시계 방향으로 측정)를 나타냅니다. OpenCV에서 바로 이 표현 방식을 사용합니다.
직선이 원점 아래를 지나면 ρ는 양수가 되고 θ는 180도보다 작습니다. 반대로 직선이 원점 위를 지나면 180도보다 큰 각도 대신 180도 미만의 각도를 사용하고 ρ를 음수로 처리합니다. 또한 모든 수직선은 0도, 수평선은 90도의 각도를 갖습니다.
누적 배열(Accumulator)의 동작 원리
모든 직선은 (ρ, θ) 두 값으로 표현할 수 있습니다. 허프 변환은 먼저 이 두 매개변수의 값을 저장할 2차원 배열, 즉 누적 배열(accumulator)을 생성하고 모든 값을 0으로 초기화합니다. 행(row)은 ρ를, 열(column)은 θ를 나타냅니다.
배열의 크기는 필요한 정밀도에 따라 달라집니다. 예를 들어 각도 정밀도를 1도로 설정하려면 180개의 열이 필요하며, ρ의 최댓값은 이미지 대각선의 길이이므로 픽셀 단위 정밀도를 적용하면 행의 개수 역시 이미지 대각선 길이가 됩니다.
투표(Voting) 과정 예시
가운데에 수평선 하나가 있는 100×100 크기의 이미지를 생각해 보겠습니다.
- 직선 위 첫 번째 점의 (x, y) 값을 구합니다.
- θ에 0, 1, 2, 3…180의 값을 차례대로 대입하며 계산된 ρ 값을 확인합니다.
- 모든 (ρ, θ) 조합에 대해 누적 배열의 해당 셀 값을 1씩 증가시킵니다. 이 시점에서 셀 (50, 90) = 1이 됩니다.
- 직선 위 두 번째 점에서 같은 과정을 반복하면 이번에는 셀 (50, 90) = 2가 됩니다.
즉, 우리는 (ρ, θ) 값에 투표(voting)를 하는 것입니다. 이 과정을 직선 위의 모든 점에 대해 반복하면, 매 지점마다 셀 (50, 90)의 값은 계속 증가하는 반면 다른 셀들은 그렇지 못합니다. 최종적으로 셀 (50, 90)이 최다 득표를 하게 되고, 누적 배열에서 최댓값을 찾으면 "원점에서 거리 50, 각도 90도 위치에 직선이 존재한다"는 결과를 얻습니다. 이것이 바로 직선 검출을 위한 허프 변환의 작동 원리입니다.
OpenCV에서의 허프 변환 활용
앞서 설명한 모든 과정은 OpenCV 함수인 cv2.HoughLines()에 내장되어 있습니다. 이 함수는 ρ(픽셀 단위)와 θ(라디안 단위) 값들의 배열을 반환합니다.
아래는 주차장 사진에 허프 직선 변환(Hough Line Transform)과 OpenCV 라이브러리를 적용해 직선을 감지하는 예제입니다.
원본 이미지

예제 코드
import cv2
import numpy as np
img = cv2.imread("parkingLot1.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 75, 150)
lines = cv2.HoughLinesP(edges, 1, np.pi/180, 30, maxLineGap=250)
for line in lines:
x1, y1, x2, y2 = line[0]
cv2.line(img, (x1, y1), (x2, y2), (0, 0, 128), 1)
cv2.imshow("linesEdges", edges)
cv2.imshow("linesDetected", img)
cv2.waitKey(0)
cv2.destroyAllWindows()코드 설명
cv2.Canny(gray, 75, 150): 그레이스케일 이미지에서 Canny 에지 검출기를 통해 윤곽선을 추출합니다.cv2.HoughLinesP(edges, 1, np.pi/180, 30, maxLineGap=250): 확률적 허프 변환(Probabilistic Hough Transform)으로 직선 세그먼트를 검출합니다. 여기서 1은 ρ의 해상도, np.pi/180은 θ의 해상도, 30은 직선으로 판단할 최소 투표 수(임계값), maxLineGap은 같은 직선으로 묶을 수 있는 최대 픽셀 간격입니다.cv2.line(): 감지된 직선을 원본 이미지에 붉은색 계열(BGR 기준 (0, 0, 128))로 그립니다.
실행 결과
Canny 에지 검출 결과는 다음과 같습니다.

그리고 최종적으로 감지된 직선은 아래와 같습니다.

이처럼 허프 변환을 활용하면 차선 인식, 문서 스캔 보정, 객체 윤곽 분석 등 다양한 컴퓨터 비전 프로젝트에서 직선을 효과적으로 검출할 수 있습니다.