자율주행/ROS python (xytron 강의)

슬라이딩 윈도우 기반 차선인식

Tony Lim 2021. 1. 26. 22:27
728x90

1. Image Read == 카메라 영상신호를 이미지로 읽기

2. Warping == 원rms 변환으로 이미지 변형. 4점을 지정해서 저번에 했던 perspective 를 진행함

3. Gaussian Blur == 노이즈 제거 

4. Threshold == 이진 이미지로 변환 ,  cvtColor 를 통해 BGR 을 HLS으로 포맷 변환후 이진화 처리

5. Historgram == 히스토그램에서 차선의 x 좌표 위치 추출

6. Sliding Window == 슬라이딩 윈도우 좌우 x 좌표에 9개 씩 쌓기

7. Polyfit == 2차 함수 그래프로 차선 그리기

8. 차선 영역 표시 == 원본 이미지에 차선 영역 겹치게 오버레이

 

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import numpy as np
import cv2, random, math, copy

Width = 640
Height = 480

cap = cv2.VideoCapture("xycar_track1.mp4")
window_title = 'camera'

warp_img_w = 320
warp_img_h = 240

warpx_margin = 20
warpy_margin = 3

nwindows = 9
margin = 12
minpix = 5

lane_bin_th = 145

warp_src  = np.array([
    [230-warpx_margin, 300-warpy_margin],  
    [45-warpx_margin,  450+warpy_margin],
    [445+warpx_margin, 300-warpy_margin],
    [610+warpx_margin, 450+warpy_margin]
], dtype=np.float32)

warp_dist = np.array([
    [0,0],
    [0,warp_img_h],
    [warp_img_w,0],
    [warp_img_w, warp_img_h]
], dtype=np.float32)

calibrated = True
if calibrated:
    mtx = np.array([
        [422.037858, 0.0, 245.895397], 
        [0.0, 435.589734, 163.625535], 
        [0.0, 0.0, 1.0]
    ])
    dist = np.array([-0.289296, 0.061035, 0.001786, 0.015238, 0.0])
    cal_mtx, cal_roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (Width, Height), 1, (Width, Height))

def calibrate_image(frame):
    global Width, Height
    global mtx, dist
    global cal_mtx, cal_roi
    
    tf_image = cv2.undistort(frame, mtx, dist, None, cal_mtx)
    x, y, w, h = cal_roi
    tf_image = tf_image[y:y+h, x:x+w]

    return cv2.resize(tf_image, (Width, Height))

def warp_image(img, src, dst, size):
    M = cv2.getPerspectiveTransform(src, dst)
    Minv = cv2.getPerspectiveTransform(dst, src)
    warp_img = cv2.warpPerspective(img, M, size, flags=cv2.INTER_LINEAR)

    return warp_img, M, Minv

