Skip to main content
Summary

로봇은 어디에 있을까

이동 로봇을 만들다 보면 아주 단순해 보이는 질문 하나와 마주친다.

로봇은 지금 어디에 있을까?

처음에는 이 질문이 별로 어렵지 않아 보인다. 바퀴가 굴러간 거리와 방향을 계산하면 위치를 알 수 있을 것 같다. 하지만 로봇에 LiDAR를 붙이고, 카메라를 얹고, 지도를 만들고, 목표 지점까지 이동시키기 시작하면 이야기가 달라진다. 로봇의 위치는 하나의 숫자로 끝나지 않는다. 로봇 본체의 중심이 어디인지, 바퀴가 닿는 지면 기준 위치는 어디인지, LiDAR는 본체에서 얼마나 앞에 붙어 있는지, 카메라는 어느 방향을 바라보는지, 지도에서 로봇은 어디에 있는지까지 모두 함께 생각해야 한다.

사람도 비슷하다. 누군가가 책상 위 컵의 위치를 묻는다고 해 보자. 우리는 컵이 내 오른쪽에 있다고 말할 수도 있고, 책상 가운데에서 30cm 앞에 있다고 말할 수도 있고, 방 전체 도면에서 창문 근처에 있다고 말할 수도 있다. 같은 컵을 말하고 있지만 기준이 다르다. 기준이 달라지면 위치 표현도 달라진다.

로봇에서도 이 기준을 좌표계, 영어로는 frame이라고 부른다. ROS2에서 여러 좌표계 사이의 관계를 관리하는 시스템이 바로 TF다. TF는 transform의 약자로, 한 좌표계에서 표현된 위치와 방향을 다른 좌표계 기준으로 바꾸는 일을 한다.

TF를 이해하지 못하면 로봇 소프트웨어는 어느 순간부터 이상한 그림을 그리기 시작한다. LiDAR 장애물이 로봇 뒤쪽에 나타나고, RViz에서 로봇 모델과 센서 데이터가 따로 놀고, Nav2는 목표를 받았는데도 움직이지 않으며 transform timeout이라는 오류를 내뱉는다. 반대로 TF를 이해하면 로봇 시스템의 보이지 않는 뼈대가 보이기 시작한다.

이 장에서는 ROS2 이동 로봇에서 가장 자주 등장하는 좌표계를 하나씩 살펴보고, map → odom → base_link로 이어지는 표준 구조가 왜 필요한지 이해해 보자.

TF와 로봇 좌표계 이해하기

좌표계는 기준을 정하는 약속이다

좌표계는 위치를 말하기 위한 기준이다. 2차원 평면에서는 보통 x축과 y축이 있고, 3차원 공간에서는 x, y, z축이 있다. 로봇에서는 여기에 방향까지 중요하다. 로봇이 어디에 있는지만으로는 충분하지 않다. 어느 쪽을 바라보고 있는지도 알아야 한다.

예를 들어 로봇 앞 1m 지점에 장애물이 있다고 하자. 이 말은 로봇 본체 기준으로는 간단하다. 로봇의 앞쪽을 x축 양의 방향으로 정했다면 장애물은 대략 (x=1.0, y=0.0)에 있다. 그런데 지도 기준으로는 이야기가 달라진다. 로봇이 방의 북쪽을 보고 있느냐, 동쪽을 보고 있느냐에 따라 그 장애물의 지도상 위치가 달라진다.

즉, 위치 데이터에는 항상 질문이 따라붙는다.

이 좌표는 무엇을 기준으로 한 좌표인가?

ROS2 메시지에서는 이 기준 좌표계를 보통 frame_id로 표현한다. 예를 들어 LiDAR 메시지인 sensor_msgs/msg/LaserScan에는 이 스캔 데이터가 어떤 좌표계 기준인지 알려 주는 헤더가 포함된다. 그 안의 frame_id가 laser_frame이라면, 레이저 거리값은 laser_frame을 기준으로 해석해야 한다.

문제는 로봇 시스템이 하나의 좌표계만 쓰지 않는다는 점이다. 이동 로봇에는 최소한 로봇 본체 좌표계, 센서 좌표계, 오도메트리 좌표계, 지도 좌표계가 필요하다. 카메라와 IMU까지 붙으면 좌표계는 더 늘어난다. TF는 이 많은 좌표계 사이의 관계를 계속 관리해 준다.

TF는 좌표계 사이의 번역기다

TF를 어렵게 느끼는 이유는 눈에 보이지 않기 때문이다. 토픽은 ros2 topic list로 볼 수 있고, 메시지는 ros2 topic echo로 확인할 수 있다. 하지만 TF는 단순한 데이터 하나가 아니라 좌표계들의 연결 관계다. 로봇 시스템 안에서 계속 흐르는 공간 정보라고 생각하면 좋다.

