지난번에 Unity에서 ROS로 topic을 publish해주고 파이썬 코드를 이용해 띄워보는 것까지 했다.
그런데 문제가 하나 생겼는데.. UNIYT에서 이미지를 Publish해줄때, Message type이 Compressed Image로 고정이 되어 있다는 점이다.
이게 뭐가 문제이냐 하면, Compressed Image는 rviz에도, rqt에도 띄울 수가 없다.
Compressed Image를 이용해 파이썬에서 코드를 돌리고 딥러닝을 돌리는 것에도 문제가 없지만, 파이썬 코드에서 여러대의 카메라 Subscribe를 받고 여러 window를 띄우게 되면.. 내 노트북이 너무 아파해서 문제가 되었다.. 또한, 그 외에 여러 기능들을 사용하지 못한다는게 너무 아쉬워서 메세지 타입을 바꿔주는 코드를 만들었다.
물론 Publish를 할 때 Image message type을 바꾼채로 보낼 수 있지 않을까? 라는 생각으로 script를 수정해보았으나.. message type을 변경하고 run을 하면 unity내에서 계속 에러가 나는 바람에 포기했다...
지금 하고 있는 프로젝트에서 unity를 이용해서 진행하기로 하였다. Unity에 있는 가상 카메라로 카메라 정보를 받아 이를 처리하는 방식인데,, 문제는 Unity는 C#을 베이스로 만들어진 툴이기 때문에 다른 언어로 만든 코드를 script로 넣어줄수가 없었다. 그리고 무엇보다도.. 내가 C#을 다뤄본적이 별로 없어서.. python으로 카메라 이미지를 보내줄 수 있는 방법을 찾아보았다. 우선 여러가지 방법을 찾았다. 그렇지만 기왕 ubuntu환경에서 하기로 한거, 한번 rosbridge를 이용해서 해보려고 한다. rosbridge는 ros와 다른 tool을 이어주는 말 그대로 bridge이다. IoT를 공부하다보면 bridge기기라는게 나오는데, 센서와 엑추에이터 사이를 이어주는 역할을 해준다. rosbridge의 경우 나한테 조금은 친숙하다. 마카롱에서 나간 모라이 대회가 똑같이 rosbridge를 통해 ros와 모라이가 통신할 수 있도록 했었다. 이때 디버깅 하면서 조금 공부해 둔게 있기도 하고... 그나마 친숙한 rosbridge를 사용하기로 하였다... 근데..? ROS-Unity통신 관련 자료 왜이리 없냐... 날 너무 힘들게 한 아이.. 그치만 해치웠지.. 후후후...
rosbridge 설치하기
sudo apt-get install ros-noetic-rosbridge-server
rosbridge를 설치해주면 다음은 path를 지정해 주면된다.
source /opt/ros/noetic/setup.bash
그 다음은 로스브릿지 런치파일을 실행해주면 되는데... 이부분은 모라이 할때 단축어 해둔게 있어서 나는 편했다.
그 다음 내 IP주소를 찾아내야 하는데, 이때 ifconfig를 통해 찾아도 되지만, 나는 그냥 localhost:9090으로 진행하였다. 모라이할 때 실험해보니.. 이게 제일 편하드라고.. 자 이렇게 하면 rosbridge의 준비는 끝난다. 이제 unity로 넘어가보자
Unity에서 topic publish해주기
우선, Unity에 ROS# 패키지를 install해주어야 한다. Unity내에서 패키지를 찾아서 Assets에 넣는 방법도 있지만.. 뭔가 지금 내가 사용하고 있는 unity버전이 정상적인 버전은 절대 아닌것같아.. git을 이용한 방법으로 소개를 하겠다... (unity깔다가 내 ubuntu 맛탱이 가버렸다.. 나 우분투에서 한글 안쳐짐.. 이상하게 C드라이브에 접근할 수 있어지고.. 개발 환경도 좀 많이 바뀜.. 뭔가를 단단히 잘못한 듯.. ㅠㅠ) home에 아래 git을 clone 받아주면 된다.
clone이 다 받아졌다면 사용하고자 하는 프로젝트 Assets에 드래그 해주면 된다. 쉽죠? 이제 ros에 접근할 수 있게 된다. 여기서 잠깐..!! Test1/Assets/RosSharp/Scripts/RosBridgeClient/RosCommuncation/에 들어가면 RosConnector.cs파일이 있다. 기본적으로 토픽으로 보낼 모든 센서에 이 RosConnector script를 넣어줘야 하기 때문에, 미리 수정해서 시작하면 편하다. 이 파일을 열어보면, 아래와 같다.
여기서 크게 바꿔줘야 할 부분은 없지만.. 나는 로컬 환경에서 진행하기로 하였으니, IP주소를 조금 바꿔주려고 한다. 코드의 32번째 줄의 ip주소를 아래와 같이 바꿔주면, 앞으로 script import할 때마다 주소를 바꿔주는 수고를 덜 수 있다.
public string RosBridgeServerUrl = "ws://localhost:9090";
여기서 하나!! IP주소를 공부하다보면 정말 많이 헷갈린다.. ifconfig를 치면 내 IP Address를 알 수 있다는건 코딩을 하는 모두가 아는 사실일 것이다. 그런데..! 사실 ifconfig를 출력해보면 정말 많은 주소들이 뜨고, 그중 무엇을 이용해야하는지는 모르는 경우가 많다. 그렇기 때문에 여기서 한 번 정리해주도록 하겠다.
127.0.0.1과 localhost는 같은 의미이다. 그래서 위에서 localhost:9090으로 해주었지만, 이때 127.0.0.1로 해도 같은 곳에 연결된다. 127.0.0.1은 가장 흔히 사용되는 루프백 주소로, 로컬 호스트를 나타낸다. 이 주소를 사용하면 컴퓨터 자체와 통신하는 데 사용할 수 있다.
반면에 192.로 시작하는 주소는 사설 IP 주소 범위 중 하나이며, 일반적으로 내부 네트워크에서 사용된다. 루프백 주소와 사설 IP 주소의 가장 큰 차이는 사용 목적이다. 루프백 주소는 동일한 기기 내에서의 통신을 위한 것이며, 사설 IP 주소는 여러 기기 간의 내부 네트워크 통신을 위한 거다.
자, 여기서 하나 더! ifconfig를 치면 1: lo:<LOOPBACK~~~~ 2: wlp2s0: <BROADCAST~ 이런식으로 뜨게 된다. 이렇게 뜰때 우리는 ip주소를 확인할 수 있는데, 하나에 두개가 보여지게 된다. 이때 brd는 broadcasting의 약자로.. inet과 속도 부분에서 큰 차이는 보이지 않는다. 그러나 역시 p-to-p통신보다는 취약하고.. 좀 번거롭다. 그러니 inet 주소를 가져오고 brd주소는 특수한 경우에 사용하면 된다.
여기서 둘!! 이번엔 IP주소에 대해 알아보자. IP주소에는 크게 두가지 버전이 있다. IPv4(Internet Protocol version 4)와 IPv6(Interent Protocol version 6) 각 버전에는 주소가 할당되는 방식과 주소의 형식에 차이가 있다. 나는 지금 IPv4에 대한 얘기를 하려고 한다. IPv4는 32비트 주소 공간을 사용해, 보통 4개의 8bit 블록으로 나눠진 10진수로 표현한다. 1. Public Address(공용 주소): 인터넷에서 사용되며, 고유하게 식별된다. 주소 범위는 1.0.0.0에서 223.255.255.255까지이다.
2. Private Address(사설 주소): 내부 네트워크에서 사용된다. 라우터와 NAT(Network Address Translation)을 통해 공용 주소와 통신할 수 있다. 주소 범위에는 세가지가 있다. 10.0.0.0에서 10.255.255.255 - 대규모 기업 등에서 내부 네트워크로 사용하기 적합. 172.16.0.0에서 172.31.255.255 - 중간 규모 기업 혹은 조직에서 내부 네트워크로 사용. 192.168.0.0에서 192.168.255.255 - 가정용 혹은 소규모 비즈니스 네트워크에서 사용.
3. Reserved Address: 특수한 용도로 예약된 주소이다. 대표적으로 0.0.0.0이나, 앞에서 소개한 것처럼 127.0.0.1 혹은 255.255.255.255가 있다.
이것만 알면 당신도 네트워크 할 수 있다..! 장난이고.. 사실 내가 분명 전공 수업때 배운 내용이지만, 간혹 헷갈리는 일이 있어 한 번 정리해 보았다. 이제 안까먹었으면 좋겠다..
자 사담은 여기까지 하고 unity-ros통신을 이어서 진행해 보도록 하겠다.
SampleScence에 Emtpy Object를 추가해주고, 이름은 RosConnecter로 설정해준다. Inspector에 들어가서 Add Component를 눌러주고 ros를 검색하면 사용가능한 Script 목록이 뜬다. 그 중 RosConnector를 선택해주고 저장해주자. 이렇게 하면 이 프로젝트는 ros와 연결이 되게 된다. 여기까지 RosConnector를 만들어주었다.
자, 내가 하려고 한건 Ros와 Unity사이 통신을 통해 Ros로 Image를 보내주는 것이다. Camera를 원하는 곳에 설치하고, 화각(Field of View)까지 설정해주자. 헷갈림 방지를 위해 카메라 이름 꼭 변경해주자. 그 다음 똑같이 Add Component를 해주면 되는데, 이때 RosConnector말고 Image Publisher의 Script를 선택해주면 자동으로 RosConnector까지 추가가 된다. 이제 topic이름과 어느카메라인지 다시 설정해주면 준비가 얼추 끝나게 된다. 잘 모르겠다면 아래 사진을 참고해볼까요??
오른쪽의 Inspector를 보고 참고해보자.
이제 Ros로 넘어와보면, rosbridge 터미널에 아래와 같이 연결되었음을 알리는 것을 볼 수 있다.
이제 topic 잘 들어오는지 확인 해보자. 여러가지 방법이 있지만, 나는 내가 만들어둔 파이썬 코드를 실행해서 카메라가 잘 들어오고 있는지 확인했다.
너무 잘 들어온다... 나처럼 미리 만들어둔 코드가 없는데 지금 당장 잘 들어오는지 확인하고 싶다면, rviz나 rqt를 이용하는 방법이 있다. 지금 Image publish의 Frame Id는 Camera로 되어있기 때문에, rviz에서 frame id 바꿔주고 image add 해주면 볼 수 있다. 다른 방법으로는 rqt_graph와 rqt_image이다.
$ rqt_graph
$ rqt_image_view
위의 두가지 명령어로 토픽이 잘 들어오고 있는지 확인할 수 있다.
아이 이뻐라~
여기서 다 소개하지는 않았지만, 그 외에도 unity 사용법 익히고.. 내 프로젝트에 맞게 통신환경 설정하고.. 무엇보다도 Unity-Ros통신 자료가 많은 편은 아니라.. 고생 좀 했다.. 지금은 이렇게 받아온 이미지 처리를 진행중이다.
현재 진행하고 있는 프로젝트에서 yolo로 object를 인지하고 이에 id값을 부여해 데이터 가공을 진행하고 있다.
Object Tracking
openCV는 몇번 다뤄봤지만, Yolo는 아예 처음이라 데이터 입력값이 어떻게 들어오는지도 몰랐기에.. 유튜브 보면서 공부했고, 내가 알아보기 편하도록 조금 바꿔서 사용하려고 한다. 직전 프레임에서 detection된 object의 center_point를 저장해 뒀다가 그 다음 프레임과 비교했을 때, 거리가 일정 이하이면 같은 id로 판단하는 형식이다. 이를 연속적으로 사용할 수 있도록 하는 것과, 주변에 있는 다른 object와 겹치지 않도록 파라미터 튜닝을 진행하는 것이 관건이다.
#!/usr/bin/env python
#-*-coding:utf-8-*-
import cv2
import numpy as np
import math
from object_detection import ObjectDetection
od = ObjectDetection()
cap = cv2.VideoCapture("blackbox.mp4")
count = 0
center_points_prev = []
tracking_objects = []
track_id = []
while True:
ret, frame = cap.read()
count += 1
if not ret:
break
# Point current frame
center_points_cur = []
# Detect objects on frame
(class_ids, scores, boxes) = od.detect(frame)
for box in boxes:
(x, y, w, h) = box
cx = int((x + x + w) / 2)
cy = int((y + y + h) / 2)
center_points_cur.append((cx, cy))
# print("FRAME N", count, " ", x, y, w, h)
# cv2.circle(frame, (cx, cy), 5, (0, 0, 255), -1)
cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
# Only at the beginning we compare previous and current frame
if count <= 2:
for pt in center_points_cur:
for pt2 in center_points_prev:
distance = math.hypot(pt2[0] - pt[0], pt2[1] - pt[1])
if distance < 20:
tracking_objects[track_id] = pt
track_id += 1
else:
tracking_objects_copy = tracking_objects.copy()
center_points_cur_frame_copy = center_points_cur.copy()
for object_id, pt2 in tracking_objects_copy.items():
object_exists = False
for pt in center_points_cur_frame_copy:
distance = math.hypot(pt2[0] - pt[0], pt2[1] - pt[1])
# Update IDs position
if distance < 20:
tracking_objects[object_id] = pt
object_exists = True
if pt in center_points_cur:
center_points_cur.remove(pt)
continue
# Remove IDs lost
if not object_exists:
tracking_objects.pop(object_id)
# Add new IDs found
for pt in center_points_cur:
tracking_objects[track_id] = pt
track_id += 1
for object_id, pt in tracking_objects.items():
cv2.circle(frame, pt, 5, (0, 0, 255), -1)
cv2.putText(frame, str(object_id), (pt[0], pt[1] - 7), 0, 1, (0, 0, 255), -2)
cv2.imshow("Frame", frame)
center_points_prev = center_points_cur.copy()
key = cv2.waitKey(1)
if key == 27:
break
cap.release()
cv2.destroyAllWindows()
나는 여기서 ID유지 뿐만 아니라, 움직이는 방향을 고려해 현재 어느 방향으로 움직이고 있는지 계산하도록 진행할 예정이다. 현재 알고리즘에서는 과거의 center_point는 tracking을 하는데에만 사용된다. 나는 생성된 ID별로 center_point들의 history를 이용해 vector값을 구해보려고 한다.
아래는 내가 본 유튜브 링크이다. Object tracking 뿐만 아니라, 카메라를 이용한 다양한 알고리즘을 잘 설명하고 있어, 좋은 것 같다.
오토파일럿(AutoPilot)은 전방 카메라와 레이더, 차량 둘레에 있는 12개의 초음파 센서로 차량을 조종하고 속도를 조절하는 기능이다.
지금까지는 카메라가 오토파일럿 기능의 주된 정보 원천이었다. 그동안 레이더는 이미지 처리 기술의 보조 역할을 해온것이다. 그러나 2016년 5월 오토파일럿 모드에서 사망한 사고를 계기로 작동 방식을 바꿨다. 이제는 1/10초마다 레이더를 발사해 3차원 영상을 얻는 증 사물 식별 기능을 개선했다.
오토파일럿에는 여러가지기능이 있다.
교통 인식 크루즈 컨트롤(Traffic Aware Cruise Control)
지정된 최고 속도로 운행하며 앞차와 간격이 가까워지면 속도를 줄이거나 정차. 앞차와 간격이 늘어나면다시 최고 속도로 가속
오토 스티어(Autosteer)
현재의 차로를 계속 유지하면서 운행
인접 차로자동차 관찰기능
자동 차로 변경(Auto Lane Change)
센서 혹은 측면 카메라로 옆 차로의 공간을 확인하여 차로 변경
오토파일럿 내비게이션 고속도로(Navigation on Autopilot on the highway)
고속도로 진입로에서 나들목, 진출로까지 자율주행
오토파키(AutoPark)
평행주차, 직렬주차
차량 호출(Summon; 서몬)
주차장에서 무인 단거리 직진 전진, 후진
스마트 호출(Smart Summon; 스마트 서몬)
옥외 주차장에서 차주의 스마트폰 위치까지 무인 자율 저속 이동
스마트 호출은 옥외 주차장의 주행선 정보를 오픈스트리맵에서 사용하는 것으로 알려져 있다.
120만 화소로 360도 커버가 가능한 8개의 카메라에서 얻은 초당 36프레임의 2차원 데이터를 통해 실시간으로 추론을 진행한다. 라이다는 사용하지 않으며 순차적으로 레이더, 초음파센서 등 기타 센서들이 대부분 제거되고 있는 추세이다. 각 센서마다 데이터 생성 패턴이 다르고 sql상에 부하가 생기며 이로 인해 전처리 과정이 힘들고 데이터 엔진에 부담이 가기에 시간적으로나 금전적으로나 큰 비용이 발생하기 때문이라고 한다.
실제로 레이더를 사용하지 않는주행을 2022년 5월에 성공적으로 진행하였다.
네트워크
위치에 따라 차이가 있는 각각의 카메라 특성을 제거하기 위해 정규화 과정을 거친 데이터들은 인공 신경망(RegNet, BiFPN)을 통해 이미지의 반복적 특징을 추출하고 이를 합성한다. 여기에 쿼리 기반 어텐션 신경망을 더해, 이미지 인코딩으로 3차원 위치 정보를 얻어 규정된 쿼리로 맵핑하면 실시간으로 공간상에 3차원 점유정보가 담긴 벡터 네트워크가 생성된다.
이를 기반으로 차선 네트워크와 객체 네트워크를 생성할 수 있는데, 우선 차선 네트워크는 점유 네트워케에 지도 데이터를 더해 차선 연결을 부호화한 특수 언어를 사용한다. 격자점상으로 가능한 모든 곳에 좌표를 생성하고 여러 좌표들을 이어서 선을 만들어 차선을 생성한다.
객체 네트워크는 점유 네트워크에 속도와 가속도, 위치 등의 운동 데이터를 처리하여 타객체의 경로를 예측하는 데 쓰인다.
경로 계획
네트워크들을 기반으로 가벼운 쿼리 신경망을 추가하여 최종적인 최적화 신경망을 구축한다. 벡터 공간 상에 수 백가지 후보 경로중에 하나를 선택하고 액추에이터를 제어하여 object detection, traffic detection, line change 등의 기능이 가능하게 한다.
관심있게 본 내용
주변 환경을 인지할때 어떤 센서들을 이용해서 Sensor Fusion을 진행하는지에 대해 관심있게 보았다.