[Python] OpenCV로 자율주행 만들기(Canny threshold_#02)
저번에 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()
위의 영상은 콘을 인식하고 그 좌표값으로 선을 연결한 것이다. 이전과는 다르게 차선은 인식하지 않고 정확하게 콘 정중앙의 좌표를 추출한다는 점을 확인할 수 있다. 하지만 맨 처음 차량이 있을 때 차 또한 인식한다는 정확도 문제가 있었다. 그리고 아래 영상과 같이 차선을 연결하는 알고리즘에 문제가 있었다.
절반을 기점으로 왼쪽 오른쪽 차선을 인식하게 작성하니 한쪽 차선이 절반을 넘었을 때 다른 차선으로 인식되는 것을 알 수 있었다. 다음 포스팅에서는 정확도 및 경로 연결 알고리즘 개선을 목표로 삼고 이를 해결하기 위해 노력할 것이다.