예를 들어 LiDAR가 laser_frame 기준으로 장애물을 감지했다. 그런데 Nav2의 전역 경로 계획기는 지도인 map 좌표계에서 장애물을 이해해야 한다. 그렇다면 ROS2는 다음과 같은 질문에 답할 수 있어야 한다.

laser_frame 기준의 점을 map 기준으로 바꾸려면 어떻게 해야 하는가?

이때 TF 트리가 필요하다. 트리란 여러 좌표계가 부모와 자식 관계로 이어진 구조를 말한다. 예를 들어 다음과 같은 연결이 있다고 해 보자.

map
 └── odom
     └── base_link
         └── laser_frame

이 구조가 있으면 ROS2는 laser_frame에서 출발해 base_link, odom, map을 차례로 거슬러 올라가며 좌표를 변환할 수 있다. 마치 외국어 문장을 한국어로 번역할 때 중간 언어를 거치는 것과 비슷하다. 중요한 것은 두 좌표계가 TF 트리 안에서 반드시 연결되어 있어야 한다는 점이다. 연결이 끊기면 변환할 방법이 없다.

TF는 단순히 좌표 이름을 모아 두는 표가 아니다. 각 좌표계 사이의 위치 차이와 회전 차이를 함께 저장한다. 예를 들어 base_link에서 laser_frame까지 x 방향으로 0.2m, z 방향으로 0.15m 떨어져 있고 회전은 없다고 표현할 수 있다. 반대로 로봇이 움직이면 odom에서 base_link까지의 관계는 시간에 따라 계속 변한다.

그래서 TF에는 시간 개념도 들어간다. 어떤 좌표 변환은 항상 고정되어 있고, 어떤 좌표 변환은 매 순간 바뀐다. 이 차이는 뒤에서 정적 변환과 동적 변환으로 다시 살펴보자.

base_link는 이동 로봇에서 가장 중심이 되는 좌표계다. 이름 그대로 로봇 본체를 대표하는 기준 프레임이다. 많은 로봇에서 base_link의 원점은 로봇 중심부 근처에 둔다. 보통 x축은 로봇의 앞쪽, y축은 왼쪽, z축은 위쪽을 향하게 정한다.

이 약속은 아주 중요하다. 로봇에게 cmd_vel로 선속도 x 방향 0.2m/s를 보낸다고 생각해 보자. 일반적인 이동 로봇에서는 이것을 로봇 앞쪽으로 초속 0.2m 이동하라는 뜻으로 해석한다. 이때 앞쪽이 무엇인지를 정의하는 기준이 바로 base_link다.

base_link는 로봇의 몸통이라고 생각하면 쉽다. 바퀴, LiDAR, 카메라, IMU 같은 부품은 이 몸통을 기준으로 어디에 붙어 있는지 표현된다. 예를 들어 LiDAR가 로봇 중심보다 20cm 앞에 붙어 있다면 base_link → laser_frame 변환에 그 위치 관계가 들어간다.

실제 로봇에서 base_link의 위치를 어디로 잡을지는 설계에 따라 조금씩 다르다. 하지만 한 번 정했다면 모든 센서와 부품 위치가 그 기준에 맞아야 한다. 어떤 파일에서는 로봇 중심을 기준으로 하고, 어떤 노드에서는 뒤쪽 바퀴 축을 기준으로 하면 시스템 전체가 삐걱거리기 시작한다.

base_footprint: 바닥에 투영한 로봇 기준

base_footprint는 base_link와 비슷하지만 조금 더 바닥 친화적인 좌표계다. 이름 그대로 로봇 본체를 바닥에 내려찍은 발자국 같은 기준이다. 보통 base_footprint는 지면 위에 놓이며, roll과 pitch 회전을 제거한 2차원 이동 로봇의 기준으로 사용된다.

roll은 로봇이 좌우로 기우는 회전이고, pitch는 앞뒤로 고개를 숙이거나 드는 회전이다. 실내 바퀴 로봇은 대체로 평평한 바닥을 달리기 때문에 Nav2 같은 2D 내비게이션에서는 높이 변화나 기울기를 크게 다루지 않는다. 이때 base_footprint는 로봇의 2D 위치를 깔끔하게 표현하는 기준이 된다.

예를 들어 로봇 위에 센서가 높게 달려 있어서 base_link의 z 위치가 지면보다 위에 있을 수 있다. 하지만 경로 계획에서 중요한 것은 로봇이 바닥 위 어느 영역을 차지하는가이다. base_footprint는 이런 상황에서 로봇의 바닥 기준 위치를 표현하는 데 유용하다.

