[ROS2 입문] Day 5: Nav2와 자율주행 스택 - 목적지까지 스스로 이동하기
[ROS2 입문] Day 5: Nav2와 자율주행 스택 - 목적지까지 스스로 이동하기
서론: 지도 위에서 목적지로
ROS2의 표준 내비게이션 스택 Nav2(Navigation2)는 로봇이 지도 위에서 장애물을 피하며 목적지까지 이동하도록 한다. 센서 → 지도 → 경로 계획 → 제어 명령까지 전체 파이프라인을 제공한다.
1. Nav2 전체 구조
1
2
3
4
5
6
7
8
9
10
11
12
13
LiDAR / 카메라
↓
SLAM (지도 생성) 또는 정적 지도 로드
↓
Costmap (장애물 표현)
├─ Global Costmap (전체 경로 계획용)
└─ Local Costmap (실시간 장애물 회피용)
↓
Planner Server (Global Path 생성)
↓
Controller Server (Local Path 추종)
↓
cmd_vel (속도 명령) → 로봇 하드웨어
각 서버는 ROS2 액션으로 통신한다. Nav2는 이 서버들을 BehaviorTree로 조합해 복잡한 내비게이션 시나리오를 처리한다.
2. 설치
1
2
3
sudo apt install ros-jazzy-nav2-bringup \
ros-jazzy-nav2-msgs \
ros-jazzy-slam-toolbox
3. SLAM으로 지도 만들기
slam_toolbox를 사용해 실시간으로 지도를 생성한다.
1
2
3
4
5
6
7
8
9
10
# 1. slam_toolbox 실행
ros2 launch slam_toolbox online_async_launch.py \
slam_params_file:=./slam_params.yaml
# 2. 로봇을 수동 조종하며 공간 스캔
ros2 run teleop_twist_keyboard teleop_twist_keyboard
# 3. 지도 저장
ros2 run nav2_map_server map_saver_cli -f ~/maps/my_map
# → my_map.yaml + my_map.pgm 생성
4. Nav2 실행
1
2
3
4
# 저장된 지도로 내비게이션 실행
ros2 launch nav2_bringup bringup_launch.py \
map:=$HOME/maps/my_map.yaml \
use_sim_time:=false
내부적으로 map_server, amcl(위치 추정), planner, controller, recoveries 서버가 모두 실행된다.
5. 초기 위치 설정 (AMCL)
AMCL은 파티클 필터로 지도 위의 로봇 위치를 추정한다. 처음에는 초기 위치를 수동으로 설정해야 한다.
1
2
3
4
5
6
7
# CLI로 초기 위치 퍼블리시
ros2 topic pub /initialpose geometry_msgs/msg/PoseWithCovarianceStamped \
"{ header: {frame_id: map},
pose: { pose: {
position: {x: 0.0, y: 0.0, z: 0.0},
orientation: {w: 1.0}
}}}" --once
RViz2에서 “2D Pose Estimate” 버튼으로 클릭해 설정하는 것이 더 편하다.
6. 이동 목표 전송 (Python 액션 클라이언트)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from geometry_msgs.msg import PoseStamped
from nav2_msgs.action import NavigateToPose
from rclpy.action import ActionClient
class Navigator(Node):
def __init__(self):
super().__init__('navigator')
self._client = ActionClient(
self, NavigateToPose, 'navigate_to_pose')
def go_to(self, x, y, yaw=0.0):
goal = NavigateToPose.Goal()
goal.pose = PoseStamped()
goal.pose.header.frame_id = 'map'
goal.pose.header.stamp = self.get_clock().now().to_msg()
goal.pose.pose.position.x = x
goal.pose.pose.position.y = y
goal.pose.pose.orientation.w = 1.0 # yaw=0
self._client.wait_for_server()
future = self._client.send_goal_async(
goal, feedback_callback=self._feedback_cb)
future.add_done_callback(self._goal_response_cb)
def _feedback_cb(self, feedback_msg):
dist = feedback_msg.feedback.distance_remaining
self.get_logger().info(f'남은 거리: {dist:.2f}m')
def _goal_response_cb(self, future):
result = future.result()
if result.accepted:
self.get_logger().info('목표 수락됨')
else:
self.get_logger().warn('목표 거부됨')
7. Costmap 설정
Costmap은 장애물 주변에 비용을 부여해 경로가 장애물에서 충분히 떨어지게 한다.
1
2
3
4
5
6
7
8
# nav2_params.yaml (일부)
global_costmap:
global_costmap:
ros__parameters:
robot_radius: 0.22 # 로봇 반지름 (m)
inflation_radius: 0.55 # 장애물 팽창 반경
obstacle_max_range: 2.5 # 장애물 감지 최대 거리
plugins: ["static_layer", "obstacle_layer", "inflation_layer"]
inflation_radius가 너무 작으면 좁은 공간에서 충돌 위험이 있고, 너무 크면 통로를 못 지나간다.
8. 시리즈 종합 체크리스트
- ROS2 설치 후 talker-listener로 DDS 기반 통신을 확인했다.
- 토픽/서비스/액션 중 상황에 맞는 통신 패턴을 선택할 수 있다.
colcon build로 Python/C++ 패키지를 빌드하고 노드를 실행했다.- TF 프레임 트리를 설계하고
TransformBroadcaster로 변환을 퍼블리시했다. - Nav2로 SLAM 지도를 생성하고 목적지까지 자율이동 명령을 전송했다.
시리즈 마무리
ROS2는 DDS 기반 미들웨어 위에 표준화된 통신 패턴(토픽/서비스/액션), 빌드 시스템(colcon), 공간 기하학(TF2), 내비게이션(Nav2)을 쌓아올린 생태계다.
각 레이어를 독립적으로 이해하면 문제가 생겼을 때 어느 레이어에서 발생했는지 빠르게 찾을 수 있다. 통신이 안 된다면 토픽 QoS부터, 위치가 이상하다면 TF 트리부터, 경로가 이상하다면 Costmap 설정부터 확인하는 순서로 접근한다.
This post is licensed under CC BY 4.0 by the author.