ref : towardsdatascience.com/lines-detection-with-hough-transform-84020b3b1549
허프 변환을 이용한 직선 검출 알고리즘
0. 에지 영상 준비
1. rho와 theta의 범위 지정
- theta는 -90 ~ 90도
- rho는 -d ~ d. d는 이미지 대각선 길이.
- theta와 rho를 계산할 수 있도록 이산 값이 되게 해야함
2. 허프 공간을 나타낼 2차원 누적 배열 생성
- shape : n_rho * n_theta
- 0으로 초기화
- 가능한 모든 경우의 rho, theta들을 누적할 예정
3. 에지 영상의 모든 픽셀 확인. 에지 픽셀에 대해서 가능한 모든 theta와 rho를 구하여 허프 행렬에 해당 인덱스 증감
4. 지정한 임계치를 넘는 값의 인덱스(rho, theta)가 검출된 직선
알고리즘 구현 자체는
여러 링크에 있는 내용들을 참고하다보니
대강 형태는 금방 만들었지만
시각화 할때가 많이 복잡했다.
이전 글에서 일반적인 xy 좌표계 상에서 rho, theta를 놓으면 아래 그림과 같이 그릴수 있지만
이미지 영역의 경우 y가 반대로 되어있는데
이때 rho, theta를 어떻게 보는 지가 문제였다.
제대로 이해하지 않은 상태에서 하다보니
rho와 theta를 xy 좌표계상에서 라인 플로팅을 할때,
rho와 theta로 그린 라인이 내가 생각했던 대로가 아니거나, 실제 에지와 일치 하지 않게 계속 나오더라
일단 사용한 기본 영상은 그림판에다가 일직선을 그려서 만들어봤다.
가우시안 블러링과 캐니 에지를 만들어주고
아무 생각없이(안해도 되지만) 모폴로지 닫힘도 해주었다.
이제 이 에지 영상에서 부터 시작하면 된다.
hough 공간의 누적 행렬을 만드는 함수를 구현하였다.
여기서 누적을 하는 이유는 많이 사용되는(임계치를 넘는) rho와 theta를 찾아 직선을 그려주기 위함인데,
하나의 에지 픽셀에 대해서 모든 방향 360도 -> 직선으로 나타낼태니 180도를 1도 간격(해상도)으로 나누어
각 에지의 180도 rho를 구하고
해당 rho값 자체(rho)와 theta의 인덱스(theta_idx)를
누적 행렬의 행과 열 인덱스로 사용하고 1씩 증감을 시켰다.
accumulator[rho, theta_idx] += 1
def hough_acc(edge_img, rho_resolution= 1, theta_resolution = 1):
rows, cols = edge_img.shape
d = np.round(np.sqrt(rows**2 + cols**2))
thetas = np.arange(-90, 90, theta_resolution)
rhos = np.arange(-d, d + 1, rho_resolution)
#all cos, sin thetas 0 from 180 degree
cos_thetas = np.cos(np.deg2rad(thetas))
sin_thetas = np.sin(np.deg2rad(thetas))
accumulator = np.zeros((len(rhos), len(thetas)))
cnt = 0
for row in range(rows):
for col in range(cols):
# if seletecd point is edge
if edge_img[row, col] != 0:
cnt += 1
# check all possible thetas
for theta_idx in range(len(thetas)):
# rho = (x_dist * cos(theta)) + (y_dist * sin(theta))
rho =int(d + (col* cos_thetas[theta_idx])+(row *sin_thetas[theta_idx]))
accumulator[rho, theta_idx] += 1
return accumulator, rhos, thetas
위 함수로부터 acc와 rhos, theta를 전달받아 임계치를 넘는
rho와 theta들을 모아 반환해주는 함수를 구현하였다.
예를 들자면 아래와 같이 점 3개가 있는데
각 점에서 모든 방향으로 누적을 시키게 된다.
하지만 세점이 이어진 직선이 세 번 겹치므로 accumulator에서 가장 큰 값을 가지게 된다.
이 때, accumulator[rho, theta_idx] = 겹친 횟수이며
이 겹친 횟수가 임계치를 넘으면 직선으로 포함시킨다.
def find_peak(accumulator, rhos, thetas, threhsold=60):
lines = []
for y in range(accumulator.shape[0]):
rho = rhos[y]
for x in range(accumulator.shape[1]):
if accumulator[y][x] > threhsold:
theta = np.deg2rad(thetas[x])
lines.append([rho, theta])
return np.array(lines)
마지막으로 원본 이미지와
위 함수에서 반환한 직선으로
라인을 플로팅 시키는 함수
rho와 theta로 부터
직선을 구하는데
x0, y0가 (그리고자 하는) 법선과 만나는 점이고,
x1,y1 x2,y2는 이미지 밖에서 부터 그려주기 위해 구한 좌표가 된다.
def draw_hough_lines(img, lines):
res = img.copy()
for i, line in enumerate(lines):
rho = line[0]
theta = line[1]
# reverse engineer lines from rhos and thetas
a = np.cos(theta)
b = np.sin(theta)
x0 = a*rho
y0 = b*rho
# these are then scaled so that the lines go off the edges of the image
x1 = int(x0 + 1000*(-b))
y1 = int(y0 + 1000*(a))
x2 = int(x0 - 1000*(-b))
y2 = int(y0 - 1000*(a))
cv2.circle(res, (int(x0), int(y0)),2,(0,0,255),1)
cv2.line(res, (x1, y1), (x2, y2), (0, 255, 0), 1)
plt.imshow(res,cmap="gray")
위 이미지로 부터 에지를 구할수가 있었다.
acc, rhos, thetas = hough_acc(morph)
lines = find_peak(acc, rhos,thetas,threhsold=40)
print(lines[:5])
draw_hough_lines(img, lines)
그런데 구한 rho와 theta 값이 이해되지 않았다.
rho = 38, theta= -0.7853인데
theta는 라디안이므로 육십분법으로 변환하면
- 44가 되더라
어딜 기준으로 -44도로 38만큼 떨어진 지점에서 직선을 그렷길래 찾았나 했더니
이미지 평면의 원점이 기준이더라.
일반 적인 xy평면에서
y 양의 방향으로 theta가 +, y 음의 방향으로 theta가 -가 됬었는데
y방향이 반대로 되면서 theta가 +인 경우 아래로, theta가 -인 경우 위로 가는거더라
이부분 때문에 좀 많이 햇갈렸다.
원래는 일반적인 사진에서 직선 검출을 하려고 했지만
픽셀 단위로 연산하고, 모든 픽셀들을 다루다보니 상당히 시간이 오래 걸리더라
이 부분을 보니 아에 행렬, 벡터 단위로 처리하는 코드도 있더라
그렇게 수정하면 조금 나아 지지않을까 싶다.
대신 위 이미지에 라인을 몇개 더 그려서 검출 해봤다.
임계치를 40으로 주었을때 아래와 같은 결과를 얻을수 있었다.
여기서 lines의 앞 다섯개 요소를 보니
rho가 -65, theta가 -1.38 정도 되더라
이게 어느직선인가 보기 위해서
호도법을 육십분법으로 바꾸고 원점에서 찾아보았다.
rho = 65, theta= - 80 정도로 대강 저 에지에서 검출한 직선인것 같다.
'로봇 > 영상' 카테고리의 다른 글
컴퓨터 비전 알고리즘 구현 - 13. 행렬 연산을 이용한 허프라인 구하기 (0) | 2020.12.06 |
---|---|
컴퓨터 비전 알고리즘 구현 - 11. 허프 변환 (0) | 2020.12.03 |
컴퓨터 비전 알고리즘 구현 - 10. 캐니 에지 검출기 만들기 (0) | 2020.12.01 |
컴퓨터 비전 알고리즘 구현 - 9. 이미지 그라디언트 (0) | 2020.11.30 |
컴퓨터 비전 알고리즘 구현 - 8. 모폴로지 연산 (0) | 2020.11.30 |