로봇은 자신의 발자국을 세며 움직인다
SLAM을 이야기할 때 많은 사람이 먼저 LiDAR, 카메라, 지도 알고리즘을 떠올린다. 물론 그들도 중요하다. 하지만 이동 로봇의 자율주행에서 정말 조용히, 그리고 끈질기게 모든 시스템의 바닥을 받쳐 주는 데이터가 있다. 바로 오도메트리다.
오도메트리는 로봇이 스스로의 움직임을 추정한 결과다. 바퀴가 얼마나 돌았는지, 어느 방향으로 얼마나 회전했는지, 지금 속도가 어느 정도인지를 계산해 현재 위치를 계속 갱신한다. 사람이 눈을 감고 걸을 때 자신의 걸음 수와 방향 감각만으로 대략 어디쯤 왔는지 짐작하는 것과 비슷하다.
문제는 그 짐작이 완벽하지 않다는 데 있다. 바퀴는 미끄러질 수 있고, 바닥은 생각보다 고르지 않으며, 로봇의 바퀴 지름은 설계값과 조금 다를 수 있다. 이런 작은 차이는 처음에는 보이지 않는다. 그러나 1m, 5m, 20m를 달리다 보면 점점 커진다. 로봇은 분명 복도를 똑바로 달렸다고 생각하지만, 실제로는 벽 쪽으로 조금씩 기울어 가고 있을 수 있다.
그래서 오도메트리는 두 얼굴을 가진 데이터다. 짧은 시간에는 매우 유용하다. 부드럽고 빠르며 연속적인 움직임을 알려 준다. 그러나 긴 시간에는 서서히 틀어진다. SLAM과 Nav2는 이 특성을 잘 이해하고 오도메트리를 사용해야 한다. 오도메트리를 무시하면 로봇은 순간적인 움직임을 알 수 없고, 오도메트리를 맹신하면 로봇은 언젠가 지도 밖으로 걸어 나간다.
이번 장에서는 차동구동 로봇을 기준으로 오도메트리가 어떻게 계산되는지 살펴본다. 좌우 바퀴의 엔코더 틱이 어떻게 이동 거리로 바뀌고, 그 거리 차이가 어떻게 로봇의 회전으로 이어지는지 따라가 보자. 이어서 IMU를 함께 사용해 더 안정적인 위치 추정을 만드는 방법과, ROS2에서 중요한 odom, map, base_link 프레임의 관계까지 연결해 본다.

