본문 바로가기
Programming/Capstone Design

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

by NoiB 2022. 2. 24.
반응형

이번에는 지난 포스팅에 이어서 관심영역 설정까지 완료된 이미지에서 직선의 검출을 해보도록 하겠습니다. 사실상 차선 인식에서 가장 중요한 단계라고도 할 수 있겠네요. 이전까지도 물론 중요했지만 이번 작업을 위한 전처리라고 생각해주시면 좋을 것 같습니다.

자 지난 포스팅에서 여기까지 했었죠? 깔끔하게 차선의 엣지가 잘 잡혔습니다. 이번에는 이 엣지들을 이용해서 직선의 검출을 해볼겁니다. 엥? 이미 직선이 잘 나와있는데요? 사람은 이 사진을 보면 당연히 저걸 보고 직선이라고 생각할지도 모르겠습니다. 하지만 컴퓨터가 봤을 때는 단지 하얀 부분은 255, 검은 부분은 0 의 배열로 보일 뿐이에요. 우리는 이 배열을 이용해서 직선을, 궁극적으로는 써먹을 수 있는 직선의 방정식을 얻을겁니다.

 

허프 변환(Hough Transformation)

이번에도 이미지 프로세싱을 공부했던 분들은 익숙한 단어라고 생각이 드는데요. 가장 보편적으로 사용하는 직선 검출 알고리즘이라고 해도 무방하겠죠. 저도 해당 알고리즘에 대해서 사용하기 전에 많은 공부를 했었지만 내용이 꽤 많기에 간단하게 말하면 x-y의 일반적인 직교좌표 공간을 r-theta의 극좌표 공간으로 변환하여 이용하는 것이라고 생각하면 쉬울까요? 정확하게 다 설명이 되는 이야기는 아닙니다만... 가령 y = ax + b 라는 직선의 방정식이 있다고 해보겠습니다. 해당 직선을 그리면 1차함수의 그래프가 그려지겠죠. 하지만 해당 방정식을 r-theta 공간에 그리면 어떻게 될까요? 눈치가 빠른 분들은 이미 알아차렸겠지만 하나의 점으로 표현이 가능해집니다(직선의 기울기는 직선위의 어느 점에서도 같은 값을 가지므로). 다시 위의 그림과 같이 보면서 생각을 해볼까요? Edge Detection을 통해 얻어낸 위의 사진은 검은 배경에 흰 점들이 찍혀있는 것이라고 생각할 수 있습니다. 해당 점들은 r-theta의 공간에서는 직선으로 나오게 되겠죠? 그 직선들이 만나는 교점을 (a,b) 라고 할때 위 교점은 x-y 공간에서의 기울기와 절편값을 의미하게 됩니다. 즉 직선의 방정식으로 볼 수 있다는 얘기에요.

글로만 써놓으니 이게 도통 무슨 소린가 하실겁니다. 하지만 저희의 목표는 허프 변환 그 자체를 구현하는게 아니죠? 허프 변환은 우리가 직선을 얻기 위한 방법일 뿐입니다. 개인적으로 관심이 있으신 분들은 추가적으로 다른 자료를 찾아보시고 저희는 빨리 허프 변환을 사용해보도록 합시다.

 

다행히도 OpenCV는 허프변환을 구현해놓았습니다. 저희는 만들어져있는 함수를 쓰면 되는거에요. OpenCV에서 사용할 수 있는 허프변환은 확률적 허프 변환과 허프 변환 이렇게 두 가지가 있습니다. 허프 선 검출은 모든 점에 대해 수많은 선을 그어서 직선을 찾기 때문에 연산량이 무척 많습니다. 이를 개선하기 위한 방법이 확률적 허프 선 변환입니다. 이는 모든 점을 고려하지 않고 무작위로 선정한 픽셀에 대해 허프 변환을 수행하고 점차 그 수를 증가시키는 방법입니다.

 

저같은 경우에는 라즈베리파이 환경에서 구동하는게 목표였기 때문에 어떻게든 코드를 가볍게 하려고 바로 확률적 허프 변환을 했었습니다만 여러분은 목적에 따라서 선택을 하시면 될 것 같습니다. 일단 여기서는 확률적 허프 변환에 대해서 진행을 해볼게요.

 

def hough(img,h,w):
    lines = cv2.HoughLinesP(img, rho=1, theta=np.pi/180, threshold=30, minLineLength=10, maxLineGap=30)
    line_img = np.zeros((h, w, 3), dtype=np.uint8)
    for line in lines:
        for x1,y1,x2,y2 in line:
            cv2.line(line_img, (x1, y1), (x2, y2), color=[255,0,0], thickness=2)
    return line_img

해당 함수는 이런 형태입니다. OpenCV에서 지원하는 확률적 허프 변환을 이용해서 lines라는 배열을 만들어줍시다. 해당 배열은 점의 좌표가 들어가있는 배열입니다. 또한 아래에서 선을 출력하기위해 원본 이미지와 동일한 사이즈의 텅빈 이미지를 생성해줍시다(사이즈 옆에 있는 3은 채널입니다. 입력하지 않으면 해당 이미지는 흑백으로만 이루어집니다). 반복문을 이용해서 모든 라인을 그려주고 이미지를 반환합니다.

 

조금 얇게 출력이 되긴 했지만 이건 thickness를 개인적으로 조절하여 사용하시면 됩니다. 또한 OpenCV는 기본적으로 RGB가 아닌 BGR의 순서를 가지고 있기 때문에 color를 255,0,0으로 넣으면 파란색으로 나오게 됩니다.

 

진작에 정리를 좀 했어야 했는데 뒤늦게 쓰려고 하니까 잘 기억이 안나서 막 찾아보고 새로 짜고 하느라고 시간이 더 걸리네요. 나름대로 쉽게 설명을 드리려고 하는데 잘 전달되는지 모르겠습니다. 해당 포스팅이 도움이 되셨으면 좋겠네요.

 

전체 코드 :

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([[(w/10,h), (w/10,h*3/4), (w*9/10,h*3/4), (w*9/10,h)]], dtype=np.int32)
    cv2.fillPoly(mask, vertices, 255)
    roi_img = cv2.bitwise_and(img, mask)
    return roi_img

def hough(img,h,w):
    lines = cv2.HoughLinesP(img, rho=1, theta=np.pi/180, threshold=30, minLineLength=10, maxLineGap=30)
    line_img = np.zeros((h, w, 3), dtype=np.uint8)
    for line in lines:
        for x1,y1,x2,y2 in line:
            cv2.line(line_img, (x1, y1), (x2, y2), color=[255,0,0], thickness=2)
    return line_img

if __name__ == '__main__':
    origin_img = cv2.imread('./lane_detection_sample.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)
    hough_img = hough(roi_img,h,w)

    cv2.imwrite('./hough_img.jpg',hough_img)
    cv2.imshow('hough',hough_img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
반응형