다만 모든 로봇이 반드시 base_footprint를 써야 하는 것은 아니다. 어떤 시스템은 base_link만으로 충분히 구성되기도 한다. 중요한 것은 Nav2나 SLAM 패키지에서 어떤 프레임 이름을 기대하는지 확인하고, 설정 파일과 실제 TF 트리를 일치시키는 것이다.

odom: 끊기지 않고 이어지는 로컬 위치

odom은 오도메트리 좌표계다. 오도메트리는 로봇이 스스로 얼마나 움직였는지 추정하는 방법을 말한다. 바퀴 엔코더, IMU, 비주얼 오도메트리 같은 정보를 이용해 로봇의 이동량을 계산한다.

odom 좌표계의 가장 큰 특징은 연속성이다. 로봇이 앞으로 조금 움직이면 odom → base_link 변환도 조금 변한다. 로봇이 회전하면 방향도 부드럽게 바뀐다. 중간에 갑자기 순간이동하듯 튀지 않는 것이 중요하다.

그런데 오도메트리는 완벽하지 않다. 바퀴가 미끄러지거나 바닥이 울퉁불퉁하거나 엔코더 값에 오차가 있으면 위치 추정이 조금씩 틀어진다. 처음에는 괜찮아 보이지만 시간이 지날수록 오차가 누적된다. 로봇이 실제로는 복도 중앙에 있는데, 오도메트리만 믿으면 벽 쪽으로 치우쳐 있다고 생각할 수 있다.

그래서 odom은 짧은 시간 동안 부드럽게 움직임을 추적하는 좌표계라고 이해하면 좋다. 로봇 제어기 입장에서는 이 부드러움이 매우 중요하다. 위치가 갑자기 튀면 제어 명령도 불안정해질 수 있기 때문이다.

ROS2 이동 로봇에서 보통 odom → base_link 변환은 오도메트리 노드가 발행한다. 차동 구동 로봇이라면 양쪽 바퀴의 회전량을 계산하는 노드가 이 변환을 만들 수 있고, 시뮬레이션에서는 Gazebo 플러그인이 발행할 수도 있다.

map: 전역 지도 기준

map은 지도 좌표계다. SLAM으로 만든 지도나 미리 저장한 실내 지도에서 사용하는 전역 기준이라고 생각하면 된다. 로봇이 어느 방에 있는지, 복도 어느 지점에 있는지, 목표 지점이 지도상 어디인지를 말할 때 map 좌표계를 사용한다.

map 좌표계의 특징은 전역적이라는 점이다. 로봇이 만든 지도나 불러온 지도 위에서 위치를 해석한다. Nav2에서 RViz로 목표 지점을 찍으면 보통 그 목표는 map 좌표계 기준으로 표현된다. 저장된 지도 위에서 로봇의 현재 위치를 추정하는 AMCL도 map 좌표계를 중심으로 동작한다.

하지만 map 좌표계는 odom과 달리 위치가 순간적으로 보정될 수 있다. 예를 들어 로봇이 처음에는 자신이 복도 중앙에 있다고 생각했는데, LiDAR 관측과 지도 매칭을 해 보니 실제로는 조금 왼쪽에 있었다고 하자. 위치 추정 노드는 이 차이를 보정해야 한다. 이때 map → odom 변환이 조정된다.

여기서 중요한 설계 원칙이 나온다. 로봇의 짧은 시간 움직임은 odom → base_link가 부드럽게 담당하고, 지도 기준의 전역 보정은 map → odom이 담당한다. 이렇게 역할을 나누면 로봇 제어는 안정적으로 유지하면서도 지도 위 위치는 계속 더 정확하게 보정할 수 있다.

laser_frame: LiDAR가 바라보는 세상

laser_frame은 LiDAR 센서의 좌표계다. 2D LiDAR는 주변 물체까지의 거리를 각도별로 측정한다. 이때 거리값은 센서 자신을 기준으로 한다. 센서의 정면이 어디인지, 센서가 로봇 중심에서 얼마나 떨어져 있는지에 따라 같은 장애물도 다른 좌표로 표현된다.

예를 들어 LiDAR가 로봇의 정중앙에 정확히 앞을 보고 붙어 있다면 해석이 간단하다. 하지만 실제 로봇에서는 센서가 앞쪽으로 10cm 나와 있거나, 본체 위쪽에 높게 달려 있거나, 조립 과정에서 약간 돌아가 있을 수 있다. 이 차이를 TF에 정확히 넣어 주지 않으면 로봇은 장애물 위치를 잘못 이해한다.

