상세 컨텐츠

본문 제목

차선 인식과 자동차 인식 동시에 하기 프로젝트

자율주행/컴퓨터 비젼 프로젝트(python)

by nosungmin 2023. 2. 14. 23:41

본문

import cv2
import IPython
import numpy as np
import time
import math
from google.colab.patches import cv2_imshow
 
min_confidence = 0.5
weight_file = 'yolov3.weights'
cfg_file = 'cfg/yolov3.cfg'
name_file = 'data/coco.names'

file_name = 'cabc30fc-e7726578.mp4'
 
이 파일들은 이번 프로젝트에 쓰일 가중치 파일과 딥러닝 모델, 데이터셋, 비디오 영상이다.
# Load YOLO
net = cv2.dnn.readNet(weight_file, cfg_file)
classes = []
with open(name_file, 'r'as f:
    classes = [line.strip() for line in f.readlines()]
print(classes)
 
이 코드는 name_file 경로에서 클래스 이름이 포함된 파일을 열어서 클래스 이름을 리스트로 읽어들이는 것입니다. with문을 사용하여 파일을 열고, readlines() 함수를 사용하여 파일의 모든 라인을 읽은 후에 각 라인의 공백이나 개행 문자를 제거하여 클래스 이름만 추출하고 리스트 classes에 할당합니다. 마지막으로 print() 함수를 사용하여 추출된 클래스 이름이 잘 저장되었는지 출력합니다. 이 코드는 YOLOv3 모델의 클래스 이름이 포함된 파일을 읽어들이는 데 사용될 수 있습니다.
layer_names = net.getLayerNames()
output_layers = [layer_names[i[0] - 1for i in net.getUnconnectedOutLayers()]
 
이 코드는 net 객체에서 레이어 이름 리스트를 가져와서 출력 레이어의 이름을 가져오는 것입니다. getLayerNames() 함수를 사용하여 net 객체의 모든 레이어의 이름을 가져옵니다. 그런 다음, getUnconnectedOutLayers() 함수를 사용하여 출력 레이어의 인덱스를 가져옵니다. 이 인덱스는 1부터 시작하므로 1을 뺀 다음 해당 인덱스의 레이어 이름을 가져와 output_layers 리스트에 추가합니다. 이 코드는 YOLOv3 모델의 출력 레이어의 이름을 가져오는 데 사용될 수 있습니다.
frame_count = 0
writer = None
output_name = 'output_video_car.avi'
def writeFrame(img):
    # use global variable, writer
    global writer
    height, width = img.shape[:2]
    if writer is None and output_name is not None:
        fourcc = cv2.VideoWriter_fourcc(*'MJPG')
        writer = cv2.VideoWriter(output_name, fourcc, 24, (width, height), True)
    if writer is not None:
        writer.write(img)
 

이 코드는 OpenCV를 사용하여 비디오 파일을 작성하는 함수인 writeFrame(img)을 정의하는 코드입니다.

함수는 이미지 (img)를 인자로 받습니다. 먼저, img의 높이와 너비를 추출합니다. 그런 다음, 전역 변수인 writer를 사용하여 비디오 라이터 객체를 만들고 초기화합니다. 이를 위해, writer가 None이고 output_name이 지정된 경우, MJPG 코덱을 사용하고 프레임 속도는 24fps로 설정하여 비디오 라이터를 만듭니다.

비디오 라이터가 초기화되면 writer 객체를 사용하여 img를 쓰고 비디오 파일을 작성합니다. 쓰기 작업이 성공적으로 수행되면 True가 반환됩니다.

즉, 이 함수는 이미지를 비디오 파일에 쓰기 위한 OpenCV 비디오 라이터 객체를 생성하고 초기화한 다음, 전달받은 이미지를 비디오 파일에 씁니다.

 

def detectAndDisplay(frame):
    # use global variable, writer
    global frame_count
    frame_count += 1
    start_time = time.time()
    IPython.display.clear_output(wait=True)
    height, width, channedls = frame.shape

함수 내에서 사용되는 변수와 함수의 역할은 다음과 같습니다:

  • frame_count: 전역 변수로 선언된 프레임 수를 저장하는 변수입니다.
  • start_time: 현재 프레임의 처리 시작 시간을 저장하는 변수입니다.
  • IPython.display.clear_output(wait=True): IPython.display 모듈의 clear_output 함수를 호출하여 출력된 내용을 모두 지우고, wait=True 옵션을 통해 현재 출력을 유지합니다.
  • height, width, channels: 입력된 이미지(frame)의 높이, 너비, 채널 수를 저장하는 변수입니다. 이 변수들은 얼굴 검출 및 사각형 그리기 함수에서 사용됩니다.
    blob = cv2.dnn.blobFromImage(frame, 0.00392, (416416), (000), True, crop=False)
 

cv2.dnn.blobFromImage() 함수는 입력 이미지를 딥러닝 모델에 입력할 수 있는 형태인 BLOB(binary large object)으로 변환하는 함수입니다. BLOB은 일반적으로 이미지나 비디오 같은 이진 데이터를 다룰 때 사용되는 데이터 형식입니다.

    net.setInput(blob)
    outs = net.forward(output_layers)
    class_ids = []
    confidences = []
    boxes = []
    for out in outs:
        for detection in out:
            scores = detection[5:]
            class_id = np.argmax(scores)
            confidence = scores[class_id]
            # Filter only car (2)
            if (confidence > min_confidence) and (class_id == 2):
                # Object detected
                center_x = int(detection[0] * width)
                center_y = int(detection[1] * height)
                w = int(detection[2] * width)
                h = int(detection[3] * height)

 

                # Rectangle coordinates
                x = int(center_x - w / 2)
                y = int(center_y - h / 2)

 

                boxes.append([x, y, w, h])
                confidences.append(float(confidence))
                class_ids.append(class_id)
 

이 코드는 YOLO(You Only Look Once) 딥러닝 모델을 이용해 입력된 이미지에서 자동차를 검출하고, 검출된 자동차 주위에 사각형을 그려주는 역할을 합니다.

여기서 사용된 변수와 함수의 역할은 다음과 같습니다:

  • outs: YOLO 모델의 출력을 저장하는 리스트입니다. 각 원소는 이미지에서 검출된 객체에 대한 정보를 담고 있습니다.
  • detection: 검출된 객체 정보 중 하나의 객체 정보를 담고 있는 리스트입니다.
  • scores: detection 리스트에서 5번째 인덱스부터 끝까지의 값은 해당 객체가 각 클래스에 속할 확률을 나타냅니다.
  • class_id: 확률값이 가장 높은 클래스의 인덱스를 저장하는 변수입니다.
  • confidence: 해당 객체가 차량(class_id=2)일 확률 값을 저장하는 변수입니다.
  • center_x, center_y, w, h: detection 리스트에서 각각 객체의 중심 좌표와 가로 세로 길이를 계산하여 저장하는 변수입니다.
  • x, y: 검출된 객체 주위에 그려질 사각형의 좌측 상단 꼭지점 좌표를 저장하는 변수입니다.
  • boxes: 검출된 차량에 대한 바운딩 박스 정보(x, y, w, h)를 저장하는 리스트입니다.
  • confidences: 검출된 차량의 확률값을 저장하는 리스트입니다.
  • class_ids: 검출된 차량의 클래스 인덱스를 저장하는 리스트입니다.

따라서 이 코드는 YOLO 모델의 출력을 이용하여 자동차를 검출하고, 검출된 자동차 주위에 사각형을 그리며, 검출된 차량에 대한 정보를 저장하는 것입니다.

    # Region of Interest
    mask = np.zeros((height,width), dtype='uint8')
    poly_top = int(0.65 * height)
    poly_bottom = int(0.85 * height)
    poly_left = int(0.47 * width)
    poly_right = int(0.53 * width)
    roi_left = int(0.3 * width)
    roi_right = int(0.6 * width)
    poly_margin = 50
 
    polygons = np.array([[(0+poly_margin,poly_bottom), (poly_left, poly_top), (poly_right, poly_top), (width-
poly_margin, poly_bottom)]])
#이 코드는 NumPy 라이브러리를 이용하여 다각형을 정의하는 것입니다.

#polygons 변수는 NumPy 배열로 정의되며, 배열의 각 항목은 다각형의 꼭지점 좌표를 나타내는 NumPy 배열입니다. 이

#예시에서는 4개의 점으로 이루어진 사다리꼴 모양의 다각형을 정의하고 있습니다.

#poly_margin, poly_bottom, poly_left, poly_top, 그리고 width는 모두 정수형 또는 부동소수점 숫자값이며, 다각형의 크기

#와 위치를 조정하는 데 사용됩니다.

  • #poly_margin: 다각형 왼쪽과 오른쪽 모서리에서부터의 거리를 나타내는 값
  • #poly_bottom: 다각형 하단 모서리의 y 좌표값
  • #poly_left: 다각형 왼쪽 모서리의 x 좌표값
  • #poly_top: 다각형 상단 모서리의 y 좌표값
  • #width: 다각형이 위치할 수 있는 최대 x 좌표값

#즉, 이 코드는 poly_margin, poly_bottom, poly_left, poly_top, width 값을 조정하여, 다각형의 위치와 크기를 조정하는 것

#입니다.

 

    cv2.fillPoly(mask, polygons, 255)
 

이 코드는 입력 이미지에서 차선을 검출하기 위해 관심 영역(Region of Interest, ROI)을 설정하는 과정입니다. ROI는 이미지에서 차선이 위치할 가능성이 높은 영역으로 제한함으로써 불필요한 부분을 제거하여 차선 검출의 정확도를 향상시킵니다.

여기서 사용된 변수와 함수의 역할은 다음과 같습니다:

  • mask: ROI 영역을 표현하는 마스크 이미지입니다.
  • poly_top, poly_bottom, poly_left, poly_right: ROI를 구성하는 다각형의 좌표값을 계산하는데 사용되는 변수입니다. poly_top은 다각형의 윗쪽 변의 y좌표값, poly_bottom은 아랫쪽 변의 y좌표값, poly_left는 왼쪽 변의 x좌표값, poly_right는 오른쪽 변의 x좌표값을 나타냅니다.
  • roi_left, roi_right: ROI 내부에서 차선 검출을 수행할 영역의 좌우 경계선 좌표값입니다.
  • poly_margin: ROI 다각형의 변에서 떨어진 간격을 설정하는 변수입니다.
  • polygons: ROI 다각형의 좌표값을 저장하는 배열입니다. ROI 다각형은 poly_top, poly_bottom, poly_left, poly_right의 값으로부터 계산되며, 이미지 외부 영역을 제외한 ROI 영역을 다각형으로 표현합니다.
  • cv2.fillPoly(): 다각형을 이용하여 마스크 이미지에 ROI 영역을 표시하는 함수입니다. cv2.fillPoly(mask, polygons, 255)는 mask 이미지에 polygons에 정의된 다각형의 내부를 흰색으로 채웁니다.

따라서 이 코드는 입력 이미지에서 ROI를 정의하고, ROI 영역에 해당하는 마스크 이미지를 생성하는 것입니다.

 

    indexes = cv2.dnn.NMSBoxes(boxes, confidences, min_confidence, 0.4)
    font = cv2.FONT_HERSHEY_COMPLEX
    margin = 5
 
    for i in range(len(boxes)):
        if i in indexes:
            x, y, w, h = boxes[i]
            # Bitwise operation between box and mask 
            box = np.array([[(x-margin, y+h+margin), (x-margin, y-margin), (x+w+margin, y), (x+w+margin, y+h+margin)]])
            cv2.fillPoly(mask, box, 0
            # Eliminate Small object(<50)
            if (w > 50and (x > roi_left) and (x < roi_right):
                label = str(classes[class_ids[i]])
                print(class_ids[i], label, w)
                cv2.rectangle(frame, (x, y), (x + w, y + h), (02550), 2)
                cv2.putText(frame, label, (x, y + 30), font, 0.5, (02550), 1
 

이 코드는 주어진 이미지에서 객체를 검출하고, 작은 객체를 제거하여 더 정확한 결과를 얻기 위한 코드입니다.

이 코드는 boxes 리스트에 저장된 모든 객체에 대해 반복합니다. indexes 리스트에 있는 인덱스들은 선택된 객체로 간주되며, 선택된 객체에 대해서만 작업이 수행됩니다.

각 객체의 위치와 크기를 나타내는 x, y, w, h 값을 사용하여, 객체 주위에 마스크를 만듭니다. 마스크는 검은색(0)으로 초기화됩니다. 다각형의 좌표는 box 배열에 저장되며, fillPoly() 함수를 사용하여 다각형 내부를 검은색으로 채웁니다. 이렇게 함으로써, 마스크는 객체가 위치한 영역을 검은색으로 지정합니다.

그런 다음, 객체의 크기가 50보다 크고, ROI(Region of Interest)의 범위 내에 위치하는 경우, frame에 객체를 표시하는데 사용되는 라벨(label), 좌표, 크기 등의 정보를 출력합니다. 이 정보를 rectangle() 함수를 사용하여 객체 주위에 박스를 그리고, putText() 함수를 사용하여 객체의 라벨을 표시합니다.

따라서 이 코드는, 객체를 검출하고, 마스크를 만들어 작은 객체를 제거한 후, frame에 객체를 표시하는 작업을 수행합니다.

 

    #cv2_imshow(mask)
    # Lane Detection
    gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
    # GaussianBlur for refucing noise
    blur = cv2.GaussianBlur(gray, (55), 0)
    canny = cv2.Canny(blur, 40130)

 

    # Bitwise operation between poly and mask
    masked = cv2.bitwise_and(canny, mask)
    #cv2_imshow(masked)
    # Lane Detection
    lines = cv2.HoughLinesP(masked, 2, np.pi / 18020, np.array([]), 2010)
    if lines is not None:
        for line in lines:
            for x1, y1, x2, y2 in line:
                cv2.line(frame, (x1,y1), (x2,y2), (0,  255255), 5)
 

이 코드는 이미지에서 차선을 감지하기 위한 코드입니다.

먼저, canny와 mask 이미지를 비트 연산(bitwise_and())을 수행하여, 차선 검출 영역만 마스킹된 이미지(masked)를 만듭니다.

그런 다음, HoughLinesP() 함수를 사용하여 masked 이미지에서 직선을 감지합니다. HoughLinesP() 함수는 Hough 변환 알고리즘을 사용하여, 이미지에서 직선을 검출하는 함수입니다. 함수의 매개변수는 다음과 같습니다.

  • masked: 직선 검출을 수행할 이미지
  • 2: 거리 분해능
  • np.pi / 180: 각도 분해능 (라디안 단위)
  • 20: 최소 직선 길이
  • np.array([]): 일반적으로 사용하지 않는 매개변수
  • 20: 직선 검출 임계값
  • 10: 검출된 선분에서 최대 간격

검출된 각 직선은 lines 변수에 저장되며, for 루프를 사용하여 모든 직선에 대해 반복합니다. cv2.line() 함수를 사용하여 각 직선을 frame 이미지에 그립니다.

따라서, 이 코드는 차선 검출을 수행하여 frame 이미지에 직선을 그리는 작업을 수행합니다.

 

    frame_time = time.time() - start_time 
    print("Frame {} time {}".format(frame_count, frame_time))
    cv2_imshow(frame)   
    writeFrame(frame)

이 코드는 각 프레임이 처리된 시간을 측정하고, 처리된 이미지(frame)를 화면에 출력하고, 동영상 파일에 프레임을 작성하는 작업을 수행합니다.

먼저, time.time() 함수를 사용하여 현재 시간을 측정한 다음, start_time과의 차이를 계산하여 해당 프레임의 처리 시간(frame_time)을 측정합니다.

그런 다음, print() 함수를 사용하여 해당 프레임의 번호(frame_count)와 처리 시간(frame_time)을 출력합니다.

cv2_imshow() 함수를 사용하여 frame 이미지를 화면에 표시합니다.

마지막으로, writeFrame() 함수를 사용하여 frame 이미지를 동영상 파일에 작성합니다.

따라서, 이 코드는 비디오 파일에서 각 프레임을 처리하고, 처리된 이미지를 출력하며, 처리 시간을 측정하고, 동영상 파일에 프레임을 작성하는 작업을 수행합니다.

 

#-- 2. Read the video stream
cap = cv2.VideoCapture(file_name)
if not cap.isOpened:
    print('--(!)Error opening video capture')
    exit(0)
while True:
    ret, frame = cap.read()
    if frame is None:
        print('--(!) No captured frame -- Break!')
        break
    detectAndDisplay(frame)

 

 

이 코드는 OpenCV의 cv2.VideoCapture() 함수를 사용하여 동영상 파일을 읽어들입니다. cap.isOpened를 사용하여 파일이 제대로 열렸는지 확인합니다. 그 후, cap.read() 함수를 사용하여 비디오 스트림에서 한 프레임씩 읽습니다.

detectAndDisplay() 함수를 사용하여 각 프레임을 처리하고, 이전에 설명한 것처럼 각 프레임을 처리하는 과정에서 프레임을 출력하고, 처리 시간을 측정하고, 동영상 파일에 프레임을 작성합니다.

while True 루프를 사용하여 비디오 스트림의 끝까지 모든 프레임을 읽고 처리하며, if frame is None으로 프레임이 없는 경우, 즉 동영상의 끝에 도달한 경우, 루프를 종료합니다.

따라서, 이 코드는 동영상 파일을 읽고, 각 프레임을 처리하여 동영상 파일을 출력하는 작업을 수행합니다.

관련글 더보기