반자율 주행
영상처리가 재밌어 openCV를 공부한 뒤 뭘 만들까 고민하다 간단한 반자율 프로그램을 만들어봤다. 구동 방식은 간단하다. 허프 변환을 통해 관심 영역의 직선을 추출하고, 직선과 특정 좌표 간의 거리를 계산해 차가 제대로 가는지를 판단하는 방식이다. 핸들 조향은 키보드 모듈을 이용했다.
허프 변환
허프 변환(Hough Line Transform)은 이미지에서 직선을 찾기 위해 사용되는 알고리즘이다. 원리는 다음과 같다. 우선, 선형방정식을 직각 좌표계에서 극좌표계로 변환해준다. 변환한 식 r = xcosθ+ysinθ에 선형방정식의 한 점을 대입하고, θ를0~180까지 증가시키면서 r값을 구하게 되면 사인 곡선이 나온다.
같은 방식으로 선형 방정식의 다른 점들을 대입하게 되면 각각 곡선들이 나오게 된다. 그때 곡선들은 한 점에서 만나게 된다. 그 점을 다시 직각 좌표계로 변환하면 선형 방정식이 나온다.
openCV에서는 허프 변환을 위한 함수인 HoughLinesP와 HoughLines를 제공한다. HoughLineP는 HoughLines를 최적화한 것으로 모든 점이 아닌 임의의 점을 이용하여 직선을 추출한다.
lines = cv2.HoughLinesP(image, rho, theta, threshold, minLineLength, maxLineGap)
"""
image – 8bit, single-channel binary image, canny edge를 선 적용.
rho – r 값의 범위 (0 ~ 1 실수)
theta – 𝜃 값의 범위(0 ~ 180 정수)
threshold – 만나는 점의 기준, 숫자가 작으면 많은 선이 검출되지만 정확도가 떨어지고, 숫자가 크면 정확도가 올라감.
minLineLength – 선의 최소 길이. 이 값보다 작으면 reject.
maxLineGap – 선과 선사이의 최대 허용간격. 이 값보다 작으며 reject.
"""
결과 영상
게임 구동 환경은 창모드(1280 * 900)이다.
구현 코드
※ 스파게티 소스이다... 파이썬은 시작한 지 얼마 안돼서...
from PIL import ImageGrab
import cv2
import keyboard
import numpy as np
import math
import time
class SideCap:
def __init__(self):
self.img_src = np.zeros((640, 480, 1), np.uint8) # tmp 값, 흑백
self.img_edge = np.zeros((640, 480, 1), np.uint8) # tmp 값, 흑백
self.img_result = np.zeros((640, 480, 3), np.uint8) # tmp 값, 컬러
# 이진화 변수, 환경에 따라 값이 조금 씩 다르다.
self.thresh = 200
self.maxValue = 255
self.interval = 0
self.side_coords = (1090, 685, 1340, 940) # 원화는 좌표로 설정.
def side_cap(self):
"""
차로 캡쳐
"""
print("캡쳐 시작.")
while True:
self.img_src = cv2.cvtColor(np.array(ImageGrab.grab(
bbox=self.side_coords)), cv2.COLOR_BGR2GRAY)
self.make_binary()
self.houghline()
self.auto_control()
cv2.imshow("side cap", self.img_src)
cv2.imshow("result", self.img_result)
key = cv2.waitKey(100)
if key == ord('q'):
print("캡쳐 중단.")
cv2.destroyAllWindows()
return
def make_binary(self):
"""
canny를 통한 이진화
"""
ret, self.img_edge = cv2.threshold(self.img_src, self.thresh, self.maxValue,
cv2.THRESH_BINARY)
self.img_result = cv2.cvtColor(self.img_edge, cv2.COLOR_GRAY2BGR)
def houghline(self):
"""
직선 추출 및 간격 계산
"""
lines = cv2.HoughLinesP(
self.img_edge, 1, np.pi/180, 90, None, 10, 30)
if lines is not None:
for i in range(0, len(lines)):
l = lines[i][0]
self.cal_interval(l)
cv2.line(self.img_result, (l[0], l[1]), (l[2], l[3]),
(0, 0, 255), 3, cv2.LINE_AA)
else:
self.interval = 0
def cal_interval(self, lane_coords):
"""
점과 직선 사이의 거리
"""
m = round((lane_coords[2]-lane_coords[0]) /
(lane_coords[3]-lane_coords[1]), 2)
print("좌표 0 : {}, {}".format(lane_coords[2], lane_coords[3]))
print("좌표 1 : {}, {}".format(lane_coords[0], lane_coords[1]))
print("m : {}".format(m))
(a, b, c) = (-m, 1, m*lane_coords[1]-lane_coords[0])
self.interval = round(abs(a*1930+b*930+c)/math.sqrt(a**2+b**2), 2)
print("간격 : {}".format(self.interval))
def auto_control(self):
"""
interval 값을 기준으로
차로 유지하는 함수
좌향 : a, 우향 :d
"""
# 차로 좌측으로 이탈 중
if 550 < self.interval < 1500:
print("차로를 조정합니다.\n",
"auto control : ->")
keyboard.press('D')
time.sleep(0.008)
keyboard.release('D')
# 차로 우측으로 이탈 중
elif 50 < self.interval < 300:
print("차로를 조정합니다.\n",
"auto control : <-")
keyboard.press('A')
time.sleep(0.008)
keyboard.release('A')
else:
pass
side = SideCap()
keyboard.add_hotkey("alt+C", lambda: side.side_cap())
keyboard.wait('esc')
print("프로그램 종료")
사실 auto_control 함수를 처음에는 비동기 함수로 했는 데, 오류가 계속 나서 동기 함수로 그냥 사용했다. 코드를 짜는데 3일 정도 걸렸는 데, 제일 오래 걸렸던 부분은 조향 부분이다. 유로트럭2를 조향 하는 키보드로 조향 할 때 누른 시간만큼 핸들이 돌아가는 데, keyboard 모듈로 구현하기가 어려웠다..
질문있으면 댓글로 남겨주세요. 아는 한에서 답변해드리겠습니다. 피드백이나 조언도 대환영입니다!
'openCV' 카테고리의 다른 글
[C++ openCV 도형 인식] 게임 매직 타일 매크로 [2] (완) (0) | 2021.01.28 |
---|---|
[파이썬 openCV 도형 인식] 게임 매직타일 매크로 [1] (0) | 2021.01.27 |
[파이썬 openCV] - 트랙바로 임계값 조정하기 (0) | 2021.01.21 |
[파이썬 openCV] - 픽셀, 컬러와 흑백 이미지 (0) | 2021.01.20 |
[파이썬 openCV] - 이미지 데이터 공유 & 복사 (0) | 2021.01.19 |