[66호]Computer Vision과 Visual Servoing을 이용한 군집 로봇 시스템, minister
2020 ICT 융합 프로젝트 공모전 장려상
Computer Vision과 Visual Servoing을
이용한 군집 로봇 시스템, minister
글 | KAIST 이준, 정송현, 박종건, 고형석, 강민수
1. 심사평
칩센 지원자의 작품 개요에도 표현되어 있다시피 군집 로봇 시스템을 위해서는 로봇 내부에 다양한 센서나 위치 측정 부품, 또는 자체 시스템 구축 등을 위하여 비용이 많이 들게 되어 있습니다. 금번 작품과 같이 Computer visioning 을 통해 구분을 할 경우 판단이 필요한 부분을 모두 Control system 하나에서 해결이 가능할 수도 있습니다. 하지만, 군집 로봇 시스템이 적용되는 형태나 최종 사용 목적을 보자면, 현재 구성한 것과 같은 카메라를 통한 Visioning만으로 한계가 분명 존재하게 됩니다. 군집 드론을 위해서는 카메라는 어디에 있어야 할까요? 주변 환경을 판단하는 센서가 없다면, 카메라에 보이지 않는 사각 공간 또는 변수에서는 어떤 동작을 해야 할까요? 아이디어와 핫한 기술 트렌드에 대하여 심플하게 접근하여 어느 정도의 결과를 얻어내는 것은 좋지만, 작품이 적용될 목표 환경에 구체적인 고민도 함께하면 더 완벽한 작품이 나올 듯 합니다.
펌테크 아이디어와 실용성을 갖춘 작품이라고 생각이 듭니다. 단 제출된 보고서 내용을 감안하자면 목표대비 작품의 기획과정 및 소프트웨어, 하드웨어에 관련된 일부 진행 과정이 확인이 되었을 뿐 출품작의 구체적인 진행 과정이나 최종 완성도를 정확히 판단할 수가 없었습니다.
위드로봇 계획한 대로 모두 만들어졌다면 더 좋은 점수를 받을 수 있었던 작품입니다.
2. 작품 개요
군집 로봇은 IoT의 발전과 함께 다양한 방면에서 사용되고 있다. 로봇 하나로는 해결하지 못 하는 일도 여러 대의 로봇이 협력하면 해결할 수 있게 되는 문제들의 해법으로 주로 군집 로봇이 제시된다. 대형 물류 회사의 창고 정리를 위한 로봇이나, 어두운 하늘에 아름다운 무늬를 펼치는 드론 쇼가 대표적이다. 이런 군집 로봇 시스템을 제작하기 위해서는 다양한 센서가 필요하거나 비싼 위치인식 센서가 필요한 경우가 많다. 이러한 점을 해결하기 위해서 마커와 엔코더를 이용한 방식의 군집로봇도 많이 등장하게 되었다. 하지만, 엔코더나 마커를 사용하는 방식은 개별 로봇에 처리능력을 부여해야 하기에 시스템을 구축하는 데에 여전히 적지 않은 비용이 필요하게 된다. 우리 팀은 이러한 문제에 대한 해법을 Visual Servoing이라는 방법을 통해서 해결해 보고자 했다. Visual Servoing은 Computer Vison과 연관이 깊은 기술로, 이미지에서 시스템의 위치나 자세 등의 정보를 알아내 그것을 이용해 Feedback 하여 시스템을 제어할 수 있는 기술이다. 따라서 개별 로봇의 단가를 더욱 낮추면서 손쉽게 사용할 수 있는 군집로봇 시스템에 적용하기 적합했다. 프로젝트의 결과로 카메라를 이용한 로봇의 Motion Tracking 및 Path Planning을 수행하는 코드를 작성하였으며, WiFi를 이용한 통신으로 보안성과 신뢰성을 가진 시스템을 구축할 수 있었다.
3. 작품 설명
3.1. 주요 동작 및 특징
완성된 로봇은 가로 7cm, 세로 7cm, 높이 10cm의 직육면체 형태이며, 바퀴 두개를 이용하여 움직일 수 있도록 설계되었다. 로봇의 윗부분에 마커로 사용한 스티커를 부착하였으며, 카메라가 이를 인식해 로봇의 좌표를 읽고, 각각의 로봇을 스티커의 색으로 분간하여 특정 로봇을 특정 위치로 제어 가능하다.
3.2. 전체 시스템 구성
전체 시스템의 컨셉은 아래 그림과 같다. 여러 대의 로봇이 카메라가 비추는 평면 안에서 돌아다닐 수 있으며, 높은 곳에 고정되어 서버와 연결된 카메라는 로봇을 관찰하고, 위치를 파악하며, 서버는 카메라를 통해 얻은 이미지르 바탕으로 로봇에게 명령을 내린다.
3.2.1. 로봇
로봇은 Wi-Fi를 사용할 수 있는 IoT 보드 중에서 좋은 성능을 보이는 ESP32칩을 내장한 보드인 LOLIN32 Lite 보드를 사용하였다. 그 외의 부품으로는 모터는 작으면서도 높은 기어비의 DC모터를 사용했으며, Li-Po 배터리와 모터드라이버, 배터리 보호회로를 내장했다. 동체는 3D 프린팅으로 제작하였다.
LOLIN32 Lite 보드는 디바이스 마트에서 판매중인 ESP32 칩셋 기반 개발보드로 Atmega 칩셋을 사용한 제품군이 주력인 아두이노에 비해 비교적 저렴한 값으로 높은 성능을 얻을 수 있으며, Bluetooth와 WiFi 기능을 기본 내장하고 있어 IoT 제품 뿐만 아니라 간단한 로봇을 제작하는 데까지 폭 넓게 이용될 수 있는 다재다능한 개발보드이다.
LOLIN32 Lite 보드는 대부분의 핀이 PWM과 ADC 기능을 지원하고 있으며, 5V 동작전압을 가진 Arduino Uno에 비해 3.3V로 낮은 동작전압과 작은 사이즈를 가지고 있음에도 핀 수와 기능으로는 전혀 밀리지 않는다. 게다가 4MB의 큰 플래시 메모리를 가지고 있어(Uno의 경우 32KB) MicroPython과 같은 소형 파이썬 인터프리터 바이너리를 업로드하여 사용할 수도 있다.
LOLIN32 Lite는 많은 기능을 가지고 있지만 사용하기도 매우 쉽다. 기존에 아두이노를 프로그래밍하기 위해 나온 Arduino Ide에 새로운 보드 프로필을 추가하기만 하면 마치 아두이노를 프로그래밍하듯 쉽게 프로그래밍 할 수 있다는 강력한 장점이 있다. 또한 ESP32에 내장된 WiFi나 Bluetooth를 사용한 NetBIOS나 OTA와 같은 다양한 기능들을 사용한 예제들을 기본적으로 제공하기 때문에, 복잡하게 참고 문서들을 뒤져가면서 코딩할 필요 없이 예제를 참고하기만 하면 사용하고자 하는 기능들을 쉽게 사용할 수 있다. 이와 같은 여러가지 장점들로 인해, minister 프로젝트에서는 LOLIN32 Lite 보드를 로봇의 메인 보드로 삼았다.
3.2.2. 서버
서버는 파이썬으로 작성된 프로그램을 실행시킬 수 있는, 로봇과 같은 네트워크상에 위치한 컴퓨터다. 카메라를 USB를 통해 연결하였으며, 로봇과는 WiFi를 통해 연결된다. 서버의 파이썬 시스템은 아래와 같이 구성되어 있다.
아래는 코드에 대한 자세한 설명이다.
크게 통신을 담당하는 스레드인 clientObj() 클래스와 로봇 위치인식, 모터 속도 계산을 담당하는 스레드인 robotObj() 클래스로 나누어져 동작한다. 두 클래스는 모두 연결된 로봇의 개수만큼 존재하며 일대일 대응 관계를 가진다.
ListenRobot() : 무한루프를 돌면서 새로운 로봇이 서버에 연결되는 즉시 해당 로봇의 인덱스 i와 소켓 객체를 이용해서 robotObj(), clientObj() 객체(스레드)를 새로 생성한다.
clientObj() : Threading 모듈을 상속받아 새로운 스레드를 만든다. comms[i] 명령 큐를 계속 관찰하다가 새로운 명령이 들어오면 아스키로 인코딩해서 로봇으로 전송하고 전송한 명령은 pop한다.
robotObj() : Threading 모듈을 상속받아 새로운 스레드를 만든다. 카메라로 로봇의 위치를 인식하고 waypoint를 입력 받은 뒤 로봇으로 전송해야 할 명령을 연산해서 전역 리스트 comms[i]에 집어넣는다.
Computer Vision 노드는 아래와 같이 실행된다.
로봇의 위치를 파악하기 위해서 사용한 Computer Vison 프로그램의 demo이다. 기본적으로 파이썬의 OpenCV 라이브러리를 이용하였으며, 로봇에 부착된 컬러 마커 이미지는 웹 캠으로 읽어온다. 상세한 코드는 5. 기타에 첨부하였다.
로봇의 위치를 CV 알고리즘으로 알아낼 수 있게 되었기에 로봇을 특정 위치로 옮겨가게 만들 수 있다. 아래 그림은 알고리즘을 설명한다.
그림에서 설명하는 것과 같이 로봇에 방향을 알아낼 센서가 없기 때문에 짧은 이동과 진행해야 하는 방향의 차이를 이용하여 이동 각도를 수정하고 그것을 속도에 반영해서 움직이도록 짜여 있다.
3.3. 개발 환경
개발은 모두 우분투 16.04 LTS 환경에서 진행하였다. LOLIN32 Lite 보드에 코딩하기 위해서 Arduino IDE를 사용했으며, 파이썬 코드의 작성은 Visual Studio Code를 이용해 작성하였다. 영상 처리에서는 OpenCV를 사용하였으며, 로봇과 TCP로 통신하였다.
4. 단계별 로봇 제작 과정
4.1. 개발
4.1.1. 부품 선정
부품은 디바이스마트에서 선정하여 주문 후 제작하였다. 모터는 로봇이 너무 빠르게 움직이면 추적이 어렵기에 높은 기어비를 가진 모터를 선정하였으며, 배터리와 배터리 보호 회로, 모터 드라이버를 사용하는 전력량에 맞추어 넉넉한 스펙으로 준비하였다.
4.1.2. 부품 사이즈
측정 부품의 사이즈를 실측하는 과정을 통해 설계 파일과의 오차를 확인하고, 3D 프린팅을 통해 프레임을 제작하였을 때 잘 들어맞도록 한다. 본 과정을 거치면서 3D 프린팅 모델의 치수가 실제 부품과 잘 맞게 된다.
4.1.3. 3D 모델링
개선 실제로 모델을 출력하여 로봇을 조립해보며 부족한 점을 개선하고 수정하여 최종 모델을 향해 나아간다.
4.2. 제작
4.2.1. 메인보드의 제작
납땜으로 LOLIN32 Lite 보드와 배터리 보호회로, 모터 드라이버 및 I/O를 연결해준다. I/O를 통해 모터와 배터리를 쉽게 교체 가능하게 제작하였다.
4.2.2. 3D 프린팅
몸체는 PLA로 FDM방식의 3D 프린터를 이용해 제작하였다. 간단하고 작은 구조를 가지기에 3D 프린터로 출력하기에 알맞은 모델이다. 출력이 완료되면 조금의 사후처리를 거치면 완성이다.
4.2.3. 조립
몸체에 모터를 고정하고 배터리를 넣어준 후, 메인보드에 선을 연결하고 몸체에 끼워주면 조립이 끝난다. 조립이 완료된 후, 로봇의 윗부분에 인식용 마커를 붙여준다. 완성된 로봇의 모습은 <Fig 1>과 같다.
4.3. 향후 추가 개발 계획
Minister의 새로운 디자인은 햄스터볼과 스타워즈의 BB-8을 연상케 하는 굴러가는 형태이다. 이전의 단순한 직육면체에서 업그레이드된 디자인으로, 외관의 공에 색을 칠하여 로봇 전체가 이미지 인식에 용이한 디자인을 염두에 두었다. 바퀴로 구르는 대신 공을 안에서 굴리기 때문에 구동방식과 공간 배분에 다소 디자인 노력이 들어갔다.
첫째로, 모든 부품이 둥그런 껍질 속에 빽빽하게 들어간다. 허용되는 공간이 줄어들수록 디자인이 힘들어지는데, 현재 개발은 야구공 정도의 작은 크기를 염두에 두고 있어 공간 배분이 첫번째 난관이었다. 게다가 둥그렇게 잘 굴러야 해서 외부에 들어간 곳도 나온 곳도 없도록 충전과 데이터 포트를 무선으로 전환했다. 모터, 배터리, 프로세서, 충전포트가 모두 공 안에 들어가며 외부는 깔끔한 구면(球面)이다.
공간 배분에 있어서 가장 큰 문제는 내부에서 껍데기와 마찰이 필요한 곳만 닿게 하는 것이었다. 최우선으로 배치한 부품은 배터리로, 크기도 가장 크고 무게도 가장 무거워서 나머지 부품들의 위치선정과 무게중심의 위치에 가장 기여도가 컸다. 그 위치에 따라 바퀴는 껍데기에 닿지만 모터는 닿지 않는 바퀴 크기와 모터 마운트, 회로와 배선 또한 닿지 않게 뚜껑이 있는 상자를 설계하였다.
다른 문제는 구동 방식이었다. 평범하지 않은 구동을 채택하면서 로봇을 어떻게 움직일지 몇 가지 방법이 논의되었다. 이 디자인에서는 그 중 보조바퀴를 이용하여 접촉을 유지한 채 공을 굴리는 방법이 고려되었다. 방향 전환은 정지상태에서 두 바퀴를 반대 방향으로 돌려 내부의 동체가 원하는 방향을 바라보게 한 후 움직이도록 설계하였다.
5. 소스코드
5.1. CV 노드 상세 파이썬 코드
#from collections import deque
import cv2
import imutils
#import time
import numpy as np
waypoint = [] #로봇이 이동할 지점의 좌표
hsv = np.zeros((1,3), dtype = int)
colorUpper = (0,0,0) #color 범위 지정
colorLower = (0,0,0)
CAM_ID = 0
def mouse_callback(event, x, y, flags, param):
—————————-
마우스 콜백 함수
마우스 왼쪽 버튼 클릭할 경우 이동할 좌표를 waypoint 리스트에 추가
마우스 오른쪽 버튼 클릭할 경우 해당 마우스가 해당하는 pixel의 색깔을 추출한 후, 색상 범위 설정
마우스 휠 클릭할 경우 waypoint 에서 가장 최근의 좌표 제거(경로 취소할 때 이용)
—————————-
global waypoint, hsv, colorUpper, colorLower
if event == cv2.EVENT_LBUTTONDOWN:
waypoint.append((x,y))
if event == cv2.EVENT_RBUTTONDOWN:
color = img[1][y, x]
one_pixel = np.uint8([[color]])
hsv = cv2.cvtColor(one_pixel, cv2.COLOR_BGR2HSV)
hsv = hsv[0][0]
colorUpper = np.array([hsv[0] + 35, hsv[1] + 35, hsv[2] + 35])
colorLower = np.array([hsv[0] – 35, hsv[1] – 35, hsv[2] – 35])
if event == cv2.EVENT_MBUTTONDOWN:
if len(waypoint) != 0:
waypoint.pop(len(waypoint)-1)
def Tracking(frame, hsvUpper, hsvLower):
—————————-
컬러마커를 둘러싸는 원을 그리고 원의 중심을 return
(1. 지정한 색깔 범위에 포함되는 부분에 마스크를 생성한다.
2. 마스크로부터 contour를 구한 후 contour를 둘러싸는 외접원을 구한다.
3. 외접원의 반지름이 5 이상일 경우 원을 frame에 그리고, 원의 중심을 return한다. )
—————————-
frame = frame[1]
frame = imutils.resize(frame, width = 600)
blurred = cv2.GaussianBlur(frame, (11,11),0)
hsv = cv2.cvtColor(blurred, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, hsvLower, hsvUpper)
mask = cv2.erode(mask, None, iterations = 2)
mask = cv2.dilate(mask, None, iterations = 2)
cnts = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
center = None
if len(cnts) > 0:
c = max(cnts, key=cv2.contourArea)
((x,y), radius) = cv2.minEnclosingCircle(c)
M = cv2.moments(c)
center = (int(M["m10"]/M["m00"]), int(M["m01"]/M["m00"]))
if radius > 5:
cv2.circle(frame, (int(x),int(y)), int(radius), (180,170,220),5)
cv2.imshow(“Frame”, frame)
#time.sleep(0.1)
return (center)
def AddTracking():
—————————-
컬러마커가 이동하여(로봇이 이동하여) 지정한 경로의 좌표에 근접하는 경우, 그 좌표를 waypoint list에서 빼낸다.
—————————-
global waypoint, X1, Y1
if len(waypoint) != 0:
for i in waypoint:
X2 = i[0]
Y2 = i[1]
print(waypoint)
if abs(X1-X2)<25 and abs(Y1-Y2)<25:
waypoint.pop(0)
print(waypoint)
vs=cv2.VideoCapture(CAM_ID)
cv2.namedWindow(“Frame”)
cv2.setMouseCallback(“Frame”, mouse_callback)
while True:
img = vs.read()
if Tracking(img, colorUpper , colorLower) != None:
X1, Y1 = Tracking(img, colorUpper , colorLower)
AddTracking()
key = cv2.waitKey(1) & 0xFF
if key == ord(“q”):
break
cv2.destroyAllWindows()
cs