팀 프로젝트/자율주행 개발(baqu4)

[Python] OpenCV로 자율주행 만들기(Canny threshold_#02)

MIRIP 2022. 9. 10. 00:00
반응형

저번에 Canny threshold에서 추출한 물체의 좌표를 기반으로 선을 그어서 화면에 표현해 보았다.

  • 목표 : 콘 인식 및 주행
  • 개발환경 : IDLE
  • 언어 : python
  • 방식 : 단순 알고리즘(Canny threshold)

직전 포스팅에서 개선된 사항 및 발견한 오류에 관해 작성했다.

이전 포스팅을 보고 싶다면 아래 포스팅을 참조하기 바란다.

 

[Python] Canny threshold를 이용하여

이번에는 threshold방식을 이용하여 인식하고자 하는 물체의 윤곽선을 얻었다. 목표 : 콘 인식 및 주행 개발환경 : IDLE 언어 : python 방식 : 단순 알고리즘(Canny threshold) 위와 같이 계획을 세웠다. Canny

codezaram.tistory.com


먼저 저번 포스팅과 같이 Canny threshold 알고리즘을 사용하여 콘 인식 알고리즘을 개발하였다. 콘 인식을 하는 데에 있어서 가장 큰 문제점이 차선을 인식한다는 점이었는데, OpenCV의 Canny threshold와 관심 영역 지정 알고리즘은 콘을 제외한 배경을 모두 없애주었다. 이러한 점에서 Canny threshold 알고리즘이 지금껏 시도하고 실패한 모든 알고리즘 중 "콘 인식"에 가장 효과적이라는 점을 깨달았다. 이후에는 콘 인식에 알고리즘을 사용하고, 또 개선해 나갈 것이다.

가장 먼저 콘 인식을 하는 것에서 정확도가 떨어진다는 문제가 있었다. 이 문제를 해결하기 위해 Canny threshold로 검은색과 흰색으로 추출한 이미지에서 물체를 검출하는 코드를 수정하였다.

obj = cv2.imread('라바콘 이미지 경로', cv2.IMREAD_GRAYSCALE)

이 코드에서 아래 코드로 변경하였다.

obj_b = cv2.imread('가까운(큰) 라바콘 이미지 경로', cv2.IMREAD_GRAYSCALE)#wad
obj_s = cv2.imread('먼(작은) 라바콘 이미지 경로', cv2.IMREAD_GRAYSCALE)#wad

이렇게 함으로써 가까울 때와 작을 때의 라바콘들을 모두 검출할 수 있었다. 물체를 검출할 때 openCV의 findContours() 함수를 사용했다.

코드 수정 뒤 콘 인식

이렇게 수정했을 시 원치 않는 노이즈까지 발생했다. 앞으로의 개선 방향은 이러한 노이즈를 잡는 것을 고민하는 것이 될 것 같다. 이렇게 수정한 코드는 다음과 같다.

import matplotlib.pyplot as plt
import numpy as np
import cv2


path = "../source/2022-07-04_16-10-12.mp4"
obj_b = cv2.imread('../source/corn_data/lavacorn_b.png', cv2.IMREAD_GRAYSCALE)#wad
obj_s = cv2.imread('../source/corn_data/lavacorn_s.png', cv2.IMREAD_GRAYSCALE)#wad
cap=cv2.VideoCapture(path)
obj_contours_b,_=cv2.findContours(obj_b, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)#wad
obj_contours_s,_=cv2.findContours(obj_b, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)#wad
obj_pts_b=obj_contours_b[0]#wad
obj_pts_s=obj_contours_s[0]#wad

fps = cap.get(cv2.CAP_PROP_FPS)
width  = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
codec = cv2.VideoWriter_fourcc(*'DIVX')
out=cv2.VideoWriter('../output/output_5.mp4', codec, 30.0, (int(width),int(height)), False)

kernel_size=5

low_threshold=200
high_threshold=255

rho=2
theta=np.pi/180
threshold=90
min_line_len=10
max_line_gap=50

a=0.8
b=1.
theta_w=0.

def grayscale(img):
    hsl=cv2.cvtColor(img, cv2.COLOR_BGR2HLS)
    imgH,imgS,imgL=cv2.split(hsl)
    return imgL
    
def gaussian_blur(img, kernel_size):
    return cv2.GaussianBlur(img, (kernel_size, kernel_size), 0)

def canny(img, low_threshold, high_threshold):
    """Applies the Canny transform"""
    return cv2.Canny(img, low_threshold, high_threshold)

def auto_canny(img, sigma):
    """Applies the Canny transform"""
    v=np.median(img)

    lower=int(max(0,(1.0-sigma)*v))
    upper=int(min(255, (1.0+sigma)*v))
    
    return cv2.Canny(img, lower, upper)

def region_of_interest(img, vertices):
    mask=np.zeros_like(img)
    if len(img.shape)>2:
        channel_count = img.shape[2]
        ignore_mask_color=(255,)*channel_count
    else:
        ignore_mask_color=255

    cv2.fillPoly(mask, vertices, ignore_mask_color)

    masked_image=cv2.bitwise_and(img,mask)
    return masked_image

def draw_line(img, line, color=[255,0,0], thickness=5):
    if len(line)<1:
        return
    else:
        for i in range(1, len(line)):
            cv2.line(img, (line[i-1][0],line[i-1][1]), (line[i][0],line[i][1]), color, thickness)

def draw_lines(img, l_mid, r_mid):
    line_img=np.zeros((img.shape[0], img.shape[1], 3), dtype=np.uint8)
    draw_line(line_img, r_mid)
    draw_line(line_img, l_mid)
    return line_img

def weighted_img(img, initial_img):
    return cv2.addWeighted(initial_img, a, img, b, theta_w)

while cap.isOpened():
    ret, img = cap.read()
    if not ret:
        print("프레임을 수신할 수 없습니다. 종료 중 ...")
        break

    img_s = cv2.resize(img, dsize=(640, 360))
    gray=grayscale(img_s)
    blur_gray=gaussian_blur(gray, kernel_size)

    edges=canny(blur_gray, low_threshold, high_threshold)
    mask=np.zeros_like(img)
    
    if len(img.shape)>2:
        channel_count=img.shape[2]
        ignore_mask_color=(255,) * channel_count
    else:
        ignore_mask_color=255
    imshape=img.shape

    vertices=np.array([[(30, 305),
                        (30, 240),
                       (180, 170),
                       (450, 170),
                        (610, 240),
                       (610, 305)]], dtype=np.int32)
    cv2.fillPoly(mask, vertices, ignore_mask_color)
    masked_image=region_of_interest(edges, vertices)

    bigimg=cv2.resize(masked_image, dsize=(1280, 720))

##    작업공간    ##

    contours,_=cv2.findContours(bigimg, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    r_mid=[]
    l_mid=[]
    for pts in contours:
        if cv2.contourArea(pts) <200:
            continue
        rc=cv2.boundingRect(pts)

        dist_b=cv2.matchShapes(obj_pts_b, pts, cv2.CONTOURS_MATCH_I3, 0)
        dist_s=cv2.matchShapes(obj_pts_s, pts, cv2.CONTOURS_MATCH_I3, 0)
        if dist_b <0.3 or dist_s<0.3:
            cv2.rectangle(bigimg, rc, (255, 0,0),1)
            mid = [int((rc[0]*2+rc[2])/2), int((rc[1]*2+rc[3])/2)]
            if mid[0]>530:  #by vertices[2~3]/2 value
                r_mid.append(mid)
            else:
                l_mid.append(mid)
    line_img = draw_lines(bigimg, l_mid, r_mid)
    lines_edges=weighted_img(line_img, img)

    cv2.imshow('out', bigimg)
    
    out.write(bigimg)
    
    if cv2.waitKey(10) == ord('q'):
        break
# 작업 완료 후 해제
cap.release()
out.release()
cv2.destroyAllWindows()

콘 인식 후 경로 연결

위의 영상은 콘을 인식하고 그 좌표값으로 선을 연결한 것이다. 이전과는 다르게 차선은 인식하지 않고 정확하게 콘 정중앙의 좌표를 추출한다는 점을 확인할 수 있다. 하지만 맨 처음 차량이 있을 때 차 또한 인식한다는 정확도 문제가 있었다. 그리고 아래 영상과 같이 차선을 연결하는 알고리즘에 문제가 있었다.

차량이 기울었을 때 경로

절반을 기점으로 왼쪽 오른쪽 차선을 인식하게 작성하니 한쪽 차선이 절반을 넘었을 때 다른 차선으로 인식되는 것을 알 수 있었다. 다음 포스팅에서는 정확도 및 경로 연결 알고리즘 개선을 목표로 삼고 이를 해결하기 위해 노력할 것이다.

728x90
반응형