자율주행

9장 자율주행을 위한 이미지 머신러닝 III

coding art 2022. 10. 21. 17:53
728x90

9-7 YOLOv3 OpenCV 차선 감지

자율주행을 연구하고자 하는 초보자라 할지라도 YoLov3를 설치하여 pretrained 된 학습 웨이트 값을 사용하여 비데오 영상을 대상으로 반드시 객체 탐지를 실습해 볼 필요가 있으며 더 나아가 단편적으로 실습했던 OpenCV 비젼 코드에 의한 차선감지 코드를 작성하여 YoLov3 에 삽입하여 나름대로의 간단한 ADAS(Advanced Driving Assistance System) 수준의 코드를 작성해 보면 향 후 자율주행을 위한 인공지능 코드 개발에 자신감이 크게 들 것이라 본다.

 

YoLov3 라이브러리에 numpy를 추가한다. 휴 변환에서 π 값을 np.pi 로 참조하기 위함이다.

이어지는 아래의 # 처리된 내용들은 사용자 PC에 GPU가 없으므로 배제하였다. 만약 GPU 사용을 원한다면 위 3줄의 명령을 살려 실행하도록 한다.

 

아래의 파라메터 값들은 YoLOv3에서 사용하는 값드이므로 그대로 두자. model_size 의 경우 (360, 360,3) 정도까지 수정도 가능하긴하다. 하지만 이미지 분석 소프트웨어들이 모델 해상도로서 PyTorch 의 (224,224,3) 라든지 나름대로최적화된 값을 사용하므로 그대로 두는게 좋을 듯하다.

 

다음은 YoLov3 코드의 OpenCV 비젼 코드 수정에 필요한 파라메터 값들이다. 성공적으로 코드 실행 후 사용자 영상에 맞춰 값을 조절해야 할 필요가 있다.

 

Region Of Interest 함수는 이미지에 다각형 꼭지점 좌표값 세트를 주면 다각형 외부는 검정색 마스크 처리를 하며 다거형 내부에서는 차선을 찾아서 마름모꼴로 둘러싸는 알고리듬이다.

 

YoLov3 객체 탐지를 위한 model 설정, 웨이트 값 로딩, 80개의 학습된 클라스 라벨 명칭, YoLov3 윈도우 명을 설정해 주고 cv2.VideoCaptture에서 웹캠 대신 도로주행 비데오 영상 ‘solidWhiteRight.mp4’를 사용하자.

 

한편 읽어 들인 비데오 영상의 폭과 높이 해상도를 출력해 보면 (960, 540)임을 알 수 있다. 이 크기는 YoLov3 의 객체탐지를 위한 모델 (416, 416) 과 다를 수있다.

 

함수 def main():에서 YOLOv3Net을 사용하여 model 을 선언하고 웨이트 값과 클라스의 라벨명을 준비하여 후반부에서 객체를 탐지한.

 

웹캠 사용 시 값은 Default 로 30이며 이는 웹캠이 제공하는 파라메터이다.

하지만 YoLov3 에서는 자체적으로 실시간 fps를 계산함에 유의하자.

YoLov3 객체 탐지가 된 영상을 fourcc 에 지정된 대로 avi 나 mp4 확장자 파일로 저장하자.

 

영상을 계속 읽을 수 있도록 무한루프인 while True: 를 실행하자. cv2.VideoCapture 명령 실행 후 영상처리 불리언 결과인 ret  False 이면 break 중단 처리하고 이상이 없으면 YoLo 처리에 앞서 비젼 처리 과정을 시작한다.

카메라에 찍힌 이미지 frame 하나하나를 흑백(gray) 처리한 후 5X5 커늘을 사용하여 GaussianBlur 필터링 처리하자. 이어서 Canny 필터를 사용하여 엣지 처리한다.

아무튼 Canny 와 ROI 와 Hough 처리를 한다고 해도 객체 간의 특징 점을 연결하는 수평선에 가까운 노이즈들이 지나치게 많이 검출되기 때문에 이런 부분을 필터링하기 위해서 vertices 에 의한 다각형 법위를 설정하여 검출된 엣지들을 대상으로 마스크 작업은 필수적이다.

ROI 는 region of interest 함수를 불러 사용한다. Hough 처리에 얻어진 직선 데이터들을 작도하여 비전 처리 과정이 종료되면 이 결과를 대상으로 YoLoV3 알고리듬에 의한 처리를 시작한다.

함수 def main(): 의 초반에 준비해 두었던 model을 사용하여 YoLov3 에 의한 객체 탐지 작업을 실행한다.

비젼코드 처리된 image를 frame 으로 두자. frame 은 tf.expans_dim( ) 명령으로 처리하여 resized_frame 텐서로 바꾼다. 텐서dls resized_frame 은 함수 resized_image 의 인수로 제공된다. Yolov3 model 에 의한 predict 작업을 실행하고 output_boxes 함수를 사용하여 객체를 탐지한 후 draw_output 함수를 사용하여 이미지에 사각형을 작도하여 출력한다.

마지막 부분은 전형적인 OpenCV 코드와 유사하다. 함수 main() 이 종료되면 main 코드에서 실행시킨다.

