Static Map이 Nav2 Costmap으로 들어가기까지
이전 글에서 환경을 격자(occupancy grid)로 표현하는 법을 봤습니다. 그런데 이 격자 자체로는 로봇이 길을 찾지 못합니다. Nav2(ROS 2의 내비게이션 스택)가 읽을 수 있는 지도(map) 로 바꾸고, 그 지도를 costmap의 한 레이어로 올려야 비로소 경로 계획이 가능해집니다.
점유 격자 → 지도 → costmap
흐름은 이렇습니다.
도식 렌더링 중…
- map.yaml: 격자 이미지(pgm/png) +
resolution(셀 크기, m/px) +origin(지도 좌측하단의 월드 좌표) - map_server: 이 지도를 읽어
/map토픽으로 한 번 발행(latched) - static_layer:
/map을 구독해 costmap의 "변하지 않는 층"을 초기화 — 벽·선반 같은 고정 장애물 - global_costmap: static layer 위에 실시간 장애물·안전여유 층이 쌓여 최종 cost 격자가 됨
여기서 핵심은 좌표계 정합입니다. ROS의 표준 좌표 프레임(REP 105)은 map → odom → base_link로 이어지는데, 이 변환(TF)이 준비되지 않으면 costmap이 자기 위치를 모릅니다. 실제로 TF가 늦으면 costmap이 수십 초간 "기다림" 상태로 멈춥니다.
"벽을 아는 지도"가 없으면 — empty map의 함정
여기서 자주 터지는 사고가 있습니다. 빈 지도(empty map) 입니다.
빈 지도는 모든 셀이 "free(빈 공간)"인 지도입니다. 디버깅이나 초기화 편의로 쓰기 쉽지만, 이걸 static layer에 넣으면 로봇은 세상에 벽이 하나도 없다고 믿습니다.
도식 렌더링 중…
우리도 실제로 이 회귀 버그를 겪었습니다. 시스템을 새 버전으로 옮기는 과정(cutover)에서, 실행 경로 어딘가에 빈 지도가 하드코딩되어 들어갔습니다. 환경에는 분명 벽이 있는데, costmap의 static layer에는 "전부 free"가 깔려서, 로봇이 멀쩡한 벽을 향해 직진하는 현상이 나타났습니다.
해결은 단순합니다 — 환경에서 만든 진짜 점유 격자를 Nav2 지도로 변환해 넣는 것. 변환 후 costmap의 lethal(절대 통과 불가) 셀 수가 0에서 수만 개로 살아났고, 로봇은 다시 벽을 인식했습니다. 교훈은 "지도가 비어 있으면 로봇은 용감해진다" 입니다.
왜 굳이 "지도 → 토픽 → 레이어"인가
처음 보면 단계가 많아 보입니다. 격자를 바로 쓰면 안 될까요? 이 간접 구조에는 이유가 있습니다.
- 출처 분리: 지도는 SLAM·시뮬·수동 편집 등 어디서 와도
map_server가 동일한/map인터페이스로 공급 - 레이어 합성: static(고정)은 한 번만 깔고, 그 위에 실시간 센서 장애물을 따로 얹어 갱신 비용을 분리
- 다중 구독자: global·local costmap, 시각화 도구가 같은
/map을 공유
이 "지도 → costmap 레이어" 구조 덕분에, 다음 글에서 볼 costmap의 3층(static / obstacle / inflation) 합성이 가능해집니다.
한 줄 정리
📌 점유 격자는 그대로 쓰이지 않는다 — Nav2 지도(map.yaml) → map_server(/map) → static layer → costmap 으로 흘러야 로봇이 "벽을 아는" 상태가 된다. 이 사슬 어딘가에 빈 지도가 끼면, 로봇은 세상에 벽이 없다고 믿고 직진한다.
