제18회 임베디드SW 경진대회 개발 과정 2
색상 범위 추출 및 추적하기
- Track bar를 이용한 hsv 값 추출하기
- 먼저 trackbar를 세팅하기 위해 cv2.createTrackbar와 cv2.setTrackbarPos라는 함수를 이용하여 원하는 개수와 0~ 255의 범위를 갖는 트랙바를 만들고 hsv값을 구하기 위해서 최소 최대를 각각 두 개씩 설정합니다. 메인문에서 함수를 호출한 다음 HSV 각각의 변수에 cv2.getTrackbarPos라는 함수를 이용해 값을 저장합니다. 그 후 각각의 HSV의 lower, upper 값을 배열에 저장합니다. 배열을 갖고 cv2.inRange 함수를 사용해 지정한 범위의 색을 추출할 수 있게 설정합니다.
- 드래그를 이용한 HSV 범위 추출
- 영상에서 원하는 곳의 색상 값을 얻어내기 위해 드래그를 통해 각 픽셀의 hsv값을 가져오고 가져온 값에서 배열을 이용해 가장 작은 값과 가장 큰 값을 불러와 lower_hsv 값과 upper_hsv값을 출력합니다.
- 추적
- 영상 내에서 원하는 결과물이 잘 출력되고 있는지는 print 함수를 사용하여 결과를 출력하거나, 새로운 Window창을 띄워서 원하는 결과물이 출력되는 프레임을 띄우거나, 실시간으로 결과물에 네모 표시나 동그라미 표시 등을 띄워서 확인할 수 있습니다. 코딩을 하는 과정에서는 실시간으로 영상 프레임에서 결과물에 컨투어를 잡아서 출력하였습니다. 컨투어란 윤곽선, 외곽선 등으로 불리는데 같은 색상이나 밝기의 연속된 점을 찾아 연결한 곡선을 찾아내서 모양 분석과 객체 인식에 사용됩니다.
- dst, contours, hierarchy = cv2.findContours(src, mode, method, [ , contours, hierarchy, offset]) 의 함수를 사용하는데 mode는 컨투어 제공 방식으로 컨투어의 라인을 그릴 때 가장 바깥쪽 라인만 제공받을 것인지, 모든 라인을 계층 없이 제공받을 것인지, 모든 라인을 2계층으로 제공 받을지 등의 기능을 말합니다. Method는 근사 값 방식을 선택하는 것인데, 근사를 계산하지 않고 모든 좌표 제공받거나, 컨투어 꼭짓점만 제공 받는 등의 기능을 말합니다. Countours는 검출한 컨투어의 좌표, 파이썬 리스트를 의미하고, hierarchy는 컨투어 계층 정보를 의미합니다. Offset은 ROI등으로 인해 이동한 컨투어 좌표의 오프셋을 뜻합니다. 만약 컨투어를 그리고 싶다면 cv2.drawContours(img, contouts, contourIdx, color, thickness) 함수를 사용하여 입력 영상을 받아오고, 그릴 컨투어 배열을 받아오고, 그릴 컨투어의 인덱스를 입력해야 하는데 만약 모든 컨투어를 표시하고 싶으면 contourIdx에 -1을 입력합니다.
ESWN 방위 검출하기
카메라에 인식되는 영상의 잡음을 제거하기 위해 모폴로지 연산을 사용했습니다.
Kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) 구조화 연산 커널을 생성을 하고
binary_ero = cv2.erode(binary, kernel) 침식 연산을 수행 후
contours, _ = cv2.findContours(binary_ero, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) 컨투어를 합니다.
for문을 이용해 컨투어 된 영역 중에서 제일 큰 부분만 선택을 하여 배경을 제거한 후 마스크를 생성 한 후 다시 컨투어를 실행합니다. Cv2.approxPolyDP(contour, 0.01 * cv2.arcLength(contour, True), True)를 사용해 각 컨투어에서 근사 컨투어로 단순화시키고 len(pprox.) 함수를 사용해 꼭짓점의 개수를 찾아냅니다. 만약 꼭짓점의 개수가 12개보다 같거나 작으면 ‘N’ 이고 18개보다 크거나 같으면 ‘S’ 그리고 위에서 사각형으로 컨투어 한 것의 좌표를 찾아 w, h를 구한 후
abs(w – h) >= 60 을 만족 시키면 ‘E’, 아니면 ‘W’가 인식되게 설계하였습니다.
ABCD 검출하기
먼저 구역의 색(빨강, 파랑)을 구분이 필요했습니다. 비교적 빨간색보다 파란색이 조명이나 사람 피부 등에서 인식되지 않으므로 파란색의 픽셀 수가 일정 수가 넘어가면 파란색, 그 이하면 빨간색으로 구분하도록 하였습니다. 다음으로 문자의 잡음을 줄이기 위해서 gaussianblur를 적용하고, contours, hierarchy = cv2.findContours(binary_mask, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)를 사용해 컨투어를 2계층 이상 검출하도록 하였습니다. 조건문 if문을 이용해 컨투어의 길이가 0이면 물체를 감지하지 못하였단 뜻이고, else로 넘어간다면 컨투어가 갑지 된 것이므로 다음 함수로 넘어갑니다. 컨투어의 외접한 사격형을 그리기 위해 인자를 받은 contour에 외접하고 똑바로 세워진 직사각형의 좌상단 꼭짓점 좌표 x, y와 가로 세로 폭을 리턴해 주는 함수 cv2.boundingRect(contr)를 사용하고, 사각형을 그리기 위해 cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 3)를 사용하였습니다. Cv2.contourArea(contr(윤곽선 배열), False(폐곡선 여부))를 사용해 컨투어 잡힌 알파벳의 넓이를 구하고, frame2.size 를 이용해 컨투어 잡힌 총 프레임의 면적을 구한 후 앞서 말한 대로 (알파벳 넓이 / 총 면적)을 계산해 각각의 비율을 구했습니다. 비율이 0.145보다 작으면 ‘C’ , 0.145보다 크고, 0.2보다 작으면 ‘A’ 로 인식을 하지만 B와 D는 서로 각각의 비율이 2.6~2.8로 비슷한 값으로 비슷해서 다른 방법을 이용해야 했는데, B와 D의 컨투어의 개수를 len(contours) 함수를 이용해 구분하면 B는 3개, D는 2개로 출력되기 때문에 ABCD 모두 거리에 상관 없이 알파벳을 정확하게 인식하게 설계할 수 있었습니다.
색상 별 구역 나누기 (미션 수행)
알파벳 색상 인식과 비슷하게 미션 영역의 색상을 구분할 때 비교적 외부 원인에 의해 덜 방해받는 초록색 마스크를 이용해 인식된 픽셀 수가 일정 수(10000) 이상 검출되면 안전구역, 그 이하면 위험구역으로 판단하였습니다. 미션 수행 여부는 앞서 저장 된 미션 색과 일치하는 우유곽의 컨투어와 그 컨투어 된 우유곽에 외접하는 직사각형을 생성한 뒤, 스레시 홀딩을 이용해 이진화를 시킨 후 흰색 영역과 검정색 영역의 비율을 이용해서 판별하였습니다.
1. 안전구역
안전구역에서는 시민을 외부에서 안전구역으로 데려와야 합니다. 영상을 이진화 했을 때 구역 외부에서는 우유곽이 검정색 안전구역에서는 배경이 검정색이기 때문에 (흰색 영역)/(검정색 영역)이 1이상이면 미션 완료라고 설계하였습니다.
2. 확진구역
확진구역에서는 시민을 위험구역에서 외부로 구출해야 합니다. 마찬가지로 영상을 이진화 했을 때 구역 외부에서는 우유곽이 검정색 안전구역에서는 배경이 검정색 이기 때문에 (흰색 영역)/(검정색 영역)이 1이히이면 미션 완료라고 설계하였습니다.
라인 트레이싱
- 노란색 인식
- 라인 트레이싱을 하기위해서는 여러가지 색깔중에 노란색을 검출하는 작업이 필요합니다. 이는 mask_yellow라는 변수를 사용하여 cv2.inRange()를 이용하여 명암 등을 조정해가며 구한 yellow1 노랑색의 최솟값과 yellow2 노랑색의 최댓값을 결정하여서 범위 안에 있는 노랑색들의 범위를 지정해준 다음 cv2.bitwise_and()를 사용하여서 영상에서 지정한 yellow 값들만 검출해서 나타낼 수 있도록 하였습니다. 라인을 추적하기 위하여 로봇의 머리를 숙이는 과정에서 라인에 생기는 로봇의 그림자로 인하여 hsv값이 변화하여 값을 맞추기가 까다로워 명도 v값의 범위를 넓게 잡아 이를 수정하였습니다. 이후 모폴로지 함수를 이용해 잡음을 줄이고 gray변환과 가우시안 블러 처리를한 후 이진화하여 컨투어를 얻습니다.
- Rectangle 함수를 이용하여 직선과 코너를 구분하기
- 처음 발상의 시작은 직선과 코너의 가로의 길이 차이입니다. 이를 이용하여 코너와 직선을 구분하면 직선은 l 모양으로 폭이 상당히 좁지만 우리가 마주 하는 교차로들은 T, ㄱ ,ㅏ , ㅓ 모양으로 모두 l보다 가로의 길이가 길다는 사실을 가져와서 이 모양을 품는 Rectangle 함수를 이용하여 구분했습니다.
- 일정 범위이상인 노랑색의 컨투어를 얻은 후 boundingRect함수를 이용하여 노란색을 둘러싸는 사각형을 만듭니다. 이때 이 사각형의 왼쪽상위 x,y좌표 값과 가로, 세로 길이를 구합니다. 로봇의 발과 라인이 일직선일 때 그려진 사각형의 가로의 길이가 라인의 최소값입니다. 최소값일때 계속 직진하라는 신호를 송신합니다.
- boundingRect함수의 가로 길이가 일정범위 이상 증가하면 로봇과 라인이 일정각도이상 벌어짐으로 인식하여 좌우 정렬을 요구하는 신호를 송신합니다. boundingRect함수의 가로 길이가 아주 커지면 (200이상) 코너로 인식합니다. ㅏ와 ㄱ의 코너의 구분은 로봇의 적외선 거리 감지를 이용하여 임계거리 앞에 장애물이 존재하면 벽으로 인식하여 ㄱ코너로 인식하고, 그렇지 않으면 ㅏ코너로 인식합니다. 코너의 오른쪽, 왼쪽 회전의 경우 ㄱ 코너의 경우 boundingRect함수를 이용하여 라인의 둘러싸는 최소한의 사각형을 그리면 사각형의 왼쪽상위의 x좌표의 값이 0이므로 왼쪽회전 신호를 송신합니다. 반대의 경우 x+w의 값이 프레임의 가로 값인 640이므로 이를 이용하여 오른쪽 회전 신호를 송신하도록 하였습니다. 또한 ㅡ 는 x좌표가 0 일 때와 x+w = 640 를 모두 만족하면 ㅡ 라 인식하도록 하였습니다.
- 좌우정렬
- 직선과 코너를 구분을 하였으면 직선으로 인식한 곳에서 중앙 정렬을 하면서 앞으로 걸어가야 합니다. 이를 위해 hough line 함수를 대입을 하였습니다. 노란색으로 인식한 값들 중에서 시작점과 끝점 두개를 잡아서 직선을 그리는 함수입니다. 이를통해 우리는 그 직선에 x 절편을 구할 수 있는데 이 x 절편을 이용하여서 화면 가로 길이가 640 이므로 중앙값이 320 임을 염두하고 일정한 오차값 이상으로 100, 500 이런 값들을 출력 받으면 좌측으로 움직이거나 우측으로 움직이면서 320에 최대한 가깝게 움직이게 만들어서 선을 따라 걸을 수 있게 하였습니다.
화살표 인식
- 화살표 인식 후 이진화
- 가우시안 블러처리된 그레이 프레임의 픽셀 값이 90이상이면(배경) 픽셀 값을 0으로, 90보다 작으면(검은색) 픽셀 값을 255로 변경해 화살표에 해당하는 영역을 흰색으로 이진화합니다.
- 화살표 영역 컨투어 후 마스크 생성
- Kernel의 크기를 (5, 5) 크기의 사각형으로 설정해서 흰색영역(검은색)을 팽창시킨 후, 그 영역을 컨투어합니다. 컨투어 된 영역(검은색) 중에서 제일 큰 부분(화살표)을 선택해 해당 영역만 프레임 크기의 검은색 마스크에 흰색으로 나타내고 다시 컨투어를 해 배경을 제거한 화살표 마스크를 생성합니다.
- 컨투어된 영역의 결함을 찾아서 주변 좌표 확인
- 컨투어된 화살표의 테두리에 convex hull 을 그리고 결함을 찾습니다. 그 중 시작점과 연결점을 연결한 선으로부터의 거리가 1000 이상인 결함을 찾는데, 이에 해당하는 결함은 2개로, 각각의 결함 주변에 점을 네 개씩 찍고, 그 점들이 컨투어된 영역 안에 있는지를 확인합니다.
- 방향 판단
- 컨투어 영역 안에 있는 점이 결함을 기준으로 오른쪽에 있으면 right 변수의 값에 1을 더하고, 왼쪽에 있으면 left 변수의 값에 1을 더한다. 최종적으로 더 큰 값의 변수를 화살표의 방향으로 판단합니다.