상상해 보자. 로봇 앞 50cm 지점에 상자가 있다. LiDAR는 상자를 정확히 감지했다. 그런데 TF 설정에서 LiDAR가 실제보다 20cm 뒤에 있는 것으로 되어 있다면, 시스템은 상자가 실제보다 더 가까이 있다고 판단할 수 있다. 반대로 센서 방향이 180도 뒤집혀 있다고 설정되어 있으면 앞에 있는 장애물이 뒤에 있는 것처럼 보인다.

이런 문제는 RViz에서 아주 극적으로 드러난다. 로봇 모델은 똑바로 있는데 레이저 스캔이 옆으로 누워 있거나, 장애물 점들이 벽 반대편에 나타나기도 한다. 센서는 멀쩡한데 좌표계 관계가 틀린 것이다.

camera_link는 카메라 본체의 좌표계다. 카메라는 LiDAR보다 좌표계가 더 까다롭게 느껴질 수 있다. 카메라에는 본체 기준 좌표계뿐 아니라 광학 좌표계가 함께 사용되는 경우가 많기 때문이다. 일반적으로 카메라 이미지 처리에서는 카메라 렌즈가 바라보는 방향, 이미지의 가로축과 세로축이 중요하다.

이 책의 흐름에서는 우선 camera_link를 카메라가 로봇 어디에 붙어 있는지 표현하는 기준으로 이해하면 충분하다. 예를 들어 카메라가 로봇 앞쪽 15cm, 위쪽 30cm에 있고 약간 아래를 바라본다면 이 위치와 방향이 base_link → camera_link 변환으로 표현된다.

카메라를 이용한 Visual SLAM이나 객체 인식에서는 이 변환이 매우 중요하다. 카메라 이미지에서 사람을 발견했다고 하자. 그 사람은 카메라 이미지 안에서는 오른쪽 아래에 보일 수 있다. 하지만 로봇이 회피하거나 접근하려면 그 사람이 로봇 기준으로 어디에 있는지, 더 나아가 지도 기준으로 어디에 있는지를 알아야 한다. 이때 카메라 좌표계에서 로봇 좌표계, 로봇 좌표계에서 지도 좌표계로 변환하는 과정이 필요하다.

즉, camera_link는 로봇의 눈이 달린 위치를 알려 주는 프레임이다. 눈이 어디에 붙어 있는지 모르면, 눈으로 본 물체가 몸 기준으로 어디에 있는지도 알 수 없다.

REP-105가 말하는 이동 로봇 TF 구조

ROS 생태계에서는 이동 로봇 좌표계에 대해 널리 쓰이는 관례가 있다. 그중 핵심이 map → odom → base_link 구조다. 이 구조는 REP-105에서 설명하는 이동 플랫폼 좌표계 관례로 알려져 있다. REP는 ROS Enhancement Proposal의 약자로, ROS에서 사용하는 설계 관례와 제안을 정리한 문서 체계다.

가장 기본적인 구조는 다음과 같다.

map
 └── odom
     └── base_link
         ├── laser_frame
         └── camera_link

이 트리에서 각 프레임의 역할은 분명하다. map은 전역 지도 기준이고, odom은 로컬 오도메트리 기준이며, base_link는 로봇 본체 기준이다. 센서 프레임들은 로봇 본체에 붙어 있는 자식 좌표계가 된다.

여기서 초보자가 가장 자주 헷갈리는 부분은 map과 odom이다. 둘 다 로봇 위치를 표현하는 데 쓰이기 때문이다. 하지만 역할은 다르다.

odom은 부드럽지만 시간이 지날수록 틀어질 수 있다. map은 지도 기준으로 더 정확한 전역 위치를 제공하지만, 위치 추정 보정 때문에 순간적으로 달라질 수 있다. 그래서 로봇의 실제 자세는 보통 map → odom과 odom → base_link를 합쳐서 계산한다.

예를 들어 로봇이 바퀴 오도메트리만으로 10m 전진했다고 생각해 보자. 하지만 실제로는 바퀴가 조금 미끄러져 9.7m만 이동했을 수 있다. SLAM이나 AMCL이 LiDAR 데이터와 지도를 비교해서 이 차이를 발견하면, odom → base_link를 갑자기 고치기보다는 map → odom을 조정한다. 이렇게 하면 로봇 제어에 필요한 로컬 움직임은 계속 부드럽게 유지되고, 지도상 위치는 더 현실에 가깝게 맞춰진다.

이 구조는 Nav2에서 특히 중요하다. Nav2는 로봇의 현재 위치, 전역 경로, 지역 장애물, 속도 명령을 모두 연결해야 한다. 이 연결의 중심에 TF가 있다. map, odom, base_link가 올바르게 이어져 있지 않으면 Nav2는 로봇이 어디에 있는지 확신할 수 없다.