def warp_process_image(img):
    global nwindows
    global margin
    global minpix
    global lane_bin_th

    blur = cv2.GaussianBlur(img,(5, 5), 0)
    _, L, _ = cv2.split(cv2.cvtColor(blur, cv2.COLOR_BGR2HLS))
    _, lane = cv2.threshold(L, lane_bin_th, 255, cv2.THRESH_BINARY)

    histogram = np.sum(lane[lane.shape[0]//2:,:], axis=0)      
    midpoint = np.int(histogram.shape[0]/2)
    leftx_current = np.argmax(histogram[:midpoint])
    rightx_current = np.argmax(histogram[midpoint:]) + midpoint

    window_height = np.int(lane.shape[0]/nwindows)
    nz = lane.nonzero()

    left_lane_inds = []
    right_lane_inds = []
    
    lx, ly, rx, ry = [], [], [], []

    out_img = np.dstack((lane, lane, lane))*255

    for window in range(nwindows):

        win_yl = lane.shape[0] - (window+1)*window_height
        win_yh = lane.shape[0] - window*window_height

        win_xll = leftx_current - margin
        win_xlh = leftx_current + margin
        win_xrl = rightx_current - margin
        win_xrh = rightx_current + margin

        cv2.rectangle(out_img,(win_xll,win_yl),(win_xlh,win_yh),(0,255,0), 2) 
        cv2.rectangle(out_img,(win_xrl,win_yl),(win_xrh,win_yh),(0,255,0), 2) 

        good_left_inds = ((nz[0] >= win_yl)&(nz[0] < win_yh)&(nz[1] >= win_xll)&(nz[1] < win_xlh)).nonzero()[0]
        good_right_inds = ((nz[0] >= win_yl)&(nz[0] < win_yh)&(nz[1] >= win_xrl)&(nz[1] < win_xrh)).nonzero()[0]

        left_lane_inds.append(good_left_inds)
        right_lane_inds.append(good_right_inds)

        if len(good_left_inds) > minpix:
            leftx_current = np.int(np.mean(nz[1][good_left_inds]))
        if len(good_right_inds) > minpix:        
            rightx_current = np.int(np.mean(nz[1][good_right_inds]))

        lx.append(leftx_current)
        ly.append((win_yl + win_yh)/2)

        rx.append(rightx_current)
        ry.append((win_yl + win_yh)/2)

    left_lane_inds = np.concatenate(left_lane_inds)
    right_lane_inds = np.concatenate(right_lane_inds)

    #left_fit = np.polyfit(nz[0][left_lane_inds], nz[1][left_lane_inds], 2)
    #right_fit = np.polyfit(nz[0][right_lane_inds] , nz[1][right_lane_inds], 2)
    
    lfit = np.polyfit(np.array(ly),np.array(lx),2)
    rfit = np.polyfit(np.array(ry),np.array(rx),2)

    out_img[nz[0][left_lane_inds], nz[1][left_lane_inds]] = [255, 0, 0]
    out_img[nz[0][right_lane_inds] , nz[1][right_lane_inds]] = [0, 0, 255]
    cv2.imshow("viewer", out_img)
    
    #return left_fit, right_fit
    return lfit, rfit

def draw_lane(image, warp_img, Minv, left_fit, right_fit):
    global Width, Height
    yMax = warp_img.shape[0]
    ploty = np.linspace(0, yMax - 1, yMax)
    color_warp = np.zeros_like(warp_img).astype(np.uint8)
    
    left_fitx = left_fit[0]*ploty**2 + left_fit[1]*ploty + left_fit[2]
    right_fitx = right_fit[0]*ploty**2 + right_fit[1]*ploty + right_fit[2]
    
    pts_left = np.array([np.transpose(np.vstack([left_fitx, ploty]))])
    pts_right = np.array([np.flipud(np.transpose(np.vstack([right_fitx, ploty])))]) 
    pts = np.hstack((pts_left, pts_right))
    
    color_warp = cv2.fillPoly(color_warp, np.int_([pts]), (0, 255, 0))
    newwarp = cv2.warpPerspective(color_warp, Minv, (Width, Height))

    return cv2.addWeighted(image, 1, newwarp, 0.3, 0)

def start():
    global Width, Height, cap

    _, frame = cap.read()
    while not frame.size == (Width*Height*3):
        _, frame = cap.read()
        continue

    print("start")

    while cap.isOpened():
        
        _, frame = cap.read()

        image = calibrate_image(frame)
        warp_img, M, Minv = warp_image(image, warp_src, warp_dist, (warp_img_w, warp_img_h))
        left_fit, right_fit = warp_process_image(warp_img)
        lane_img = draw_lane(image, warp_img, Minv, left_fit, right_fit)

        cv2.imshow(window_title, lane_img)

        cv2.waitKey(1)

if __name__ == '__main__':
    start()

 

1. warp_src == 주어진 영상의 원본 사다리꼴 좌표의 값을 잘 찾아서 넣어준다.

2. war_dist ==  임의의 직사각형의 좌표 값을 넣어준다.

3. calibrated == 자이카 카메라에 장착된 Calibration 보정값을 사용.  \

4. warp_image ==  M은 perspective transform 을 하는 매트릭스 , Minv 는 그 반대의 매트릭스 , warp_img 는 M의 변환이 적용이된 이미지

5. warp_process_image == 1. GaussianBlur 2. cvtColor 를 통해 BGR에서 HLS으로 변환

(HLS 포맷에서는 L채널을 이용하면 흰색선을 쉽게 구분할 수 있으며 LAB 포맷에서는 B채널을 이용하면 노란색선을 쉽게 구분할 수 있다.) split을 통해 HLS을 받아온다. 

threshold를 통해 L 채널을 바이너리화 해주고 lane 에 넣어준다.

lane 에서 세로축 반으로 slice 해준후에 axis =0 즉 (240,320) 인 lane에서 첫번쨰 축 을 싹다 더해줌으로 (320,) 으로 histogram 을 만들어 줄 수 있습니다.

midpoint 160을 기준으로 leftx_current ,rightx_current 를 알아내준다.

쌓을 window의 사이즈 , margin, height 등을 결제해준다. 

good_left_inds 는 위에서 nz[0] nz[1] 에 담긴것을 알아야 하는데 240 * 320 픽셀에 담긴 값중 0이 아닌것들을 index들을 nz[0] 에는 index[row][col] 중에 row 파트가 담겨잇고 nz[1]에는 col 이 담겨 있다. nz[0][12] 에 0 이 아닌게 존재한다. 라는 뜻이다. 즉 240 * 320 = 76800 중에 8360개의 흰 픽셀이 존재한다는 것이다. 


이제 위에 나온 window의 사이즈 를 만족하는 픽셀들을 추려 낸다 & 를 이용해서 완전히 4가지 조건을 만족하는지 , 즉 window안에 들어오는지 를 체크해준다. 
근데 확인해보면 무슨 7000 이상의 큰값들이 들어있다. 8360개의 흰점들중에 위의 window안에 들어가는것들이 309개 그리고 그것들의 index[row][col] 중 row 값만 나타난것이다. 

이것을 nz[1][good_left_inds] 를 해주면 nz[1]들의 값중에 good_left_inds를 index로 삼는 nz[1]의 값들만이 나온다. 즉 window의 사이즈를 만족하는 x 축, (index[row][col] 중에 col) 값들이 튀어 나오는것이다. 그것을 평균을 구해서 새로운 기준점인 leftx_current를 구하는 것이다. 오른쪽도 동일하다. 

lx ly rx ty 에 차곡 차곡 x,y좌표들의 중심점들을 모아둔다. lfit ,rfit은 이차곡선 함수를 만들때 써준다.

out_img[nz[0][left_lane_inds], nz[1][left_lane_inds]] = [255,0,0] 해당되는 왼쪽 선들의 픽셀 값을 BLUE로 바꿔준다. 

6. draw_lane ==  위의 비스듯함 사다리꼴을 perspective inverse를 통해서 다시 원래의 이미지에 겹쳐그릴수 있게 변환 시켜준다.

ploty =는 y축 좌표들을 쭈욱 넣어준다. 그것을 이차곡선함수를 통해 x값들을 구해준다. 

pts 에는 480개의 x좌표 y좌표 들이 있는데 이건 각각 오른쪽 왼쪽 240 개 씩 합 한 값들이다. 

color_wrap 은 빨간색 선사이와 파란색 선사이를 초록으로 가득 채우는 polygon을 생성해서 넣어준다.

newwrap 은 이제 Persepective를 invert해준 것을 넣어준다. 이것을 오버레이 기존이미지에 오버레이 시켜준다.

728x90