Python/파이썬 OpenCV 공부

[파이썬 OpenCV] 영상에서 직선 검출하기 - 허프 변환 직선 검출 - cv2.HoughLines, cv2.HoughLinesP

AI 꿈나무 2020. 10. 9. 18:02
반응형

황선규 박사님의 <OpenCV 4로 배우는 컴퓨터 비전과 머신 러닝>를 공부한 내용을 정리해 보았습니다.

 

예제 코드 출처 :  황선규 박사님 github홈페이지

 

『OpenCV 4로 배우는 컴퓨터 비전과 머신 러닝』

예제 소스 코드는 아래 링크를 참고하세요

sunkyoo.github.io


 

 

[파이썬 OpenCV] 영상의 윤곽선 검출하기 - 캐니 에지 검출 - cv2.Canny

황선규 박사님의 를 공부한 내용을 정리해 보았습니다. 예제 코드 출처 :  황선규 박사님 github홈페이지 『OpenCV 4로 배우는 컴퓨터 비전과 머신 러닝』 예제 소스 코드는 아래 링크를 참고하세��

deep-learning-study.tistory.com

 지난 포스팅에서 cv2.canny 함수를 이용해여 윤곽선을 검출하는 것을 공부했습니다.

 이번에는 cv2.canny로 검출한 윤곽선을 이용하여 영상에서 직선을 검출하는 것을 공부하겠습니다.

 

허프 변환: 직선 검출

 에지(윤곽선) 영상은 검정색 배경에 에지부분만 흰색입니다.

 이 입력 영상에서 에지가 있는 좌표를 추출하는 것입니다.

 에지 영상에서 좀 더 고차원적인 정보를 얻기 위해 에지 영상의 분포를 보고 직선, 사각형 등 고차원적인 형태 정보를 추출합니다.

 

 직선을 추출해내는 가장 유명한 알고리즘이 허프 변환입니다.

 축적 알고리즘을 이용하여 내가 원하는 파라미터르 찾아냅니다.

 

 허프 변환 직선 검출은 2차원 영상 좌표에서의 직선의 방정식파라미터(parameter) 공간으로 변환하여 직선을 찾는 알고리즘입니다.

 

 

 직선의 방정식 y = ax + b가 있다면 b = -xa + y로 변환합니다.

 x,y 축에 있는 직선의 점을 a,b 좌표평면으로 옮기면 직선으로 표현됩니다.

 x,y 좌표평면 두 점을 a, b 좌표평면으로 옮기면 두 개의 직선으로 표현됩니다.

 

 이 아이디어를 이용한 것이 허프 변환입니다.

 

축적 배열 - accumulation array

 축적 배열은 직선 성분과 관련된 원소 값을 1씩 증가시키는 배열입니다.

 

 x,y 좌표평면에서의 직선의 한 점을 a, b 좌표평면으로 옮기면 직선으로 표현됩니다.

 이 직선이 지나가는 행렬의 값을 1씩 증가시키는 것입니다.

 이 작업을 반복하면 최종적으로 한 점에서 값이 높아지게 됩니다.

 

직선의 방정식 y = ax + b 를 사용할 때의 문제점

 직선의 방정식 y = ax + b를 이용하면 y축과 평행한 수직선을 표현하지 못합니다.

 a값이 무한대로 가기 때문입니다.

 따라서 극좌표계 직선의 방정식을 이용합니다.

 

 

 로우는 원점에서 수선까지의 길이입니다.

 

 극좌표계 직선의 방정식을 파라미터 공간으로 변환하면 로우와 세타 좌표평면으로 표현할 수 있습니다.

 이 경우에 직선이 아닌 곡선으로 표현됩니다.

 곡선이 한 점에서 뭉치게 되는데 이 뭉치는 곳이 로우와 세타의 값이 됩니다.

 

허프 변환에 의한 선분 검출 함수 - cv2.HoughLines

 캐니 함수로 윤곽선을 검출한 값을 cv2.HoughLines에 입력 값으로 설정하면 직선 파라미터 정보를 담고 있는 결과값을 반환합니다.

 

cv2.HoughLines(image, rho, theta, threshold, lines=None, srn=None, stn=None, min_theta=None, max_theta=None) -> lines

