코드에 정의된 함수들의 기능
nothing(x) → 아무 작업을 하지 않는 함수
roi(frame, vertices) → roi 설정 함수
initializeTracbars(init_val) → 트랙바 생성 함수
valTracbars() → 트랙바 위치를 반환하는 함수
perspective_warp(img, dst_size=(1920, 1080), src=np.float32([(0.43,0.65),(0.58,0.65),(0.1,1),(1,1)]), dst=np.float32([(0,0), (1, 0), (0,1), (1,1)])) → 투시 변환 함수
drawPoints(img, src) → 이미지 안에 원 그리는 함수
color_filter(img) → 색상 변환 함수
edgeDetection(img) → erosion과 dilation으로 edge 검출 함수
serWrite(speed, steering, cnt) → speed, steering, cnt 정보 출력, result 값 넘겨주는 함수
차선 인식 코드
#!/usr/bin/env python
# -*-coding: utf-8-*
import serial
import numpy as np
import cv2
import math
import struct
ser = serial.Serial('/dev/ttyUSB0', 115200) # 차량 포트('/dev/ttyUSB0')와 속도(115200)
def nothing(x):
pass # 아무 작업을 하지 않음.
def roi(frame, vertices): # roi 설정
mask = np.zeros_like(frame) # zero들로 가득찬 array를 만들겠다 -> frame이 0으로 가득찬 array가 됨.
cv2.fillPoly(mask,vertices,255) # 채워진 다각형을 그린다 -> mask 이미지에 꼭짓점에 해당하는 부분 안에 하얀색으로 채움.
masked = cv2.bitwise_and(frame,mask) # 비트연산(and) mask에 해당하는 부분만 나타남
return masked # mask에 해당하는 영역을 리턴
def initializeTracbars(init_val): # 트랙바 초기화
cv2.namedWindow("BEV_values") # namedwindow 함수로 "BEV_values"라는 이미지 창을 생성함.
# BEV_values : window caption name
cv2.resizeWindow("BEV_values",360,240) # resizeWindow 함수로 창 크기를 조절함. 이미지도 창의 비율에 맞추어 수정됨.
# "BEV_values" - 이미지 창 이름, 360 - width, 240 - height
# "BEV_values"라는 이미지가 360*240 크기로 이미지 창 크기가 수정됨.
cv2.createTrackbar("Width Top", "BEV_values",init_val[0],100,nothing) # createTrackbar - 트랙 바 생성 함수
# createTrackbar("트랙 바 이름", "윈도우 창 제목", 최솟값, 최댓값, 콜백 함수)
# "width Top" - 트랙 바 명칭
# "BEV_values" - 트랙 바를 부착할 윈도우 창
# 최솟값과 최댓값은 트랙 바를 조절할 때 사용할 최소/최대 값을 의미함.
# 콜백 함수는 트랙 바의 바를 조절할 때 위치한 값을 전달함.
# init_val[0] 무엇?
cv2.createTrackbar("Height Top", "BEV_values", init_val[1], 200, nothing)
cv2.createTrackbar("Width Bottom", "BEV_values", init_val[2], 100, nothing)
cv2.createTrackbar("Height Bottom", "BEV_values", init_val[3], 200, nothing)
def valTracbars():
widthTop = cv2.getTrackbarPos("Width Top", "BEV_values")
# cv2.getTrackbarPos : 트랙바의 현재 위치를 리턴하는 함수, 만들어 놓은 트랙바의 값을 가져옴.
# "Width Top" - 트랙바 이름
# "BEV_values" - 트랙바가 생성된 윈도우 이름
heightTop = cv2.getTrackbarPos("Height Top", "BEV_values")
widthBottom = cv2.getTrackbarPos("Width Bottom", "BEV_values")
heightBottom = cv2.getTrackbarPos("Height Bottom", "BEV_values")
src = np.float32([(widthTop / 100, heightTop / 100), (1 - (widthTop / 100), heightTop / 100),
(widthBottom / 100, heightBottom / 100), (1 - (widthBottom / 100), heightBottom / 100)])
# float32 : 단정밀도를 가지는 실수형 자료형
# 8 bit의 지수와, 23 bit의 소수로 구성된다.
# src가 대체 무엇?
return src #src를 리턴함.
def perspective_warp(img,
dst_size=(1920, 1080),
src=np.float32([(0.43,0.65),(0.58,0.65),(0.1,1),(1,1)]), #src가 대체 무엇?
dst=np.float32([(0,0), (1, 0), (0,1), (1,1)])):
img_size = np.float32([(img.shape[1],img.shape[0])])
#image.shape[1] - 가로, img.shape[0] - 세로 : 원본 크기
src = src* img_size # src * 원본 크기 -> 원본크기를 src 크기로 바꿈.
# For destination points, I'm arbitrarily choosing some points to be
# a nice fit for displaying our warped result
# again, not exact, but close enough for our purposes
# 목적지 포인트의 경우, 저는 임의로 몇 가지 포인트를 선택하고 있습니다.
# 뒤틀린 결과를 보여주기에 적합한
# 다시 말하지만, 정확하지는 않지만, 우리의 목적에 충분히 가깝습니다.
dst = dst * np.float32(dst_size) # dst * dst_size (?)
# Given src and dst points, calculate the perspective transform matrix
# src 및 dst 점이 주어지면 원근 변환 행렬 계산
M = cv2.getPerspectiveTransform(src, dst)
# 기하학적 변환을 위하여 cv2.getPerspectiveTransform(원본 좌표 순서, 결과 좌표 순서)를 사용하여 matrix 생성
# 퍼스펙티브 변환에서 원본 이미지의 모든 직선은 출력 이미지에서 직선으로 유지됨.
# 퍼스펙티브 변환 행렬을 찾으려면 입력 이미지의 4점과 대응하는 출력 이미지의 4점이 필요함.
# Warp the image using OpenCV warpPerspective()
# OpenCV warpPerspective()를 사용하여 이미지를 뒤틀립니다.
warped = cv2.warpPerspective(img, M, dst_size) # warpPerspective를 이용하여 변환 수행
# warpPerspective는 입력 영상의 점 4개를 출력 영상의 점 4개의 위치로 변환시킴(투시 변환)
# src점이 dst 점의 위치로 대응됨.
return warped # warped가 리턴됨.
def drawPoints(img, src):
img_size = np.float32([(img.shape[1], img.shape[0])]) #image.shape[1] - 가로, img.shape[0] - 세로 : 원본 크기
# src = np.float32([(0.43, 0.65), (0.58, 0.65), (0.1, 1), (1, 1)])
src = src * img_size # 이미지 사이즈 변경
for x in range(0, 4):
cv2.circle(img, (int(src[x][0]), int(src[x][1])), 10, (0, 0, 255), cv2.FILLED)
# for in list - 코드를 필요한만큼 반복해서 실행
# range 함수 - 필요한 만큼의 숫자를 만들어내는 유용한 기능
# range(0, 4) = 0, 1, 2, 3
# range 길이(4)만큼 cv2.circle(원 그리는 함수) 실행
# cv2.circle(이미지 파일, 원의 중심좌표(x,y),원의 반지름, 색상, 선 두께, 선 종류, shift)
# 여기서는 img 안에서 (int(src[x][0]), int(src[x][1]))좌표에 10의 반지름을 가지는 red color의 원을 그림.
return img # img를 리턴함
def color_filter(img):
hsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV) # 컬러변환 함수 -> 여기서는 회색으로 변환함.
ycrcb = cv2.cvtColor(img,cv2.COLOR_BGR2YCrCb)
# YCbCr 모델은 RGB 색에서 밝기성분(Y)과 색차정보(Cb, Cr)를 분리하여 표현하는 색상모델임.
# 컬러변환 함수 -> 여기서는 YcrCb로 변환함.
lowThreshold_y = np.array([0,50,200])
# array함수의 인자로 리스트를 넣어 생성.
higherThreshold_y = np.array([25,120,255])
lowThreshold_w = np.array([200,110,105])
higherThreshold_w = np.array([255, 140, 130])
lowThreshold_r = np.array([0,0,200])
higherThreshold_r = np.array([100,200,255])
masked_y = cv2.inRange(hsv,lowThreshold_y,higherThreshold_y)
# cv2.inRange()함수는 소스인 hsv의 모든 값을 lowThreshold_y, higherThreshold_y로 지정한 범위에 있는지
# 체크한 후, 범위에 해당하는 부분은 그 값 그대로, 나머지 부분은 0으로 채워서 결과값을 반환함.
# hsv에서 lowThreshold_y와 highterThreshold_y 사이에 해당하는 값은 그대로 두고, 나머지 부분은 0으로 채워서
# masked_y에 전달함. 따라서 우리가 정의한 y 범위 이외는 모두 검정색으로 처리된 후 값을 넘겨주게 됨.
masked_w = cv2.inRange(ycrcb,lowThreshold_w,higherThreshold_w)
masked_r = cv2.inRange(ycrcb,lowThreshold_r,higherThreshold_r)
# cv2.imshow('y',mask_y)
# cv2.imshow('w',mask_w)
# cv2.waitKey(0)
return masked_y, masked_w, masked_r
def edgeDetection(img):
img=cv2.resize(img,(600,400),None)
# cv2.resize(원본 이미지, 결과 이미지 크기, 보간법)로 이미지의 크기를 조절할 수 있음.
# img를 600*400크기로 이미지 크기 조절함.
# erosion : 이미지 침식, dilation : 이미지 팽창
closed_img = cv2.erode(img, (5, 5), iterations=5)
# img : erosion을 수행할 원본 이미지
# (5, 5) : erosion을 위한 커널
# iterations : erosion 반복 횟수
# erosion 반복횟수가 1 이상이면 erosion 된 이미지를 또 erosion 하는 것이므로
# 대상 이미지는 더더욱 가늘어지게 된다. -> 여기서는 5이니까 더더욱 가늘어짐.
closed_img = cv2.dilate(closed_img, (3, 3), iterations=10)
# 이미지를 dilation함.
# 커널이 크거나 반복회수가 많아지면 erosion일 경우에는 전경의 이미지가 가늘다 못해
# 없어질 수도 있으며, dilation의 경우 그 반대로 될 수 있다.
# cv2.imshow('closing', closed_img)
# 근데 왜 침식한 후 팽창하지? 그러면 원본이미지랑 비슷한거 아닌가?
edge_img = cv2.bitwise_or(cv2.Canny(closed_img,180,200), closed_img)
# 비트연산은 말 그대로 0,1을 가지고 하는 연산이므로 두 이미지의 동일한 위치에 대한
# AND, OR, NOT, XOR 연산을 진행한다.
# or 연산 -> 두 그림에서 모두 검은색(0)인 부분만 검정색으로 나타난다.
# cv.Canny(원본 이미지, 임계값1, 임계값2, 커널 크기, L2그라디언트)를 이용하여 가장자리 검출을 적용함.
return edge_img # edge_img 리턴함.
def serWrite(speed, steering, cnt):
break_val = 0x01
print('speed : ', speed); # speed 출력
print("ser_write", speed, steering, cnt) # ser write 출력
# steering 값 2000 넘길 시 2000으로 설정
# 스티어링 : 자동차가 그 진행방향을 바꾸기 위해 앞바퀴의 회전축방향을 바꾸는 장치
if abs(steering)>2000:
if steering>0:
steering = 2000
else :
steering =-2000
# 기어 기본값 0: 전진, 1:후진
print("speed ", speed, "steering ", steering, "cnt : ", cnt)
# speed, steering, cnt 차례로 출력. cnt가 뭐지?
result = struct.pack('!BBBBBBHhBBBB', 0x53, 0x54, 0x58, 0x01, 0x00, 0x00, int(speed),
steering, break_val, cnt, 0x0D, 0x0A ) # big endian 방식으로 타입에 맞춰서 pack
# big endian : 큰 단위가 앞에 나오는 것을 말함, 보통 사람이 숫자를 쓰는 방법
print("pc : ", result[0], result[1], result[2], result[3], result[4], result[5], result[6], result[7], result[8],
result[9], result[10], result[11], result[12], result[13] )
# result[0], result[1], ... , result[13] 출력.
ser.write(result)
# img = cv2.imread("test3.jpg")
# a,b=color_filter(img)
# edgeDetection(b)
#init_val = [25,30,7,70] #wT,hT,wB,hB
init_val = [15,26,0,36] #wT,hT,wB,hB
# video = cv2.VideoCapture("test_1.mp4") # port number check
video = cv2.VideoCapture(0) # port number check
# cv2.VideoCapure()을 사용해 비디오 캡쳐 객체를 생성할 수 있음. () 안의 숫자는 장치 인덱스(어떤 카메라를 사용할 것인가).
# 1개만 부착되어 있으면 0, 2개 이상이면 첫 웹캠은 0, 두번째 웹캠은 1으로 지정함.
initializeTracbars(init_val) # 트랙바 초기화
hough_val =0.35
signal = 0
while True:
BEV_values= valTracbars()
_,orign_frame = video.read() # video 읽어오기
frame=cv2.resize(orign_frame,(600,400),None) # 이미지 크기 재조정
points_img = frame.copy() # 이미지 복사
height,width = frame.shape[:2] # 높이와 너비만 잡을 수 있음
points_img = drawPoints(points_img,BEV_values) # drawPoints로 점그리기
frame= perspective_warp(frame,dst_size=(width,height),src = BEV_values)
# perspective_warp 함수 호출
result_img = frame # frame 이미지가 result_img가 됨
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 컬러 변환 함수 -> 여기서는 회색으로 변환함.
masked_y, masked_w, masked_r = color_filter(frame)
edge_y = edgeDetection(masked_y)
edge_w = edgeDetection(masked_w)
edge_r = edgeDetection(masked_r) # edgeDetection 함수 호출.
roi_height, roi_width = edge_w.shape[:2] # roi 높이와 폭(너비)만 잡을 수 있음.
sum_angle = 0
cnt_angle = 0
steering = 0
w_lines = cv2.HoughLinesP(edge_w, 1, np.pi / 180, 50, maxLineGap=10)
# 확률 허프변환 함수 cv2.HoughLines() : 모든 점을 대상으로 하는 것이 아니라 임의의 점을 이용하여 직선을 찾음.
# 단, 임계값을 작게 해야 함. 장점 : 선의 시작점과 끝점을 return 해주기에 화면에 쉽게 표현 가능.
if w_lines is not None:
for line in w_lines:
x1,y1,x2,y2 = line[0]
if(abs(y1-y2)>roi_height*hough_val and abs(x1-x2)<roi_width*0.5):
cv2.line(result_img, (x1, y1), (x2, y2), (255, 255, 255), 5)
tmp_angle = math.degrees(math.atan2(x2 - x1, y2 - y1))
if (90 <= tmp_angle and tmp_angle <= 180): tmp_angle -= 180
sum_angle += tmp_angle * (-1)
cnt_angle += 1
elif (abs(y1 - y2) < roi_height * 0.05 and abs(x1 - x2) > roi_width * 0.25):
cv2.line(frame, (x1, y1), (x2, y2), (25, 25, 255), 5)
signal = 1;
y_lines = cv2.HoughLinesP(edge_y, 1, np.pi / 180, 50, maxLineGap=30)
# 확률 허프변환 함수 cv2.HoughLines() 적용.
if y_lines is not None:
for line in y_lines:
x1, y1, x2, y2 = line[0]
if (abs(y1 - y2) > roi_height * 0.1 and abs(x1 - x2) < roi_width * 0.4):
cv2.line(result_img, (x1, y1), (x2, y2), (51, 204, 255), 5)
tmp_angle = math.degrees(math.atan2(x2 - x1, y2 - y1))
if (90 <= tmp_angle and tmp_angle <= 180): tmp_angle -= 180
sum_angle += tmp_angle * (-1)
cnt_angle += 1 # cnt_angle -> 1 증가
if (cnt_angle != 0): # cnt_angle이 0이 아닐 경우
# print(round(sum_angle / cnt_angle, 2))
final_ang = round(sum_angle / cnt_angle, 2)
steering = final_ang
if (abs(final_ang) > 15): # final_ang의 절댓값이 15보다 클 경우
hp_tf_val = 0.1 # hp_tf_valr 값이 0.1
else: # 그 외의 경우
hp_tf_val = 0.35 # 0.35
print(hp_tf_val)
msg = str(round(sum_angle / cnt_angle, 2)) + "'"
cv2.putText(frame, msg, (30, 30), cv2.FONT_HERSHEY_PLAIN, 3, (255, 255, 255), 2)
cv2.imshow('origin',points_img) # 읽어들인 이미지 파일을 윈도우 창에 보여준다.
# 'origin' : 윈도우 창의 Title
# 'points_img' : cv2.imshow의 return 값
cv2.imshow('result', result_img)
cv2.imshow('yellow',edge_y)
cv2.imshow('white',edge_w)
cv2.imshow('red',edge_r)
cv2.imshow('masked_w',masked_w)
cv2.imshow('masked_y',masked_y)
cv2.imshow('masked_r',masked_r)
speed_lim = 3 # speed 조절
if(abs(steering>30)):
if(steering>0):
steering = 30
else:
steering = -30
#############################################################
print("===== steering :", steering) # steering 출력
result = ser.readline() # erp -> pc
print(result)
print("len", len(result)) # len 출력
if len(result) > 17:
print("erp : ", result[0], result[1], result[2], result[3], result[4], result[5], result[6], result[7], result[8],
result[9], result[10], result[11], result[12], result[13], result[14], result[15], result[16], result[17])
cnt = result[15]
print(cnt)
serWrite(int(speed_lim*10), int(steering*71), cnt)
# cnt가 10일때 패킷이 잘려서 2번에 걸쳐 들어옴.+++++++++++++++++++++++++++++ 0x0a값이 아스키코드 LF(new line)!!!
elif len(result) == 16:
print("erp : ", result[0], result[1], result[2], result[3], result[4], result[5], result[6], result[7], result[8],
result[9], result[10], result[11], result[12], result[13], result[14], result[15])
add_result = ser.readline() # erp -> pc
print(cnt)
serWrite(int(speed_lim*10), int(steering*71), 10)
# serWrite(int(speed_lim*10), int(steering*71), 10, cur_mode)
#############################################################
key = cv2.waitKey(1)
if key == 27:
break
# p.pose.pose.orientation.y = signal
# pub.publish(p)
video.release()
cv2.destroyAllWindows()