지도는 자율주행의 첫 약속이다
지금까지 우리는 ROS2의 많은 조각을 따로따로 배웠다. 노드는 로봇의 기능을 나누는 단위였고, 토픽은 센서 데이터와 명령이 흐르는 길이었다. TF는 로봇의 몸과 센서, 지도 사이의 좌표 관계를 이어 주었다. LiDAR SLAM은 로봇이 움직이면서 동시에 자기 위치와 주변 지도를 추정하는 기술이었다.
이제 그 조각들을 하나로 묶을 차례다. 이번 장의 목표는 단순하다. TurtleBot 계열 로봇이나 Gazebo 시뮬레이션을 이용해 실내 공간의 지도를 처음부터 만든다. 이 지도는 다음 장에서 AMCL로 위치를 추정하고, 그다음 장에서 Nav2로 목표 지점까지 자율 이동하는 데 사용할 기반 자료가 된다.
지도 작성은 마치 어두운 방에서 손전등을 들고 천천히 걸어 다니는 일과 비슷하다. 너무 빨리 움직이면 벽의 윤곽을 놓친다. 한 번도 되돌아오지 않으면 내가 지나온 길이 맞는지 확인하기 어렵다. 중간에 사람이 지나가거나 의자가 옮겨지면 로봇은 그것이 벽인지 임시 장애물인지 헷갈릴 수 있다. 좋은 지도는 좋은 센서, 좋은 TF, 좋은 주행 습관이 함께 만들어 낸 결과물이다.
TurtleBot4 공식 문서에서도 지도를 만들기 전에 원하지 않는 장애물을 치우고, 가능하면 사람이나 동물이 움직이지 않는 상태에서 SLAM을 수행하라고 안내한다.1 실제 로봇으로 실습한다면 이 조언은 선택 사항이 아니라 안전 규칙이다.