• image: 입력 에지 영상
• rho: 축적 배열에서 rho 값의 간격. (e.g.) 1.0 → 1픽셀 간격
• theta: 축적 배열에서 theta 값의 간격. (e.g.) np.pi / 180 → 1 간격
• threshold: 축적 배열에서 직선으로 판단할 임계값
• lines: 직선 파라미터(rho, theta) 정보를 담고 있는 numpy.ndarray. shape=(N, 1, 2). dtype=numpy.float32.
• srn, stn: 멀티 스케일 허프 변환에서 rho 해상도, theta 해상도를 나누는 값. 기본값은 0이고, 이 경우 일반 허프 변환 수행.
• min_theta, max_theta: 검출할 선분의 최소, 최대 theta 값

 로우값과 세타값의 간격은 축적 배열을 만들 때 로우 값 해당 축과 세타 해당 축의 크기를 몇으로 설정할 것인지를 의미합니다. 이 값을 작게 하면 축적 배열이 커집니다. (연산이 오래 걸리지만 정교합니다)

 간격을 크게 하면 축적배열은 작아닙니다. (연산은 빠르지만 정밀도가 떨어집니다.)

 

 lines 결과값은 3차원 형상으로 반환합니다.

 (N, 1, 2)에서 1은 값이 비어있습니다.

 

확률적 허프 변환에 의한 선분 검출 - cv2.HoughLinesP

 허프 변환과의 차이점은 결과값 lines의 파라미터가 다릅니다.

 허프 변환 함수는 로우와 세타 값으로 직선의 파라미터 정보를 제공했지만

 확률적 허프 변환 함수는 직선의 시작과 끝 정보를 제공합니다.

 

cv2.HoughLinesP(image, rho, theta, threshold, lines=None, minLineLength=None, maxLineGap=None) -> lines

• image: 입력 에지 영상
• rho: 축적 배열에서 rho 값의 간격. (e.g.) 1.0 → 1픽셀 간격
• theta: 축적 배열에서 theta 값의 간격. (e.g.) np.pi / 180 → 1 간격.
• threshold: 축적 배열에서 직선으로 판단할 임계값
• lines: 선분의 시작과 끝 좌표(x1, y1, x2, y2) 정보를 담고 있는 numpy.ndarray. shape=(N, 1, 4). dtype=numpy.int32.
• minLineLength: 검출할 선분의 최소 길이
• maxLineGap: 직선으로 간주할 최대 에지 점 간격

 주의할 점은 maxLineGap 인자입니다.

 디폴트 값은 0이지만 0보다 큰 값을 주는 것이 좋습니다.

 조명이나 외부 변수에 의해 픽셀값이 변화되서 직선이 끊길 수 있습니다.

 0은 한 픽셀 떨어져도 끊는데 5는 5픽셀 정도 떨어져도 직선을 붙입니다.

 5이상 줘야 합니다.

 

확률적 허프 변환 직선 검출 예제

 예제 코드는 황선규 박사님의 깃허브를 참고했습니다.

 

src = cv2.imread('building.jpg', cv2.IMREAD_GRAYSCALE)

if src is None:
    print('Image load failed!')
    sys.exit()

# 에지 검출
edge = cv2.canny(src, 50, 150)

# 직선 성분 검출
lines = cv2.HoughLinesP(edges, 1, np.pi / 180., 160, minLineLength=50, maxLineGap=5)

# 컬러 영상으로 변경 (영상에 빨간 직선을 그리기 위해)
det = cv2.cvtColor(edges, cv2.COLOR_GRAY2BGR)

if lines is not None: # 라인 정보를 받았으면
    for i in range(lines.shape[0]):
        pt1 = (lines[i][0][0], lines[i][0][1]) # 시작점 좌표 x,y
        pt2 = (lines[i][0][2], lines[i][0][3]) # 끝점 좌표, 가운데는 무조건 0
        cv2.line(dst, pt1m pt2m (0, 0, 255), 2, cv2.LINE_AA)
        
cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWIndows()

 

 

 

확률적 허프 변환 적용

 

 직선이 검출되었습니다.

 화단 부분의 윤곽선이 너무 복잡해서 직선으로 검출되었습니다.

 이는 후처리로 제거해줘야 합니다.

 

반응형