제9부 로보틱스
9.9 Isaac 인지 — cuVSLAM·nvblox
9.9.1 이 장에서 배우는 것 / 선수 지식
9.6(VPI)에서 로봇 비전의 클래식 CV 전단(특징·스테레오·광학 흐름)을, 9.7·9.8에서 그것을 NITROS zero-copy 그래프로 잇는 법을 배웠다. 이 장은 그 위에 올라가는 고수준 인지 GEM 두 개 — 로봇이 자기 위치를 추정하는 cuVSLAM과, 깊이로 3D 지도를 재구성하는 nvblox — 를 판다. 둘은 로봇이 "내가 어디 있고, 주변이 어떻게 생겼나"를 GPU 가속으로 실시간 푸는 한 쌍이며, 내비게이션(Nav2)·매니퓰레이션(9.10)의 토대다.
핵심 통찰: SLAM은 "위치추정(Localization)"과 "매핑(Mapping)"이라는 닭과 달걀 문제다 — 지도를 알면 위치를 알 수 있고, 위치를 알면 지도를 만들 수 있는데, 로봇은 둘 다 모르는 채 동시에 풀어야 한다. Isaac은 이를 두 GEM으로 분담한다: cuVSLAM이 카메라·IMU로 6-DOF 자세(위치+방향)를 추정(L)하고, 그 자세를 받아 nvblox가 깊이 프레임들을 하나의 3D 볼륨에 융합(M)한다. 핵심은 자세의 품질이 지도의 품질을 정한다 — cuVSLAM이 흔들리면(드리프트) nvblox의 지도가 어긋난다. 그래서 드리프트·루프 클로저·좌표 프레임을 이해하는 것이 이 장의 절반이다.
배우는 것:
- 두 문제 — Localization과 Mapping이 왜 동시에·서로 얽혀 풀려야 하는가(SLAM).
- cuVSLAM — 비주얼·관성 오도메트리(VIO)의 전단(특징, 9.6)·후단(자세 추정·번들 조정), 드리프트·루프 클로저, 좌표 프레임(map→odom→base_link, REP-105).
- nvblox — 깊이를 TSDF(표면)로 융합 → ESDF(장애물 거리)/코스트맵, Nav2 내비게이션 연동.
- 자세↔지도 의존 — cuVSLAM 자세가 nvblox 입력, "쓰레기 자세 → 쓰레기 지도".
- 실습 골격 —
isaac_ros_visual_slam·isaac_ros_nvblox를 NITROS 그래프(9.8)로 잇기, TF·검증.
선수 지식: 9.6(VPI 특징·스테레오 전단)·9.7·9.8(Isaac ROS NITROS 그래프·검증)·9.4(Jetson 통합 메모리·전력). ROS 2의 TF(좌표 변환)·nav_msgs/Odometry·센서 메시지 기초는 가정(BK-02). 매니퓰레이션(cuMotion, ESDF 충돌 세계 공유)은 9.10, 시뮬레이션 검증은 9.12.
정직: SLAM·3D 재구성은 알고리즘적으로 깊고, cuVSLAM·nvblox의 내부 구현·파라미터·패키지 API·지원 센서는 릴리스 의존이다. 이 장은 "이 인지가 무엇을·왜·어떤 한계로 푸는가"의 원리와 운영 지식(드리프트·루프 클로저·좌표 프레임·자세-지도 의존·실패 모드)을 가르치고, 구체 API·파라미터는 "그 릴리스 문서로 확인". 드리프트율·복셀 크기 같은 수치는 환경 의존 개략값이다. 비주얼 SLAM은 무텍스처·저조도·동적 환경에서 실패할 수 있음을 정직하게 다룬다.
9.9.2 위치추정과 매핑 — 로봇이 풀어야 할 두 문제
자율 로봇이 움직이려면 두 가지를 알아야 한다:
닭과 달걀(둘이 서로의 입력):
그런데 로봇은 둘 다 모른 채 시작하므로 동시에 추정해야 한다(SLAM).
- 왜 동시에(SLAM): 미리 만든 지도가 없는 새 환경에선, 위치도 지도도 처음부터 추정한다. Simultaneous Localization And Mapping — 자세를 추정하며 지도를 쌓고, 쌓은 지도로 자세를 보정하는 순환.
- Isaac의 분담: cuVSLAM이 희소(sparse) 특징 기반으로 자세를 추정(L 중심, 경량 랜드마크 지도)하고, nvblox가 그 자세를 받아 깊이로 조밀(dense) 3D 지도를 재구성(M 중심)한다. 자세 추정과 조밀 매핑을 나눠 각각 GPU로 가속.
Isaac 인지 스택의 분담:
- 자세가 매핑의 입력: nvblox는 "이 깊이 프레임을 어느 자세에서 봤나"를 알아야 여러 프레임을 한 좌표계에 융합한다. 그 자세를 cuVSLAM이 준다 — 두 GEM은 자세로 연결된다.
- 하류: 지도(ESDF·코스트맵)는 Nav2(내비게이션)·cuMotion(매니퓰레이션, 9.10)의 충돌 회피·경로 계획 입력이 된다. 인지의 끝이 곧 계획의 시작.
핵심: 자율 로봇은 위치추정(L) 과 매핑(M) 을 동시에 풀어야 한다(SLAM, 닭과 달걀). Isaac은 이를 cuVSLAM(자세, 희소) 과 nvblox(3D 지도, 조밀) 로 분담하고, cuVSLAM의 자세가 nvblox의 입력이다 — 그래서 자세 품질이 지도 품질을 정한다. 지도는 다시 Nav2·cuMotion(9.10)의 계획 입력.
9.9.3 cuVSLAM I — 비주얼·관성 오도메트리의 구조
cuVSLAM은 카메라(스테레오 또는 다중)와 선택적 IMU로 로봇의 6-DOF 자세를 실시간 추정하는 GPU 가속 비주얼 SLAM이다. IMU를 융합하면 VIO(Visual-Inertial Odometry) 가 된다.
내부를 전단/후단으로 나누면(개념):
cuVSLAM 파이프라인(개념 — 내부 구현·단계는 릴리스 의존):
- 전단(9.6의 그 특징): cuVSLAM의 입력 전단은 VPI/클래식 CV의 특징·스테레오(9.6)다 — 코너를 검출(Harris/FAST)하고 추적(KLT)해, 프레임 간 특징 이동에서 카메라 운동을 푼다. 스테레오/다중 카메라로 특징의 깊이(스케일) 를 얻는다.
- IMU 융합의 역할: 카메라는 30~60fps지만 IMU는 수백 Hz~kHz로 프레임 사이를 메운다 — 빠른 회전·이동, 모션 블러, 특징이 잠깐 빈약할 때도 자세를 유지(robustness). 모노 카메라의 스케일 모호성(영상만으론 절대 크기 모름)을 IMU·스테레오가 보완. 결합 방식은 tight(센서를 한 추정에) vs loose(따로 추정 후 합침)로 나뉜다(구현 의존).
- GPU 가속: 특징 추적·대응·최적화를 GPU로 → Jetson(9.4)에서 실시간 자세. 이것이 "cu"VSLAM의 cu(CUDA).
- 출력:
nav_msgs/Odometry(자세+속도), TF(odom→base_link), 키프레임·랜드마크. 고율·저지연이 특징 — 제어 루프가 쓸 수 있게.
핵심: cuVSLAM은 특징(9.6 전단) + IMU 융합 + 번들 조정으로 6-DOF 자세를 GPU 실시간 추정하는 VIO다. 스테레오/다중 카메라가 깊이·스케일을, IMU가 프레임 사이·빠른 움직임·모호성을 메운다. 출력은 고율 오도메트리 자세 — 제어·매핑(nvblox)의 토대. 전단이 9.6의 클래식 CV 위에 섰음을 기억하라.
9.9.4 cuVSLAM II — 드리프트·루프 클로저·좌표 프레임
오도메트리의 본질적 약점은 드리프트다. 매 프레임의 운동 추정에는 작은 오차가 있고, 이를 누적하면 시간·거리가 갈수록 추정 위치가 실제 위치에서 벗어난다.
루프 클로저: '여기 와본 곳이다'(장소 재인식) → 출발=도착 제약 → 자세 그래프 최적화로 경로 전체 보정.
- 드리프트율: 누적 오차는 보통 이동 거리의 일정 비율로 자란다(예: 수 % 이내 등, 환경·센서 의존 개략 — 무텍스처·저조도에선 악화). 짧은 구간은 정확하나 긴 주행에선 누적.
- 루프 클로저(loop closure): 로봇이 전에 와본 곳을 다시 인식하면("이 장면 봤다"), "여기는 그때 그 위치"라는 제약이 생긴다. 이를 자세 그래프에 넣어 전체 경로를 재최적화 → 누적 드리프트를 한 번에 보정. 단, 루프를 닫으려면 같은 곳을 다시 지나야 하고, 닫는 순간 자세가 점프(불연속)한다.
- 두 자세의 구분 — 좌표 프레임(REP-105): 이 점프 때문에 ROS는 자세를 두 단계 TF로 나눈다:
odom→base_link: 순수 오도메트리. 연속적(점프 없음)이라 로컬 제어·단기 계획에 안전. 단 드리프트.map→odom: SLAM의 보정량. 루프 클로저 때 갱신되며 점프할 수 있다.map→base_link(둘의 합성)가 전역적으로 일관한 자세.- 이 분리가 왜 중요한가: 제어기는 odom(연속)을 따라야 갑작스런 점프에 안 흔들리고, 전역 계획은 map(일관)을 본다. 둘을 혼동하면 점프 때 제어가 불연속해지거나 지도가 어긋난다(함정).
- cuVSLAM 출력:
odom→base_link오도메트리(고율·연속)와, SLAM 보정(map→odom)을 TF로 발행(구현·옵션 의존). nvblox는 보통 연속적인odom프레임(global_frame기본값)에서 지도를 융합한다 — 정적 TSDF는 루프 클로저 점프에 맞춰 변형되지 않으므로, 점프하는map프레임에서 융합하면 지도가 불연속이 된다.
견해: 오도메트리는 국소 정확·전역 드리프트, 루프 클로저는 그 드리프트를 장소 재인식으로 한 번에 보정(대신 점프). ROS의 map→odom→base_link(REP-105)는 이 둘을 분리해, 연속이 필요한 제어는 odom, 일관이 필요한 전역 계획·매핑은 map을 쓰게 한다. 이 프레임 분리를 이해 못 하면 "지도가 어긋난다·로봇이 점프한다"의 원인을 식별하지 못한다.
9.9.5 nvblox I — TSDF로 표면을 융합한다
nvblox는 깊이(+선택적 컬러) 프레임들을, 자세를 알 때, 하나의 3D 볼륨에 GPU로 융합해 표면·장애물 지도를 만든다. 핵심 자료구조는 TSDF다.
TSDF(Truncated Signed Distance Function) — 복셀 격자: 각 복셀(작은 정육면체)에 '가장 가까운 표면까지의 부호 있는 거리'를 저장한다.
| 부호 있는 거리 | 의미 |
|---|---|
| 거리 > 0 | 표면 앞(자유공간 쪽) |
| 거리 = 0 | 표면 위 |
| 거리 < 0 | 표면 뒤(물체 내부) |
| |거리| > truncation | 잘라냄(truncated) — 표면 근처만 정밀히 |
여러 깊이 프레임을 융합:
- 왜 TSDF인가: 깊이 한 장은 노이즈·구멍이 많다. TSDF는 많은 프레임을 복셀마다 가중 평균해 노이즈를 줄이고 표면을 0-교차면으로 일관되게 추정한다(voxblox/KinectFusion 계열의 GPU판). 표면을 메시(시각화)나 포인트로 추출 가능.
- 입력: ① 깊이 이미지(스테레오 9.6 또는 RGB-D 센서) ② 카메라 자세(cuVSLAM 9.9.4) ③ 카메라 내부 파라미터(intrinsic, 캘리브레이션 8.13) ④ (선택) 컬러. 자세가 틀리면 프레임들이 어긋나 융합이 무너진다("쓰레기 자세 → 쓰레기 지도").
- 복셀 크기 트레이드오프(개략): 작을수록(예: 1~2cm) 정밀하나 메모리·연산 폭증(부피는 세제곱), 클수록(예: 5~10cm) 경량이지만 거칠다. 내비게이션은 보통 큰 복셀(5cm급), 매니퓰레이션(9.10)은 작은 복셀 — 용도가 해상도를 정한다.
- GPU 가속: 매 깊이 프레임을 TSDF에 통합(integrate) 하는 연산이 복셀 병렬이라 GPU에 잘 맞는다 → Jetson에서 실시간 조밀 매핑. 통합 메모리(9.4)로 깊이·자세·복셀이 zero-copy 흐름(9.7).
- 동적 장면: 사람·움직이는 물체는 정적 TSDF를 오염시킨다 — 일부 버전은 동적 제거/디케이로 처리(릴리스 의존, 확인 필요).
핵심: nvblox는 깊이 + 자세 + 내부파라미터를 받아 TSDF 복셀 격자에 여러 프레임을 GPU 융합해 노이즈를 평균화한 일관된 표면을 만든다. 자세(cuVSLAM)·캘리브레이션(8.13)이 정확해야 융합이 산다. 복셀 크기가 정밀도↔메모리/연산을 정하고, 용도(내비 vs 매니퓰레이션)가 그 선택을 좌우한다.
9.9.6 nvblox II — ESDF·코스트맵·내비게이션 연동
TSDF(표면)에서 계획에 직접 쓰는 산출물을 추출한다. 핵심은 ESDF다.
ESDF(Euclidean Signed Distance Function): 각 복셀에 '가장 가까운 장애물까지의 유클리드 거리'를 저장한다.
| 값 | 의미 |
|---|---|
| 큰 값 | 장애물에서 멀다(안전) |
| 작은 값 | 장애물에 가깝다(위험) |
| 음수 | 장애물 내부 |
→ 경로 계획이 '이 점이 얼마나 안전한가/충돌하는가'를 O(1)로 질의.
- 왜 ESDF인가: 경로 계획·충돌 회피는 "이 자세가 장애물에서 얼마나 떨어졌나"를 빠르게·매끄럽게 알아야 한다. ESDF는 그 거리장(gradient 포함) 을 미리 계산해 둬, 플래너가 충돌 비용·안전 여유를 즉시 평가하고 장애물에서 멀어지는 방향(gradient) 을 얻는다. cuMotion(9.10)의 충돌 회피도 ESDF류 거리장을 쓴다 — 인지(nvblox)와 매니퓰레이션(9.10)이 충돌 세계를 공유.
- 출력 형태(릴리스 의존): ① 메시(시각화·디버깅, RViz) ② ESDF(충돌 질의·계획) ③ 2D 코스트맵(Nav2의 비용 레이어로 — 바닥 평면 투영) ④ 점유(occupancy). 같은 TSDF에서 용도별로 추출한다.
- Nav2 연동: nvblox가 Nav2 코스트맵 레이어로 들어가면, 전역/로컬 플래너가 nvblox의 3D 장애물을 반영해 경로를 짠다 — 인지(SLAM·재구성) → 매핑(코스트맵) → 계획(Nav2) → 제어의 전형 로봇 내비 스택. cuVSLAM 자세 + nvblox 지도가 Nav2에 입력된다.
- 2D vs 3D: 평지 주행 로봇은 2D 코스트맵으로 충분할 때가 많지만, 공중(드론)·매니퓰레이터·계단·돌출 장애물은 3D(ESDF)가 필요. nvblox는 3D를 만들고 2D로 투영도 준다.
견해: nvblox의 가치는 TSDF(표면)에서 ESDF(장애물 거리장) 를 추출해 계획이 즉시 쓰는 형태로 주는 데 있다 — 경로 계획·충돌 회피가 거리·gradient를 O(1)로 질의. 평지는 2D 코스트맵(Nav2), 3D 장애물·매니퓰레이션은 ESDF(cuMotion 9.10과 공유). 즉 cuVSLAM(자세) + nvblox(지도) → Nav2/cuMotion(계획) 이 한 줄로 이어지는 로봇 자율 스택.
9.9.7 워크스루 — cuVSLAM→nvblox 인지·매핑 그래프
9.8의 그래프 조립 기법으로 두 GEM을 잇는다. 핵심은 cuVSLAM의 자세(TF)가 nvblox의 입력이라는 데이터·TF 의존이다.
# 인지·매핑 그래프: 스테레오 카메라 → cuVSLAM(자세) + 깊이 → nvblox(지도)
# (개념 골격 — 패키지·플러그인 클래스명·토픽·파라미터는 릴리스 의존, 반드시 확인)
from launch import LaunchDescription
from launch_ros.actions import ComposableNodeContainer
from launch_ros.descriptions import ComposableNode
def generate_launch_description():
visual_slam = ComposableNode(
package='isaac_ros_visual_slam',
plugin='nvidia::isaac_ros::visual_slam::VisualSlamNode', # 예시 클래스명
name='visual_slam',
parameters=[{'enable_imu_fusion': True,
'map_frame': 'map', 'odom_frame': 'odom',
'base_frame': 'base_link', 'publish_odom_to_base_tf': True}],
remappings=[('stereo_camera/left/image', '/camera/left/image_rect'),
('stereo_camera/right/image', '/camera/right/image_rect'),
('visual_slam/imu', '/camera/imu')])
nvblox = ComposableNode(
package='isaac_ros_nvblox',
plugin='nvidia::isaac_ros::nvblox::NvbloxNode',
name='nvblox',
parameters=[{'voxel_size': 0.05, # 5cm 복셀(내비, 개략)
'global_frame': 'odom', # 자세가 정의된 프레임
'esdf': True, 'mesh': True}],
remappings=[('depth/image', '/camera/depth'),
('depth/camera_info', '/camera/depth_camera_info'),
('color/image', '/camera/color')])
container = ComposableNodeContainer(
name='perception_container', namespace='',
package='rclcpp_components', executable='component_container_mt',
composable_node_descriptions=[visual_slam, nvblox], # 한 컨테이너(zero-copy, 9.7)
output='screen')
return LaunchDescription([container])
실행·검증:
# 0) 컨테이너·빌드·소스(9.8.2), 스테레오/RGB-D + IMU 입력 발행(카메라 또는 rosbag)
ros2 launch my_perception slam_mapping.launch.py
# 1) 자세(cuVSLAM) 흐르나
ros2 topic hz /visual_slam/tracking/odometry # 고율 오도메트리
ros2 run tf2_ros tf2_echo map base_link # TF 체인 확인(map→odom→base_link)
ros2 run tf2_tools view_frames # TF 트리 그림(끊김=프레임 미발행)
# 2) 지도(nvblox) 생기나
ros2 topic hz /nvblox/mesh # 메시(시각화)
ros2 topic list | grep -i esdf # ESDF/코스트맵 토픽
# RViz로 메시·ESDF 시각화(자세가 흔들리면 지도가 어긋나는지 눈으로)
# 3) zero-copy·가속 검증(9.8.6)
ros2 component list # 두 GEM이 한 컨테이너에
# Nsight(2.13): 내부 memcpy 없는지 / jtop(9.4): GPU 점유·CPU 한가
데이터·TF 흐름:
검증 포인트:
- 자세가 끊기면(TF 미발행/특징 부족) → nvblox 융합 불가/어긋남.
- 자세가 드리프트/점프 → 지도가 휨/겹침 → 루프 클로저·프레임(map vs odom) 확인.
- TF가 생명줄: nvblox는 "이 깊이를 어느 자세에서 봤나"를 TF로 안다. cuVSLAM이 TF를 안 내거나(특징 부족·초기화 실패) 프레임 이름이 안 맞으면 nvblox가 융합을 못 한다 →
tf2_echo·view_frames로 TF 체인부터 확인. - 시간 동기: 깊이·자세·IMU의 타임스탬프가 정합해야(8.7 정신) 올바른 자세에 깊이가 융합된다. 어긋나면 지도가 흐릿·겹침 →
use_sim_time·동기 확인. - 개발은 rosbag으로(9.8): 카메라·IMU·TF를 녹화해 재생하면 결정적으로 SLAM·매핑을 반복·회귀(8.14·7.7).
견해: cuVSLAM→nvblox 그래프의 핵심은 자세(TF)가 두 GEM을 잇는 다리라는 것이다 —
tf2_echo로 map→odom→base_link 체인을 먼저 확인하는 것이 디버깅의 출발점. 자세가 끊기거나 드리프트하면 지도가 무너지므로, "지도가 이상하다"의 90%는 자세(cuVSLAM)·TF·시간 동기 문제다. 둘을 한 컨테이너에 둬 NITROS zero-copy(9.7)로 깊이·자세를 흘린다.
9.9.8 중간 점검 (체크포인트)
- [ ] Localization·Mapping이 동시에 얽혀 풀리는 SLAM이고, Isaac이 cuVSLAM(자세)·nvblox(지도) 로 분담함을 안다.
- [ ] cuVSLAM이 특징(9.6)+IMU 융합+번들 조정의 VIO이고, 스테레오/IMU가 깊이·스케일·빠른 움직임을 메움을 안다.
- [ ] 드리프트(누적)·루프 클로저(장소 재인식 보정, 점프)와 map→odom→base_link(REP-105) 프레임 분리의 이유(연속 vs 일관)를 안다.
- [ ] nvblox가 깊이+자세+intrinsic을 TSDF로 융합(노이즈 평균·표면)하고, 자세 품질이 지도 품질임을 안다.
- [ ] TSDF에서 ESDF(장애물 거리장)·코스트맵을 추출해 Nav2/cuMotion(9.10) 계획을 먹임을 안다.
- [ ] 두 GEM을 한 NITROS 컨테이너로 잇고 TF 체인·시간 동기로 검증할 줄 안다(9.8).
증상→의심: 지도가 휘거나 겹침 → 자세 드리프트·루프 클로저 안 됨·프레임(map vs odom) 혼동 → TF·루프 확인. nvblox 지도가 안 생김 → cuVSLAM TF 미발행(특징 부족·초기화 실패)·프레임 이름 불일치·깊이 미발행 → tf2_echo·view_frames. 자세가 갑자기 튐 → 루프 클로저 점프(정상, map 프레임)거나 추적 실패 재초기화 → 제어는 odom(연속) 사용. 무텍스처/저조도서 추적 끊김 → 비주얼 SLAM 한계 → IMU·조명·센서 보완(9.9.10). 지도가 흐릿·이중 → 시간 동기·캘리브레이션(8.13) 어긋남. 메모리 폭증/느림 → 복셀 너무 작음·맵 너무 큼 → 복셀 크기↑·범위 제한. GPU는 유휴인데 CPU만 바쁨 → zero-copy 폴백 → 한 컨테이너·Nsight(9.8.6).
9.9.9 트레이드오프·대안
| 결정 | 트레이드오프 |
|---|---|
| VIO(카메라+IMU) vs 비주얼만 | 빠른 움직임·스케일·강건 vs 단순·IMU 캘리브 불필요 |
| 희소 SLAM(cuVSLAM) vs 조밀 SLAM | 경량·실시간 자세 vs 조밀 지도(고부하) → nvblox로 분담 |
| 작은 복셀 vs 큰 복셀(nvblox) | 정밀(매니퓰레이션) vs 경량·넓은 범위(내비) |
| 2D 코스트맵 vs 3D ESDF | 평지 주행 단순 vs 공중·매니퓰레이션·돌출 장애물 |
| odom 프레임 vs map 프레임 | 연속(제어) vs 전역 일관(계획) — 용도별 사용 |
| LiDAR SLAM vs 비주얼 SLAM | 기하 정확·조명 무관·고가 vs 저가·풍부 정보·조명 민감 |
견해: 로봇 자율의 표준 조합은 cuVSLAM(VIO, 자세) + nvblox(3D 지도) → Nav2/cuMotion이다 — 자세는 경량 희소 SLAM으로, 조밀 지도는 따로 GPU 매핑으로 분담해 둘 다 실시간. 복셀 크기·2D/3D는 용도(내비 vs 매니퓰레이션, 평지 vs 공중)로 정하고, map/odom 프레임은 용도(계획 vs 제어)로 구분해 쓴다. 비주얼 SLAM의 약점(조명·텍스처)이 치명적이면 LiDAR·다중 센서 융합을 더한다 — 단일 센서 SLAM만으로는 모든 경우를 해결할 수 없다.
비엔비디아 정직: SLAM·3D 재구성은 풍부한 오픈소스가 있다 — 비주얼/VIO(ORB-SLAM, VINS-Fusion, OpenVINS 등), LiDAR SLAM(LIO-SAM, Cartographer 등), 조밀 재구성(voxblox/Voxfield — nvblox의 CPU 계열 전신, KinectFusion, OctoMap). cuVSLAM·nvblox의 강점은 GPU 가속 실시간·NITROS 통합·Jetson 최적화·cuMotion 등과의 충돌 세계 공유다. 알고리즘 자체는 학계 공통 — NVIDIA가 더하는 것은 엣지 실시간 성능과 스택 통합. 정확도·강건성은 환경·센서·튜닝 의존이라 벤치마크를 자기 환경에서 확인해야 한다.
9.9.10 흔한 함정
- 자세를 못 믿고 지도를 만듦. cuVSLAM 드리프트·추적 실패면 nvblox 지도가 어긋남("쓰레기 자세→쓰레기 지도") → 자세·TF·루프 클로저부터 검증(9.9.7).
- map과 odom 프레임 혼동. 제어에 map(점프)을 쓰면 궤적이 불연속해지고, 전역 계획에 odom(드리프트)을 쓰면 어긋남 → 제어=odom(연속), 계획·매핑=map(일관)(REP-105).
- 무텍스처·저조도·동적 환경. 비주얼 SLAM이 특징을 추출하지 못해 추적 끊김 → IMU 융합·조명·다중 센서(LiDAR)·환경 개선. 한계를 인정하고 설계(8.10 안전).
- 캘리브레이션·시간 동기 무시. intrinsic/extrinsic(8.13)·타임스탬프(8.7)가 어긋나면 스테레오·융합이 흐릿·이중 → 캘리브·동기 먼저.
- 복셀을 너무 작게. 메모리·연산 세제곱 폭증으로 OOM/느림 → 용도에 맞게(내비 5cm급), 맵 범위 제한.
- 루프 클로저 점프를 버그로 오해. map 프레임의 점프는 정상 보정 → 제어는 odom(연속)으로 흡수.
- IMU 미캘리브·장착 오류. VIO가 IMU 바이어스·외부 변환에 민감 → IMU-카메라 캘리브·장착 강성 확인.
- 두 GEM을 따로 프로세스로. NITROS zero-copy 폴백 → 한 컨테이너(9.7·9.8).
- 지도를 무한히 키움. 장시간 주행 시 메모리 증가 → 범위 제한·디케이·서브맵(릴리스 기능 확인).
- 시뮬 성능을 실환경으로 단정. Sim2Real 갭(10.7)·센서 노이즈 차이 → 실환경 검증.
9.9.11 연습문제
연습 1 (분담과 의존). SLAM이 왜 "동시" 문제인지 설명하고, Isaac이 cuVSLAM·nvblox로 어떻게 분담하며 둘이 어떤 데이터로 의존하는지, "쓰레기 자세→쓰레기 지도"의 의미를 답하라.
정답
왜 동시(SLAM): 위치추정(Localization)과 매핑(Mapping)은 닭과 달걀이다 — 지도를 알면 센서 관측을 지도에 맞춰 위치를 알고, 위치를 알면 관측을 그 위치에 쌓아 지도를 만든다. 새 환경엔 둘 다 없으니 동시에 추정해야(Simultaneous Localization And Mapping). Isaac 분담: cuVSLAM이 희소 특징(9.6)+IMU로 6-DOF 자세를 실시간 추정(L 중심, 경량 랜드마크 지도), nvblox가 그 자세를 받아 깊이 프레임들을 TSDF에 융합해 조밀 3D 지도를 만든다(M 중심). 자세 추정(경량·고율)과 조밀 매핑(고부하)을 나눠 각각 GPU로 가속. 의존 데이터: nvblox는 "이 깊이 프레임을 어느 자세에서 봤나"를 알아야 여러 프레임을 한 좌표계에 합친다 — 그 자세를 cuVSLAM이 TF로 준다. 즉 자세가 매핑의 입력. "쓰레기 자세→쓰레기 지도": cuVSLAM 자세가 드리프트하거나 틀리면, nvblox가 깊이 프레임을 틀린 위치에 융합한다 → 같은 벽이 두 곳에 겹치거나 표면이 휘어 지도가 무너진다. 그래서 지도 품질의 상한은 자세 품질이 정하고, "지도가 이상하다"의 디버깅은 자세·TF부터 본다. 하류: 지도(ESDF·코스트맵)는 Nav2·cuMotion(9.10)의 계획 입력 → 인지의 끝이 계획의 시작.
연습 2 (드리프트·루프·프레임). 오도메트리 드리프트가 왜 생기고 루프 클로저가 어떻게 보정하는지, 그리고 map→odom→base_link(REP-105) 분리가 왜 필요한지 — 제어와 전역 계획이 각각 어느 프레임을 써야 하는지 설명하라.
정답
드리프트: 오도메트리는 매 프레임의 운동 추정에 작은 오차가 있고, 이를 누적 적분하므로 거리·시간이 갈수록 추정 위치가 실제 위치에서 벗어난다(보통 이동 거리의 일정 비율, 무텍스처·저조도서 악화 — 개략). 짧은 구간은 정확하나 긴 주행엔 누적. 루프 클로저: 로봇이 전에 와본 곳을 재인식하면("이 장면 봤다") "여기는 그때 그 위치"라는 제약이 생긴다. 이를 자세 그래프에 넣어 전체 경로를 재최적화 → 누적 드리프트를 한 번에 보정. 단 같은 곳을 다시 지나야 하고, 닫는 순간 자세가 점프(불연속). 프레임 분리(REP-105): odom→base_link는 순수 오도메트리로 연속(점프 없음)이나 드리프트; map→odom은 SLAM 보정량으로 루프 때 점프하나 map→base_link는 전역 일관. 왜 필요: 한 자세에 "연속"과 "일관"을 동시에 담을 수 없다 — 보정하면 점프(연속 깨짐), 안 하면 드리프트(일관 깨짐). 사용: 제어·로컬 계획 = odom(연속이라 점프에 안 튐, 단기엔 드리프트 무시 가능), 전역 계획 = map(전역 일관, 점프는 재계획으로 흡수; 단 nvblox의 TSDF 융합 자체는 보통 연속적인 odom에서 — 점프에 지도가 안 찢기게). 혼동하면: 제어에 map 쓰면 루프 클로저 점프 때 로봇 궤적이 불연속해지고, 전역 계획에 odom 쓰면 드리프트로 목표가 어긋난다. nvblox의 TSDF 융합은 보통 연속적인 odom에서 한다(정적 볼륨이 루프 클로저 점프에 변형되지 않으므로) — 점프하는 map에서 융합하면 누적 지도가 불연속이 된다.
연습 3 (nvblox 자료구조). TSDF와 ESDF의 정의·용도 차이를 설명하고, 왜 계획에 ESDF가 필요한지, 복셀 크기를 내비게이션과 매니퓰레이션에서 어떻게 다르게 설정하는지 답하라.
정답
TSDF(Truncated Signed Distance Function): 각 복셀에 가장 가까운 표면까지의 부호 있는 거리(앞 +, 위 0, 내부 −, |거리|>truncation은 잘라냄). 여러 깊이 프레임을 복셀마다 가중 평균해 노이즈를 줄이고 표면을 0-교차면으로 일관 추정 — 표면 재구성·메시·시각화용. ESDF(Euclidean Signed Distance Function): 각 복셀에 가장 가까운 장애물까지의 유클리드 거리(멀수록 안전, 음수=내부). 계획에 ESDF가 필요한 이유: 경로 계획·충돌 회피는 "이 자세가 장애물에서 얼마나 떨어졌나"를 빠르게·매끄럽게(gradient 포함) 알아야 한다 — ESDF는 거리장을 미리 계산해 둬 플래너가 충돌 비용·안전 여유를 O(1)로 질의하고 장애물에서 멀어지는 방향(gradient) 을 얻는다. TSDF는 표면 거리(절단)라 먼 자유공간의 거리장이 없어 계획에 직접 부적합 → ESDF로 변환. cuMotion(9.10)의 충돌 회피도 이 거리장을 공유. 복셀 크기: 내비게이션 — 큰 복셀(예 5~10cm)로 충분(로봇이 크고 여유가 큼), 메모리·연산 절약, 넓은 범위; 매니퓰레이션(9.10) — 작은 복셀(예 1~2cm)로 정밀(손가락·작은 물체·좁은 틈 충돌). 부피는 복셀 크기의 세제곱으로 비용이 들어 맹목적으로 작게 못 한다 — 용도가 정밀도↔메모리/연산 트레이드오프를 정한다. 평지 주행은 2D 코스트맵 투영으로도 충분하나 공중·돌출·매니퓰레이션은 3D ESDF 필요.
연습 4 (디버깅). "cuVSLAM→nvblox 그래프를 띄웠는데 nvblox 지도가 안 생기거나 휜다." 원인 후보를 우선순위로 나열하고 각 진단 명령·해결을 제시하라.
정답
우선순위(자세·TF부터): ① cuVSLAM TF 미발행/추적 실패 — 특징 부족(무텍스처·저조도)·초기화 실패·IMU 문제로 자세가 안 나오면 nvblox가 융합 불가. 진단: ros2 run tf2_ros tf2_echo map base_link(체인 끊김?), view_frames(트리), cuVSLAM 추적 상태 토픽/로그. 해결: 조명·텍스처·IMU 캘리브, 초기 정지 후 시작. ② 프레임 이름 불일치 — nvblox의 global_frame이 cuVSLAM이 내는 프레임(map/odom)과 다름. 진단: 파라미터·TF 프레임 비교. 해결: 프레임 이름 정합. ③ 깊이/camera_info 미발행·remap 오타 — nvblox 입력 없음. 진단: ros2 topic hz /camera/depth, rqt_graph. 해결: 센서/remap 수정(9.8). ④ 시간 동기·use_sim_time — 깊이·자세 타임스탬프 어긋나면 틀린 자세에 깊이 융합 → 지도 흐릿·이중. 진단: 타임스탬프·sim_time 설정. 해결: 동기·/clock(8.7). ⑤ 캘리브레이션(8.13) — intrinsic/extrinsic 틀리면 스테레오 깊이·투영 오류 → 휜 표면. 해결: 재캘리브. ⑥ 드리프트/루프 미클로저 — 긴 주행서 자세 누적 오차로 지도 겹침. 해결: 루프 경로·전역(map) 프레임 융합. ⑦ 복셀·메모리 — OOM으로 통합 실패. 진단: jtop(9.4). 해결: 복셀↑·범위 제한. 공통: TF 체인이 1순위 — "지도가 이상하다"의 대부분은 자세·TF·동기. zero-copy 폴백(GPU는 유휴인데 CPU만)도 ros2 component list·Nsight로 점검(9.8.6).
연습 5 (센서·대안 선택). 비주얼 SLAM(cuVSLAM)의 강점과 약점을 들고, 어떤 환경에서 IMU·LiDAR·다중 센서를 더해야 하는지, 비NVIDIA 대안과 cuVSLAM/nvblox의 차별점을 답하라.
정답
비주얼 SLAM 강점: 저가 카메라로 풍부한 정보(텍스처·의미), 실내외 범용, 작고 경량. 약점: 조명 의존(저조도·역광·과노출서 특징 빈약), 무텍스처(흰 벽·균일 바닥서 추적 끊김), 동적 환경(움직이는 물체가 특징을 오염), 모션 블러(빠른 움직임), 모노의 스케일 모호성. 보완 선택: IMU(VIO) — 빠른 회전·이동·모션 블러·짧은 시각 결손을 메우고 스케일·중력 방향 제공(거의 항상 권장). LiDAR — 조명 무관·기하 정확이 필요한 곳(저조도 창고·야외·안전 critical 8.10), 비주얼이 약한 환경. 다중 카메라·휠 오도메트리·GNSS — 시야 확대·실외 전역 위치. 환경이 비주얼에 적대적(어둡고 무텍스처·동적)이고 실패가 치명적이면 다중 센서 융합이 필수 — 단일 센서 SLAM만으로는 모든 경우를 해결할 수 없음. 비NVIDIA 대안: 비주얼/VIO(ORB-SLAM·VINS-Fusion·OpenVINS), LiDAR SLAM(LIO-SAM·Cartographer), 조밀 재구성(voxblox=nvblox의 CPU 전신·OctoMap·KinectFusion). cuVSLAM/nvblox 차별점: 알고리즘은 학계 공통이나 NVIDIA는 GPU 가속 실시간(Jetson 엣지)·NITROS zero-copy 통합(9.7)·cuMotion(9.10) 등과 충돌 세계(ESDF) 공유·Isaac/ROS 2 스택 통합을 더한다. 즉 "더 똑똑한 알고리즘"이 아니라 엣지 실시간 성능 + 스택 통합이 강점. 정확도·강건성은 환경·튜닝 의존이라 자기 환경 벤치마크로 검증(시뮬 성능을 실환경으로 단정 금지, Sim2Real 10.7).
9.9.12 요약 · 다음 장
- SLAM = L+M 동시 문제. Isaac은 cuVSLAM(자세, 희소) 과 nvblox(3D 지도, 조밀) 로 분담하고, cuVSLAM 자세가 nvblox 입력 — 자세 품질이 지도 품질을 정한다("쓰레기 자세→쓰레기 지도").
- cuVSLAM = VIO. 특징(9.6)+IMU 융합+번들 조정으로 6-DOF 자세 GPU 실시간. 드리프트(누적)는 루프 클로저(장소 재인식)로 보정(점프). map→odom→base_link(REP-105): 제어=odom(연속), 계획·매핑=map(일관).
- nvblox = TSDF→ESDF. 깊이+자세+intrinsic을 TSDF(표면, 노이즈 평균)로 융합 → ESDF(장애물 거리장)·코스트맵·메시. 복셀 크기가 정밀↔메모리(내비 5cm·매니퓰레이션 1~2cm).
- 계획 연동. cuVSLAM(자세)+nvblox(지도) → Nav2·cuMotion(9.10) 의 충돌 회피·경로 계획. ESDF를 매니퓰레이션과 공유.
- 검증·한계. 두 GEM을 한 NITROS 컨테이너로(zero-copy), TF 체인·시간 동기가 디버깅 1순위. 비주얼 SLAM은 조명·텍스처·동적에 약함 → IMU·LiDAR·다중 센서 보완. 정직: API·파라미터 릴리스 의존, 수치 개략, 알고리즘은 학계 공통(voxblox 등) + NVIDIA의 엣지 실시간·통합.
다음 장: 9.10 Isaac 매니퓰레이션 — cuMotion. 위치추정·매핑(인지)을 봤으니, 로봇 팔이 장애물을 피해 움직이는 모션 플래닝으로 간다 — nvblox의 ESDF 충돌 세계를 공유해 GPU로 수천 궤적을 병렬 평가하는 cuMotion. 인지(어디·무엇)가 이제 행동(어떻게 움직일까) 으로.