실제 자율주행 차량에서는 비젼 처리 결과에 따라 제어문을 넣어서 경고음 처리라든지 아니면 운전대 조향 제어를 해야 할 것이다. 윈도우즈 PC가 아닌 젯슨나노라든지 또는 라즈베리 파이 보드를 사용한다면 OS가 Ubuntu 나 Rasbean 이므로 GPIO 라이브러리 및 하드웨어 인터페이스가 지원되며 이를 통해 스피커나 액츄에이터나 모터를 제어할 수 있을 것이다.

 

아래의 코드 lanevideo.py 와 동영상 SolidWhiteRight.mp4 는 YOLOv3 가 설치된 darknet-master-YOLO3 폴더 내에 위치해야 한다.

solidWhiteRight.mp4
2.52MB

 

#lanevideo.py

import tensorflow as tf
import numpy as np
from utils import load_class_names, output_boxes, draw_outputs, resize_image
from yolov3 import YOLOv3Net
import cv2
import time

#physical_devices = tf.config.experimental.list_physical_devices('GPU')
#assert len(physical_devices) > 0, "Not enough GPU hardware devices available"
#tf.config.experimental.set_memory_growth(physical_devices[0], True)

model_size = (416, 416,3)
num_classes = 80
class_name = './data/coco.names'
max_output_size = 100
max_output_size_per_class= 20
iou_threshold = 0.5
confidence_threshold = 0.5
cfgfile = 'cfg/yolov3.cfg'
weightfile = 'weights/yolov3_weights.tf'

low_threshold = 100
high_threshold =250
kernel_size = 5
rho = 2
theta = np.pi/180
threshold = 50
min_line_len = 100
max_line_gap = 200

def region_of_interest(img, vertices):
    """
    Applies an image mask.
    
    Only keeps the region of the image defined by the polygon
    formed from `vertices`. The rest of the image is set to black.
    """
    #defining a blank mask to start with
    mask = np.zeros_like(img)   
    #defining a 3 channel or 1 channel color to fill the mask with depending on the input image
    if len(img.shape) > 2:
        channel_count = img.shape[2]  # i.e. 3 or 4 depending on your image
        ignore_mask_color = (255,) * channel_count
    else:
        ignore_mask_color = 255        
    #filling pixels inside the polygon defined by "vertices" with the fill color    
    cv2.fillPoly(mask, vertices, ignore_mask_color)
    
    #returning the image only where mask pixels are nonzero
    masked_image = cv2.bitwise_and(img, mask)
    return masked_image

def main():
    model = YOLOv3Net(cfgfile,model_size,num_classes)
    model.load_weights(weightfile)
    class_names = load_class_names(class_name)
    win_name = 'Yolov3 detection'
    cv2.namedWindow(win_name)
    #specify the vidoe input.
    # 0 means input from cam 0.
    # For vidio, just change the 0 to video path
    #cap = cv2.VideoCapture('video1.avi')
    #cap = cv2.VideoCapture(0)
    cap = cv2.VideoCapture('solidWhiteRight.mp4')
    width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
    height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
    frame_size = (width,height)
    print("frame size:", frame_size)
    fps = 30
    #fourcc = cv2.VideoWriter_fourcc(*"XVID")
    fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
    out = cv2.VideoWriter('result.mp4', fourcc, fps, (int(width), 
                                                      int(height)))
    try:
        while True:
            start = time.time()
            ret, frame = cap.read()
            if not ret:
                break
            
            # Start Vision Process!
            
            image = np.copy(frame)
            gray_img = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
            blur_img = cv2.GaussianBlur(gray_img, (kernel_size, kernel_size), 0)        
            edges = cv2.Canny(blur_img, low_threshold, high_threshold) 
            #cv2.imshow("canny", edges)
            vertices = np.array([[(0,image.shape[0]),(450, 315), (490, 315), (image.shape[1],image.shape[0])]], dtype=np.int32)
            masked_edges = region_of_interest(edges,vertices)
            #cv2.imshow("masked", masked_edges)
            lines = cv2.HoughLinesP(masked_edges, rho, theta, threshold, np.array([]), minLineLength=min_line_len, maxLineGap=max_line_gap)
            
            for line in lines:
                x1,y1,x2,y2 = line[0]
                cv2.line(image, (x1, y1), (x2, y2), (0,0,255), 3)
            image = cv2.resize(image,(480,270),interpolation = cv2.INTER_AREA)    
            #cv2.imshow("Hough Lines", image)
           
            frame = image
            #print(frame.shape)
            resized_frame = tf.expand_dims(frame, axis=0)
            #print(frame.shape)
            resized_frame = resize_image(resized_frame, (model_size[0],model_size[1]))
            pred = model.predict(resized_frame)
            boxes, scores, classes, nums = output_boxes( \
                pred, model_size,
                max_output_size=max_output_size,
                max_output_size_per_class=max_output_size_per_class,
                iou_threshold=iou_threshold,
                confidence_threshold=confidence_threshold)
            img = draw_outputs(frame, boxes, scores, classes, nums, class_names)
            
            cv2.imshow(win_name, img)
            out.write(img)
            stop = time.time()
            seconds = stop - start
            # print("Time taken : {0} seconds".format(seconds))
            # Calculate frames per second
            fps = 0.001 / seconds
            print("Estimated frames per second : {0}".format(fps))
            key = cv2.waitKey(1) & 0xFF
            if key == ord('q'):
                break
    finally:
        cv2.destroyAllWindows()
        cap.release()
        print('Detections have been performed successfully.')
if __name__ == '__main__':
    main()