코드에 정의된 함수들의 기능

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()