본문 바로가기
Programming/Capstone Design

[Capstone Design]3. 차선 인식(Lane Detection) - 6

by NoiB 2022. 4. 15.
반응형

지난 포스팅까지 해서 이제 얼추 차선으로 써먹을만한 친구들만 뽑아내는 과정까지 진행했습니다. 혹시 코드를 직접 실행시켜보신 분이라면 최소 길이 트랙바를 조절하다 보면 차선 후보가 선 하나만 나오는 게 아니라 여러 개가 겹쳐있다는 사실을 눈치채셨을 것 같은데요. 이번 포스팅은 그렇게 겹쳐있는 선들을 하나로 만들고 중심 기준으로 왼쪽 차선과 오른쪽 차선을 분리해보는 시간을 가지겠습니다.

 

먼저 왼쪽 차선과 오른쪽 차선을 나누는 것 부터 먼저 해보겠습니다. 미리 나눠놓고 나중에 각각의 대표선을 계산하는 게 훨씬 효율적일 것 같거든요. 사실 왼쪽선 오른쪽 선을 나누는 것은 그렇게 어려운 일이 아닙니다. 잠깐 그림을 한 번 볼까요.

그림의 퀄리티는 신경쓰지 마시기 바랍니다. 왼선을 붉은색, 오른선을 푸른색으로 구분해놓았습니다. 이 두 선을 교차시켜서 보면 뭔가 감을 잡을 것 같은데요. 양 선은 각도로 구분이 가능할 것 같다는 생각이 드시나요? 맞습니다. 그냥 단순하게 0도보다 크면 왼선, 작으면 오른선으로 구분하면 되겠죠. 어차피 각도는 60도나 -120도나 같은 말이기에 이런 식으로 구분이 가능한 겁니다.

그럼 이제 왼쪽 오른쪽 구분은 했으니 자기들끼리 나눠서 대표선을 정해주면 되겠네요. 단순하게 x1,x2,y1,y2를 끼리끼리 모아서 더한 다음에 평균을 내어볼까요?

def separate_line(lines,slope_deg):
    l_lines, r_lines = lines[(slope_deg>0),:], lines[(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)]
    return l_line, r_line

구분 밑 대표선까지의 작업에 해당하는 코드입니다. 무식하게 짜는 바람에 길이가 좀 깁니다.

하나로 잘 출력되는 것 같기는 한데 이게 지금 잘나오고 있는 건지는 도통 모르겠죠? 그래서 이번엔 원본 사진 위로 겹쳐서 보도록 코드를 좀 수정해보겠습니다.

 약간 파란색이 잘 안맞는 듯한 느낌이 드네요. 이유는 아시다시피 선이 여러 개 나오는 상황에서 해당 선들의 각각 좌표를 평균 내서 하나의 직선으로 만드는 방식이기 때문에 그렇습니다. 그럼 선의 최소 길이를 조금 타이트하게 잡으면 훨씬 깔끔하게 나오겠죠. 과정을 동영상으로 잠깐 첨부해보겠습니다.

https://youtu.be/L3v599sjpN8

결과를 보면 훨씬 낫죠? 항상 이렇게 트랙바를 계속 만지고 있을 수는 없지만 나중에 어느 정도 상황이 통제될 때는 아마 이렇게 귀찮게 안 하셔도 될 겁니다. 오늘도 긴 글 읽으시느라 고생 많으셨습니다.

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_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)]
    return l_line, r_line

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 = 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



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 = hough(roi_img,h,w,min_line_len,min_slope,max_slope)
    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

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 = 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.
반응형