아마도 이번 포스팅이 차선 인식의 마지막 포스팅이 되지 않을까 싶은데요. 지난 시간에 왼쪽&오른쪽 대표선을 검출해내는 것까지 진행을 했습니다. 차선 인식이 주제였으니까 이제 끝이라고 보면 될까요? 지난번에도 말씀을 드렸었지만 목적과 수단을 혼동해서는 안된다고 했었죠. 저희가 차선 인식 알고리즘을 구현했던 목적이 뭘까요? 차량이 주행을 함에 있어서 차선 안에서 움직이도록 하는 것이 목적이었죠. 차선 인식은 그를 위한 수단이었고요. 그렇다면 차선 인식만 해가지고 차량을 계속 차선 안에서 움직이도록 하는 것이 가능할까요? 아니죠. 그냥 선 두 개 화면에 띄운다고 갑자기 차가 자기가 알아서 움직일 리가 없죠.
그래서 우리는 차량이 차선 안에서 움직일 수 있도록 하는 파라미터를 만들어줘야겠죠. 사실 이제부터는 본인이 어떤 설계를 하려고 하는지에 따라서 진행과정이 완전히 달라질 것입니다. 일단 저는 모터를 사용해서 차량을 제어하려고 했습니다. 처음에는 조향파트와 전진을 담당하는 파트를 따로 구성을 하려고 했었으나 이런저런 토의를 통해서 따로 조향부를 두지 않고 구동부의 바퀴만 이용해서 조향을 하는 쪽으로 방향을 틀었는데요. 이미 프로젝트는 끝나서 확인할 방법은 없습니다만 바꾼 쪽이 좋은 선택이었는지는 아직도 의문입니다.
사설이 길었습니다만 저는 그래서 해당 부분을 소실점의 x좌표에 따라서 제어하는 방향으로 진행을 했습니다. 이번 프로젝트에 소실점을 적용시켜서 설명을 드려보자면 원근 때문에 평행선일지라도 단안시점 기준 한 점으로 모이는 것처럼 보이게 된다는 사실이 기억 나실겁니다. 혹시 차체의 중심선이 변위 오차도 각도 오차도 없이 정확하게 차선과 평행하다고 가정할 때 카메라로 해당 시점을 촬영하면 어떻게 보일까요? 정확하게 소실점과 카메라의 정중앙이 일치할 겁니다.
해당 사진은 약간 변위 오차가 있는 듯 보이지만 이해를 돕기 위해 삽입했다는 점을 감안해주시면 감사하겠습니다. 이때 붉은 선과 푸른 선을 연장하면 정확히 (width/2, height/2)의 좌표에서 만나게 되겠죠. 따라서 저는 해당 추측을 근거로 차선의 교점이 어디에 생기는지에 따라 조향을 하는 방향으로 프로젝트를 진행했습니다.
이번에는 굳이 따로 차선의 연장선에서 생기는 교점을 구하지 않고 이미 구해놓은 각 차선의 각도를 합해서 진행을 해보려고 했는데 각도 값이 제 생각과는 조금 다르게 나옵니다. 마치 0도의 기준선이 12시인 것 같아요. 그대로 진행을 해도 문제는 없을 것 같기는 해서 교점을 구하는 과정을 생략하고 양 차선의 기울기를 합한 값을 조향값으로 이용을 해볼까 합니다.
l_slopes, r_slopes = slope_deg[(slope_deg>0)], slope_deg[(slope_deg<0)]
l_slope = int(sum(l_slopes)/len(l_slopes))
r_slope = int(sum(r_slopes)/len(r_slopes))
steer_value = l_slope+r_slope
코드는 이렇게 간단하게 나옵니다. 교점을 구하는 과정도 다 빠졌기 때문에 수행 시간도 이전에 비해 줄어들 것으로 기대되네요. 참고로 해당 사진 기준으로 조향값은 -1 이었습니다. 차량을 시계방향으로 약간 회전해주면 되겠죠.
사실 처음에 글을 작성할 때까지만 해도 교점을 구해서 진행할 생각이어서 7번을 붙여서 진행을 했지만 문득 이 방법을 떠올리는 바람에 글이 상당히 줄어버렸네요. 아마 다음 포스팅에서는 시리얼 통신에 관해서 작성을 하게 될 것 같습니다.
import cv2
import numpy as np
def grayscale(img):
return cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
def gaussian_blur(img, kernel_size):
return cv2.GaussianBlur(img, (kernel_size, kernel_size), 0)
def canny(img, low_threshold, high_threshold):
return cv2.Canny(img, low_threshold, high_threshold)
def roi(img,h,w):
mask = np.zeros_like(img)
vertices = np.array([[(0,h), (0,h*2/3), (w,h*2/3), (w,h)]], dtype=np.int32)
cv2.fillPoly(mask, vertices, 255)
roi_img = cv2.bitwise_and(img, mask)
return roi_img
def restrict_deg(lines,min_slope,max_slope):
slope_deg = np.rad2deg(np.arctan2(lines[:,1]-lines[:,3],lines[:,0]-lines[:,2]))
lines = lines[np.abs(slope_deg)<max_slope]#cannot use and & index true catch
slope_deg = slope_deg[np.abs(slope_deg)<max_slope]
lines = lines[np.abs(slope_deg)>min_slope]
slope_deg = slope_deg[np.abs(slope_deg)>min_slope]#where can i use slope
return lines, slope_deg
def separate_line(lines,slope_deg):
l_lines, r_lines = lines[(slope_deg>0),:], lines[(slope_deg<0),:]
l_slopes, r_slopes = slope_deg[(slope_deg>0)], slope_deg[(slope_deg<0)]
l_line = [sum(l_lines[:,0])/len(l_lines),sum(l_lines[:,1])/len(l_lines),sum(l_lines[:,2])/len(l_lines),sum(l_lines[:,3])/len(l_lines)]
r_line = [sum(r_lines[:,0])/len(r_lines),sum(r_lines[:,1])/len(r_lines),sum(r_lines[:,2])/len(r_lines),sum(r_lines[:,3])/len(r_lines)]
l_slope = int(sum(l_slopes)/len(l_slopes))
r_slope = int(sum(r_slopes)/len(r_slopes))
return l_line, r_line, l_slope, r_slope
def hough(img,h,w,min_line_len,min_slope,max_slope):
lines = cv2.HoughLinesP(img, rho=1, theta=np.pi/180, threshold=30, minLineLength=min_line_len, maxLineGap=30)#return = [[x1,y1,x2,y2],[...],...]
lines = np.squeeze(lines)#one time ok
lanes, slopes = restrict_deg(lines,min_slope,max_slope)
l_lane, r_lane, l_slope, r_slope = separate_line(lanes,slopes)
#lane_img = np.zeros((h, w, 3), dtype=np.uint8)
#for x1,y1,x2,y2 in l_lanes:
#cv2.line(lane_img, (int(l_lane[0]), int(l_lane[1])), (int(l_lane[2]), int(l_lane[3])), color=[0,0,255], thickness=2)
#for x1,y1,x2,y2 in r_lanes:
#cv2.line(lane_img, (int(r_lane[0]), int(r_lane[1])), (int(r_lane[2]), int(r_lane[3])), color=[255,0,0], thickness=2)
return l_lane, r_lane, l_slope, r_slope
def lane_detection(min_line_len,min_slope,max_slope):
origin_img = cv2.imread('./left_right.jpg')
h,w = origin_img.shape[:2]
gray_img = grayscale(origin_img)
blur_img = gaussian_blur(gray_img, 5)
canny_img = canny(blur_img, 50, 200)
roi_img = roi(canny_img,h,w)
l_lane,r_lane,l_slope,r_slope = hough(roi_img,h,w,min_line_len,min_slope,max_slope)
steer_value = l_slope+r_slope#maybe 0 deg is on 12
cv2.line(origin_img, (int(l_lane[0]), int(l_lane[1])), (int(l_lane[2]), int(l_lane[3])), color=[0,0,255], thickness=5)
cv2.line(origin_img, (int(r_lane[0]), int(r_lane[1])), (int(r_lane[2]), int(r_lane[3])), color=[255,0,0], thickness=5)
return origin_img, steer_value
def nothing(pos):
pass
if __name__ == '__main__':
cv2.namedWindow(winname='Lane Detection')
cv2.createTrackbar('houghMinLine', 'Lane Detection', 20, 200, nothing)#don't write keyword
cv2.createTrackbar('slopeMinDeg', 'Lane Detection', 100, 180, nothing)
cv2.createTrackbar('slopeMaxDeg', 'Lane Detection', 160, 180, nothing)
while cv2.waitKey(1) != ord('q'):
min_line_len = cv2.getTrackbarPos(trackbarname='houghMinLine', winname='Lane Detection')
min_slope = cv2.getTrackbarPos('slopeMinDeg','Lane Detection')
max_slope = cv2.getTrackbarPos('slopeMaxDeg','Lane Detection')
result_img, steer_value = lane_detection(min_line_len,min_slope,max_slope)
cv2.imshow('Lane Detection',result_img)
cv2.imwrite('./hough_img3.jpg',result_img)
cv2.destroyAllWindows()
#It will be great that we can select the instant roi region using click when we run the code.
'Programming > Capstone Design' 카테고리의 다른 글
[Capstone Design]4. 시리얼 통신(Serial Communication) - 2 (0) | 2022.04.17 |
---|---|
[Capstone Design]4. 시리얼 통신(Serial Communication) - 1 (0) | 2022.04.17 |
[Capstone Design]3. 차선 인식(Lane Detection) - 6 (0) | 2022.04.15 |
[Capstone Design]3. 차선 인식(Lane Detection) - 5 (0) | 2022.04.14 |