2024년도 연구 기록
연구 분야: 로보틱스, 자율 주행
연구 주제: 자율주행을 위한 경로계획 적용
연구원: 조형남
별칭: 마식(馬植)
본 연구 일지를 쓰게된 이유
올 해에 들어서 1~4월은 논문을 위한 기간을 가졌었고, 5~6월은 논문 때문에 뒤로 미뤘던 학부생으로서 해야할 졸업 프로젝트와 수업 등을 챙기느라 연구 주제에 대해서 생각하는 것은 소홀했습니다. 이후 여러 고민 끝에 7월부터는 로보틱스와 자율주행이라는 분야에 발을 들이기로 하였고, 여러 과정을 거쳐서 기록을 작성하기 시작한 2024년 11월 8일에 연구를 위한 환경 세팅이 되었습니다. 아직 연구를 제대로 시작도 하지 않았고 그 하드웨어와 소프트웨어 세팅을 하는 과정이 길었습니다.그러나 그 과정에서 고민한 것들과 공부한 것들, 그리고 고난을 극복하는 과정들은 비록 논문으로 쓸 수는 없지만 저에게 큰 삶의 교훈을 주었습니다. 그래서 이 일련의 과정들을 적으려고 합니다.
주의사항: 연구 과정에서 방황을 하기도 했고, 현재 작성중인 과정에서 기억과 기록물을 통해 정리한 것이지만, 그 과정을 깔끔하게 정리하는 것도 한계가 있고 이 정리하는 시간에 많은 시간을 투자할 수도 없기 때문에 글이 다소 두서가 없을 수 있습니다.
목차
- 도입
- Hello ROS2!
- Rotation and Straight(Point to point)
- 전역 경로
- NAV2
- RRT
- 슬럼프
- Proportional Speed(Point to point)
- DWA
- DWA 구현
- DWA in Rviz
- 데모 준비 시작
- Bag file 데모 준비
- 데모 시연 마지막 고비
- 부록
- 후기
도입
학부 4학년 1학기가 끝나고 정리가 된 이후 로보틱스의 분야 중에서 자율주행이라는 것을 토픽으로 삼았음. 이 것은 아마 논문 작업이 끝난 4월 쯤에 비슷하게 결론이 난 것으로도 기억하는데, 이 때는 시멘틱 네비게이션(LiDAR+세그멘테이션+LLM을 자율주행에 더한 것)이라는 큰 그림을 그렸었다. 그러나 필자가 크게 방황을 하여 지도교수님께서 우선 세부 목표들을 찾아주셨는데, 기본적인 자율주행 기법들을 공부해서 그걸 바로 로봇으로 움직여 보는 것이었다. 이 시점부터 현재 DWA를 로봇에 적용하여 구동하기까지 단 하나의 준비를 한 것이다. 하지만 이 세부 목표 또한 쉽지 않은 길이었다.
이제부터 펼쳐지는 이야기는, 신규 연구실이 만들어 질 때 초반에 들어와서 중도 포기될 뻔한 과제를 이어받아 평소에 공부해 두었던 딥러닝을 통해 과제와 논문 쓰기를 마무리 한 대학교 4학년이 된 학부생이 자만심에 차있다가 7월부터 생전 처음보는 ROS2 라는 시스템을 공부하며(머리가 깨져가며) 자율주행을 추구하는 이야기이다. (일부러 만연체로 작성하였습니다.) (흔한 라노벨의 도입부 흉내)
참고 사항.
지금 와서 생각해보면 자율주행을 다룬다는 것은 크게 ‘지도’+’전역경로’+’지역경로’ 이렇게 3가지를 염두하고 시스템을 설계해야 한다. 이 관점을 그대로 유지하고 보는 걸 추천한다.
Hello ROS2!
6월 4주차 무렵. Turtlebot 4라는 로봇을 통해서 ROS2를 통해서 어떻게 제어를 하는지 공부하였다. 사실 이렇게만 말하면 그닥 와닿지 않지만 이 과정에서 알아둬야 할 것이 너무 많다.
첫째로 ROS2에서 사용되는 node, topic, package, launch, bagfile, service, massage type 등 기본 개념들은 물론이고, 당연하게도 이를 사용하기 위해서 Python과 C/C++를 알아야 한다. 둘째로 ROS2는 일명 ‘통신 딸깍’ 이기 때문에 lan, wan, ip 등의 개념들을 명확하게 알고 사용할 줄 알아야 한다. 셋째로 앞에서 C/C++를 알아야 한다고 했는데, 이를 비롯해서 Cmake나 colcon build 같은 개념들도 알아야 한다. 부끄럽게도 나는 7월까지 Python이랑 C/C++로 알고리즘 문제를 풀어본 샌님이었다. 이것에 대한 설명들은 굳이 이곳에 적지 않겠다. 분량도 많고 기본 내용이라 인터넷에 설명도 많기 때문이다. (참고로 네번째로는 LiDAR 에 대한 것도 알아야 하는데 이건 나중에 설명한다.)
중요한 것은 모른다면 맞으면서 배우면 된다는 것이다.
Rotation and Straight(Point to point)
7월 1주차 무렵. 이제 나는 ROS2를 공부했기 때문에 /cmd_vel을 통해서 로봇을 움직일 수 있다. linear 로는 x 축을 조절하고, angluar 로는 z(yaw)를 조절해서 조종 가능한 turtlebot4와 Jackal을 이용했다. 그리고 이들은 각각 바퀴에 엔코더가 달려서 odom 좌표계를 사용할 수 있었는데, 우선 이걸 통해서 사용했다.
우선 목표는 로봇을 통해서 어느 목적지까지 이동하는 것이다. 정확히 정의하자면 로봇의 중심점과 목표점을 2차원 지도상에 정했을 때 두 점의 위치를 목표 반경 내에 머무르게 하는 것이다. 그렇다면 가장 직관적인 방법은 회전한 다음 직진하는 것이다.
위에 Fig 1을 보면 알 수 있듯 이해하기도 쉽고 목적을 달성하는 과정이 어렵지 않음을 알 수 있다. 하지만 이 방식은 문제가 있는데, 2번 과정인 회전에서 매우 정확해야만 그 목적을 제대로 달성 가능하다는 것이다. yaw 값을 최초 1회 계산하고 그대로 고정한 채로 직진하기 때문에 첫 계산 오차에 대한 의존성이 너무 높다. 특히 이게 계산을 제대로 한다고 해도 결국 저장되는 값은 그 한계가 있는 컴퓨터 상의 소수이기 때문에 길이가 길어질수록 그 정확도는 떨어질 수 밖에 없다. 의의라면 로봇을 처음 컨트롤 해본 실험이라는 것 정도?
전역 경로
7월 2주차. 앞에서 한 과정은 로봇을 어떻게 다루는가에 대한 것이다. 일종의 지역 경로 계획이라 할 수 있다. 사실 나도 이 흐름을 이어서 작성하고 싶으나 역사의 수레바퀴는 그렇지 못하였다. 이제 슬슬 처음보는 것들에 대해서 조금씩 익히는 중이기 때문에 뭔가 더 진도를 나가기 위해서는 그 베이스가 되는 것을 공부하는 것이 수반되었기 때문이다. 아무리 chatGPT가 있다고 하나 모든 코딩들이 뜻대로 짜여지지 않았기 때문이다. 이러한 이유로 나는 딥러닝쪽은 잘 알고 있다고 한들 이 분야에서는 초보자였기 때문에 수동적으로 행동할 수 밖에 없었고, 그런 나에게 주어진 지도교수님의 지시는 전역 경로를 한번 고려하라는 것이었다.
이는 매우 당연한 것인데, 로봇이 전체적으로 어떻게 움직일지를 두고 결정할 수 있어야 거기에 원하는 서비스를 제공하러 움직일 수 있기 때문이다. 그래서 나는 미리 공부해 두었던 A*와 RRT 중에서 RRT를 선택하여 진행하였다.
PNG 형식의 지도 파일을 받으면 그것을 통해서 경로를 계획하는 것이었는데, 이를 위해서는 Opencv를 사용하였고, RRT로는 OMPL(Open Motion Planning Library)를 사용했다. 다만, 이미 존재하는 라이브러리를 잘 쓰는게 또 어려웠다. 원하는 대로 튜닝해서 쓰는 것도 헷갈리고, 이걸 천천히 뜯어 보기엔 마음이 조급했다. 그래서 이걸 통해서 임의적인 경로계획을 한다는 것이 아래와 같은 결과물이다.
Fig 2를 보면 좌상단에서 시작하여 우하단으로 이동하는 것을 보여준다. 매우 심각한 모습을 보여주는데, 한동안 이런 형태의 RRT를 썼다. 조금씩 나아지긴 했지만 이 직후에는 일종의 재앙이 기다리고 있다.
바로 NAV2라는 것과 조우하였고, 나는 이걸 지도교수님께 보고했다.
NAV2
내가 잘못된 선택을 한 것임을 안다면 그것을 바로 고칠 수 있어야 한다. 하지만 잘못된 선택이라는 것 자체를 내가 판단할 수 조차 없는 상황또한 존재할 수 있다. 우린 그런 상황을 가능하다면 빨리 벗어나야만 한다. 그렇지 않으면 나와 내 주변의 사람들의 소중한 시간과 자원을 낭비하게 될 것이다.
7월 3주차와 4주차동안 NAV2라는 것을 공부했고 이걸 어떻게 사용해야 하는지 연구했다. 여기서 연구라는 표현이 맞을까 싶지만, NAV2를 진정 어떻게 쓸 수 있을지 탐구하고 테스트 해봤으니 연구라고 하겠다.
그 과정에 대한 것은 전에 작성한 게시글을 통해서 알 수 있다.
이 과정이 좀 절망적인 것이, 우선 이게 우연찮게도 개발중이었다는 것. 그래서 아직 오류가 존재하는 버전을 두고 계속 테스트 하니 오류만 뜨는 것이었다. 그리고 colcon build 하는 과정에서 의존성 문제가 많았다는 것. ROS2로 된 프로젝트 결과물을 사용하는 것이 늘 으레 그렇듯 버전 호환 문제와 특정 라이브러리나 프레임워크에 대한 의존도에 따라서 실행이 되고 안되고가 있었다.
이런 문제들을 극복하는 과정이 너무 어려웠다. 우선 내가 이 분야를 잘 모르고, ROS2를 좀 써봤다는 동료들도 잘 몰랐다. ROS2라고 해서 다 관련 있는 것이 아니기 때문이기도 하고, 결과론적으로 보면 NAV2는 사용할 만한 라이브러리는 아니었기 때문이다. 결과적으로 이걸 공부한 내용은 이곳에 따로 작성하지 않겠다. NAV2를 공부해서 얻은 결론이 아래와 같은 오류 대처에 대한 것들이기 때문이다.
- 터틀봇3 시뮬레이션 nav 사용
- Jackal navigation을 안 쓰고 nav2 binary만 사용
- /odom 토픽이 필요할 것 같아서 Jackal wifi 연결 후 사용
- 패키지 재설치
- TF변환이 안 돼서 URDF&Xacro 파일 로드
- bringup_launch.py 수정
- Lifecycle_manager 실행
- navigation.launch.py 파일 수정
- nav2_params.yaml 파일 수정
- amcl_nav2_launch.py 파일 작성 및 실행
- Fast DDS 설정파일을 통한 포트 변경
- Map, AMCL Planner, Controller, BT_navigator server lifecycle 강제 부팅
- .xacro 파일을urdf 파일로 변환 후 수정 및 사용
- Jackal/jackal 재설치
- AMCL 초기화 및 재실행
상당히 절망적인 과정들이었다. 지금은 NAV2에 솔직히 PTSD도 느낀다. 이것을 다루려고 여름방학 2주라는 귀한 시간을 그대로 날린 것이다. 내 분야가 아니라서 잘모르고. 잘 몰라서 연구에 대해서 수동적이게 되고. 수동적이다 보니 NAV2를 하다가 잘 안되면 이게 잘못된 선택이라는 것을 깨닫기보단 남들은 잘 되는데 내가 잘 몰라서 안되는 것일지도 모른다고 생각하게 되고. 이런 악순환 끝에 그다지 좋지 못한 자세를 가지게 되었었다. 2주가 끝나갈 무렵 이건 진짜 해도해도 안되겠다 싶어서 NAV2 종료 선언을 하였지만, 너무 늦은 시기였다.
이후엔 자율주행에 관련한 일련의 과정들은 직접 내가 처음부터 코드를 짜면서 진행하기로 결정하였다. 그게 더 빠를 것 같아서.
NAV2를 통해서 배운 점은 두 가지이다. 내가 모르는 분야더라도 자신을 믿고 과감히 포기할 줄 알아야 한다는 것. 만약 지금의 나였다면 이틀동안 하다가 NAV2를 포기했을 것이다. (여기서 포기라는 것은 negative 스러운 표현이 아니라 결단력을 가져야 한다는 말이다.) 그리고 자율주행이라는 것은 전체적으로 코드들을 잘 설계해야 한다는 점. 하나의 단일 코드로 진행하는 것이 아니라, 시스템을 구성해서 움직이는 것이다.
RRT
8월 1주차. 위의 Fig 3과 Fig 4를 보면 알 수 있듯 전보다는 더 좋아진 상태의 RRT를 구현하였다. 물론 이 결과물도 개선점이 많긴 하지만 상당히 진전하였다. 이 시점에서 NAV2와 마찬가지로 OMPL을 사용하지 않고 그냥 새로 코드를 짜서 하기로 하였다.
지도가 원래는 회색과 검은 색으로 이루어졌는데, 회색도 검은색으로 처리해서 저런 지도가 된 것이다. 그리고 살짝 빈틈이 있거나 벽에 딱 붙는 경로를 없애기 위해서 마진을 줘서 벽(장애물)과 띄워서 이동하도록 하였다.
그리고 8월 2주차. 이 무렵부터 계획을 세웠다. NAV2를 보고 느낀 시스템을 형성하는 것이었다.
엉성해 보이지만 저것도 되게 오래 고민하면서 그린 그림이었다. 이 시스템을 해석하자면 다음과 같다. 가운데 있는 빨간색 상자는 전에 했던 Rotation and Straight 코드를 의미한다. 실질적으로 로봇을 컨트롤 하는 부분과 관련이 있는 부분을 의미한다.
그리고 오른쪽의 초록색 상자는 앞서 진행한 RRT 코드를 의미한다. 그리고 왼쪽의 노란색 상자는 위치인식 모듈을 의미한다. 그래서 위치인식 모듈이 출발지를 알려주면 RRT를 통해서 최종 목적지 까지의 경로를 주게 되고 그걸 따라서 이동하는 것이다. 그렇게 설계 했었다. 위 계획은 장애물 회피도 고려하지 않았고 오류투성이다. 하지만 오래 고민해서 세운 첫 큰 그림이었고, 이 기조는 후에도 쭉 이어지며 일종의 융중대가 되었다.
이때 시스템을 세우고 코드를 짜고 그런 작업을 중심으로 하였다. 시스템을 세우고 코딩을 하고. 그게 될지 안될지 테스트는 할 수 없는 상태로. 불안한 마음으로 나아갈 수 밖에 없었다.
슬럼프
8월 3주차. 연구 과정에서 많이 헤맨 시기이다. 질책도 많이 받은 시기이다. 올해 1~6월 동안 거만했던 것이 7월에 들어오면서 아예 새로운 분야를 접하니 오히려 더 위축되고 수동적이게 되는 시기였기도 했고, 그 타이밍에 NAV2라는 벽에 부딪혀서 시간을 상당히 많이 낭비했었다. 그리고 그 영향으로 좋지 못한 상태로 계속 연구를 끌고 갔던 것이다. 아래 계획은 그런 심경을 보여준다.
며칠동안 생각하면서 코딩을 하였다. 자율주행이라는 것은 정확히 어떤 시스템을 가지고 무엇을 할지에 대한 큰 그림이 필요하기 때문이다. 서로 주고 받는 토픽이 어떻게 되는지 내부적으로 파악할 필요도 있기 때문이다. 다만 교수님께서는 이번 랩미팅에서 크게 꾸짖으셨다. 그 이유는 아마 이 글을 읽는 분들도 어렴풋이 알 것이다. 연구가 점점 붕 뜨기 때문이다.
분명 로봇 컨트롤을 하고 있었는데, RRT를 다루고 NAV2를 다루다가 이젠 전체적인 시스템만 다루고 있었다는 것. 사실 전부 동시에 진행하거나 제대로 하나씩 처리했어야 할 것이었다. 하지만 수동적인 태도를 가지게 된 상태에서 이걸 잘 소화하지 못해서 헤매다가 중심을 놓쳐버린 것이다.
전역 경로나 전체 시스템 설계가 잘못된 것은 아니다. 하지만 지역 경로 계획… 아니, 하다 못해 로봇에 대한 직접적인 컨트롤에 대한 부분을 먼저 해야함에도 불구하고 이를 놓치고 진행한 것이다.
솔직히 이때 본인은 매우 연구가 하기 싫어졌다. 잘 못하는 분야에서 더 이러고 있어야 할 필요가 있을까 싶었고, 짊어지고 있는 짐이 너무 스스로에게는 무겁다고 생각했기 때문이었다. 내가 이걸 책임져야 한다고? 말이 안된다고 생각했다. 열심히 안 한거면 또 모르는데, 그것도 아니니. 그냥 스스로에게 절망했다.
그리고 이 시점에서 본인은 포기하지 않기로 하였다. 어떤 희망을 가지고 포기하지 않았다기 보단, 이정도 시련에서 포기한다면 사회로 나가면 제대로 사람 행세 못할 것이라 생각했기 때문이다. 이 당시엔 그렇게 생각했다.
지금 이 시절을 회상하자면, 인생에서 처음으로 제대로 된 연구자가 되보려는 과정에서 고민하고 헤메는건 매우 당연하다고 생각한다. 오히려 그걸 가볍게 생각하고 성찰하지 않는 것이 더 이상하다고 할 수 있다. 그래서 이때 내가 포기를 하지 않았으리라 생각한다. 주변 사람 모두에게 연구를 그만둘지를 물었었는데, 그 사람들 모두가 나중에 하는 말이 “나는 너가 결국 계속 할 줄 알았다” 였다. 내가 평소 고민을 많이 하고 연구에 진심이었기 때문에 그런 평가를 한 것이다.
그러므로 연구에서 제일 중요한 것은 그 마음이다. 전심전력으로 뛰어들 각오를 했는가? 이게 핵심이다. 연구만의 이야기는 아니다. 마음먹기에 따라서 큰 일을 이룰 수 있다고 하는데, 정말 옳은 말이다.
왜 이런 이야기까지 연구 기록에 쓰는가? 연구에서 가장 중요한 것이라 생각해서다.
Proportional Speed(Point to point)
8월 4주차. 기존 방식의 문제를 분석하였다. 기본적으로 등속도였기 때문에 제동구간이 좁아지고, 이에 따라서 구간 인식률이 /odom 토픽으로 인지되는 순간과의 시간적 오차에 의존하게 된다. 즉, 오차가 커진 상태로 도착하거나 실제로는 도착하더라도 인식하지 못하게 되엇 계속 직진할 수도 있다. 때문에 이런 오류를 바꾼 방식을 생각한 것이 바로 Proportional Speed이다.
참고로 Proportional Speed나 Point to point나 Rotation and Straight는 그냥 임의로 적은 이름이다.
Fig 9를 보면 등가속도 구간을 추가한 것을 볼 수 있는데, 도착할 때 보면 구간 인식률이 높다는 것을 볼 수 있다. 이게 무슨 말이냐면, 1m/s 속도로 0.5초 이내 거리를 계산하면 50cm 이내의 거리를 탐지한다는 말이지만 이걸 등가속도로 낮춰서 인식하면 평균 0.5m/s 속도로 50cm를 1초간 이동하게 된다는 말이다. 간단한 예시로는 2배정도 되는 시간을 벌게 되었지만, 사실 시간을 더 늘릴 수 있다. 시간을 늘리게 되면 제동되기 시작하는 범위가 늘어나게 되므로 목표에 대한 접근성이 높아진다.
사실 여기에서 핵심은 각속도이다. 이유는 앞선 Rotation and Straight의 문제는 사실상 실시간으로 로봇의 위치와 목적지에 관한 계산이 이루어지지 않았기 때문이다. 둘 사이의 관계를 실시간으로 고려할만한 것은 직선 거리 정보와 그 각도가 있다. 직선 거리에 대한 정보는 실시간으로 고려하는데, 각도에 대한 것은 실시간으로 고려하지 않으니 오차가 발생하는 것이다.
그래서 Fig 10을 보면 알 수 있듯 목적지가 존재하는 방향과 내가 향하는 방향의 차이가 클수록 Angular Speed를 크게 주도록 하였다. 자동차로 비유하면 향하는 방향이 클수록 핸들을 크게 꺾고, 향하는 방향이 목적지와 어느정도 일치하면 핸들을 적게 꺾어서 미세 조정을 하는 것이다.
그래서 Proportional Speed를 사용해서 테스트들을 진행해보았다. Fig 11은 단일 목적지를 주었을 경우이고, Fig 12는 순서가 있는 다중 목적지를 주었을 경우이다. 이렇게 하다보니 조금 더 특이한 실험을 해보았다.
Fig 13을 통해 알 수 있는 것은 별의 끝 부분에서 Proportional Speed 알고리즘대로 움직인다는 것이다. 처음에는 휙 돌다가 점차 천천히 각을 변경하는 것을 통해 알 수 있다. 그리고 별 모양의 안쪽 각은 36°인데, 이정도는 실생활에서 접할 수 있는 예각으로 상당히 급격한 변화를 줘도 잘 이동하는 것을 확인할 수 있다.
로봇이 다양한 궤적을 그리면서 이동하게 하는 것은 어느정도 되었다고 볼 수 있다. 이제는 로봇이 장애물을 피하면서 가는 것을 만들 차례이다.
DWA
DWA란 Dynamic Window Approach의 약자로 로봇과 로봇의 환경정보를 바탕으로 속도와 회전율의 조합을 이루는 Window를 구성한 다음 평가함수를 사용하여 최적의 경로를 실시간으로 계산하여 구하는 Local Path Planning의 방법 중 하나이다.
정확한 내용이 알고 싶다면 해당 논문을 찾아 보는 것을 추천한다. DWA
이제부터는 실험에 앞서서 본인이 이해한 DWA를 설명할 것이다.
Fig 14의 왼쪽을 보면 가로의 빨간 선이 있는데 이는 각속도(Angular Speed)에 대한 변화이다. 그리고 파란 선이 있는데 이는 직진속도(Linear Speed)에 대한 변화이다. 그리고 Fig 14의 오른쪽을 보면 3가지의 평가 지표와 그것들을 합쳐서 만든 최종 점수가 있다. 목적지와 가까워지는 속도(각속도+직진속도)일수록 점수를 높게 주는 것이 Target. 멀리 있을 수록 단위 시간당 이동하는 거리가 길어야 하므로 각속도와는 관계 없이 멀수록 직진 속도가 일관되게 점수가 높아지는 (장애물 제외) 것이 Velocities. 그 끝이 장애물과 이어지면 감점을 하는 것이 Distances이다.
그렇다면 Fig 14의 왼쪽 그림에서 보면 actual velocity가 있는데 이건 정확히 무엇을 의미하는 것일까?
그것은 그 좌표까지 단위 시간동안 정해진 속도(각속도+직진속도)로 원 호를 그리면서 이동하여 도착하는 지점을 의미한다. Fig 15에서 위에 보면 5가지를 더한 것이 있는데, 이는 본인이 생각하는 DWA의 핵심이다. 그중 이 그림은 DWA의 5가지 특징 중 찰나의 순간(미분 값)동안 이동하는 컨트롤을 원형으로 한다는 것을 의미한다.
왜 찰나의 순간(미분 값)이라 표현했는가? 저렇게 제어하는 것은 순간적이라 할 정도로 바뀌기 쉽기 때문이다. 우리가 자동차에서 커브를 돌 때 한번 핸들을 고정하고 그대로 5초동안 똑같이 이동해서 원을 그리지는 않듯이 말이다. 앞서 별 모양을 그렸던 Fig 13과 비슷한 움직임을 보면 왜 찰나인지 이해할 것이다. 실시간으로 환경 정보를 받아들여서 그 계산 값을 이용하기 때문이다.
그리고 이걸 좀 더 실험을 해보면 재밌는 현상을 볼 수 있는데, Fig 16을 보면 각속도만 키우면 원의 크기가 작아지지만 이동 거리는 같은걸 볼 수 있다. 이는 보통 자동차가 유턴할 때 느낄 수 있다. 그리고 직진 속도를 늘리면 원은 그대로 유지하고 이동 거리가 호의 형태 그대로 유지하면서 늘어나는 것을 볼 수 있다.
이러한 특징을 잘 이용하면서 DWA상에서 로봇은 그때 그때의 원형 컨트롤을 선택한다. 왜 어느 속도(각속도+직진속도)를 가지는 것을 굳이 원형 속도라고 표현하는가? 그것은 나중에 설명할 것이다. 그보다 재밌는 것은 이 다음이다.
Fig 17은 각도가 차이날 수록 더 큰 각속도를 계산해내서 적용하는 DWA와 비슷한 면이 있음을 보여준다. 정확히는 장애물을 고려하지 않는 DWA가 사실상 Proportional Speed 방식임을 알 수 있다. 저 방식을 그냥 생각해서 사용했을 때는 몰랐지만, DWA를 공부하고 다시 바라보니 둘의 메커니즘이 부분적으로 일치한 것이다. 즉, 이전의 Proportional Speed는 틀린 접근법은 아니었다는 것이다.
Fig 14에서 3가지 지표를 종합하여 평가한다고 했는데, 그 과정을 Fig 18에서 잘 보여준다. 평가함수는 3가지 지표의 점수 값을 더하는데 각각에 가중치를 더해서 진행한다. 이 가중치는 DWA를 튜닝하는 중요한 파라미터이다. 이들 가중치에 따라서 DWA의 성향이 확 바뀌는데, 목적지에 대한 인력 가중치를 많이 높히면 장애물 따위 아무래도 상관 없어지는 상남자 로봇이 탄생하고 장애무에 대한 척력을 많이 높히면 로봇이 어디로도 움직이지 못하고 있는 우유부단한 로봇이 만들어지게 된다. 따라서 이를 잘 조율하여 적절한 값을 찾는 것이 중요하다.
위에서 굳이 원 모양을 그리는 이유가 있다고 했는데, Dynamic Window상에 존재하는 모든 점들은 그 원 위에서 단위 시간동안 이동하는 거리를 의미한다. 그리고 그 원들이 로봇에서 퍼져나가는 모습이 Fig 19의 자석에서 자기장이 나가는 모습과 비슷하다. 추후 실제로 DWA 알고리즘이 여러 경로들을 후보로 두고 계산하는 모습을 보면 자석 자기장과 흡사함을 알 수 있다.
여러 평가 점수를 계산하였다면 Fig 19의 왼쪽 그래프와 같이 나오고, 이중에서 가장 평가 점수가 좋은 속도(직진 속도 + 각속도)를 선택해서 이동하는 것이다. 예시를 들자면, 설정에 따라 다르지만 0.1초동안 그렇게 이동한다고 생각할 수 있다.
DWA 구현
9월 1주차. 위에서 설명한 DWA를 구현하고자 하였다.
그러나 Fig 20을 보면 알 수 있듯이 장애물과 관련없이 움직이는 왼쪽 이미지나 장애물을 피하며 이동하는 오른쪽 이미지를 보면 이상한 움직임을 보임을 알 수 있다. 이 현상이 마치 왕관모양과 같아서 Crown 현상이라 불렀는데, 이에 대한 원인으로는 몇 가지 생각할 수 있다. 우선 실시간적으로 받아들이는 시간의 간격이 너무 길다는 것. 0.1초에 한 번씩 업데이트를 하면서 가야하는데 2초에 한 번씩 업데이트를 해서 한번 원의 형태를 그어서 가는 기간이 길다는 것이다. 또 다른 이유로는 장애물에 대한 가중치가 지나치게 높아서 생긴 현상일 수 있다. 그리고 마지막 이유가 있는데, 사실상 이게 가장 큰 지분이 있을 것이라 생각한다. 애초에 DWA를 잘못 설계한 것.
그래서 위 사항들을 다시 염두하여 DWA를 새로 설계하였다.
그렇게 하여 개선을 한 DWA는 정상작동을 하였다. Fig 21을 보면 세차례의 테스트를 볼 수 있는데 장애물을 바라보고 움직이기 시작하더라도 제대로 잘 피하는 것을 확인할 수 있었다. 해당 코드는 세간에서 사용중인 DWA코드를 기반으로 작성하였다. Python으로 작성된 것들이라서 그 알고리즘만 따와서 C++로 재구성해서 작동시켰다.
그렇다면 모든 장애물을 피하며 이동할 수 있는가? 그렇지는 않다.
Fig 22를 보면 장애물이 길게 존재할 경우 잘 회피하지 못하는 것을 볼 수 있는데, 이런 경우는 예외 처리를 할 수 있게 하여 전역 경로 계획을 새로 불러오도록 할 수 있어야 한다.
DWA를 통한 파라미터는 가중치 3가지(목표, 장애물, 직진)와 조정 5가지(최대 직선 속도, 최대 각속도, 시뮬레이션 시간, 시뮬레이션 단계 시간, 안전 거리)가 있다. 그중 3가지에 대한 파라미터를 바꿔서 테스트를 진행한 결과가 Fig 23이다. 여기서 보면 장애물에 대한 가중치가 커지면 초록색 이동 경로가 더 멀리 떨어져 가는 것을 볼 수 있다. 그리고 최대속도가 증가할 경우 이동 경로가 매끈함을 볼 수 있다. 자동차가 운전속도가 느리면 구불구불 갈 수 있지만 빠르면 선회능력이 좋지 못한 것과도 같다. 마지막으로 안전거리가 늘어날 수록 초록색 경로가 장애물과 닿지 않는 거리가 증가함을 볼 수 있는데, 마지막에는 두 장애물 사이의 안전거리 범위가 겹치게 되어 더 못가는 것을 관찰할 수 있다.
그리고 이런 진전을 바탕으로 새로이 전체적인 계획을 세우게 되었으니 그 계획은 아래와 같다.
3차 시스템 구성 계획에서는 주로 다루는 주제가 지도이다. 이제 자율 주행에서 중요하게 다뤄야 할 지도가 드디어 시스템 계획 단계에서 주로 다뤄지기 시작한 시점이다.
Fig 24를 보면 아래로 내려갈 수록 실제 로봇 제어와 가까워지는 것을 볼 수 있는데, 가장 위에 있는 지도는 SLAM을 통해서 제작된 지도로 전체적인 계획 수립을 할 수 있게 만든다. 이후 로컬 지도가 있는데, 3D LiDAR 를 통해서 받아들인 정보를 작은 지도화 하여 사용한다.
이후 반영 지도는 두 지도를 합친 형태이다. 합친다는 말이 무엇이냐면, 전체 지도에서 부분적으로 로컬 지도를 오버레이 한다는 의미이다. 그리고 그 전체를 다 쓰는 것이 아니고 로봇 주위에 있는 곳을 추출하여 사용하는 것이 부분지도이다. 이렇게 부분적으로 쓰는 이유는 DWA의 연산량이 불필요한 부분까지 번지는 것을 막기 위함이다.
DWA in Rviz
9월 2주차. 이 기간에는 Rviz 사용 방법을 익히고 전역 경로를 제공하는 것을 서비스로 다시 만드는 작업을 진행하였다. 그리고 3D LiDAR 로부터 PCL(Point Cloud Library)을 사용하여 지도 데이터로 쓸 수 있는 방법을 구하였다.
시각화 도구를 Opencv로 계속 사용하는 것은 한계가 있기 때문에, 로봇이 정확히 어떤 모습을 보이며 진행하는지 정확히 알기 위해서는 Rviz라는 도구를 사용해야 할 필요가 있었다.
최초의 Rviz 시각화인데, 아직 부족한 부분이 있다. 우선 DWA가 어떻게 경로를 고려해서 움직이는지 전혀 알 수 없다. 그리고 장애물과 목적지가 너무 엉성한 감이 있다. 그러므로 이를 반영하여 다시 만들어야 할 필요가 있다.
그래서 다시 만들었더니 Fig 26의 왼쪽처럼 잘 안되고 보라색(선택된 경로)가 뭔가 여러가지가 중첩되서 나타났다. 이는 오른쪽의 코드 부분에서 문제가 있으리라 생각하였었다. 이걸 업데이트 하면서 앞에 계산한 것을 지워가면서 했어야 하지 않았나 했던 것이었다. (지금 말하자면 오른쪽 코드부분이 문제는 아니었다.)
이 무렵 깨달은 것이 있는데 코딩 인생에서 디버깅 모드랑 릴리즈 모드가 따로 있다는 것과, 릴리즈 모드로 실행하면 훨씬 더 빠르게 코드가 작동한다는 것이었다. 그래서 그 릴리즈 모드를 공부할 겸 A star 알고리즘과 Theta star 알고리즘을 사용하여 이들이 각각 어느정도의 시간이 소요되는지 알아보았다.
Fig 27은 실험 한 것 중 일부를 가져온 것이다. 결론적으로 촘촘하게 탐색을 진행할 수록 더 시간이 많이 걸린다는 것을 알 수 있다. 물론 Theta star의 577ms도 되게 빠른 것이긴 하지만. 현재 전역 경로는 RRT로 결정하였으나 A star나 Theta star의 경우도 언젠가 쓸 수도 있기 때문에 조사 알고리즘 구현 차원에서 진행하였다.
여담으로 Theta star는 A star 계열의 한 종류로 최대한 직선으로 잇되 그게 대각선이라도 예외를 두지 않는다.
그리고 3D LiDAR 데이터를 지도로 만드는 작업을 진행하였다.
위 작업은 앞으로 전체 지도위에 오버레이 할 실시간 장애물을 감지하는 지도이다. 전체 지도에는 반영되지 않은 동적 장애물들을 인지하는 중요한 지도이다.
앞서 진행한 코드들은 모두 Fig 24를 기반으로 진행된 것들인데, 이들을 하나씩 정리하여 만든 새로운 시스템이 Fig 29이다. 이 내용상의 철학은 앞과 동일하다. Fig 29의 빨간 점선의 타원은 당시에 아직 구현하지 못한 부분을 의미한다.
9월 3주차와 4주차. 당시 추석 연휴가 걸쳐있었고, 논문 작성 기간이 있었다. 이 기간중에서 연구와 관련된 부분은 Rviz 시각화만 관련되어 있다.
이 작업은 어느 일본인이 작성한 Python으로 구현된 DWA를 보고 그 알고리즘을 차용하여 C++로 재구성해서 만들었다. DWA를 구현하는 것과 DWA가 선택한 후보를 따로 골라내는 것을 구현하는 건 다른 이야기였기 때문에 별도 조사할 필요가 있었다. 결과적으로는 의도한 대로 잘 돌아간다.
데모 준비 시작
우선 10월 1주차때는 동료의 논문을 쓰기 위해 집중하였다. 이 시기에 라이다 정합에 관한 자료조사를 하였음. 이후에도 논문 쓰는 것은 병행하면서 진행함.
10월 2주차. 본격적으로 DWA로 장애물 회피하는 데모를 준비하기 시작하였다.
우선 RRT star를 다시 구현하였다. Fig 31을 보면 파라미터를 넣어서 코드의 성능을 쉽게 튜닝할 수 있게 하였는데, goalRadius는 목표 반경이고 stepSize는 한 단계당 최대 이동할 수 있는 거리를 의미하며, margin은 장애물과 띄워서 가야하는 범위를 뜻하고, radius는 신규 노드가 생성되었을 때 해당 거리내에 있는 곳 중에서 출발지랑 가장 가까운 곳 까지를 직계 이웃 노드로 삼는다는 것이다. 여기서 radius가 제일 중요한데, 이 값이 5, 10 이런식으로 작은 값을 주게 되면 사실상 RRT star 구실을 할 수 없게 된다. 그래서 일단 최대한 큰 값으로 미리 준 값이 10000이었다.
그리고 지도를 다루는 3개의 코드가 있다. Point cloud를 Occupancy Grid map으로 바꾸는 코드와 그 Grid map을 cv::Mat 로 바꾸는 코드, 그리고 이들을 전체 지도에 Overlay하는 코드 3가지를 통합하였다. 그 결과 상태를 확인하는 것이 Fig 32 이다. 이 과정에서 Overlay 하는 위치와 각도를 변경하고, 좌우 반전하는 것을 구현하여 추후 위치정보 모듈과의 협업을 준비하였다.
사실 위 과정은 단순히 성능을 합친 것이지만 기존의 복잡한 관계속의 코드들을 정리하고자 하는 시도가 담긴 과정이 담겨서 중요하다고 생각함. 이후 이 경험을 바탕으로 여러 계획을 세우며 코드 통합과 코드 기능 등을 구현하려고 하였고, 여기서 새로운 시도를 하며 새로 배우게 된 것이 있다.
Fig 33의 왼쪽을 보면 지도를 Occupancy Grid로 주는데 이렇게 줘서 각각에서 Overlay하려고 하였으나, 이 과정에서 연산량이 2배로 많아지므로 좋지 못한 계획이었다. Fig 33의 오른쪽을 보면 멀티스레드로 하려 하였으나 … … 이게 되긴 하는데, cpu에게 너무 많은 연산을 줘서 그런지 잘 되지 못하였다. 결론적으로 두 시도 모두 연산을 다 받쳐주지 못한다는 특징이 있다.
여기서 중요한 것은 Occupancy 가 안되서 새로 Image 자체를 cv::Mat를 저장해서 토픽으로 퍼블리시 하는 방법을 새로 조사하다가 알게 되었고, 멀티스레드를 사용하는 방법을 알아보다가 훗날 쓰게 되는 다중 토픽 단일 콜백 함수를 사용하는 방법을 알게 되는 등 여러 발전을 이루게 한 실패였다.
그리고 그 다중 토픽을 통한 단일 콜백 함수를 사용하는 싱크를 사용해서 만든 계획이 Fig 34이다. 동적 지도를 계속 만들어서 주는 노드가 P2M&O, 로봇 컨트롤과 서비스 클라이언트를 합친 것이 DWA, 그리고 그 클라이언트의 요청에 따라 동적 지도와 DWA의 정보를 수집해서 전역 경로를 새로 계산하여 보내주는 서비스 서버가 Global Server이다. 이 과정에서 어려웠던 것은 DWA와 Service Client를 통합하는 것이다. 이는 콜백 함수를 부르기 직전에 Client 과정을 if문으로 섞어서 해결하였다.
이쯤 오면 알 수 있는 것이 역할이 이제 한 분야씩 통합되고 하나의 노드 하나의 코드로 묶이는 것을 볼 수 있다. 초반의 정신사나운 시스템 구성에서 시작하여 조금씩 변화함을 느낄 수 있음.
Bag file 데모 준비
10월 3주차. 코드 구성은 어느정도 되었으나 위치인식 모듈을 사용할 순 없었기 때문에 그 전까지 어떻게든 bag file을 사용해서 구현하고자 하였다. 그 결과가 Fig 35이다.
사실 이 과정에서 설명할 것들이 많은데, 다른 테스트도 한번 보여주고 같이 설명을 하도록 하겠다.
Fig 36을 보면 빨간 큐브가 이상한 곳에 있는데, 의도한 것이 맞다. 위치인식 모듈도 없고 bag file이기 때문에 서비스로부터 받은 좌표들을 그냥 표시만 해두게 만들었다. 중요한 것은 여기까지 코드가 정상적으로 작동하게 만드는 과정이다.
위의 Fig 35와 Fig 36은 총 4가지의 목적을 달성하고자 하였다. Real time topic을 받아서 로봇 주위의 장애물을 인식하는 것, 후보 경로를 띄우는 것, 선택된 경로를 띄우는 것, 서비스로부터 받은 목적지를 띄우는 것, 이 4가지가 있다.
이게 처음에는 선택된 경로(Best route)가 보이지 않았다. 그래서 이걸 알아내고자 std::cout 을 막 써서 디버깅해보니 … 안보이는 것은 아니었고 1개만 보이는 것이었다. 그리고 더 알아보니 1개만 보이는 것이 아니라 1개가 아니라 30개가 하나로 겹쳐서 하나로 보이는 것이었다. 왜 그랬을까?
30개가 겹친 이유는 linear 속도가 0이고 angular 속도만 존재하는 경우만 있었기 때문에 제자리 회전하는 것이었고, Rviz에서는 Bag file을 쓴 것이어서 제자리 회전을 하도록 경로가 선택 되었는지를 확인할 수 없었던 것이다. 그래서 더 나아가서 linear와 angular 속도가 0인 경우는 없게 하여 DWA에서 시뮬레이션을 하도록 실행하였더니 점이 여러개 뜨긴 하는데… 같은 현상이었다. 최소한의 Linear와 최대한의 angular가 섞여서 나오는 것.
그러면 왜 저 값만 나올지 생각했는데, std::cout으로 값들을 검출하다 보니 min_obstacle_distance가 무한대라고 나오는 것이었다. 즉, Fig 37에서 빨간색으로 표시해둔 부분이 그대로 남았던 것이다. 그렇다면 저 무한대 값이 문제일까? … 아닌것은 아니지만, 저건 사실 그렇게 문제는 아니다. 진짜 문제는 장애물이 인식되지 않았다는 점이다. 장애물이 인식되지 못해서 제자리에서 빙글빙글 돌기만 한 것.
Fig 35랑 Fig 36은 문제가 해결되어서 검은색 장애물(벽)이 보이지만 처음엔 저것도 보이지 않았었다. 그냥 로봇의 odom 좌표계상의 위치인 초록색 구와 경로만 표시되었다. 즉, 장애물을 인식할 수 있다면 문제가 해결된다는 것이다!
그래서 장애물 인식을 하는 부분을 수정하였다. 일단 굳이 장애물 인식 함수를 만들어서 하지 않고 바로 해당 코드를 풀어서 넣었다. (사실 이렇게 할 필요는 없었지만, 보다 확실하게 처리하려고 이렇게 하였다.) 그리고 콜백 함수 부르기 전에 장애물을 인식하지 않고, 콜백 함수에서 장애물을 인식해서 처리하도록 바꾸었다. 그렇게 했더니 되었다.
처음에 이걸 뮤텍스를 사용해서 해결해야 하나 싶었는데, ROS2의 어떤 구조적인 문제로 해결이 되었다. 왜 저러는지는 정확히 설명을 못하겠고, 여러차례 시도해보니 콜백 함수에서 실행하니까 잘 되었다.
해당 문제를 해결하여 잘 뜨게 되었다. 이후 서비스를 받아서 목적지를 띄우는 것이 목표가 되었는데, 이제 또 목적지를 인식하지 못하였다. 정확한 원인은 목적지는 받았는데 콜백 함수가 실행이 안되는 오류가 난 것이다. 이는 Service Client와 관련이 있다.
앞의 Fig 38처럼 Private의 콜백 함수로 옳겨도 잘 안되었다. 그러다가 오히려 노드가 중복되는 현상이 발생하여 문법 오류가 터져서 다시 롤백 하였다.
그래서 이걸 public에서 콜백 함수를 부르기 전에 실행하는 것으로 돌아오긴 했는데… 이게 원래 if문을 써서 목적지가 없으면 서비스를 호출하고, 있으면 else문 작동해서 콜백함수 호출하였었는데… 혹시나 하여 목적지가 없어서 if문의 서비스 호출을 한 뒤에 마지막에 콜백 함수를 작동하도록 바꾸었더니 제대로 전체 코드가 정상작동 하였다. 즉, Fig 39에서 두번째 빨간색 밑줄이 없었는데, 추가했다는 의미이다. 즉, 어떤 경우라도 콜백 함수는 실행시켜야 한다는 것이다. (사실 if문 끝난 뒤에 else 쓸 필요 없이 콜백함수 불러도 되긴 할 것 같다.)
그리고 동적인 장애물들을 반영한 cv::Mat 지도를 생성한 코드(Fig 34의 P2M&O) 쪽에서는 이것을 “sensor_msgs::msg::Image” 토픽으로 퍼블리시한다.
이제 Fig 40을 보면 주황색으로 된 것은 중요하지 않고, 파란색으로 된 부분이 실질적으로 필요하다. Bag file 재생은 실전에서는 당연히 쓰지 않고, 타임 싱크 1과 2는 모두 백파일의 헤더에 있는 시간을 동일하게 맞춰서 DWA 코드의 싱크에 의한 콜백함수 호출을 원활하게 하기 위함이기 때문이다. 그리고 이 3개 코드 체제까지 오는 과정에서 최적화를 진행하였고, 이러한 상태로 데모를 준비하였다.
그리고 이들 코드를 매번 일일히 쳐서 테스트할 수는 없기 때문에 런치파일을 생성하였다.
데모 시연 마지막 고비
10월 4주차와 11월 1주차에는 다른 일들이 있었다. 우선 학부 시험이 있었기에 한문이나 DB(블로그의 DB관련 글이 있는데 그게 공부한 흔적이다), 생물학 개론 등을 공부하였다. 그리고 로봇 세팅이 안되어 있었기 때문에 사용할 로봇에 데모 및 실험을 할 수 있는 세팅을 진행하였다. 외에도 일은 많이 있었는데 생략하겠다.
11월 2주차. 다음 과정들은 로봇의 위치인식 모듈을 받은 이후의 이야기이다. 위치인식 모듈을 11월 6일 수요일 0시 24분에 전수 받았었는데, 이 이후로 11월 7일 목요일 저녁 9시까지 쉬지 않고 달려서 마지막 마스터업을 이루었다.
1차 고비 /rain/reg에 대한 custom msg 적용하는 방법을 몰랐다. 우선 우선 이게 내가 짠 코드가 아니다보니 CMakeList 라던가 코드에서 어떻게 사용해야 하는지 동료로부터 말을 듣지 못해서 직접 해결하느라 오래걸렸다. 왜 동료에게 물어보지 않았는가? 그때가 새벽 2시였는데, 이미 퇴근하고 없는 사람에게 그 시간에 질문해서 답변을 원하는건 좀 도의상 아니라고 생각했다. 일단 해당 문제는 시간을 과투자 해서 어떻게든 해결했다. ROS2를 아직 완벽하게 알지는 못한 것 같다.
2차 고비 1차 고비를 해결하고 맞이한 상황은 바로 Fig 41이었다. 실시간 라이다 데이터를 전체 지도에 오버레이해서 적용한 결과 이상한 좌표에 오버레이되는 것이다. 심지어 그와중에 라이다에 찍혀서 보이는 두 복도의 폭이 서로 달랐다. 위치가 잘못된 곳에 있다면 어떻게든 해결할 수 있겠지만, 찍히는 픽셀이 잘못된 것은 내가 어찌할 수 있는 것이 아니었다.
이해가 안갔던 부분은 해상도 1 pixel당 0.05m로 하여 해상도를 정했는데, 방금 위 이미지를 기준으로 픽셀별로 조사해서 보니 오버레이쪽 복도 픽셀 간격은 77개(385cm)로 실제와 비교해보니 약 19cm 더 넓었다. 그리고 이미지(slam 지도)상의 픽셀 복도 간격은 92개(460cm)로 실제와는 56cm길었다. DWA 의 장애물 회피 능력을 저해하지는 않지만, 차이가 꽤 있었다(약 75cm).
사실 위 사항 말고도 다른 문제로는 Fig 41에서 보면 빨간색 오버레이 주위로 지도가 회색으로 칠해진 것을 볼 수 있다. 이것도 해결해야한다. 사실 당장 위의 내용을 해결하기는 어렵기 때문에 이거부터 해결을 해야했다.
한편, 왜 복도의 폭이 다르게 찍히는가 생각하였다. z축으로 땅굴을 파서 세로 길이가 늘었을 것이라 예측하였다. SLAM을 사용할 때 로봇이 기울어 있어서 자꾸 z축 방향으로 아래로 내려갔기 때문이다. 이것 때문에 세로 방향으로 좀 안맞는거 아닌가 하였다. (하지만 이 문제 때문은 아니었다.)
3차 고비 아무리 해도 오버레이가 이상한 위치에 떴다. 왜 그런지 디버깅 해보았었는데, 이게 pixel과 물리좌표간의 변환식을 이상하게 적용하여 그런 것이었다. 그리고 방식을 지금까지 Occupancy Grid 를 사용해서 2차원 지도를 만들고 사용하였는데, 이 시점부터는 그냥 바로 point cloud 좌표들을 변환하고 그걸 중간 과정 없이 전체 지도 cv::Mat에 오버레이하는 방식을 사용하였다. 이로써 오버레이 근처가 전부 회색이 되는 현상은 해결하였다.
그리고 위에서 언급한 복도의 폭 문제가 있는데, 계산 결과와 Fig 42를 비교한 결과 결론은 둘 다 옳다였다. 실시간 라이다를 통해서 받아들인 데이터의 오버레이 부분도 맞고, 전체 지도도 맞다. 그 이유는, 벽에는 학생들의 짐을 담을 수 있는 캐비넷이 있기 때문이다. SLAM은 최대한 건물의 벽면을 반영하여서 캐비넷을 치워서 실제 건물 도면이 어떤지를 알아낸 것이고, 실시간 라이다 데이터는 캐비넷을 반영해서 장애물이 어디 있는지를 알아낸 것이기 때문이다. 아마 관측하는 z축 상의 값이 다른 것으로 보인다.
4차 고비 저 오버레이 된 이미지를 어떻게 사용하는지를 두고 오류가 많았다. 저게 cv::Mat인데, 단순 흑백이라면 Mono 형식의 데이터를 사용하여 1차원 데이터가 있을 건데, rgb를 쓴다면 4차원(명암 추가)이 되어서 쓰이기 때문에 이런 문제들을 분석하고 해결하였다. 원래 코드에서는 image를 토픽으로 퍼블리시 할 때 흑백으로 했었다가, 새로 만든 코드가 rgb로 바꿔서 주니까(빨간색 오버레이 부분) 이 토픽을 받아서 사용하는 DWA같은 코드들이 자꾸 오류를 내는 것이었다.
이 간단한 문제가 시간을 상당히 잡아 먹었고, 이때 전역경로 서비스 코드를 잠시 주석처리하여 데모에서 제외하였다. 시간 내로 오류들을 모두 잡아낼 자신이 없어져서.
5차 고비 본 과정이 가장 미스테리 했었다. 4차 고비에서 데이터 형식을 주고 받는 문제를 해결했음에도 불구하고 오류가 나는 것이다. 문법적인 오류가 아니어서 더 이해가 안갔다. std::cout으로 알아보아도 사실 무엇이 문제인지 결국 검출하지 못하여 헛돌고 있었다.
Node Started terminate called after throwing an instance of 'std::bad_alloc'
what(): std::bad_alloc
[ros2run]: Aborted
이런 오류가 DWA랑 그 다른 코드랑 계속 뜨는 것이었다. 이게 진짜 이해가 안갔다. 오류를 검색해도 뭔가 동떨어진 분석만 나오고 있고. 아마 범용적인 에러의 형태인 것으로 보이는데, 정확한 원인을 알아내는 그 과정이 어려웠다. 이 문제에 관련해서 연구실 동료들에게 많이 물어보고 같이 보면서 오랜 시간을 투자하고 나서야 그 원인을 알게 되었다.
DWA코드는 두개의 토픽을 받아서 하나의 콜백을 실행하는 메세지 필터(message_filters) 라이브러리를 사용한 토픽 동기화(Topic Synchronization) 방식을 사용한다. 즉, 두개의 토픽의 싱크를 따진다는 것인데… 원인을 알아본 결과 지도교수님께서 만들어주신 위치인식 모듈이 주는 토픽에 헤더가 없어서 그 싱크를 비교할 시간이 없었다. 즉, 한쪽 토픽은 헤더가 있어서 싱크 비교가 가능한데 다른 한쪽은 헤더 자체가 없는 데이터 형식의 토픽이라서 싱크 비교를 하게되니 오류가 난 것이다. 그러니 문법 오류도 아닌 형태로 문제가 드러난 것이다.
재밌는 점은 지도교수님께 헤더가 없는 위치정보 데이터 형식인 ‘geometry_msgs::msg::Pose’으로 퍼블리시 해달라고 말한 사람이 바로 3개월 전의 나였다. ROS2를 잘 모르는 과거의 내가 ROS2를 어느정도 알고있는 나를 뒤통수 친 격이다.
아무튼, 다른 코드에서 Pose에 헤더를 임의로 붙혀서(시간은 now()를 사용하였다) ‘geometry_msgs/PoseStamped’ 데이터 형식으로 퍼블리시하여 사용하였더니 잘 동작하였고, 데모 시연은 성공하였다.
부록
추가적인 문제들이 있어서 따로 적어보자면, 터틀봇 4는 고질적인 문제가 있었는데 그건 라이다를 종종 인식하지 못한다는 것이다. 사실 터틀봇 4가 문제가 한둘이 아닌데, 이 문제는 주로 통신 관련된 문제이다. 결국 해결 방법을 찾았는데, 껐다 켰다를 할 때 라우터를 먼저 켜고 난 다음에 터틀봇을 켜야한다. 이 순서를 지키면서 1~2번 반복하면 해결할 수 있다.
되게 무책임한 말이긴 하지만, 터틀봇의 문제는 대체로 시간이 해결해 주거나, 전원 on/off 를 그냥 반복해서 하는 수밖에 없다.
서비스 코드까지는 이때 당장 해결할 점이 보이지 않아서 해결하지 않고 뒤로 미뤘는데 해결해야 할 과제로 남아있다.
5차 고비에서도 로봇이 제자리에서 빙글빙글 도는 현상이 나타났었다. 이것도 알아보니 앞에서 겪었던 대로 좌표 변환 수식이 잘못되어서 장애물이 인식 안되는 바람에 제자리에서 돈 것이었다.
그리고 5차 고비에서 겪은 문제중 하나가 자꾸 라이다에 로봇 한 가운데에 장애물이 있는걸로 찍혀서 DWA가 제대로 작동하지 못하였는데, 이는 나중에 로봇 중심 근처에 있는 좌표들은 모두 무시하는 조건문을 작성하여 해결하였다.
후기
지금까지 2024년도 연구기록이었습니다. 지금껏 연구… 아니, 정확히는 연구를 하기 위한 밑바탕 준비를 하면서 겪은 경험들을 정리하였습니다. 이 내용들을 정리하는 과정에서 그 과정을 논리적으로 복기해보기도 하고, 그 때의 감정을 다시 생각해보기도 했습니다. 이런 관점에서 이번 연구 기록을 평가하자면, 감정에 상당히 휘둘렸다고 생각했는데 의외로 이성적으로 잘 해결해왔다고 말하고 싶습니다.
솔직히 저거 윗 부분에 NAV2부터는 그냥 계속 연구가 하기 싫었습니다. 일주일에 세번 정도는 진짜 때려치고 싶었고, 실제로 연구실 나가려고 짐 다 싸고 준비한적도 많습니다. 동료들에게는 나갈지 말지 논의도 하고. 그래서 위 문단에서 감정에 휘둘렸다고 표현한겁니다. 연구를 할 때 문제 해결 과정 자체는 되게 재밌고 유쾌한 일이지만 어떤 과정에서 단계별로 완벽하게 해결되지 못하면 그 뒤의 단계를 진행하는 것에 대해서 계속 불안해 했습니다.
이런 불안감은 대체로 개인에게 어떤 감정을 만드느냐. 내가 이 일을 정말로 잘 처리할 수는 있을까? 하는 중압감을 가지게 합니다. 그리고 개인적으로 그런 역경을 맞이한 순간이 위의 5차례의 고비라고 생각합니다. 애초에 저런 5차례의 고비라는걸 천천히 고민하면서 해결하는 것을 좋아하는데, 왜 급하게 마지막에 몰아서 해결하게 되는지… 이런걸 싫어했습니다. 그렇게 되는 상황도 싫고 그런 상황 자체도 싫습니다. 그래서 이건 내가 감당 가능한 일이 아니라 생각했습니다.
하지만 이건 틀린 생각이죠. 애초에 연구뿐 아니라 세상 만사 자기가 원하는 순서대로 순차적이면서 적절한 일을 처리할 수 있는 직업들이 얼마나 많겠습니까. 그런 형편좋은 일은 애시당초 거의 드물죠. 때문에 이러한 부담감을 적게 느끼게 하면서 일을 합리적으로 해결하는 방법들을 익히는 과정을 배웠다고 생각합니다. 그렇게 생각하는 이유는 실제로 위 일들은 잘 해결되었기 때문이고, 그 과정은 이성에 근거했기 때문입니다.
문제가 생기면 그 규칙성에 근거하여 원인을 파악하려고 하고, 그래도 모르겠으면 검색하거나 주위 동료에게 자문을 구하고, 그 커다란 문제에 대해서 감정적인 압박을 잘 조절하여 스스로를 컨트롤 할 수 있게 하는 능력. 연구자로서 필요한 능력일 뿐 아니라 사회구성원이라면 응당 지녀야 할 덕목이라고 생각합니다. 이러한 것들은 어렵기 때문에 도망치려고 하는 생각이 드는 것도 당연한 일이고요. 하지만 결국 포기하지 않았고 어떻게든 했습니다. 중요한건 이것이죠.
제 석사 졸업한 친구가 한 말이 생각 나네요. “악으로 깡으로 버텨라 아쎄이”
2024년 11월 11일 기준으로 영상 자료가 부족한 느낌이 있는데, 이는 추후 내용을 보강해서 GIF를 넣는 식으로 해서 업데이트 진행하겠습니다.