이번 프로젝트의 결과물
이번 장을 마치면 다음 두 파일을 얻게 된다.
maps/indoor_lab_v1.yaml
maps/indoor_lab_v1.pgm또는 PNG 형식으로 저장했다면 다음처럼 될 수 있다.
maps/indoor_lab_v1.yaml
maps/indoor_lab_v1.png이미지 파일은 점유 격자 지도의 실제 그림이다. 보통 검은색은 벽이나 장애물, 흰색은 이동 가능한 공간, 회색은 아직 알 수 없는 공간을 나타낸다. YAML 파일은 이미지 파일을 실제 좌표계에서 어떻게 해석할지 알려 주는 메타데이터다. 예를 들어 해상도, 원점, 점유 판단 임계값 같은 정보가 들어 있다.
최종 목표는 예쁜 그림 한 장을 얻는 것이 아니다. 목표는 Nav2가 믿고 사용할 수 있는 지도를 만드는 것이다. 벽이 두 겹으로 그려져 있거나, 복도가 휘어 있거나, 문 입구가 막힌 것처럼 표현되어 있다면 지도 이미지는 그럴듯해 보여도 자율주행에는 좋지 않다.
실습 환경 선택하기
이번 프로젝트는 두 가지 방식 중 하나로 진행할 수 있다. 첫 번째는 Gazebo에서 TurtleBot3나 TurtleBot4 계열 로봇을 실행하는 방식이다. 두 번째는 실제 TurtleBot 계열 로봇을 실내 공간에서 움직이는 방식이다.
처음 실습한다면 시뮬레이션을 권한다. Gazebo에서는 로봇이 벽에 부딪혀도 마음이 조금 덜 아프다. 속도를 잘못 주어도 모터가 망가지지 않는다. 토픽, TF, RViz 표시를 확인하는 연습을 충분히 한 뒤 실제 로봇으로 옮기는 편이 안전하다.
실제 로봇으로 진행한다면 준비가 조금 더 엄격해야 한다. 배터리는 충분해야 하고, 바닥에는 케이블이나 작은 물체가 없어야 한다. LiDAR 높이에 걸리는 의자 다리, 박스, 낮은 선반도 미리 확인해야 한다. 로봇이 지나갈 공간에는 사람이나 동물이 움직이지 않도록 해야 한다. 지도 작성 중 움직이는 물체가 많으면 SLAM은 그것을 고정된 구조물처럼 지도에 반영할 수 있다.
Gazebo의 최신 세대인 Ignition/Gazebo Sim 계열을 직접 구성하는 경우에는 Gazebo 내부 토픽과 ROS2 토픽을 연결하기 위해 ros_gz_bridge가 필요할 수 있다. 예를 들어 Gazebo의 LiDAR 데이터를 ROS2의 sensor_msgs/msg/LaserScan 토픽으로 넘겨야 RViz와 SLAM 노드가 사용할 수 있다.2 TurtleBot 패키지의 기본 launch를 사용한다면 대부분의 연결이 이미 준비되어 있으므로 처음에는 공식 패키지의 예제 월드로 시작하는 것이 좋다.
터미널을 여는 순서
ROS2 실습에서 순서는 생각보다 중요하다. SLAM 노드는 센서 데이터와 TF가 들어오기를 기다린다. RViz는 /map, /scan, /tf를 시각화한다. teleop 노드는 /cmd_vel로 속도 명령을 보낸다. 어느 하나가 빠지면 로봇은 움직이지만 지도가 그려지지 않거나, 지도는 나오지만 로봇 모델이 엉뚱한 곳에 떠 있을 수 있다.
이 장에서는 기본적으로 네 개의 터미널을 사용한다.
터미널 1: Gazebo 시뮬레이션 또는 실제 로봇 bringup
터미널 2: SLAM 실행
터미널 3: RViz2 실행
터미널 4: teleop 또는 조이스틱 조종
모든 터미널에서 먼저 ROS2 환경을 source한다.
source /opt/ros/$ROS_DISTRO/setup.bash직접 만든 워크스페이스가 있다면 이어서 source한다.
cd ~/ros2_ws
source install/setup.bashTurtleBot3를 사용한다면 모델 환경 변수도 맞춘다.
export TURTLEBOT3_MODEL=waffle_piburger, waffle, waffle_pi 중 실제 사용하는 모델에 맞게 선택한다. 이 값을 잘못 지정하면 로봇 모델, 센서 위치, footprint 관련 설정이 실제와 달라질 수 있다.
방법 1: Gazebo에서 TurtleBot3로 지도 만들기
가장 익숙한 TurtleBot3 시뮬레이션부터 시작해 보자. 터미널 1에서 Gazebo 월드를 실행한다.
source /opt/ros/$ROS_DISTRO/setup.bash
export TURTLEBOT3_MODEL=waffle_pi
ros2 launch turtlebot3_gazebo turtlebot3_world.launch.py다른 월드를 사용하고 싶다면 turtlebot3_house.launch.py나 직접 만든 launch 파일을 사용할 수 있다. 중요한 것은 로봇이 /scan, /odom, /tf, /tf_static, /cmd_vel 같은 기본 토픽을 제대로 내보내는 상태로 시작해야 한다는 점이다.
터미널 2에서는 SLAM을 실행한다. TurtleBot3 예제에서는 Cartographer를 많이 사용한다.
source /opt/ros/$ROS_DISTRO/setup.bash
export TURTLEBOT3_MODEL=waffle_pi
ros2 launch turtlebot3_cartographer cartographer.launch.py use_sim_time:=true시뮬레이션에서는 반드시 use_sim_time:=true를 기억하자. Gazebo는 자체 시뮬레이션 시간을 /clock으로 발행한다. SLAM 노드와 RViz가 시스템 시간이 아니라 시뮬레이션 시간을 기준으로 데이터를 맞춰야 센서, TF, 지도 업데이트가 어긋나지 않는다.
SLAM Toolbox를 사용한다면 다음처럼 실행할 수 있다. Nav2 공식 튜토리얼에서도 SLAM을 사용할 때 SLAM 노드가 /map 토픽과 map → odom 변환을 제공해야 한다고 설명한다.3
source /opt/ros/$ROS_DISTRO/setup.bash
ros2 launch slam_toolbox online_async_launch.py use_sim_time:=true터미널 3에서는 RViz2를 실행한다.
source /opt/ros/$ROS_DISTRO/setup.bash
rviz2처음부터 Nav2 기본 RViz 설정을 쓰고 싶다면 다음 명령도 가능하다.
ros2 run rviz2 rviz2 -d $(ros2 pkg prefix nav2_bringup)/share/nav2_bringup/rviz/nav2_default_view.rvizRViz2에서 Fixed Frame은 map으로 둔다. 그리고 Map, LaserScan, TF, RobotModel 표시를 추가한다. LaserScan의 토픽은 보통 /scan이다. Map의 토픽은 /map이다.
터미널 4에서는 키보드 조종 노드를 실행한다.
source /opt/ros/$ROS_DISTRO/setup.bash
export TURTLEBOT3_MODEL=waffle_pi
ros2 run turtlebot3_teleop teleop_keyboard이제 로봇을 움직이면 RViz2의 회색 공간이 조금씩 흰색과 검은색으로 바뀌기 시작한다. 지도가 태어나는 순간이다.
방법 2: 실제 TurtleBot 계열 로봇으로 지도 만들기
실제 로봇에서는 시뮬레이션보다 조심해야 할 점이 많다. 가장 먼저 로봇의 컴퓨터와 원격 PC가 같은 네트워크에 있어야 한다. ROS_DOMAIN_ID를 사용한다면 로봇과 원격 PC의 값이 같아야 한다.
로봇에 SSH로 접속한 뒤 bringup을 실행한다.
ssh ubuntu@ROBOT_IP
source /opt/ros/$ROS_DISTRO/setup.bash
export TURTLEBOT3_MODEL=waffle_pi
ros2 launch turtlebot3_bringup robot.launch.py use_sim_time:=False실제 TurtleBot3에서 SLAM을 실행할 때는 먼저 로봇 bringup이 실행되어 있어야 하며, SLAM 노드는 보통 원격 PC에서 실행한다.4 실제 로봇에서는 Gazebo 시간이 아니라 실제 시스템 시간을 사용하므로 use_sim_time은 False로 둔다.
원격 PC의 새 터미널에서 SLAM을 실행한다.
source /opt/ros/$ROS_DISTRO/setup.bash
export TURTLEBOT3_MODEL=waffle_pi
ros2 launch turtlebot3_cartographer cartographer.launch.py use_sim_time:=FalseSLAM Toolbox를 사용한다면 다음과 같이 실행한다.
source /opt/ros/$ROS_DISTRO/setup.bash
ros2 launch slam_toolbox online_async_launch.py use_sim_time:=FalseRViz2도 원격 PC에서 실행한다. 로봇의 작은 SBC에서 RViz2를 직접 실행하는 것은 권장하지 않는다. 화면 출력과 3D 시각화는 원격 PC가 맡고, 로봇은 센서와 구동에 집중하는 편이 좋다.
rviz2teleop도 원격 PC에서 실행할 수 있다.
source /opt/ros/$ROS_DISTRO/setup.bash
export TURTLEBOT3_MODEL=waffle_pi
ros2 run turtlebot3_teleop teleop_keyboard실제 로봇을 움직이기 전에는 손을 키보드의 정지 키에 올려 두자. TurtleBot3 teleop에서는 space 또는 s가 정지에 해당한다. TurtleBot3 문서에서도 SLAM 중에는 선속도와 각속도를 급격하게 바꾸지 말고, 모든 모서리를 충분히 스캔하는 것이 좋다고 안내한다.4
방법 3: TurtleBot4에서 SLAM 실행하기
TurtleBot4를 사용한다면 패키지 이름과 launch 파일이 조금 다르다. TurtleBot4 공식 문서에서는 ROS2 환경을 source한 뒤 turtlebot4_navigation의 SLAM launch를 실행하고, 사용자 PC에서 RViz2를 열어 지도를 확인하는 흐름을 안내한다.1
예를 들어 ROS2 Humble 환경에서는 다음처럼 실행할 수 있다.
source /opt/ros/$ROS_DISTRO/setup.bash
ros2 launch turtlebot4_navigation slam.launch.pyRViz2는 다음과 같이 실행한다.
source /opt/ros/$ROS_DISTRO/setup.bash
ros2 launch turtlebot4_viz view_navigation.launch.py또는 문서와 배포판에 따라 다음 launch를 사용할 수 있다.
ros2 launch turtlebot4_viz view_robot.launch.pySLAM 파라미터를 바꾸고 싶다면 기본 설정 파일을 복사한 뒤 params:=/full/path/to/slam.yaml처럼 전달할 수 있다. 좁은 통로나 복잡한 실내에서는 해상도, 스캔 처리 주기, 루프 클로저 관련 설정이 지도 품질에 영향을 줄 수 있다. 다만 처음부터 파라미터를 많이 바꾸기보다는 기본 설정으로 한 번 지도를 만들어 보고, 문제가 반복될 때 원인을 좁혀 가는 편이 좋다.
출발 전 생체 신호 확인하기
로봇이 살아 있는지 확인하는 가장 좋은 방법은 토픽과 TF를 보는 것이다. 로봇이 움직이지 않아도 /scan은 들어와야 한다. 로봇을 손으로 밀거나 teleop으로 조금 움직이면 /odom이 변해야 한다. SLAM이 정상적으로 실행되면 /map이 발행되고, map → odom → base_link 또는 map → odom → base_footprint 연결이 만들어져야 한다.
먼저 토픽 목록을 확인한다.
ros2 topic list다음 토픽들이 보이면 출발선에 선 것이다.
/scan
/odom
/tf
/tf_static
/map
/cmd_vel/map은 SLAM 노드가 실행된 뒤에 나타난다. SLAM 실행 전에는 없는 것이 정상이다.
LiDAR가 실제로 데이터를 내보내는지도 확인한다.
ros2 topic hz /scan메시지가 일정한 주기로 들어오면 좋다. 값이 전혀 나오지 않으면 LiDAR 드라이버, Gazebo bridge, 네임스페이스, 토픽 이름을 확인해야 한다.
한 번만 내용을 보고 싶다면 다음 명령을 쓴다.
ros2 topic echo /scan --onceranges 배열에 거리 값들이 들어 있다면 LiDAR가 주변을 보고 있는 것이다.
오도메트리도 확인한다.
ros2 topic echo /odom --once로봇을 살짝 움직였을 때 pose 값이 변하면 바퀴 오도메트리나 시뮬레이션 오도메트리가 정상적으로 들어오는 것이다.
이제 TF를 확인한다.
ros2 run tf2_ros tf2_echo odom base_link로봇 모델에 따라 base_link 대신 base_footprint를 써야 할 수도 있다.
ros2 run tf2_ros tf2_echo odom base_footprintSLAM 실행 후에는 다음 변환도 확인한다.
ros2 run tf2_ros tf2_echo map odom이 변환이 존재한다는 것은 SLAM이 오도메트리 좌표계와 지도 좌표계를 이어 주고 있다는 뜻이다. Nav2의 SLAM 튜토리얼도 SLAM 구현이 /map과 map → odom 변환을 제공해야 한다고 강조한다.3
TF 전체 구조를 파일로 보고 싶다면 다음 명령을 사용한다.
ros2 run tf2_tools view_frames생성된 PDF를 열어 map → odom → base_link → laser처럼 자연스럽게 이어지는지 확인한다. 여기서 laser, base_scan, lidar_link 같은 이름은 로봇 모델에 따라 다를 수 있다.
RViz2에서 반드시 봐야 할 네 가지
RViz2는 단순한 그림판이 아니다. 지도 작성 중 로봇의 생각을 들여다보는 창이다. 이번 프로젝트에서는 최소한 네 가지를 함께 보자.
첫째, /map이다. Map display를 추가하고 토픽을 /map으로 설정한다. 처음에는 작은 회색 사각형처럼 보이다가 로봇이 움직일수록 벽과 빈 공간이 드러난다.
둘째, /scan이다. LaserScan display를 추가하고 토픽을 /scan으로 설정한다. 점들이 벽 위에 잘 얹히면 LiDAR 위치와 TF가 대체로 맞는 것이다. 스캔 점들이 로봇 모델보다 한참 앞이나 뒤에 있거나, 벽과 평행하지 않고 회전된 것처럼 보이면 LiDAR frame과 base_link 사이의 TF를 의심해야 한다.
셋째, 로봇 pose다. RobotModel, TF, Odometry 또는 SLAM이 제공하는 pose 표시를 함께 본다. 로봇이 실제 Gazebo 화면이나 실제 공간에서 움직이는 방향과 RViz2의 방향이 맞아야 한다.
넷째, 궤적이나 pose graph다. 사용하는 SLAM 패키지에 따라 slam_toolbox의 그래프, Cartographer의 trajectory, 또는 별도 path 토픽을 표시할 수 있다. 꼭 표시하지 않아도 지도는 만들 수 있지만, 루프 클로저가 일어났을 때 궤적이 어떻게 보정되는지 보면 SLAM의 동작을 훨씬 잘 이해할 수 있다.
지도 작성 중 가장 흔한 문제는 LiDAR 스캔과 지도 벽이 조금씩 어긋나는 것이다. 작은 어긋남은 자연스럽다. 하지만 같은 벽이 두 줄로 그려지거나, 복도가 부채꼴처럼 벌어지거나, 출발 지점으로 돌아왔는데 지도상 위치가 맞지 않는다면 주행 방식을 바꿔야 한다.
천천히, 겹치게, 다시 돌아오기
좋은 SLAM 지도는 로봇을 얼마나 멀리 보냈는지가 아니라, 얼마나 잘 관측했는지로 결정된다. 초보자가 가장 많이 하는 실수는 빠르게 한 바퀴 돌고 끝내는 것이다. 사람 눈에는 공간을 다 본 것 같지만, SLAM 입장에서는 센서 데이터가 듬성듬성 들어오고 오도메트리 오차가 쌓인 상태일 수 있다.
처음에는 아주 천천히 움직인다. 선속도는 낮게, 회전 속도도 낮게 둔다. TurtleBot3 teleop에서는 w, x, a, d로 속도를 조절할 수 있다. 로봇이 급하게 회전하면 LiDAR 스캔 사이의 자세 변화가 커지고, 스캔 매칭이 어려워질 수 있다.
복도에서는 중앙을 따라 직진만 하지 말고, 벽면 특징이 잘 보이도록 약간씩 위치를 바꾸며 이동한다. 문 입구에서는 잠깐 멈추거나 천천히 회전해 안쪽 벽 일부를 관측한다. 방에 들어갈 때는 입구에서 바로 크게 회전하지 말고, 방의 한쪽 벽을 따라 부드럽게 들어가며 스캔이 이어지도록 한다.
가장 중요한 것은 겹치는 경로다. 한 번 지나간 길을 조금 다른 방향에서 다시 지나가면 SLAM은 "아, 여기가 아까 그곳이구나"라고 판단할 단서를 얻는다. 이 과정을 루프 클로저라고 부른다. 루프 클로저가 잘 일어나면 오도메트리 누적 오차가 줄고, 지도 전체가 한 번에 정렬되는 것처럼 보일 수 있다.
실내를 매핑할 때는 다음 순서를 추천한다.
출발 지점 주변을 천천히 한 바퀴 돌며 초기 지도를 안정화한다.
복도나 큰 벽을 따라 이동하되, 중간중간 문 입구와 모서리를 충분히 관측한다.
가장 먼 곳까지 갔다가 다른 경로 또는 같은 경로를 겹쳐 지나 출발 지점 근처로 돌아온다.
지도에 빈틈이 남은 방이나 회색 영역이 많은 구간을 다시 방문한다.
마지막으로 전체 지도 외곽을 한 번 더 확인하며 벽이 이중으로 그려진 곳이 없는지 본다.
이런 동선은 청소기처럼 무작정 지그재그로 움직이는 것과 다르다. SLAM을 위한 동선은 "새로운 곳을 보러 가되, 자주 아는 곳으로 돌아오는" 동선이다.
지도가 망가지는 신호들
지도 작성 중에는 RViz2를 계속 보아야 한다. 로봇이 잘 움직인다고 해서 지도가 잘 만들어지는 것은 아니다. 다음 신호가 보이면 잠시 멈추고 원인을 확인하자.
벽이 두 겹으로 그려지는 경우가 있다. 같은 벽이 검은 선 두 개로 나란히 나타난다면 오도메트리와 스캔 매칭이 어긋났을 가능성이 크다. 속도를 줄이고, 이미 방문한 구간으로 돌아가 루프 클로저를 유도한다. 급회전을 줄이고 넓은 반경으로 부드럽게 돈다.
복도가 휘어지는 경우도 있다. 실제로는 직선 복도인데 지도에서는 바나나처럼 휘어 보일 수 있다. 긴 복도는 특징이 반복되어 SLAM이 위치를 헷갈리기 쉽다. 중간의 문, 기둥, 모서리 같은 특징을 충분히 관측하고, 복도 끝에서 천천히 회전해 반대 방향으로 다시 지나간다.
로봇 모델과 LiDAR 스캔이 따로 노는 경우도 있다. RViz2에서 로봇 모델은 중앙에 있는데 LaserScan 점들이 옆으로 밀려 있다면 센서 TF가 잘못되었을 수 있다. URDF, xacro, robot_state_publisher, static_transform_publisher 설정을 확인한다. LiDAR가 실제보다 앞에 있다고 설정되어 있으면 벽도 그만큼 밀려서 지도에 반영된다.
/map이 나오지 않는 경우도 있다. 이때는 SLAM 노드가 실행 중인지, /scan이 들어오는지, map_frame, odom_frame, base_frame, scan_topic 파라미터가 맞는지 확인한다. SLAM Toolbox를 직접 설정한다면 odom_frame: odom, map_frame: map, base_frame: base_link 또는 base_footprint, scan_topic: /scan이 실제 로봇과 일치해야 한다.
시뮬레이션에서 시간이 멈춘 것처럼 보이면 use_sim_time과 /clock을 확인한다.
ros2 topic echo /clock --onceGazebo가 실행 중인데 /clock이 나오지 않거나, SLAM 노드가 use_sim_time:=false로 실행되어 있으면 데이터 시간이 맞지 않아 문제가 생길 수 있다.
좁은 공간에서는 더 보수적으로
좁은 통로, 미로형 환경, 책상 사이 공간은 SLAM과 Nav2 모두에게 어려운 장소다. 지도 해상도가 너무 낮으면 좁은 통로가 막힌 것처럼 보일 수 있다. 반대로 해상도를 지나치게 높이면 계산량이 늘고 지도 업데이트가 느려질 수 있다.
일반적인 실내 지도는 0.05 m 해상도, 즉 픽셀 하나가 5 cm인 설정을 많이 사용한다. 좁은 미로형 환경에서는 0.03 m처럼 더 세밀한 해상도를 실험해 볼 수 있다. 하지만 해상도를 바꿀 때마다 지도 품질뿐 아니라 CPU 사용량, 저장 파일 크기, Nav2 costmap 설정까지 함께 고려해야 한다.
좁은 공간에서는 주행도 달라져야 한다. 빠른 회전은 금물이다. 벽 가까이 붙어서 회전하면 LiDAR 스캔이 급격하게 바뀌고, 실제 로봇은 충돌 위험도 커진다. 가능하면 통로 중앙에서 천천히 회전하고, 막다른 길에서는 후진보다 작은 전진과 회전을 조합해 자세를 바꾼다.
실제 로봇이라면 속도 제한을 더 낮게 잡자. SLAM을 위해서는 빨리 도는 로봇보다 꾸준히 관측하는 로봇이 낫다.
지도 저장하기
실내 전체가 충분히 커버되었고, RViz2에서 벽이 안정적으로 보인다면 지도를 저장한다. 저장은 nav2_map_server의 map_saver_cli를 사용한다. Nav2 튜토리얼에서도 SLAM으로 생성한 지도를 파일로 저장할 때 이 도구를 사용한다.3
먼저 프로젝트 패키지에 maps 디렉터리를 만든다.
mkdir -p ~/ros2_ws/src/my_robot_navigation/maps파일 이름에는 장소, 날짜, 버전을 넣는 습관을 들이자.
ros2 run nav2_map_server map_saver_cli \
-f ~/ros2_ws/src/my_robot_navigation/maps/indoor_lab_2026_05_14_v1이 명령은 보통 다음 두 파일을 만든다.
indoor_lab_2026_05_14_v1.yaml
indoor_lab_2026_05_14_v1.pgmPNG 형식으로 저장하고 싶다면 다음처럼 --fmt png 옵션을 사용할 수 있다.
ros2 run nav2_map_server map_saver_cli \
--fmt png \
-f ~/ros2_ws/src/my_robot_navigation/maps/indoor_lab_2026_05_14_v1환경이나 배포판에 따라 /map 토픽의 QoS 때문에 저장 도구가 지도를 받지 못하는 경우가 있다. 그럴 때는 transient local 구독 옵션을 함께 준다.
ros2 run nav2_map_server map_saver_cli \
-f ~/ros2_ws/src/my_robot_navigation/maps/indoor_lab_2026_05_14_v1 \
--ros-args -p map_subscribe_transient_local:=trueTurtleBot4의 SLAM Toolbox 기반 흐름에서는 /slam_toolbox/save_map 서비스를 호출하는 방법도 제공된다.1
ros2 service call /slam_toolbox/save_map slam_toolbox/srv/SaveMap "name:
data: 'indoor_lab_2026_05_14_v1'"네임스페이스를 쓰는 로봇이라면 서비스 이름이나 토픽 이름 앞에 네임스페이스가 붙을 수 있다. 예를 들어 /robot1/map, /robot1/slam_toolbox/save_map처럼 바뀐다. 이때는 ros2 topic list와 ros2 service list로 실제 이름을 확인하고 저장 명령을 조정한다.
YAML 파일 읽기
저장된 YAML 파일을 열어 보자.
cat ~/ros2_ws/src/my_robot_navigation/maps/indoor_lab_2026_05_14_v1.yaml대략 다음과 비슷한 내용이 보인다.
image: indoor_lab_2026_05_14_v1.pgm
mode: trinary
resolution: 0.05
origin: [-7.96, -6.60, 0]
negate: 0
occupied_thresh: 0.65
free_thresh: 0.25image는 실제 지도 이미지 파일 이름이다. resolution은 픽셀 하나가 실제 몇 미터인지를 뜻한다. 0.05라면 픽셀 하나가 5 cm다. origin은 지도 이미지의 원점이 map 좌표계에서 어디에 놓이는지를 나타낸다. occupied_thresh와 free_thresh는 픽셀 값을 장애물 또는 빈 공간으로 판단하는 기준이다.
이 YAML 파일은 다음 장에서 매우 중요해진다. AMCL과 Nav2는 이미지 파일만 보고 지도를 이해하지 않는다. YAML 파일을 통해 이미지, 해상도, 원점, 임계값을 함께 읽는다. 그러므로 이미지 파일만 복사하고 YAML 파일을 빼먹으면 안 된다.
지도 파일을 프로젝트 자산으로 관리하기
지도는 코드처럼 관리해야 한다. 같은 사무실 지도라도 언제 만들었는지, 어떤 로봇으로 만들었는지, 어떤 SLAM 설정을 사용했는지에 따라 결과가 달라진다. 나중에 Nav2가 이상하게 움직일 때 "이 지도가 언제 만들어진 것이지?"라는 질문에 답할 수 있어야 한다.
maps 디렉터리에 간단한 메모 파일을 함께 두자.
~/ros2_ws/src/my_robot_navigation/maps/
├── indoor_lab_2026_05_14_v1.yaml
├── indoor_lab_2026_05_14_v1.pgm
└── README.mdREADME.md에는 다음과 같은 내용을 적어 둔다.
# indoor_lab_2026_05_14_v1
- 생성일: 2026-05-14
- 로봇: TurtleBot3 Waffle Pi simulation
- SLAM: turtlebot3_cartographer
- 해상도: 0.05 m/pixel
- 환경: turtlebot3_world 기반 실내 월드
- 주행 방식: teleop 수동 주행, 출발 지점으로 2회 복귀
- 비고: 동적 장애물 없음, 복도 끝 영역 재방문 완료실제 로봇이라면 더 자세히 기록한다. 바닥 상태, 조명, 문 개폐 상태, 임시 장애물 제거 여부, LiDAR 높이, 로봇 시작 위치까지 적어 두면 좋다.
지도 버전 이름도 규칙적으로 관리한다.
indoor_lab_2026_05_14_v1.yaml
indoor_lab_2026_05_14_v2.yaml
office_floor2_2026_05_20_v1.yaml
warehouse_test_zone_2026_06_01_v1.yaml지도 파일을 덮어쓰는 습관은 위험하다. 어제 만든 지도가 오늘 만든 지도보다 더 좋을 수도 있다. 특히 SLAM은 같은 공간을 같은 로봇으로 주행해도 동선과 속도에 따라 결과가 달라진다. 좋은 결과는 남겨 두고, 새 지도는 새 버전으로 저장하자.
저장한 지도 빠르게 확인하기
저장 직후에는 파일이 만들어졌다는 사실만으로 만족하지 말고, 다시 불러와 확인해야 한다. 다음 명령으로 map server를 실행한다.
ros2 run nav2_map_server map_server \
--ros-args \
-p yaml_filename:=~/ros2_ws/src/my_robot_navigation/maps/indoor_lab_2026_05_14_v1.yaml단독 실행 시 lifecycle 상태 때문에 바로 지도가 발행되지 않을 수 있다. 간단히 확인하려면 Nav2 bringup을 통해 map server를 실행하거나 lifecycle 명령을 사용한다. 프로젝트에서는 보통 다음 장에서 AMCL과 함께 map server를 실행하게 된다.
이미지 자체도 열어 보자.
xdg-open ~/ros2_ws/src/my_robot_navigation/maps/indoor_lab_2026_05_14_v1.pgmPGM 파일을 기본 이미지 뷰어가 열지 못하면 GIMP 같은 이미지 편집 도구를 사용할 수 있다. 다만 지도 이미지를 수정할 때는 주의해야 한다. 작은 노이즈를 지우는 정도는 도움이 될 수 있지만, 실제로 막힌 벽을 임의로 뚫거나 통로 폭을 과장하면 Nav2가 현실과 다른 판단을 하게 된다. 지도는 보기 좋은 그림이 아니라 로봇이 믿는 세계라는 사실을 잊지 말자.
문제 해결 체크리스트
지도 작성이 뜻대로 되지 않을 때는 감으로 launch 파일을 고치지 말고 순서대로 확인한다. ROS2에서 대부분의 문제는 데이터가 없거나, 이름이 다르거나, 좌표계가 끊어졌거나, 시간이 맞지 않는 데서 시작된다.
먼저 /scan이 있는지 확인한다.
ros2 topic hz /scan없다면 LiDAR 드라이버, Gazebo bridge, 토픽 remapping을 확인한다.
다음으로 /odom이 있는지 확인한다.
ros2 topic echo /odom --once로봇이 움직여도 값이 변하지 않으면 구동계, diff drive controller, Gazebo plugin, 실제 로봇의 bringup을 확인한다.
그다음 TF를 확인한다.
ros2 run tf2_ros tf2_echo odom base_link
ros2 run tf2_ros tf2_echo map odommap → odom은 SLAM 실행 후에 있어야 한다. odom → base_link는 로봇 오도메트리 쪽에서 제공해야 한다. base_link → laser 또는 base_link → base_scan은 URDF나 static TF에서 제공해야 한다.
RViz2의 Fixed Frame도 확인한다. SLAM 중에는 보통 map으로 둔다. Fixed Frame이 odom이나 존재하지 않는 프레임으로 되어 있으면 데이터가 제대로 보이지 않을 수 있다.
시뮬레이션에서는 use_sim_time을 확인한다.
ros2 param get /slam_toolbox use_sim_time노드 이름은 사용하는 SLAM 패키지에 따라 다르다. ros2 node list로 실제 노드 이름을 확인한 뒤 파라미터를 조회한다.
마지막으로 네임스페이스를 확인한다. TurtleBot4나 다중 로봇 환경에서는 /scan이 아니라 /robot1/scan, /bot4/scan처럼 되어 있을 수 있다. 이 경우 SLAM 파라미터의 scan_topic과 RViz2 display의 topic을 실제 이름에 맞게 바꿔야 한다.
오늘 만든 지도 위에 다음 장이 세워진다
이 장에서 우리는 로봇을 움직여 실내 지도를 만들었다. Gazebo 또는 실제 TurtleBot을 준비했고, ROS2 환경을 source한 뒤 bringup, SLAM, RViz2, teleop을 순서대로 실행했다. /scan, /odom, /tf, /tf_static, /map을 확인했고, map → odom → base_link 좌표계 연결이 정상인지 점검했다. RViz2에서 LiDAR 스캔과 로봇 모델이 일관되게 보이는지 확인하며 천천히 주행했다.
그리고 가장 중요한 감각을 익혔다. SLAM은 버튼 하나로 완성되는 기능이 아니다. 로봇이 어떤 속도로 움직였는지, 같은 장소를 다시 방문했는지, 벽과 문 입구를 충분히 관측했는지에 따라 지도 품질이 달라진다. 지도 작성은 로봇에게 공간을 소개하는 과정이다. 대충 소개하면 로봇도 대충 기억한다.
다음 장에서는 이 지도를 다시 불러온다. 이제 로봇은 더 이상 빈 종이에 지도를 그리지 않는다. 이미 만들어진 지도 위에서 "나는 지금 어디에 있는가?"를 추정한다. 그 역할을 맡는 대표적인 노드가 AMCL이다. 오늘 저장한 yaml과 pgm 또는 png 파일이 다음 장의 출발점이다.
참고
1TurtleBot4 User Manual - Generating a map
2Gazebo documentation - Use ROS 2 to interact with Gazebo
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.