지도 위 장애물 하나를 해석하는 과정

TF가 왜 필요한지 가장 잘 보여 주는 예는 LiDAR 장애물이다. 로봇이 복도를 달리다가 앞에 놓인 의자를 LiDAR로 감지했다고 해 보자.

LiDAR가 처음 얻는 정보는 지도 좌표가 아니다. LiDAR는 단지 자기 기준으로 몇 도 방향에 몇 미터 떨어진 물체가 있다고 말한다. 예를 들어 laser_frame 기준으로 x 방향 1.2m, y 방향 0.3m 위치에 점 하나가 있다고 하자. 이것은 LiDAR 입장에서 오른쪽 앞에 물체가 있다는 뜻이다.

하지만 Nav2의 costmap은 이 점을 로봇 주변 장애물로 표시해야 하고, 경우에 따라 지도 좌표에서도 해석해야 한다. 그러려면 이 점이 다음 과정을 거쳐야 한다.

laser_frame 기준 장애물
→ base_link 기준 장애물
→ odom 기준 장애물
→ map 기준 장애물

먼저 laser_frame → base_link 변환을 통해 센서 기준 점을 로봇 본체 기준으로 바꾼다. LiDAR가 로봇 중심보다 앞에 붙어 있다면 그만큼 위치가 보정된다. 다음으로 base_link → odom 변환을 이용해 로봇이 현재 로컬 공간에서 어디에 있는지 반영한다. 마지막으로 odom → map 관계를 통해 지도 기준 위치로 바꿀 수 있다.

이 과정이 제대로 되면 RViz에서 레이저 스캔 점들이 로봇 주변 벽과 잘 맞아떨어진다. 복도 벽은 지도상의 벽 위치와 겹쳐 보이고, 로봇 앞 의자는 costmap에 장애물로 표시된다.

반대로 TF가 잘못되어 있으면 이상한 일이 벌어진다. LiDAR가 앞을 보고 있는데 TF에서는 뒤를 보고 있다고 설정되어 있으면, 로봇은 앞 장애물을 뒤 장애물로 착각한다. LiDAR 높이나 위치가 틀어져 있으면 스캔 점들이 로봇 모델과 어긋난다. map → odom 연결이 없으면 지도 좌표로 변환할 수 없어 Nav2가 장애물과 경로를 일관되게 다루지 못한다.

센서 데이터 자체는 정직하다. 센서는 자신이 본 것을 말할 뿐이다. 그 말을 로봇 전체의 언어로 번역하는 것이 TF다.

정적 변환: 로봇에 고정된 관계

정적 변환은 시간이 지나도 변하지 않는 좌표계 관계다. 로봇 본체에 나사로 고정된 LiDAR, 카메라, IMU의 위치가 대표적인 예다. 로봇이 움직이든 멈추든, LiDAR가 base_link에서 20cm 앞에 붙어 있다는 사실은 변하지 않는다.

예를 들어 다음 관계는 정적 변환으로 표현할 수 있다.

base_link → laser_frame
base_link → camera_link
base_link → imu_link
base_link → base_footprint

물론 base_link → base_footprint는 로봇 모델에 따라 해석이 조금 달라질 수 있지만, 일반적인 평면 이동 로봇에서는 고정 관계로 두는 경우가 많다.

정적 변환은 한 번만 정확히 발행하면 된다. ROS2에서는 static_transform_publisher를 사용하거나, URDF에서 로봇 링크와 조인트를 정의한 뒤 robot_state_publisher가 이를 발행하도록 구성할 수 있다. 뒤 장에서 URDF를 다룰 때 이 부분을 더 구체적으로 보게 된다.

정적 변환에서 중요한 것은 실측이다. 센서가 대략 앞에 붙어 있으니 x를 0.2m쯤으로 넣자는 식으로 시작할 수는 있다. 하지만 SLAM이나 장애물 회피 품질을 높이려면 실제 장착 위치를 최대한 정확히 반영해야 한다. 몇 센티미터 차이는 처음에는 작아 보이지만, 좁은 복도나 문턱 근처에서는 로봇의 판단을 바꿀 수 있다.

동적 변환: 시간에 따라 바뀌는 관계

동적 변환은 로봇이 움직이면서 계속 변하는 좌표계 관계다. 대표적인 예가 odom → base_link다. 로봇이 앞으로 이동하면 base_link는 odom 좌표계 안에서 앞으로 움직인다. 회전하면 방향도 변한다. 이 값은 매 순간 새로 계산되어 발행된다.