오도메트리란 무엇인가
오도메트리라는 말은 이동 거리 측정에서 출발했다. 이동 로봇에서는 보통 로봇의 위치와 방향, 그리고 속도를 추정한 값을 의미한다. ROS2에서는 이 정보가 주로 /odom 토픽과 odom → base_link TF 변환으로 표현된다.
오도메트리가 알려 주는 것은 대략 다음과 같다.
로봇이 기준 위치에서 x 방향으로 얼마나 이동했는가
로봇이 y 방향으로 얼마나 옆으로 이동했는가
로봇의 머리가 어느 방향을 보고 있는가
현재 선속도와 각속도는 얼마인가
이 추정값을 얼마나 믿을 수 있는가
여기서 중요한 표현은 추정값이다. 오도메트리는 절대 진실이 아니다. GPS처럼 지구상의 절대 위치를 알려 주는 것도 아니고, SLAM처럼 주변 환경과 비교해 위치를 교정하는 것도 아니다. 오도메트리는 로봇 내부 센서만으로 이전 상태에서 현재 상태까지의 변화를 누적한다.
이 방식을 데드 레코닝, 즉 추측 항법이라고 부른다. 이름 그대로 이전 위치를 알고 있고, 그 이후에 얼마나 움직였는지 안다면 현재 위치를 추측할 수 있다는 생각이다. 예를 들어 로봇이 처음에 (0, 0)에 있고 동쪽을 바라본다고 하자. 바퀴 회전량을 계산했더니 앞으로 1m 이동했다면 로봇의 위치는 대략 (1, 0)이 된다. 다시 제자리에서 90도 회전하고 앞으로 1m 이동했다면 위치는 대략 (1, 1)이 된다.
간단해 보이지만 이 계산은 모든 자율주행의 출발점이다. SLAM은 LiDAR 스캔을 맞춰 지도를 만들 때 로봇이 이전 순간에서 얼마나 움직였는지 알아야 한다. Nav2의 컨트롤러는 목표 경로를 따라가면서 로봇이 방금 낸 속도 명령에 실제로 어떻게 반응했는지 알아야 한다. AMCL 같은 위치 추정 알고리즘도 현재 로봇의 움직임 예측값을 오도메트리에서 얻는다.
오도메트리가 엉망이면 그 위에 올라가는 알고리즘도 흔들린다. 지도 작성이 휘어지고, 위치 추정이 불안정해지며, 목표 지점까지 가는 경로 추종도 삐걱거린다. 반대로 오도메트리가 안정적이면 SLAM과 Nav2는 훨씬 편안한 바닥 위에서 동작한다.
엔코더 틱을 이동 거리로 바꾸기
차동구동 로봇의 가장 기본적인 오도메트리 입력은 바퀴 엔코더다. 엔코더는 회전을 잘게 쪼개 세는 센서다. 바퀴나 모터 축이 조금 돌 때마다 전기적인 펄스가 발생하고, 제어기는 그 펄스를 세어 회전량을 계산한다.
엔코더에서 바로 얻는 값은 보통 틱 수다. 예를 들어 왼쪽 바퀴가 이전 측정 시점보다 620틱 증가했고, 오른쪽 바퀴가 640틱 증가했다고 하자. 이 숫자만으로는 로봇이 몇 미터 움직였는지 알 수 없다. 틱을 거리로 바꾸려면 로봇의 기계적인 조건을 알아야 한다.
필요한 값은 주로 세 가지다.
바퀴 반지름 또는 지름
엔코더의 한 바퀴당 틱 수
엔코더가 모터 축에 붙어 있는지, 바퀴 축에 붙어 있는지에 따른 기어비
바퀴 반지름이 r이면 바퀴가 한 바퀴 돌 때 이동하는 거리는 원둘레인 2πr이다. 만약 바퀴가 한 바퀴 도는 동안 엔코더가 N틱을 만든다면, 틱 하나가 의미하는 이동 거리는 다음과 같다.
틱 하나당 이동 거리 = 바퀴 원둘레 / 바퀴 한 바퀴당 틱 수
= 2πr / N그런데 실제 로봇에서는 엔코더가 바퀴 축이 아니라 모터 축에 붙어 있는 경우가 많다. 이때는 기어비를 고려해야 한다. 예를 들어 모터가 30번 돌아야 바퀴가 1번 도는 구조라면, 바퀴 한 바퀴에 해당하는 엔코더 틱 수는 모터 한 바퀴당 엔코더 틱 수에 30을 곱한 값이 된다.
바퀴 한 바퀴당 틱 수 = 모터 한 바퀴당 엔코더 틱 수 × 기어비단, 여기서 조심해야 할 점이 있다. 어떤 모터 드라이버나 컨트롤러는 이미 기어비가 반영된 값을 제공한다. 또 어떤 엔코더는 상승 에지만 세는지, 상승과 하강을 모두 세는지, A상과 B상을 모두 쓰는지에 따라 실제 카운트 해석이 달라진다. 데이터시트에 적힌 PPR, CPR, ticks per revolution이 정확히 무엇을 의미하는지 확인해야 한다.
예를 들어 바퀴 반지름이 0.033m이고, 바퀴 한 바퀴당 엔코더 해상도가 4096틱이라고 하자. 바퀴 원둘레는 대략 0.207m다. 그러면 틱 하나당 이동 거리는 약 0.0000506m, 즉 0.0506mm 정도다. 왼쪽 바퀴가 620틱 움직였다면 왼쪽 바퀴 이동 거리는 약 0.031m가 된다.
바퀴 원둘레 = 2 × π × 0.033 ≈ 0.207m
틱 하나당 이동 거리 = 0.207 / 4096 ≈ 0.0000506m
620틱 이동 거리 = 620 × 0.0000506 ≈ 0.031m이렇게 좌우 바퀴의 틱 증가량을 각각 거리로 바꾸면, 이제 로봇이 얼마나 전진했고 얼마나 회전했는지 계산할 준비가 된다.
차동구동 로봇의 움직임 계산
차동구동 로봇은 왼쪽 바퀴와 오른쪽 바퀴의 속도 차이로 방향을 바꾼다. 두 바퀴가 같은 속도로 앞으로 돌면 로봇은 직진한다. 오른쪽 바퀴가 왼쪽 바퀴보다 더 많이 돌면 로봇은 왼쪽으로 돈다. 왼쪽 바퀴만 앞으로 돌고 오른쪽 바퀴만 뒤로 돌면 제자리 회전도 가능하다.
차동구동 오도메트리를 계산할 때 필요한 핵심 값은 좌우 바퀴 사이의 거리다. 흔히 휠 베이스 또는 트랙 폭이라고 부른다. 여기서는 B라고 하자. 왼쪽 바퀴 이동 거리를 dL, 오른쪽 바퀴 이동 거리를 dR이라고 하면, 로봇 중심이 앞으로 이동한 거리는 두 바퀴 이동 거리의 평균으로 볼 수 있다.
중심 이동 거리 d = (dR + dL) / 2
회전 변화량 dθ = (dR - dL) / B이 공식은 직관적이다. 두 바퀴가 똑같이 10cm 움직였다면 평균은 10cm이고, 차이는 0이므로 회전하지 않는다. 오른쪽 바퀴가 12cm, 왼쪽 바퀴가 8cm 움직였다면 중심은 10cm 전진했지만 오른쪽이 더 많이 밀었기 때문에 로봇은 왼쪽으로 회전한다.
ROS의 일반적인 좌표 규칙에서는 base_link 기준으로 x축이 앞, y축이 왼쪽, z축이 위를 향한다. 위에서 내려다봤을 때 반시계 방향 회전이 양의 yaw다. 따라서 오른쪽 바퀴가 왼쪽 바퀴보다 더 많이 전진하면 dR - dL이 양수가 되고, yaw도 양수로 증가한다.
시간 간격을 dt라고 하면 선속도와 각속도도 구할 수 있다.
선속도 v = d / dt
각속도 ω = dθ / dt이제 이 값을 현재 위치에 누적해야 한다. 로봇의 현재 위치를 (x, y), 방향을 θ라고 하자. 아주 짧은 시간 동안 중심이 d만큼 이동하고 방향이 dθ만큼 바뀌었다면 다음과 같이 갱신할 수 있다.
중간 방향 θ_mid = θ + dθ / 2
x_new = x + d × cos(θ_mid)
y_new = y + d × sin(θ_mid)
θ_new = θ + dθ여기서 중간 방향을 쓰는 이유는 로봇이 이동하는 동안 방향도 함께 변하기 때문이다. 단순히 이전 방향 θ만 사용하면 회전하며 전진하는 경우 오차가 조금 더 커질 수 있다. 시간 간격이 아주 짧다면 차이는 작지만, 오도메트리는 작은 차이가 계속 쌓이는 세계다. 가능하면 조금 더 나은 근사를 쓰는 편이 좋다.
이 계산을 매 제어 주기마다 반복하면 로봇의 현재 위치가 계속 업데이트된다. 50Hz로 계산한다면 1초에 50번, 100Hz라면 1초에 100번 로봇의 자세가 갱신된다. 이것이 바퀴 오도메트리의 기본 흐름이다.
작은 숫자가 큰 오차가 되는 이유
오도메트리는 처음에는 놀라울 정도로 잘 맞는 것처럼 보인다. 로봇을 1m 전진시키면 /odom도 거의 1m를 가리킨다. 제자리에서 90도 돌리면 RViz의 로봇 모델도 멋지게 90도 돌아간다. 그래서 초보자는 오도메트리를 절대 위치처럼 믿고 싶어진다.
하지만 로봇을 복도 끝까지 달리게 하거나 여러 번 회전시키면 이야기가 달라진다. 처음에는 1cm였던 차이가 10cm가 되고, 1도였던 방향 오차가 15도가 된다. 방향이 15도 틀어진 상태에서 계속 전진하면 위치 오차는 더 빠르게 커진다. 오도메트리의 오차는 단순히 더해지는 것이 아니라 방향 오차와 결합해 증폭된다.
대표적인 원인은 미끄러짐이다. 엔코더는 바퀴가 얼마나 돌았는지를 측정한다. 하지만 바퀴가 돈 만큼 로봇이 실제로 움직였다는 보장은 없다. 매끄러운 바닥에서 급가속하면 바퀴가 헛돌 수 있다. 카펫 위에서는 바퀴가 눌리며 유효 반지름이 달라질 수 있다. 문턱이나 케이블을 넘을 때 한쪽 바퀴가 잠깐 공중에 뜨면 엔코더는 회전을 세지만 로봇의 실제 이동은 다르게 나타난다.
바퀴 지름 오차도 중요하다. 설계상 바퀴 반지름이 33mm라고 해도 실제 바퀴는 32.5mm일 수 있다. 타이어가 닳거나 하중 때문에 눌리면 유효 반지름은 더 달라진다. 반지름이 1퍼센트만 틀려도 10m를 달렸을 때 거리 추정은 약 10cm 틀어질 수 있다. 좌우 바퀴의 반지름이 서로 다르면 더 까다롭다. 로봇은 직진한다고 명령받았지만 실제로는 아주 완만한 곡선을 그리며 움직이고, 오도메트리는 그 곡선을 제대로 알아차리지 못할 수 있다.
휠 베이스 오차도 회전 추정에 영향을 준다. 공식에서 dθ = (dR - dL) / B를 사용했는데, 여기서 B가 실제보다 작게 설정되어 있으면 같은 바퀴 차이를 더 큰 회전으로 해석한다. 반대로 B가 실제보다 크게 설정되어 있으면 회전을 과소평가한다. 로봇이 제자리에서 360도 회전했는데 오도메트리가 330도 또는 390도를 가리킨다면 휠 베이스나 바퀴 반지름 설정을 의심해야 한다.
엔코더 해상도와 시간 측정도 영향을 준다. 낮은 해상도의 엔코더는 아주 느린 움직임에서 틱 변화가 듬성듬성 나타난다. 제어 주기가 불안정하거나 dt 계산이 정확하지 않으면 속도 추정이 출렁인다. 하드웨어 드라이버에서 좌우 바퀴 값을 서로 다른 시각에 읽는 경우도 미세한 오차를 만든다.
바닥 재질은 생각보다 강력한 변수다. 실험실의 매끈한 바닥에서 잘 맞던 오도메트리가 카펫, 고무 매트, 경사면, 먼지가 많은 창고에서는 전혀 다르게 동작할 수 있다. 그래서 이동 로봇 개발에서는 오도메트리 튜닝을 한 번 하고 끝내지 않는다. 로봇이 실제로 달릴 환경에서 반복 측정하며 보정해야 한다.
데드 레코닝은 왜 드리프트하는가
데드 레코닝의 본질은 누적이다. 이전 위치에 이번 이동량을 더하고, 다시 그 결과에 다음 이동량을 더한다. 이 과정은 빠르고 매끄럽지만 한 가지 약점이 있다. 중간에 생긴 오차를 스스로 지울 방법이 없다.
로봇이 실제로는 1.00m 이동했는데 오도메트리가 1.01m로 계산했다고 하자. 이 1cm 오차는 다음 계산의 출발점이 된다. 그다음 1m를 또 이동하며 1cm가 추가로 생기면 전체 오차는 2cm가 된다. 방향 오차가 섞이면 더 복잡하다. 로봇이 실제보다 2도 왼쪽을 보고 있다고 생각한 채 전진하면, 앞으로 간 거리의 일부가 옆 방향 오차로 쌓인다.
이런 장기적인 틀어짐을 드리프트라고 부른다. 드리프트는 오도메트리의 숙명에 가깝다. 아무리 좋은 엔코더와 정교한 모델을 써도 바닥과 타이어 사이의 상호작용을 완벽히 알 수는 없다. 그래서 오도메트리는 장기적인 절대 위치를 책임지기보다, 짧은 시간 동안의 연속적인 움직임을 안정적으로 제공하는 역할에 집중한다.
이 지점에서 SLAM과 AMCL의 역할이 등장한다. LiDAR SLAM은 주변 벽과 장애물의 형태를 보며 로봇의 위치를 교정한다. AMCL은 이미 만들어진 지도와 현재 LiDAR 스캔을 비교해 로봇이 지도 위 어디에 있는지 추정한다. 이들은 오도메트리의 드리프트를 완화하지만, 오도메트리를 대체하지는 않는다. 오히려 좋은 오도메트리가 있어야 스캔 매칭과 위치 추정도 안정적으로 시작할 수 있다.
IMU는 무엇을 보완하는가
바퀴 엔코더는 이동 거리 추정에 강하다. 특히 평평한 바닥에서 천천히 움직이는 차동구동 로봇이라면 짧은 시간의 전진 거리를 꽤 잘 계산한다. 그러나 회전과 미끄러짐에는 약하다. 제자리 회전 중 바퀴가 조금 미끄러지면 엔코더 기반 yaw 추정이 크게 틀어질 수 있다.
이때 IMU가 도움을 줄 수 있다. IMU는 관성 측정 장치로, 보통 각속도를 측정하는 자이로스코프와 가속도를 측정하는 가속도계를 포함한다. 일부 IMU에는 지자기 센서가 포함되기도 한다. 이동 로봇에서 특히 자주 사용하는 값은 z축 각속도, 즉 yaw rate다. 로봇이 위에서 내려다봤을 때 얼마나 빠르게 도는지를 알려 주는 값이다.
엔코더는 바퀴 회전으로 로봇의 회전을 추정한다. IMU는 로봇 본체의 회전 자체를 측정한다. 그래서 두 센서는 서로 다른 관점에서 같은 현상을 본다. 바퀴가 미끄러져도 IMU는 실제 몸체가 얼마나 돌았는지를 더 직접적으로 감지할 수 있다. 반대로 IMU 자이로는 시간이 지나며 바이어스가 누적될 수 있고, 진동이나 온도 변화에 영향을 받는다. 엔코더와 IMU 중 하나만 절대적으로 옳다고 보기 어렵다.
가속도계로 위치를 직접 계산할 수 있지 않을까 생각할 수도 있다. 이론적으로는 가속도를 한 번 적분하면 속도, 두 번 적분하면 위치가 된다. 하지만 실제로는 가속도 노이즈와 중력 보정 오차가 빠르게 커진다. 작은 가속도 오차를 두 번 적분하면 위치는 금방 말도 안 되는 값으로 튀어 오른다. 그래서 실내 이동 로봇에서는 IMU 가속도만으로 x, y 위치를 추정하는 경우는 조심해야 한다.
IMU는 만능 GPS가 아니다. 대신 엔코더가 약한 부분, 특히 회전 속도와 자세 변화의 짧은 시간 안정성을 보완한다. 엔코더는 바닥과 바퀴의 접촉을 통해 움직임을 보고, IMU는 로봇 몸체의 관성을 통해 움직임을 본다. 두 눈으로 보면 한쪽 눈으로 볼 때보다 깊이를 더 잘 느끼듯, 두 센서를 함께 쓰면 더 안정적인 위치 추정이 가능해진다.
EKF로 센서 정보를 섞기
엔코더와 IMU를 함께 사용하려면 두 값을 적절히 섞어야 한다. 단순히 평균을 내면 될 것 같지만 실제 상황은 조금 더 복잡하다. 엔코더는 선속도에 강하고, IMU는 각속도에 강하다. 어떤 센서는 빠르지만 노이즈가 많고, 어떤 센서는 느리지만 안정적이다. 또 각 센서가 제공하는 값의 좌표계와 시간도 맞아야 한다.
ROS2에서 자주 사용하는 방법 중 하나가 robot_localization 패키지의 EKF, 즉 확장 칼만 필터를 이용하는 것이다. 칼만 필터는 현재 상태를 예측하고, 센서 측정값으로 그 예측을 보정하는 방식의 추정기다. 확장 칼만 필터는 로봇처럼 비선형적인 움직임을 다루기 위해 이 아이디어를 확장한 것이다.
아주 단순하게 말하면 EKF는 다음 흐름으로 동작한다.
이전 상태와 속도를 바탕으로 현재 상태를 예측한다
엔코더 오도메트리와 IMU 측정값을 입력으로 받는다
각 측정값의 신뢰도를 공분산으로 판단한다
예측값과 측정값을 적절히 조합해 더 그럴듯한 상태를 만든다
필터링된 오도메트리를
/odometry/filtered같은 토픽으로 내보낸다
여기서 공분산은 이 값을 얼마나 믿을 수 있는지를 나타내는 숫자다. 공분산이 작으면 센서 값을 더 신뢰한다는 뜻이고, 공분산이 크면 이 값은 불확실하다고 보는 것이다. 예를 들어 바퀴 오도메트리의 x 방향 속도는 꽤 믿을 수 있지만, y 방향 속도는 차동구동 로봇에서 직접 움직일 수 없는 축이므로 의미가 다르다. IMU의 yaw rate는 신뢰하되, 가속도 기반 위치는 신뢰하지 않을 수도 있다.
지상 이동 로봇에서는 대개 2D 모드를 사용한다. 로봇이 날아다니거나 기울어진 지형을 적극적으로 주행하지 않는다면 z 위치, roll, pitch를 강하게 추정할 필요가 없다. robot_localization에서는 이런 용도로 two_d_mode 설정을 사용할 수 있다. 이 설정은 로봇의 상태 추정을 평면 이동에 맞게 단순화하는 데 도움을 준다.
개념적으로는 다음과 같은 구성이 된다.
ekf_filter_node:
ros__parameters:
frequency: 30.0
two_d_mode: true
map_frame: map
odom_frame: odom
base_link_frame: base_link
world_frame: odom
odom0: /wheel/odom
imu0: /imu/data
publish_tf: true이 예시는 전체 설정이 아니라 구조를 보여 주기 위한 것이다. 실제 설정에서는 각 센서에서 어떤 변수, 예를 들어 x 위치, y 위치, yaw, 선속도, 각속도 중 무엇을 사용할지 배열로 지정한다. 또한 센서 타임스탬프, 프레임 이름, 공분산 값도 꼼꼼히 맞춰야 한다.
중요한 설계 원칙은 하나다. 같은 TF를 여러 노드가 동시에 발행하지 않게 해야 한다. 예를 들어 바퀴 오도메트리 노드가 odom → base_link TF를 발행하고, EKF도 필터링된 odom → base_link TF를 발행하면 TF 트리에 충돌이 생긴다. 보통은 원시 바퀴 오도메트리는 토픽만 내보내고, EKF가 최종 필터링된 오도메트리와 TF를 발행하도록 구성한다. 또는 반대로 EKF가 TF를 발행하지 않게 하고 다른 노드가 책임지도록 할 수도 있다. 핵심은 책임자가 하나여야 한다는 점이다.
ROS2에서 오도메트리는 어떻게 표현되는가
ROS2에서 오도메트리 정보는 주로 nav_msgs/Odometry 메시지로 전달된다. 이 메시지는 위치와 자세를 담은 pose, 선속도와 각속도를 담은 twist, 그리고 각각의 불확실성을 나타내는 covariance를 포함한다.
오도메트리 메시지를 볼 때 특히 중요한 필드는 header.frame_id와 child_frame_id다. 일반적으로 header.frame_id는 odom, child_frame_id는 base_link가 된다. 이 말은 오도메트리 메시지 안의 pose가 odom 프레임을 기준으로 한 base_link의 위치와 자세를 나타낸다는 뜻이다.
header.frame_id: odom
child_frame_id: base_link
pose: odom 기준에서 본 base_link의 위치와 방향
twist: base_link의 속도 정보여기서 pose와 twist의 기준을 혼동하면 디버깅이 어려워진다. pose는 보통 부모 프레임인 odom 기준의 위치다. twist는 구현에 따라 해석에 주의가 필요하지만, 이동 로봇에서는 로봇 몸체 기준의 전진 속도와 yaw 각속도로 이해하며 사용하는 경우가 많다. 중요한 것은 사용하는 컨트롤러와 필터가 기대하는 좌표계 규칙을 맞추는 것이다.
TF도 함께 확인해야 한다. 오도메트리 토픽만 있고 TF가 없다면 RViz나 Nav2는 로봇의 좌표 변환을 제대로 따라가지 못한다. 반대로 TF만 있고 /odom 토픽이 없으면 일부 알고리즘이나 디버깅 도구에서 속도와 공분산 정보를 얻기 어렵다. 실전에서는 토픽과 TF를 함께 정상화하는 것이 좋다.
전형적인 이동 로봇에서는 다음과 같은 흐름이 만들어진다.
엔코더 값
→ 바퀴 이동 거리 계산
→ 차동구동 운동학으로 /wheel/odom 생성
→ IMU와 함께 EKF에 입력
→ /odometry/filtered 생성
→ odom → base_link TF 발행이 흐름이 안정적이면 RViz에서 로봇 모델은 부드럽게 움직이고, Nav2는 로봇의 현재 속도와 자세 변화를 믿고 경로를 따라갈 수 있다.
odom 프레임과 map 프레임의 차이
이제 가장 중요한 좌표계 이야기로 넘어가자. 이전 장에서 SLAM은 위치 추정과 지도 작성을 동시에 하는 문제라고 했다. 이때 ROS2 이동 로봇에서 자주 등장하는 프레임이 map, odom, base_link다. 세 프레임의 역할을 정확히 이해하면 SLAM과 Nav2의 절반은 이미 정리된 셈이다.
base_link는 로봇 몸체의 기준 프레임이다. 로봇이 움직이면 base_link도 함께 움직인다. LiDAR, 카메라, IMU 같은 센서 프레임은 보통 base_link에 고정된 관계로 연결된다.
odom은 로봇의 짧은 시간 움직임을 부드럽게 표현하는 프레임이다. odom → base_link 변환은 연속적이어야 한다. 로봇이 갑자기 순간이동한 것처럼 튀면 안 된다. 컨트롤러는 이 연속성을 매우 중요하게 여긴다. 속도 제어와 경로 추종은 로봇의 위치가 매 순간 부드럽게 변한다고 가정하기 때문이다.
하지만 odom은 장기적으로 드리프트할 수 있다. 바퀴 미끄러짐과 센서 오차가 누적되기 때문이다. 그래서 odom은 매끄럽지만 영원히 정확하지는 않은 프레임이라고 볼 수 있다.
반면 map은 지도 기준의 전역 프레임이다. SLAM이나 AMCL은 LiDAR 스캔과 지도 정보를 사용해 로봇이 지도 위 어디에 있는지 추정한다. 이 과정에서 누적 오차를 교정할 수 있다. 하지만 교정이 일어날 때 위치가 조금 점프할 수 있다. 예를 들어 AMCL이 로봇 위치를 다시 강하게 잡으면 map 기준의 로봇 위치는 순간적으로 바뀔 수 있다.
그래서 ROS2 내비게이션에서는 보통 다음 TF 구조를 사용한다.
map → odom → base_link여기서 odom → base_link는 오도메트리 또는 EKF가 담당한다. 로봇의 짧은 시간 움직임을 연속적으로 표현한다. map → odom은 SLAM 또는 AMCL 같은 전역 위치 추정기가 담당한다. 오도메트리 드리프트를 지도 기준으로 보정하는 역할을 한다.
이 구조를 처음 보면 왜 map → base_link를 바로 발행하지 않는지 궁금할 수 있다. 이유는 역할 분리다. base_link의 움직임은 부드러워야 하지만, 지도 기반 보정은 때때로 점프할 수 있다. 두 요구를 하나의 변환에 섞어 버리면 컨트롤러가 불안정해진다. map → odom이 보정량을 흡수하고, odom → base_link가 부드러운 움직임을 유지하면 두 세계가 깔끔하게 나뉜다.
요약하면 odom은 단기적으로 부드럽고, map은 장기적으로 더 일관된 기준이다. 자율주행 로봇은 이 둘을 함께 사용한다. 한 발 한 발의 움직임은 odom으로 보고, 내가 지도 위 어디에 있는지는 map으로 본다.
오도메트리 튜닝을 위한 실험
오도메트리는 책상 위에서 공식만 맞춘다고 끝나지 않는다. 실제 로봇을 움직이며 확인해야 한다. 가장 좋은 방법은 단순한 실험을 반복하는 것이다. 복잡한 SLAM을 켜기 전에 오도메트리부터 점검하자. 바닥이 흔들리면 그 위에 세운 건물도 흔들린다.
먼저 직진 실험을 한다. 로봇을 평평한 바닥에 놓고 1m 또는 2m를 천천히 전진시킨다. 실제 이동 거리를 줄자나 바닥 표시로 측정하고, /odom에서 x 이동량을 확인한다. 실제 1m를 갔는데 오도메트리가 0.92m라면 바퀴 반지름, 틱 해석, 기어비 설정을 의심해야 한다. 실제보다 작게 계산한다는 것은 틱 하나당 이동 거리를 너무 작게 잡았을 가능성이 있다.
다음은 회전 실험이다. 로봇을 제자리에서 360도 회전시킨다. 실제로 한 바퀴를 돌았을 때 오도메트리 yaw도 한 바퀴에 가까운지 확인한다. 회전이 계속 과대 또는 과소 추정된다면 휠 베이스 설정을 조정해야 할 수 있다. 단, 바닥 미끄러짐이 큰 환경에서는 회전 실험 결과가 불안정할 수 있으므로 여러 번 반복해 평균적인 경향을 보는 것이 좋다.
그다음은 사각형 주행이다. 로봇을 앞으로 이동, 90도 회전, 앞으로 이동, 90도 회전하는 식으로 사각형 경로를 따라 움직인다. 마지막에 출발점으로 얼마나 돌아오는지 확인한다. 이 실험은 직진 거리 오차와 회전 오차가 함께 드러난다. 단순 직진과 단순 회전에서는 보이지 않던 좌우 바퀴 차이도 나타날 수 있다.
점검할 때는 다음 항목을 함께 본다.
/odom의 위치 변화가 실제 이동 방향과 일치하는가로봇을 앞으로 밀었을 때 x가 증가하고, 왼쪽으로 회전했을 때 yaw가 양수로 증가하는가
header.frame_id와child_frame_id가 각각odom,base_link로 올바르게 설정되어 있는가odom → base_linkTF를 발행하는 노드가 하나뿐인가엔코더 시간 간격
dt가 안정적이며 속도 값이 과도하게 튀지 않는가IMU의 z축 각속도 방향이 ROS 좌표계와 맞는가
공분산 값이 전부 0이거나 비현실적으로 작게 설정되어 있지 않은가
공분산을 모두 0으로 두는 것은 나는 완벽하다고 주장하는 것과 같다. 실제 센서는 완벽하지 않다. EKF나 위치 추정 알고리즘은 공분산을 보고 센서의 신뢰도를 판단하므로, 말도 안 되게 작은 값은 필터를 오히려 망칠 수 있다. 처음에는 보수적으로 설정하고, 센서 특성과 실험 결과를 보며 조정하는 편이 안전하다.
오도메트리가 나쁠 때 나타나는 증상
오도메트리 문제는 SLAM이나 Nav2 문제처럼 보이는 경우가 많다. 지도가 휘어지면 SLAM 파라미터를 먼저 의심하고, 로봇이 목표를 지나치면 컨트롤러를 먼저 의심한다. 하지만 뿌리는 오도메트리일 수 있다.
예를 들어 SLAM 중 로봇을 천천히 회전시키는데 지도 전체가 찢어지거나 벽이 두 겹으로 생긴다면, LiDAR 스캔 매칭이 어려운 환경일 수도 있지만 회전 오도메트리가 크게 틀렸을 가능성도 있다. 로봇이 실제보다 덜 돌았다고 생각하면 스캔을 잘못된 자세에 끼워 맞추게 되고, 지도가 점점 비틀린다.
Nav2에서 로봇이 경로를 따라가다가 계속 좌우로 흔들린다면 컨트롤러 파라미터만 문제가 아닐 수 있다. 오도메트리 속도가 지연되거나 튀면 컨트롤러는 로봇이 실제와 다르게 움직인다고 판단한다. 그러면 보정 명령이 과해지고, 다시 그 보정이 다음 오차를 만든다.
AMCL에서 위치가 자주 튀는 경우도 오도메트리를 살펴봐야 한다. AMCL은 입자 필터를 사용해 로봇 위치 후보를 퍼뜨리고, 오도메트리로 그 후보들을 이동시킨 뒤 LiDAR 스캔으로 평가한다. 오도메트리 이동 예측이 너무 틀리면 입자들이 엉뚱한 곳으로 퍼지고, 위치 추정이 불안정해질 수 있다.
따라서 자율주행 문제를 디버깅할 때는 멋진 알고리즘의 이름보다 먼저 기본 생체 신호를 확인하자. /cmd_vel을 줬을 때 바퀴가 의도대로 도는가. 엔코더 값이 정상적으로 증가하는가. /odom이 실제 이동과 비슷한가. TF가 끊기지 않는가. 이 네 가지를 확인하는 습관은 앞으로의 SLAM, AMCL, Nav2 실습에서 계속 도움이 된다.
SLAM과 Nav2를 위한 바닥 데이터
오도메트리는 화려하지 않다. RViz에서 지도처럼 멋진 그림을 그려 주지도 않고, 목표 지점까지 스스로 달려가는 장면처럼 눈에 띄지도 않는다. 하지만 오도메트리는 로봇이 자기 몸을 이해하는 가장 기본적인 감각이다. 사람으로 치면 발바닥 감각과 균형 감각에 가깝다.
차동구동 로봇에서는 좌우 바퀴 엔코더 틱을 거리로 바꾸고, 그 거리의 평균과 차이로 선속도와 각속도를 계산한다. 이때 바퀴 반지름, 기어비, 바퀴 한 바퀴당 틱 수, 휠 베이스가 모두 정확해야 한다. 작은 설정 오차는 긴 주행에서 큰 위치 오차가 된다.
바퀴 오도메트리는 미끄러짐과 바닥 재질에 약하다. IMU는 회전 정보를 보완할 수 있지만, IMU 역시 노이즈와 바이어스를 가진다. 그래서 robot_localization의 EKF처럼 여러 센서를 각자의 신뢰도에 맞춰 융합하는 방식이 자주 사용된다. 이때 공분산, 프레임 이름, TF 발행 책임을 제대로 설정해야 한다.
마지막으로 odom과 map의 차이를 기억하자. odom은 연속적이지만 드리프트한다. map은 지도 기준으로 장기적인 일관성을 제공하지만 보정 과정에서 점프할 수 있다. ROS2 자율주행 시스템은 보통 map → odom → base_link 구조로 이 둘의 장점을 함께 사용한다.
다음 장에서는 이 오도메트리를 바닥에 깔고 LiDAR SLAM으로 들어간다. 로봇은 바퀴로 자신이 얼마나 움직였는지 짐작하고, LiDAR로 주변 벽과 장애물을 바라보며 그 짐작을 고쳐 나간다. 이제 로봇은 단순히 움직이는 기계에서, 자신이 어디에 있고 주변이 어떻게 생겼는지 조금씩 이해하는 존재로 한 걸음 더 나아간다.
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.