map → odom도 동적 변환이다. 로봇이 지도 위에서 자신의 위치를 계속 보정하기 때문이다. SLAM 노드나 위치 추정 노드는 센서 데이터와 지도를 비교해 map과 odom 사이의 관계를 조정한다.

정적 변환과 동적 변환을 구분하는 간단한 질문이 있다.

이 관계가 로봇이 움직여도 변하지 않는가?

센서 장착 위치처럼 로봇 몸체에 고정된 관계라면 정적 변환이다. 로봇이 방 안에서 이동하면서 바뀌는 위치라면 동적 변환이다.

이 구분은 단순한 개념 문제가 아니다. 시스템 설계에서 매우 중요하다. 예를 들어 base_link → laser_frame을 동적 변환으로 계속 발행할 필요는 없다. 센서가 흔들리는 특수한 구조가 아니라면 고정된 값을 반복해서 바꿀 이유가 없다. 반대로 odom → base_link를 정적 변환으로 둔다면 로봇은 영원히 같은 위치에 있는 것으로 보인다. 바퀴는 굴러가는데 TF상 로봇은 움직이지 않는 이상한 상태가 된다.

TF 트리는 한 부모 규칙을 따른다

TF 트리는 이름 그대로 트리 구조다. 트리 구조에서는 하나의 자식 프레임이 여러 부모를 동시에 가질 수 없다. 예를 들어 base_link가 odom의 자식이면서 동시에 map의 직접 자식이 되면 구조가 꼬인다.

잘못된 구조는 다음과 같다.

map ───────┐
           ├── base_link
odom ──────┘

이렇게 되면 base_link의 위치를 누가 결정해야 하는지 모호해진다. 오도메트리 노드도 로봇 위치를 말하고, 위치 추정 노드도 로봇 위치를 직접 말한다면 서로 충돌할 수 있다.

표준 구조에서는 역할을 나눈다.

map → odom → base_link

위치 추정 노드는 map → odom을 담당하고, 오도메트리 노드는 odom → base_link를 담당한다. 이렇게 하면 각 노드가 자기 책임만 명확히 수행한다. ROS2에서 좋은 시스템은 단지 노드를 많이 나누는 것이 아니라, 각 노드가 어떤 좌표 변환을 책임지는지까지 분명해야 한다.

frame_id와 child_frame_id

오도메트리 메시지나 TF 메시지를 보다 보면 frame_id와 child_frame_id라는 이름이 나온다. 처음에는 둘이 비슷해 보여 헷갈리지만, 의미는 명확하다.

frame_id는 부모 좌표계를 뜻하고, child_frame_id는 자식 좌표계를 뜻한다. 예를 들어 오도메트리 메시지에서 frame_id가 odom, child_frame_id가 base_link라면, 이 메시지는 odom 기준에서 본 base_link의 위치와 방향을 표현한다.

frame_id: odom
child_frame_id: base_link

이 관계는 odom → base_link 변환을 뜻한다. 로봇의 현재 위치가 odom이라는 로컬 기준 안에서 어디에 있는지 말하는 것이다.

LiDAR 메시지의 경우에는 보통 child_frame_id가 따로 있는 것이 아니라 헤더의 frame_id가 중요하다. LaserScan 메시지의 header.frame_id가 laser_frame이면, 그 스캔 데이터는 laser_frame 기준으로 측정되었다는 뜻이다. 이후 이 데이터를 다른 좌표계로 변환하려면 TF 트리 안에 laser_frame이 연결되어 있어야 한다.

프레임 이름 하나가 틀려도 문제가 생긴다. 예를 들어 URDF에서는 laser_link라고 했는데 LiDAR 드라이버 메시지에서는 laser_frame이라고 보낸다면, ROS2는 이 둘이 같은 센서라는 사실을 알지 못한다. 사람에게는 비슷해 보이는 이름이지만, 시스템에게는 완전히 다른 좌표계다.

TF가 맞지 않을 때 나타나는 증상

TF 문제는 처음에는 하드웨어 문제처럼 보일 때가 많다. 로봇이 이상하게 움직이면 모터가 문제인가 싶고, 지도와 스캔이 맞지 않으면 LiDAR가 고장인가 의심한다. 하지만 실제로는 좌표계 설정이 틀린 경우가 많다.

대표적인 증상은 다음과 같다.

  • RViz에서 로봇 모델과 LiDAR 스캔이 서로 어긋나 보인다.

  • 로봇을 앞으로 움직였는데 RViz에서는 옆으로 이동하거나 뒤로 이동하는 것처럼 보인다.

  • 지도 위에서 로봇 위치가 갑자기 튀거나, 방향이 90도 또는 180도 틀어져 있다.

  • Nav2가 목표를 받았지만 경로를 만들지 못하거나, transform timeout 오류를 출력한다.

  • costmap에 장애물이 실제 위치와 다른 곳에 찍힌다.

이런 증상을 볼 때는 센서값 자체를 의심하기 전에 TF 트리를 먼저 확인하는 습관을 들이는 것이 좋다. 특히 map, odom, base_link, 센서 프레임이 하나의 트리로 연결되어 있는지 확인해야 한다.

TF 문제는 대개 아주 작은 오타나 책임 중복에서 시작된다. base_link를 base-link로 썼다든지, 어떤 노드가 odom → base_link를 발행하는데 다른 노드도 같은 변환을 발행한다든지, Nav2 설정의 robot_base_frame은 base_footprint인데 실제 TF에는 그 프레임이 없다든지 하는 식이다.

Nav2를 실행하다 보면 다음과 비슷한 오류를 만날 수 있다.

Timed out waiting for transform from base_link to map

또는 다음처럼 표현될 수도 있다.

Transform data too old
Could not transform from odom to base_link
Lookup would require extrapolation

문구는 상황에 따라 다르지만 핵심은 비슷하다. Nav2가 어떤 좌표계에서 다른 좌표계로 변환하려고 했는데, 필요한 TF를 제때 얻지 못했다는 뜻이다.

예를 들어 Nav2는 지도 위에서 로봇의 현재 위치를 알아야 한다. 그러려면 map → odom → base_link 경로가 연결되어 있어야 한다. 만약 map → odom이 없다면 Nav2는 지도 기준에서 로봇 위치를 계산할 수 없다. odom → base_link가 없다면 로봇이 오도메트리 기준에서 어디에 있는지 알 수 없다. base_link 대신 base_footprint를 쓰도록 설정했는데 해당 프레임이 없다면 역시 변환에 실패한다.

transform timeout의 흔한 원인은 다음과 같다.

  • TF 트리에서 필요한 프레임 연결이 끊겨 있다.

  • 프레임 이름이 설정 파일과 실제 발행 이름에서 서로 다르다.

  • TF를 발행해야 할 노드가 실행되지 않았거나 늦게 시작되었다.

  • 시뮬레이션 시간 설정인 use_sim_time이 노드마다 일치하지 않는다.

  • TF 메시지의 timestamp가 너무 오래되었거나 미래 시간으로 찍혀 있다.

  • 같은 변환을 여러 노드가 동시에 발행해 값이 충돌한다.

특히 시뮬레이션에서는 use_sim_time 불일치가 자주 문제를 만든다. 어떤 노드는 시뮬레이션 시간을 쓰고, 어떤 노드는 실제 컴퓨터 시간을 쓰면 서로 같은 순간을 보고 있지 않게 된다. 좌표 변환은 시간에 민감하기 때문에, 시간 기준이 어긋나면 TF가 있어도 사용할 수 없는 데이터가 된다.

Nav2의 transform timeout은 단순히 기다리면 해결되는 문제가 아닐 때가 많다. 메시지는 timeout이라고 말하지만, 실제 원인은 연결 누락, 이름 불일치, 시간 불일치일 수 있다. 그래서 이 오류를 만나면 먼저 TF 트리와 프레임 이름, 시간 설정을 차례로 확인해야 한다.

RViz에서 Fixed Frame을 고르는 의미

RViz를 사용할 때 왼쪽 설정 패널에 Fixed Frame이라는 항목이 있다. 이 값은 RViz가 화면을 그릴 때 기준으로 삼는 좌표계다. Fixed Frame을 map으로 설정하면 지도 기준으로 로봇과 센서 데이터를 표시하려고 한다. odom으로 설정하면 오도메트리 기준으로 표시한다. base_link로 설정하면 로봇 본체 기준으로 주변 데이터가 움직이는 것처럼 보일 수 있다.

Fixed Frame을 잘못 고르면 데이터가 사라진 것처럼 보일 수 있다. 예를 들어 Fixed Frame이 map인데 현재 TF 트리에 map이 없다면 RViz는 laser_frame이나 base_link 데이터를 map 기준으로 변환할 수 없다. 그러면 화면에 아무것도 안 나오거나 오류 표시가 뜬다.

초기 테스트에서는 odom을 Fixed Frame으로 두면 편할 때가 많다. 아직 지도나 위치 추정이 없더라도 오도메트리와 로봇 본체, 센서 프레임만 연결되어 있으면 데이터를 볼 수 있기 때문이다. SLAM이나 Nav2를 실행해 map 프레임이 생기면 Fixed Frame을 map으로 바꾸어 지도 기준 전체 상황을 확인할 수 있다.

RViz는 TF 문제를 눈으로 확인하는 강력한 도구다. 로봇이 안 움직인다고 생각했는데 사실은 Fixed Frame이 잘못되어 있었던 경우도 있다. 반대로 RViz에서 스캔이 로봇과 맞지 않는다면 TF 설정을 의심해야 한다.

좌표계 이름은 설계 문서처럼 다루자

작은 예제에서는 프레임 이름을 대충 정해도 동작하는 것처럼 보인다. 하지만 프로젝트가 커질수록 좌표계 이름은 설계 문서만큼 중요해진다. 로봇 모델, 센서 드라이버, SLAM, Nav2, RViz 설정이 모두 같은 이름을 바라봐야 하기 때문이다.

예를 들어 다음과 같은 이름을 프로젝트 표준으로 정했다고 하자.

map
odom
base_footprint
base_link
laser_frame
camera_link
imu_link

그렇다면 URDF, launch 파일, 파라미터 파일, 드라이버 설정에서 모두 이 이름을 일관되게 사용해야 한다. Nav2 파라미터에서 global_frame은 보통 map, odom_frame은 odom, robot_base_frame은 base_link 또는 base_footprint로 맞춘다. SLAM 패키지나 AMCL도 이 프레임 이름과 일치해야 한다.

프레임 이름은 단순한 문자열이지만, 로봇 시스템 전체를 묶는 계약이다. 계약이 깨지면 각 노드는 자기 일을 하고 있어도 전체 시스템은 협력하지 못한다.

좋은 TF 구조를 만드는 습관

TF는 한 번 이해하고 끝나는 개념이 아니다. 로봇을 만들 때마다 다시 확인해야 하는 기본기다. 센서를 하나 추가할 때마다 새 프레임이 생기고, SLAM을 붙이면 map 관계가 생기며, Nav2를 붙이면 모든 프레임의 일관성이 중요해진다.

실제 프로젝트에서는 다음 습관이 도움이 된다.

  • 로봇을 설계할 때 먼저 필요한 프레임 목록을 적는다.

  • 각 프레임의 부모가 누구인지 정하고, 트리 구조로 그려 본다.

  • 정적 변환과 동적 변환을 구분해 어떤 노드가 발행할지 결정한다.

  • 프레임 이름을 URDF, launch, 파라미터 파일에서 일관되게 사용한다.

  • RViz에서 로봇 모델, 센서 데이터, TF 표시를 함께 확인한다.

  • Nav2 오류가 나면 토픽보다 먼저 TF 연결과 시간 설정을 확인한다.

이 습관은 디버깅 시간을 크게 줄여 준다. TF가 제대로 잡혀 있으면 센서 데이터와 지도, 로봇 위치가 한 장의 그림 위에 정렬된다. 반대로 TF가 흔들리면 모든 것이 조금씩 어긋난다.

작은 로봇의 좌표계 이야기

마지막으로 작은 TurtleBot을 떠올려 보자. 이 로봇은 방 안을 돌아다니며 LiDAR로 벽을 보고, 바퀴 엔코더로 이동량을 추정하고, SLAM으로 지도를 만든다. 겉으로 보기에는 단순히 바퀴 두 개 달린 원반형 로봇일 뿐이지만, ROS2 안에서는 여러 좌표계가 함께 움직인다.

LiDAR는 laser_frame 기준으로 벽까지의 거리를 말한다. 로봇 본체는 base_link 기준으로 자신의 앞뒤와 좌우를 정한다. 바퀴 오도메트리는 odom 기준으로 로봇이 얼마나 움직였는지 추정한다. SLAM은 map 기준으로 로봇이 지도 어디에 있는지 보정한다. Nav2는 이 모든 정보를 연결해 목표까지 가는 경로를 만든다.

로봇이 자연스럽게 움직이는 순간, 그 뒤에서는 수많은 좌표 변환이 조용히 일어나고 있다. 로봇 앞 장애물 하나를 피하는 일에도 laser_frame, base_link, odom, map이 함께 등장한다. 그래서 TF는 ROS2 자율주행 로봇의 숨은 골격이다.

이제 우리는 로봇의 좌표계를 이름으로만 외우는 단계에서 벗어나, 각 좌표계가 어떤 책임을 갖는지 이해했다. 다음 단계에서는 이 좌표계 관계를 실제로 발행하고 읽는 방법을 다루게 된다. 좌표계가 머릿속에서 트리로 그려지기 시작하면, RViz의 화면과 Nav2의 오류 메시지도 더 이상 낯선 암호처럼 보이지 않을 것이다.

책 목록

ROS2로 완성하는 자율주행 로봇: 노드·TF·SLAM·Nav2 실전 설계

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기