☰
GUIDE ON BECOMING A ROBOTICIST
No matching sections
Rendering guide...
# GUIDE ON BECOMING A ROBOTICIST 초안 작성일자: 2025.12.28 개정일자: 2026.03.21 > 연구실 신입생을 위한 Spatial AI 분야 종합 안내서 > Enhanced Edition — 맥락 설명, 추천 자료, 기술 흐름 추가 **대상**: 학부 또는 학부 졸업 단계의, 로봇 인식 연구자가 되고자 하는 학생 **범위**: Spatial AI 전반 (인식, SLAM, 3D 비전, 딥러닝, VFM, VLA 등) **언어**: 한국어 중심, 기술 용어는 영어 병기 --- ## 목차 **기초 지식** 1. [서론: Spatial AI란?](#1-서론-spatial-ai란) 2. [센서 (Sensors)](#2-센서-sensors) 3. [수학적 기초 (Mathematical Foundations)](#3-수학적-기초-mathematical-foundations) **로봇** 4. [기구학 & 메카트로닉스 (Kinematics & Mechatronics)](#4-기구학--메카트로닉스-kinematics--mechatronics) 5. [강체 역학 & 동역학 (Rigid Body Dynamics)](#5-강체-역학--동역학-rigid-body-dynamics) 6. [제어 이론 (Control Theory)](#6-제어-이론-control-theory) 7. [모션 플래닝 & 궤적 최적화 (Motion Planning & Trajectory Optimization)](#7-모션-플래닝--궤적-최적화-motion-planning--trajectory-optimization) 8. [로봇 러닝 (Robot Learning)](#8-로봇-러닝-robot-learning) **인식과 공간 이해** 9. [컴퓨터 비전 기초 (Computer Vision Fundamentals)](#9-컴퓨터-비전-기초-computer-vision-fundamentals) 10. [딥러닝 기반 인식 (Deep Learning for Perception)](#10-딥러닝-기반-인식-deep-learning-for-perception) 11. [Vision Foundation Models (VFM)](#11-vision-foundation-models-vfm) 12. [Vision-Language-Action (VLA) & Embodied AI](#12-vision-language-action-vla--embodied-ai) 13. [3D 비전 (3D Vision)](#13-3d-비전-3d-vision) 14. [SLAM & Odometry](#14-slam--odometry) **연구 실전** 15. [로봇 프레임워크 (Robot Frameworks)](#15-로봇-프레임워크-robot-frameworks) 16. [개발 환경 & 도구](#16-개발-환경--도구) 17. [데이터셋 & 벤치마크](#17-데이터셋--벤치마크) 18. [연구실 연구 방향](#18-연구실-연구-방향) 19. [AI 코딩 에이전트와 협업하기](#19-ai-코딩-에이전트와-협업하기) 20. [추천 자료](#20-추천-자료) - [20.8 연구 스킬](#208-연구-스킬) 21. [부록](#21-부록) 22. [마무리: 어디서부터 시작할까?](#마무리-어디서부터-시작할까) --- ## 1. 서론: Spatial AI란? Spatial AI라는 분야의 전체 지도를 먼저 머릿속에 그려야 한다. 그래야 이후 챕터에서 배우는 개별 기술들이 왜 필요한지, 서로 어떻게 연결되는지 감이 온다. ### 1.1 Spatial AI의 정의 **Spatial AI**는 기계가 3차원 공간을 이해하고 그 안에서 행동할 수 있도록 하는 인공지능 기술의 총칭이다. 단순히 이미지를 분류하거나 객체를 인식하는 것을 넘어서, 다음과 같은 질문에 답할 수 있어야 한다: - "나는 지금 어디에 있는가?" (Localization) - "주변 환경은 어떻게 생겼는가?" (Mapping) - "저 물체는 무엇이고, 어디에 있는가?" (Object Detection & Localization) - "어떻게 목적지까지 갈 수 있는가?" (Navigation & Planning) 일반적인 AI/딥러닝은 "이미지에 고양이가 있는가?"라는 질문에 답하는 것이지만, Spatial AI는 "저 고양이가 나로부터 몇 미터 떨어져 있고, 어느 방향으로 움직이고 있으며, 고양이를 피해 어떻게 이동할 것인가?"까지 답해야 한다. 즉, **공간적 맥락**이 관건이다. Spatial AI는 다음 기술들의 융합이다: - **Computer Vision**: 카메라 이미지에서 정보 추출 - **3D Vision**: 깊이 인식, 포인트 클라우드 처리 - **SLAM**: 동시적 위치 추정과 지도 작성 - **Deep Learning**: 학습 기반 인식 및 예측 - **Sensor Fusion**: 여러 센서 정보의 통합 > **추천 자료** > - [Andrew Davison — FutureMapping: The Computational Structure of Spatial AI](https://www.youtube.com/watch?v=JX8PHJnSXMc) — Spatial AI의 비전을 제시한 Andrew Davison 교수의 강연. 이 분야의 방향성을 잡는 데 볼 만하다. > - [FutureMapping 논문 (arXiv:1803.11288)](https://arxiv.org/abs/1803.11288) — Spatial AI 개념을 처음 체계화한 논문 > - [Cyrill Stachniss — Introduction to Mobile Robotics](https://www.youtube.com/playlist?list=PLgnQpQtFTOGQrZ4O5QzbIHgl3b1JHimN_) — 본 대학교 Cyrill Stachniss 교수의 모바일 로보틱스 강의. Spatial AI의 기반 개념들을 잘 정리해서 강의한다. ### 1.2 왜 중요한가? 자기가 어떤 도메인에서 일하고 싶은지에 따라 깊이 파야 할 기술이 달라진다. 자율주행을 할 거면 LiDAR와 센서 퓨전에 집중해야 하고, AR/VR을 할 거면 Visual-Inertial 시스템을 파야 한다. 전체 응용 분야를 알아야 자기 로드맵을 짤 수 있다. Spatial AI는 다음 분야의 핵심 기술이다: | 분야 | 응용 예시 | | --- | --- | | **자율주행** | 차량의 위치 인식, 장애물 감지, 경로 계획 | | **서비스 로봇** | 실내 내비게이션, 물체 조작, 인간과 협업 | | **드론** | 자율 비행, 3D 지도 생성, 검사/배송 | | **AR/VR** | 공간 추적, 가상 객체 배치, 손 추적 | | **산업 자동화** | 물류 로봇, 품질 검사, 조립 자동화 | ### 1.3 로보틱스는 왜 어려운가 "AI가 발전하면 로보틱스도 다 풀리지 않나?"라는 질문을 종종 받는다. 안 풀린다. AI가 개선하는 부분과 로보틱스가 어려운 부분이 다르기 때문이다. 로보틱스의 난이도는 대부분 물리 세계와의 접점에서 발생한다: - **되돌릴 수 없다.** 코드 버그가 로봇 충돌로 이어지면 장비가 파손되거나 사람이 다친다. 롤백이 안 된다. - **이터레이션이 느리다.** 코드 수정 → 업로드 → 환경 리셋 → 안전 확보 → 실행 → 물리적 확인. 한 사이클에 수 분에서 수십 분이 걸린다. - **센서 데이터는 항상 더럽다.** 역광, 모션 블러, 드리프트, 프레임 드롭. "깨끗한 데이터에서 잘 작동한다"는 건 로보틱스에서 의미가 없다. - **실시간 제약이 있다.** 장애물 회피가 200ms 늦으면 충돌한다. 정확도가 높아도 속도가 안 나오면 못 쓴다. - **엣지 케이스가 치명적이다.** LLM이 100번 중 5번 틀려도 유용하다. 자율주행이 100만 번 중 1번 틀리면 사고다. - **sim-to-real gap이 있다.** 시뮬레이터의 마찰 계수, 관성, 노이즈는 근사치다. 이 오차가 누적되면 실제 로봇에서 행동이 달라진다. 정리하면: | 일반 소프트웨어 | 로보틱스 | |---|---| | 버그 → 로그 → 수정 → 재배포 | 버그 → 충돌 → 파손 → 수리 → 재시도 | | 이터레이션 수 초 | 이터레이션 수 분~수 시간 | | 입력이 정형화됨 | 센서 데이터가 노이즈 투성이 | | 99% 정확도면 훌륭 | 99.9999%도 부족할 수 있음 | | 응답 지연 → 불편 | 응답 지연 → 사고 | | 같은 입력 → 같은 출력 | 같은 코드라도 환경에 따라 결과가 다름 | AI 발전으로 개선되는 영역이 분명히 있다. 인식 정확도, 의사결정 품질, 자연어 명령 이해 같은 것들. 하지만 위 표의 오른쪽 — 이터레이션 속도, 센서 노이즈, 실시간 제약, 파손 위험 — 이건 물리 법칙의 영역이다. 모델을 키운다고 해결되지 않는다. 이 문서에서 수학, 센서, SLAM, 캘리브레이션을 다루는 이유가 여기에 있다. AI 모델 하나 학습시키는 것만으로는 로봇이 안 돌아간다. #### AI 시대에 로보티시스트가 하는 일 AI가 코드를 짜고, 논문을 요약하고, 실험을 제안하는 시대에 로보티시스트의 가치는 어디에 있는가? - **문제 정의**: AI는 "이 문제를 풀어줘"라고 하면 풀지만, "어떤 문제를 풀어야 하는가"는 판단하지 못한다. 어떤 센서 조합이 이 환경에 적합한지, 어떤 정확도가 이 application에 충분한지, 어떤 trade-off가 acceptable한지 — 이건 도메인을 아는 사람만 판단할 수 있다. - **시스템 통합**: 인식 모듈, 제어 모듈, 통신 스택, 하드웨어를 하나의 작동하는 시스템으로 만드는 것. AI는 각 모듈의 코드를 짤 수 있지만, 모듈 간의 인터페이스, 타이밍, 예외 처리를 설계하는 건 엔지니어의 일이다. - **물리 세계와의 접점**: 케이블이 빠졌는지, 센서 렌즈에 먼지가 낀 건지, 모터가 과열된 건지 — AI는 ssh로 접근할 수 없는 문제를 해결하지 못한다. 로봇 앞에 서서 손으로 만지는 사람이 필요하다. - **신뢰성 판단**: AI가 "99% 정확도"라고 보고해도, 그 1%가 안전 사고로 이어질 수 있는 상황인지 아닌지는 엔지니어가 판단해야 한다. 자율주행에서 99%는 충분하지 않고, 실내 서빙 로봇에서 99%는 충분할 수 있다. AI가 코드를 짜주고 논문을 요약해줘도, 위 네 가지는 대신 못 한다. ### 1.4 이 문서의 활용 방법 이 문서는 **참고서(Reference)**로 활용하길 권장한다: 1. **처음 읽을 때**: 목차를 훑어보고 전체 그림을 파악 2. **연구 시작할 때**: 관련 섹션을 깊이 읽고 추천 자료 학습 3. **막힐 때**: 부록의 용어 사전과 트러블슈팅 참고 **추천 학습 순서**: ``` 수학적 기초 → 센서 → 컴퓨터 비전 기초 → SLAM → 딥러닝 → VFM/VLA → 연구실 방향 ``` 수학 없이 SLAM 논문을 읽으면 수식에서 막히고, 센서 특성을 모르면 왜 알고리즘이 특정 상황에서 실패하는지 이해할 수 없다. 기초부터 순서대로 쌓아가는 것이 결국 가장 빠른 길이다. #### 단계별 학습 경로 아래는 보다 구체적인 단계별 로드맵이다. 본인의 배경에 따라 속도를 조절하되, 각 단계를 건너뛰지 않는 것을 권장한다. **입문 단계 — 도구 손에 익히기** 이 단계의 목표는 연구에 필요한 기본 도구를 자유롭게 다루는 것이다. 코드를 읽고, 돌려보고, 결과를 해석할 수 있어야 한다. **학습 내용**: 1. **C++ 코드 읽기** — 연구실의 핵심 코드(SLAM, ROS 패키지)가 C++이다. 처음부터 짤 필요는 없지만, 구조를 읽고 수정할 수 있어야 한다. 2. **Python 기초** — 딥러닝 학습 스크립트, 데이터 전처리, 시각화에 사용. AI 에이전트의 도움을 받기 좋은 언어이다. 3. **선형대수, 확률/통계 복습** — 학부 때 배운 내용을 로보틱스 관점에서 다시 정리. 3장의 내용을 참고하자. 4. **ROS2 기본** — 토픽, 서비스, 액션, launch 파일. 연구실에서 사용하는 로봇 프레임워크이다. 5. **Git 사용법** — branch, merge, rebase까지. 연구실에서 코드 관리는 Git으로 한다. **실습 과제**: - OpenCV로 이미지 처리 파이프라인 구축 (읽기 → 필터링 → 특징점 추출 → 시각화) - 간단한 ROS2 노드 작성 (퍼블리셔/서브스크라이버) - 카메라 캘리브레이션 수행 (체스보드 패턴 사용) **중급 단계 — 핵심 기술 체화** 이 단계에서는 Spatial AI의 핵심 알고리즘들을 직접 돌려보고 결과를 분석할 수 있어야 한다. 논문을 읽기 시작하는 단계이기도 하다. **학습 내용**: 1. **딥러닝 기초 (PyTorch)** — 텐서, 자동 미분, 학습 루프, 모델 설계. TensorFlow보다 PyTorch가 연구에서 주류이다. 2. **Object Detection (YOLO 계열)** — 바운딩 박스, NMS, mAP 등 인식 파이프라인의 기본 개념 이해. 3. **Visual SLAM 이해 (ORB-SLAM3)** — 특징점 기반 SLAM의 대표 알고리즘. 돌려보고 코드를 뜯어보자. 4. **포인트 클라우드 처리 (Open3D)** — 3D 데이터를 다루는 법. 필터링, 정합, 시각화. 5. **VFM 이해 및 활용 (DINOv2, SAM)** — Foundation Model이 기존 파이프라인을 어떻게 바꾸는지 이해. **실습 과제**: - KITTI 데이터셋으로 벤치마크 실험 수행 - YOLOv8 파인튜닝 (커스텀 데이터셋) - ORB-SLAM3 실행 및 궤적 분석 - TUM RGB-D 데이터셋으로 SLAM 정확도 평가 **고급 단계 — 연구자로서의 첫걸음** 이 단계부터는 논문을 읽고, 아이디어를 내고, 실험하고, 글을 쓰는 연구의 전체 사이클을 경험한다. **학습 내용**: 1. **논문 읽기 및 구현** — 주당 최소 1-2편 논문 읽기. 핵심 논문은 코드까지 분석. 2. **새로운 아이디어 실험** — 기존 방법의 한계를 파악하고 개선 아이디어를 실험. 3. **벤치마크 평가** — 공정한 비교를 위한 평가 프로토콜 숙지. **실습 과제**: - 최신 논문 코드 분석 및 재현 - 자체 개선 아이디어 실험 및 정량적 비교 - 논문 작성 시도 (학회 워크숍 투고 목표) > **추천 자료** > - [Missing Semester of Your CS Education (MIT)](https://missing.csail.mit.edu/) — Git, Shell, 디버깅 등 연구에 필요한 실전 도구를 가르쳐주는 MIT 강의 > - [ROS2 공식 튜토리얼](https://docs.ros.org/en/humble/Tutorials.html) — ROS2 Humble 기준 공식 학습 자료 > - [Andrej Karpathy — Neural Networks: Zero to Hero](https://www.youtube.com/playlist?list=PLAqhIrjkxbuWI23v9cThsA9GvCAUhRvKZ) — 딥러닝을 밑바닥부터 구현하며 배우는 명강의 ### 1.5 선수 지식 체크리스트 연구를 시작하기 전에 다음 항목들을 확인하자. 각 항목이 왜 필요한지도 함께 적어두었으니, 단순히 체크만 하지 말고 "이걸 왜 해야 하는지"를 이해하고 넘어가자. **필수**: - [ ] **C++ 읽기 능력** - 연구실의 핵심 코드(SLAM, 실시간 제어, ROS 패키지)가 C++이다. ORB-SLAM3, LOAM 같은 오픈소스를 이해하고 수정하려면 C++에 익숙해야 한다. - [ ] **Linux 기본 명령어 (cd, ls, cp, mv, grep)** - 연구실 서버는 거의 100% Ubuntu이다. GPU 서버에 SSH로 접속해서 실험을 돌리려면 터미널이 편해야 한다. - [ ] **Git 기본 사용법 (clone, commit, push, pull)** - 연구 코드 관리, 논문 코드 받기, 연구실 내부 코드 공유 전부 Git으로 한다. GitHub에서 오픈소스 코드를 clone해서 돌려보는 것이 일상이다. **권장**: - [ ] **Python 기초 (함수, 클래스, 모듈)** - 딥러닝 학습 스크립트, 데이터 전처리, 시각화에 사용된다. AI 에이전트가 잘 다루는 언어이므로 직접 작성할 일은 줄고 있지만, 읽고 이해할 수 있어야 한다. - [ ] **NumPy 기본 사용법** - 행렬 연산, 브로드캐스팅, 인덱싱. 센서 데이터 처리와 좌표 변환에 사용된다. - [ ] **선형대수 기초 (행렬 연산, 고유값)** - 3D 변환, 카메라 모델, 최적화 전부 선형대수이다. "이 수식이 뭘 의미하는지"를 이해하려면 행렬의 기하학적 의미를 알아야 한다. 3장에서 자세히 다룬다. - [ ] **확률/통계 기초 (정규분포, 베이즈 정리)** - 센서 노이즈 모델링, 상태 추정, 필터링 전부 확률 기반이다. "이 센서의 측정값을 얼마나 믿을 수 있는가?"를 수학적으로 표현하려면 이 지식이 필요하다. - [ ] **미적분학 기초 (편미분, 체인 룰)** - Gradient descent, Jacobian, 최적화 알고리즘의 기본이다. 딥러닝의 역전파도, SLAM의 Bundle Adjustment도 결국 미분이다. > **추천 자료** > - [3Blue1Brown — Essence of Linear Algebra](https://www.youtube.com/playlist?list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab) — 선형대수의 기하학적 직관을 잘 설명하는 영상. 수식에 들어가기 전에 보면 도움이 된다. > - [3Blue1Brown — Essence of Calculus](https://www.youtube.com/playlist?list=PLZHQObOWTQDMsr9K-rj53DwVRMYO3t5Yr) — 미적분의 직관적 이해. 체인 룰과 편미분이 왜 중요한지 시각적으로 보여준다. > - [Python for Data Analysis (Wes McKinney)](https://wesmckinney.com/book/) — NumPy, Pandas 등 데이터 분석 도구의 표준 교재. 무료 온라인 버전 제공. > **기술 흐름: Spatial AI 분야 전체** > - **~2005**: 고전적 로보틱스 — 수학적 모델 기반, Kalman Filter, EKF-SLAM. 수작업 특징점과 기하학적 방법이 주류. > - **2007~2015**: 실시간 Visual SLAM의 등장 — MonoSLAM(2007), PTAM(2007), ORB-SLAM(2015). 카메라만으로 실시간 위치 추정과 지도 생성이 가능해짐. > - **2012~2018**: 딥러닝 혁명 — AlexNet(2012)을 시작으로 ResNet(2015), Faster R-CNN(2015) 등 인식 성능 급상승. Spatial AI에도 학습 기반 방법 도입 시작. > - **2020~2023**: Foundation Model 시대 — CLIP(2021), SAM(2023), DINOv2(2023) 등 대규모 사전학습 모델 등장. 이전에는 새 환경마다 데이터 수집→라벨링→학습을 반복해야 했으나, zero-shot으로 처리 가능한 태스크가 크게 늘었다. > - **2024~**: End-to-End 시스템과 Embodied AI — VLA(Vision-Language-Action) 모델, World Models, 3D Gaussian Splatting + SLAM 등. 인식-계획-제어를 단일 모델로 통합하는 시도가 나오고 있으나, 2026년 기준 실제 배포 시스템은 여전히 모듈형이 주류다. > - **지금 주목할 것**: Foundation Model을 로봇 인식에 접목하는 연구(예: Open-vocabulary SLAM, VFM 기반 Scene Understanding)가 CVPR/ICRA 2025에서 눈에 띄게 늘었다. 고전적 기하학과 학습 기반 방법을 결합하는 흐름도 계속되고 있다. > **실습 자료**: 이 문서의 주요 개념에 대한 interactive 실습은 [여기](https://alexjunholee.github.io/robotics-practice/)에서 확인할 수 있다. --- ## 2. 센서 (Sensors) 로봇이 환경을 인식하기 위해서는 센서가 있어야 한다. 각 센서의 특성을 이해하면 적절한 센서 선택과 알고리즘 설계가 가능하다. 알고리즘을 아무리 잘 짜도, 센서 특성을 모르면 "왜 이 알고리즘이 이 상황에서 실패하는가?"를 진단할 수 없다. 예를 들어, SLAM을 돌렸는데 특정 구간에서 트래킹이 날아가는 원인이 Rolling Shutter 때문인지, LiDAR 반사 특성 때문인지, IMU 바이어스 때문인지를 센서 지식 없이는 파악하기 어렵다. 센서는 로보틱스 시스템의 입구이며, 입구에서 들어오는 데이터의 특성을 모르면 나머지 모든 것이 흔들린다. ### 2.1 카메라 (Camera) 카메라는 가장 정보량이 풍부한 센서이다. 사람이 시각으로 세상의 대부분을 이해하듯, 로봇도 카메라에서 가장 많은 정보를 얻는다. 하지만 카메라 종류마다 특성이 많이 다르기 때문에, 각각의 장단점을 이해하고 목적에 맞는 카메라를 선택하는 것이 중요하다. ### 2.1.1 Monocular Camera (단안 카메라) 가장 기본적인 시각 센서로, 하나의 렌즈로 2D 이미지를 촬영한다. 단안 카메라는 가장 저렴하고 가벼운 센서이면서도 Visual SLAM, 객체 인식, 시맨틱 이해 등 대부분의 비전 태스크의 출발점이다. 다만 깊이를 직접 측정할 수 없다는 구조적 한계 때문에, 이를 극복하기 위한 다양한 알고리즘(Monocular Depth Estimation, SfM 등)이 발전해왔다. 이 한계를 이해해야 왜 스테레오 카메라나 깊이 카메라가 필요한지도 알 수 있다. **장점**: - 저렴하고 가벼움 - 풍부한 색상 및 텍스처 정보 - 높은 해상도 **단점**: - 단일 이미지에서 깊이(depth) 직접 측정 불가 - Scale ambiguity: 물체의 실제 크기를 알 수 없음 **주요 사양**: - 해상도: 720p, 1080p, 4K 등 - Frame rate: 30fps, 60fps, 120fps 등 - Field of View (FoV): 좁은 화각 vs 광각 (fisheye) - Global shutter vs Rolling shutter ``` 일반적인 카메라 센서: - 웹캠: Logitech C920, C930e - 산업용: FLIR (Point Grey), Basler, Allied Vision - 임베디드: Raspberry Pi Camera, OAK-D ``` > **추천 자료** > - [First Principles of Computer Vision — Camera and Imaging](https://www.youtube.com/playlist?list=PL2zRqk16wsdoCCLpou-dGo7QQNks1Ppzo) — Columbia 대학교 Shree Nayar 교수의 카메라 원리 강의. 핀홀 모델부터 렌즈 왜곡까지 설명. > - [OpenCV Camera Calibration Tutorial](https://docs.opencv.org/4.x/dc/dbb/tutorial_py_calibration.html) — 카메라 캘리브레이션을 직접 해보는 실습 가이드 ### 2.1.2 Stereo Camera (스테레오 카메라) 두 개의 카메라를 일정 간격(baseline)으로 배치하여 깊이를 측정한다. 인간의 양안 시각과 유사한 원리이다. 실외 환경에서 깊이를 얻으려면 스테레오 비전을 알아야 한다. 스테레오 카메라는 패시브(능동적 빛 방출 없이) 방식으로 깊이를 얻을 수 있는 거의 유일한 방법이다. 자율주행, 드론 등 실외 환경에서는 Structured Light나 ToF 방식이 햇빛 때문에 제대로 작동하지 않기 때문에, 스테레오 비전의 원리를 아는 것이 매우 중요하다. 에피폴라 기하학(Epipolar Geometry)과 직결되므로 수학적 기초와도 연결된다. **깊이 계산 원리**: ``` Depth (Z) = (focal_length × baseline) / disparity ``` - **Disparity**: 좌우 이미지에서 동일 점의 x좌표 차이 - **Baseline**: 두 카메라 사이의 거리 **장점**: - 패시브 센서 (조명 불필요) - 실외 환경에서도 사용 가능 - RGB 정보와 깊이를 동시에 획득 **단점**: - 텍스처가 없는 표면에서 매칭 실패 (흰 벽, 유리) - 계산 비용이 높음 - Baseline에 따라 측정 범위 제한 **대표 제품**: - Intel RealSense D435/D455: Active IR 패턴 투사로 매칭 보조 - ZED 2: 넓은 baseline, 장거리 측정 - OAK-D: 엣지 AI 내장 > **추천 자료** > - [Cyrill Stachniss — Stereo Vision](https://www.youtube.com/watch?v=SyB7Wg1e62A) — 스테레오 비전의 수학적 원리를 명확하게 설명 > - [Stanford CS231A — Epipolar Geometry and Stereo](https://web.stanford.edu/class/cs231a/) — Stanford의 Computer Vision 강의. 에피폴라 기하학을 잘 다룬다. > **실습**: [Stereo Disparity 시각화](https://alexjunholee.github.io/robotics-practice/app.html#stereo_disparity) > 스테레오 이미지 쌍에서 disparity를 계산하고, baseline과 focal length가 깊이 추정에 미치는 영향을 확인할 수 있다. ### 2.1.3 RGB-D Camera (깊이 카메라) RGB 이미지와 Depth 이미지를 직접 제공하는 센서이다. 연구실에서 가장 먼저 접하게 될 센서가 바로 RGB-D 카메라일 가능성이 높다. 데스크탑 환경에서 SLAM이나 3D 복원을 실험할 때 가장 편리하기 때문이다. 하지만 ToF와 Structured Light 방식의 차이를 모르면, 왜 실외에서 깊이 값이 날아가는지, 왜 여러 대를 동시에 쓰면 간섭이 생기는지 이해할 수 없다. **ToF (Time of Flight) 방식**: - 적외선을 발사하고 돌아오는 시간을 측정 - 장점: 텍스처 무관, 실시간 처리 - 단점: 햇빛 간섭, 반사 표면 문제 - 예시: Microsoft Azure Kinect, PMD Pico Flexx **Structured Light 방식**: - 알려진 패턴을 투사하고 변형을 분석 - 장점: 높은 정확도, 저비용 - 단점: 실외 사용 어려움, 다중 센서 간섭 - 예시: Intel RealSense D400 시리즈, Orbbec Astra **비교**: | 특성 | ToF | Structured Light | |------|-----|------------------| | 실외 사용 | 제한적 | 어려움 | | 정확도 | 중간 | 높음 | | 범위 | 0.2-5m | 0.2-10m | | 다중 센서 | 가능 | 간섭 발생 | > **추천 자료** > - [Intel RealSense — Depth Camera Technology Overview](https://www.youtube.com/watch?v=UaGRGBQxjGQ) — Intel에서 직접 설명하는 깊이 카메라 원리 > - [Open3D RGB-D Reconstruction Tutorial](http://www.open3d.org/docs/release/tutorial/pipelines/rgbd_integration.html) — RGB-D 데이터로 3D 복원을 실습하는 튜토리얼 **RealSense 드라이버 설치 (Ubuntu 22.04)** ```bash # Intel RealSense SDK 설치 sudo mkdir -p /etc/apt/keyrings curl -sSf https://librealsense.intel.com/Debian/librealsense.pgp | sudo tee /etc/apt/keyrings/librealsense.pgp > /dev/null echo "deb [signed-by=/etc/apt/keyrings/librealsense.pgp] https://librealsense.intel.com/Debian/apt-repo `lsb_release -cs` main" | \ sudo tee /etc/apt/sources.list.d/librealsense.list sudo apt-get update sudo apt-get install -y librealsense2-dkms librealsense2-utils librealsense2-dev # 테스트 realsense-viewer ``` ROS2에서 사용하려면 추가로: ```bash sudo apt install ros-humble-realsense2-camera ros2 launch realsense2_camera rs_launch.py ``` (참고: [정진용 블로그](https://jinyongjeong.github.io/2020/06/20/Realsense-Ubuntu-driver-%EC%84%A4%EC%B9%98/)) ### 2.1.4 Event Camera (이벤트 카메라) 기존 카메라와 다른 패러다임의 센서이다. 프레임 단위로 촬영하는 것이 아니라, 각 픽셀이 **밝기 변화**가 발생할 때만 비동기적으로 이벤트를 출력한다. Event Camera 관련 논문은 CVPR 기준 2019년 5편 수준에서 2024년 30편 이상으로 늘었다. 고속 환경(드론 고속 비행, 자동차 급회전)에서 모션 블러 없이 동작하기 때문이다. 아직 주류는 아니지만, 고속(>100km/h) 환경이나 HDR 조건을 다룰 예정이라면 Gallego et al. survey (TPAMI 2020)와 rpg_dvs_ros 패키지를 살펴보라. **이벤트 출력 형식**: ``` (x, y, timestamp, polarity) - x, y: 픽셀 좌표 - timestamp: 마이크로초 단위 시간 - polarity: 밝아짐(+1) 또는 어두워짐(-1) ``` **장점**: - 매우 높은 시간 해상도 (마이크로초 단위) - 높은 다이나믹 레인지 (140dB vs 일반 카메라 60dB) - 낮은 전력 소모, 낮은 지연 - 모션 블러 없음 **단점**: - 정지 장면에서는 출력 없음 - 전통적인 CV 알고리즘 적용 어려움 - 비교적 높은 가격 **대표 제품**: - Prophesee: 고해상도 이벤트 센서 - iniVation: DAVIS (이벤트 + 프레임 동시 출력) - Samsung: 모바일용 이벤트 센서 개발 중 > **추천 자료** > - [Davide Scaramuzza — Event Cameras: A Paradigm Shift for Computer Vision](https://www.youtube.com/watch?v=LauQ6LWTkxM) — Event Camera 분야의 선구자인 Scaramuzza 교수의 개요 강연 > - [Gallego et al. — Event-based Vision: A Survey (TPAMI 2020)](https://arxiv.org/abs/1904.08405) — Event Camera 기술의 종합 서베이 논문. 이 분야를 이해하는 데 좋은 출발점이다. > - [rpg_dvs_ros — Event Camera ROS 드라이버](https://github.com/uzh-rpg/rpg_dvs_ros) — Event Camera를 ROS에서 다루는 오픈소스 패키지 ### 2.2 LiDAR **LiDAR (Light Detection and Ranging)**는 레이저를 이용하여 거리를 측정하는 센서이다. 3D 포인트 클라우드를 직접 생성한다. 카메라가 "풍부하지만 깊이 없는" 데이터를 준다면, LiDAR는 "정확한 3D 좌표를 직접" 준다. 자율주행 분야에서 LiDAR가 핵심 센서로 자리잡은 이유가 바로 이 정확한 거리 측정 능력 때문이다. 카메라만으로 깊이를 추정하면 오차가 크고 날씨 영향을 받지만, LiDAR는 수 센티미터 정확도로 100m 이상 떨어진 물체까지 측정할 수 있다. 최근 주목할 트렌드: Solid-State LiDAR가 기존의 Spinning(기계식) LiDAR를 빠르게 대체하고 있다. 움직이는 부품이 없어 내구성이 높고 대량 생산에 유리하며, 자동차 양산에 적합하기 때문이다. Livox의 비반복 스캔 패턴 같은 새로운 접근도 나오고 있어, 포인트 클라우드 처리 알고리즘에도 변화가 필요한 시점이다. ### 2.2.1 2D LiDAR vs 3D LiDAR **2D LiDAR**: - 단일 평면 스캔 - 용도: 실내 로봇 내비게이션, 장애물 회피 - 예시: SICK TiM, Hokuyo URG, RPLIDAR **3D LiDAR**: - 다중 레이어 또는 회전 스캔으로 3D 포인트 클라우드 생성 - 용도: 자율주행, 대규모 매핑 - 예시: Velodyne VLP-16/32/64, Ouster OS1, Hesai > **추천 자료** > - [Cyrill Stachniss — LiDAR-based SLAM](https://www.youtube.com/watch?v=BuC4o0Zr6Xo) — LiDAR 데이터를 이용한 SLAM의 원리를 설명 > - [PCL (Point Cloud Library) 공식 튜토리얼](https://pcl.readthedocs.io/projects/tutorials/en/latest/) — 포인트 클라우드 처리의 사실상 표준 라이브러리 ### 2.2.2 Spinning vs Solid-State **Spinning (기계식)**: - 레이저와 수광부가 회전 - 360° FoV 제공 - 단점: 움직이는 부품으로 인한 내구성 이슈 - 예시: Velodyne, Ouster **Solid-State**: - 움직이는 부품 없음 - 제한된 FoV (보통 120° 이하) - 장점: 높은 내구성, 저비용 가능성 - 예시: Livox (비반복 스캔 패턴), Innoviz 알고리즘 설계에 직접 영향을 미치는 차이다. Spinning LiDAR는 360° 균일한 포인트 클라우드를 생성하므로, 기존 SLAM 알고리즘(LOAM, LeGO-LOAM 등)이 이 특성을 전제로 설계되어 있다. Solid-State LiDAR로 넘어가면 스캔 패턴이 크게 달라지므로 알고리즘 수정이 필요하다. Livox의 비반복 스캔을 위한 FAST-LIO2 같은 알고리즘이 등장한 것이 이 때문이다. ### 2.2.3 주요 사양 | 사양 | 설명 | | --- | --- | | Channels | 수직 레이어 수 (16, 32, 64, 128) | | Range | 최대 측정 거리 (50m ~ 300m) | | Points/sec | 초당 포인트 수 (300K ~ 2M) | | Accuracy | 측정 정확도 (±2cm ~ ±5cm) | | FoV | 수평/수직 화각 | > **추천 자료** > - [Livox 기술 문서](https://www.livoxtech.com/downloads) — Solid-State LiDAR의 비반복 스캔 패턴과 그 장점을 설명하는 기술 자료 > - [Xu et al. — FAST-LIO2 (RA-L 2022)](https://arxiv.org/abs/2107.06829) — Solid-State LiDAR에 최적화된 LiDAR-Inertial Odometry 논문 ### 2.3 IMU (Inertial Measurement Unit) IMU는 관성을 이용하여 움직임을 측정하는 센서이다. SLAM을 돌렸는데 드리프트가 심할 때, IMU 특성을 이해하지 못하면 원인조차 파악하기 어렵다. "IMU 바이어스가 제대로 보정되고 있나?", "이 등급의 IMU로 이 정도 정확도를 기대할 수 있나?" 같은 질문에 답하려면 IMU의 오차 모델을 제대로 이해해야 한다. Visual-Inertial Odometry(VIO)나 LiDAR-Inertial Odometry(LIO)에서 IMU는 카메라/LiDAR의 프레임 사이를 메워주는 역할을 하는데, 이 역할을 제대로 하려면 IMU 데이터의 한계를 알아야 한다. ### 2.3.1 구성 요소 **Accelerometer (가속도계)**: - 3축 선형 가속도 측정 (m/s²) - 중력 가속도 포함 **Gyroscope (자이로스코프)**: - 3축 각속도 측정 (rad/s 또는 deg/s) - 회전 속도 감지 **Magnetometer (지자기 센서)** (일부 IMU): - 3축 자기장 측정 - 절대 방위(heading) 추정 가능 - 자기장 왜곡에 취약 ### 2.3.2 주요 오차 특성 이 부분은 IMU를 쓰는 사람이라면 알아야 한다. IMU 오차를 모델링하지 못하면 센서 퓨전 시스템 전체가 흔들린다. **Bias (바이어스)**: - 정지 상태에서도 0이 아닌 출력 - 온도에 따라 변화 (bias instability) **Noise**: - 고주파 랜덤 노이즈 - Allan Variance로 특성화 **Integration Drift**: - 가속도 이중 적분 → 위치 오차 누적 - 각속도 적분 → 자세 오차 누적 - 짧은 시간 동안만 신뢰 가능 (보통 수 초) 실제로 겪어보면 바로 느끼는데, 가속도를 두 번 적분해서 위치를 구하면 노이즈와 바이어스 오차가 시간의 제곱에 비례하여 누적된다. Consumer 등급 IMU(스마트폰 내장)로는 10초만 지나도 위치 오차가 수 미터에 달할 수 있다. 그래서 IMU를 단독으로 쓰는 경우는 거의 없고, 항상 카메라나 LiDAR와 퓨전하여 드리프트를 보정한다. **IMU 등급**: | 등급 | 용도 | 가격 | 예시 | |------|------|------|------| | Consumer | 스마트폰, 게임 | $1-10 | MPU6050, BMI160 | | Industrial | 로봇, 드론 | $100-1K | VectorNav VN-100, Xsens MTi | | Tactical | 자율주행, 항공 | $1K-10K | KVH 1750 | | Navigation | 선박, 항공기 | $10K+ | Honeywell HG1700 | > **추천 자료** > - [Probabilistic Robotics, Ch.5 — Robot Motion (Thrun, Burgard, Fox)](https://www.probabilistic-robotics.org/) — 센서 노이즈 모델링과 모션 모델의 핵심 참고서. IMU 오차 모델링의 이론적 기초를 다룬다. > - [Titterton & Weston — Strapdown Inertial Navigation Technology](https://ieeexplore.ieee.org/book/5765860) — IMU 원리와 관성 항법의 교과서 > - [Cyrill Stachniss — IMU and Inertial Navigation](https://www.youtube.com/watch?v=uHbRKvD8TWg) — IMU의 작동 원리와 오차 특성을 시각적으로 설명 > - [Allan Variance — IMU 노이즈 분석 가이드 (Vectornav)](https://www.vectornav.com/resources/inertial-navigation-primer/specifications--background/specifications--allan-variance) — Allan Variance를 이용한 IMU 노이즈 파라미터 추출 방법 > - [정진용 블로그 — IMU Filter (AHRS)](https://jinyongjeong.github.io/2020/01/10/IMU_filter/) — IMU 센서의 AHRS 필터 개요. Madgwick 필터와 ROS 패키지 소개 ### 2.4 GPS/GNSS **GNSS (Global Navigation Satellite System)**는 위성 신호를 이용한 위치 측정 시스템이다. GPS는 미국 시스템이며, GNSS는 GPS, GLONASS(러시아), Galileo(유럽), BeiDou(중국) 등을 포함하는 총칭이다. 실외 자율주행이나 드론에서 GNSS는 "전역 좌표"를 제공하는 유일한 센서이다. SLAM은 상대적 위치(어디서 출발해서 얼마나 움직였나)를 추정하지만, GNSS는 지구상 절대 위치(위도, 경도, 고도)를 알려준다. 이 두 가지 정보를 결합하는 것이 실외 로봇의 핵심 과제이다. RTK-GPS의 센티미터 급 정확도가 자율주행 고정밀 측위의 Ground Truth로도 사용되므로, 원리를 이해해두어야 한다. **정확도**: - 일반 GPS: 2-5m - DGPS (Differential): 0.5-2m - RTK-GPS (Real-Time Kinematic): 1-2cm **RTK-GPS 원리**: - 고정된 Base Station이 보정 데이터 제공 - Rover가 보정 데이터를 수신하여 정확도 향상 - 실시간 통신 필요 (Radio 또는 인터넷) **한계**: - 실내, 터널, 도심 캐년에서 사용 불가 - 멀티패스 오차 (건물 반사) - 고도 정확도는 수평보다 낮음 > **추천 자료** > - [Cyrill Stachniss — GNSS and Localization](https://www.youtube.com/watch?v=MKq4IXBAMWE) — GNSS 원리와 로보틱스에서의 활용 > - [u-blox GNSS 가이드](https://www.u-blox.com/en/technologies/gnss) — GNSS 기초부터 RTK까지 실용적 가이드 ### 2.5 기타 센서 **Radar** 자율주행과 로보틱스에서 레이더의 중요성이 높아지고 있다. LiDAR와 카메라가 안 되는 환경 — 안개, 비, 먼지, 강한 역광 — 에서도 레이더는 안정적으로 동작한다. 가격도 LiDAR보다 저렴하다. **FMCW (Frequency Modulated Continuous Wave) Radar**: - 주파수를 시간에 따라 변조하여 송신하고, 반사파와의 주파수 차이로 거리와 속도를 동시에 측정한다. - 출력: Range-Doppler map (거리 × 속도 2D 맵), Range-Azimuth map - 자동차용 77GHz radar가 가장 흔하다. **로보틱스에서의 활용**: - 자율주행: 전방 충돌 감지, 적응형 크루즈 컨트롤 (ACC) - Radar odometry: 레이더만으로 자기 위치 변화 추정 - Radar SLAM: 레이더 기반 지도 작성 + 위치 추정 **카메라/LiDAR와의 비교**: | 특성 | 카메라 | LiDAR | Radar | |------|--------|-------|-------| | 해상도 | 매우 높음 | 높음 | 낮음 | | 거리 측정 | 불가 (단안) | 정확 | 가능 | | 속도 측정 | 불가 | 불가 (직접) | 가능 (Doppler) | | 악천후 | 취약 | 취약 (비, 안개) | 강건 | | 가격 | 저렴 | 비쌈 | 중간 | | 야간 | 불가 | 가능 | 가능 | **대표 제품**: Texas Instruments AWR1843, Continental ARS548, Navtech CTS350-X (spinning radar) > **추천 자료** > - [김기섭 블로그 — ICRA 2021 Radar in Robotics Workshop 요약](https://gisbi-kim.github.io/blog/2021/05/31/icra21-radar-ws.html) — 레이더 로보틱스의 전반적 동향 정리 > - [김기섭 블로그 — Radar Odometry Results on MulRan dataset](https://gisbi-kim.github.io/blog/2021/05/30/yeti-radar-odom-mulran1.html) — 레이더 오도메트리 실험 결과. 도시 환경에서 LiDAR급 성능 > - [Kim et al., "MulRan: Multimodal Range Dataset for Urban Place Recognition" (ICRA 2020)](https://sites.google.com/view/mulran-pr/home) — LiDAR + radar + GPS 멀티모달 데이터셋 **Ultrasonic (초음파)**: - 가까운 거리 장애물 감지 (0.2-5m) - 저비용 - 주차 보조, 근접 센서 **Wheel Encoder (휠 엔코더)**: - 바퀴 회전량 측정 - Dead reckoning 기반 위치 추정 - 슬립에 취약 이 센서들이 "기타"로 분류되어 있다고 중요하지 않은 것은 아니다. Radar는 자율주행에서 LiDAR가 실패하는 악천후 상황의 안전망 역할을 하며, Wheel Encoder는 지상 로봇의 가장 기본적인 오도메트리 소스이다. 센서 퓨전에서는 이런 "보조" 센서들이 시스템 전체의 로버스트니스를 결정한다. > **추천 자료** > - [Probabilistic Robotics, Ch.6 — Robot Perception (Thrun, Burgard, Fox)](https://www.probabilistic-robotics.org/) — 각종 센서의 확률 모델을 꼼꼼하게 다룬다. 센서 모델링의 교과서. ### 2.6 센서 퓨전 (Sensor Fusion) 단일 센서는 각각의 한계가 있으므로, 여러 센서를 결합하여 보완한다. 현실 세계에서 단일 센서만으로 완벽한 인식을 구현하는 건 불가능하다. 자율주행차는 카메라, LiDAR, Radar, IMU, GNSS를 전부 동시에 사용하고 있으며, 이 센서들의 데이터를 언제, 어디서, 어떻게 결합하느냐가 시스템 성능을 좌우한다. **왜 필요한가?** | 센서 | 장점 | 단점 | | --- | --- | --- | | Camera | 풍부한 정보, 저렴 | 조명 의존, 깊이 없음 | | LiDAR | 정확한 3D, 조명 무관 | 비쌈, sparse | | IMU | 고주파, 조명 무관 | 드리프트 | | GPS | 전역 위치 | 실외 전용, 저주파 | **퓨전 방식**: 1. **Early Fusion**: Raw 데이터 레벨에서 결합 2. **Late Fusion**: 각 센서의 결과를 결합 3. **Mid-Level Fusion**: Feature 레벨에서 결합 각 방식의 선택은 트레이드오프가 있다. Early Fusion은 정보 손실이 적지만 계산 비용이 높고, Late Fusion은 각 센서를 독립적으로 처리할 수 있어 모듈화에 유리하지만 정보 손실이 발생한다. Mid-Level Fusion은 그 중간인데, 최근 딥러닝 기반 퓨전에서는 이 방식이 많이 사용된다. **대표적인 조합**: - Camera + IMU → VIO (Visual-Inertial Odometry) - LiDAR + IMU → LIO (LiDAR-Inertial Odometry) - Camera + LiDAR + IMU → 멀티모달 SLAM > **추천 자료** > - [State Estimation for Robotics (Tim Barfoot) — 무료 PDF](http://asrl.utias.utoronto.ca/~tdb/bib/barfoot_ser17.pdf) — 센서 퓨전의 수학적 기초를 다루는 대표적인 교재. Kalman Filter, Factor Graph 기반 추정을 모두 커버한다. > - [Cyrill Stachniss — Sensor Fusion and State Estimation](https://www.youtube.com/watch?v=TA3TBaE5eHs) — 센서 퓨전의 개념과 칼만 필터를 시각적으로 설명 > - [Qin et al. — VINS-Mono (TRO 2018)](https://arxiv.org/abs/1708.03852) — Visual-Inertial 퓨전의 대표 논문. 실제 VIO 시스템이 어떻게 구현되는지 보여준다. > **⚠ AI 에이전트 주의**: 센서가 "데이터가 안 온다"는 문제의 원인은 대부분 소프트웨어가 아니라 물리적 연결(케이블, IP 설정, 전원, USB 대역폭)이다. AI는 드라이버 재설치부터 권하지만, `dmesg`, `lsusb`, `ping` 같은 시스템 명령으로 물리 연결부터 확인하라. > **기술 흐름: 센서 기술** > - **~2010**: 2D LiDAR(SICK, Hokuyo)와 단안 카메라 중심. 센서가 비싸고 크며, 처리 능력도 제한적. Stereo 카메라는 계산 비용 때문에 실시간 처리가 어려웠다. > - **2012~2017**: 3D LiDAR(Velodyne VLP-16) 보급, RGB-D 카메라(Kinect) 대중화. LiDAR 가격이 수만 달러에서 수천 달러로 하락. Visual-Inertial 시스템(VIO)이 실용화되기 시작. > - **2018~2022**: Solid-State LiDAR(Livox) 등장, 가격이 수백 달러 수준까지 하락. Event Camera 연구 활발해짐. 멀티모달 센서 퓨전(Camera + LiDAR + IMU)이 표준으로 자리잡음. > - **2023~**: Solid-State LiDAR가 Spinning 방식을 빠르게 대체 중. Event Camera가 고속/HDR 응용에서 본격적으로 채택되기 시작. 4D Radar(도플러 속도 포함)가 새로운 보조 센서로 부상. > - **지금 주목할 것**: Solid-State LiDAR의 대중화로 인해 기존 Spinning LiDAR 전제의 알고리즘을 재설계해야 하는 상황이다. Event Camera는 아직 주류는 아니지만, 고속 드론, 자율주행 등 기존 카메라의 한계가 명확한 분야에서 빠르게 채택되고 있다. 센서 하드웨어의 진화가 알고리즘 연구 방향을 바꾸고 있으므로, 최신 센서 트렌드를 항상 주시해야 한다. --- ## 3. 수학적 기초 (Mathematical Foundations) Spatial AI를 제대로 이해하려면 수학적 기초가 필요하다. 이 섹션에서는 핵심 개념만 간략히 다루고, 깊은 학습은 추천 자료를 참고하자. 솔직하게 말하면, 수학 파트를 건너뛰고 싶은 마음은 이해한다. 하지만 논문을 읽다 보면 결국 수식에서 막힌다. SLAM 논문에서 "SE(3) 위의 최적화"라는 말이 나오는데 SE(3)이 뭔지 모르면 논문의 핵심 아이디어를 놓치게 되고, "Jacobian을 유도하여 Gauss-Newton으로 풀었다"는 한 줄이 이해가 안 되면 그 논문의 방법론 전체를 이해할 수 없다. 여기서 다루는 수학은 "수학 시험을 위한 수학"이 아니라, "로봇 논문을 읽고 구현하기 위한 수학"이다. 공대 3학년이면 선형대수를 들었을 테니, 여기서는 "학부 때 배운 것이 로보틱스에서 어떻게 쓰이는지" 연결하는 데 초점을 맞추겠다. 참고로, 고전적인 수학적 도구가 여전히 핵심이지만, 최근에는 Differentiable Programming과 Auto-Differentiation(자동 미분)이 최적화 문제 접근 방식을 바꾸고 있다. 예전에는 Jacobian을 손으로 유도해야 했지만, 이제는 PyTorch나 JAX의 자동 미분을 활용하여 복잡한 파이프라인의 gradient를 자동으로 계산할 수 있다. 이것이 End-to-End 학습 기반 SLAM, Differentiable Rendering(NeRF, 3D Gaussian Splatting) 등을 가능하게 한 기반 기술이다. 하지만 자동 미분이 있다고 해서 수학을 몰라도 되는 것은 아니다 — 오히려 "자동 미분이 내부적으로 무엇을 하고 있는지" 이해하려면 여기서 다루는 기초가 필요하다. ### 3.1 선형대수 (Linear Algebra) 선형대수는 Spatial AI 전반에서 쓰이는 기본 도구이다. 좌표 변환, 카메라 모델, 최적화, 딥러닝까지 전부 행렬과 벡터로 표현된다. "학부 때 선형대수를 들었다"와 "선형대수를 로보틱스에 활용할 수 있다"는 다른 레벨이다. 여기서는 로보틱스에서 가장 많이 쓰이는 개념들을 짚어본다. ### 3.1.1 벡터와 행렬 **벡터**: 크기와 방향을 가진 양 ``` v = [v_x, v_y, v_z]^T (열 벡터) ``` 로보틱스에서 벡터는 3D 공간의 점, 힘, 속도 등을 나타낸다. "로봇이 월드 좌표계에서 (3, 2, 1)에 있다"는 것은 위치를 벡터로 표현한 것이다. **행렬 연산**: - 덧셈/뺄셈: 요소별 연산 - 곱셈: 행×열 내적 - 전치(Transpose): A^T - 역행렬(Inverse): A^(-1), AA^(-1) = I 좌표 변환, 회전, 투영(projection) 전부 행렬 곱으로 표현된다. 카메라가 3D 점을 2D 이미지로 투영하는 것도, 로봇의 좌표계를 변환하는 것도 전부 행렬 곱이다. > **추천 자료** > - [3Blue1Brown — Essence of Linear Algebra](https://www.youtube.com/playlist?list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab) — 이 시리즈를 안 봤다면 보는 걸 권한다. 행렬 곱셈이 기하학적으로 무엇을 의미하는지, 고유값이 왜 중요한지를 시각적으로 보여준다. 선형대수를 "계산"이 아닌 "변환"으로 이해하는 데 도움이 된다. > - [Introduction to Applied Linear Algebra (Boyd & Vandenberghe) — 무료 PDF](https://web.stanford.edu/~boyd/vmls/) — Stanford의 Boyd 교수가 쓴 응용 선형대수 교재. 실용적 관점에서 선형대수를 다룬다. > - [다크 프로그래머 — 선형대수학 시리즈 (6편: 기본공식~PCA)](https://darkpgmr.tistory.com/103) — 주요용어, 역행렬, 고유값, SVD, 연립방정식, PCA를 한글로 정리 > - [다크 프로그래머 — 벡터 미분과 행렬 미분](https://darkpgmr.tistory.com/141) — 벡터/행렬 미분 규칙 정리. Jacobian 계산에 필요한 기초 ### 3.1.2 고유값 분해 (Eigenvalue Decomposition) ``` Av = λv ``` - v: 고유벡터 (eigenvector) - λ: 고유값 (eigenvalue) **활용**: PCA, 공분산 행렬 분석, 안정성 분석 포인트 클라우드를 다루면 바로 쓸 일이 생긴다. PCA(Principal Component Analysis)로 포인트 클라우드의 주축을 구할 때, 공분산 행렬의 고유벡터가 바로 주축 방향이고 고유값이 그 방향의 분산이다. "이 포인트 클라우드가 평면인지 직선인지"를 판별하는 것도 고유값의 비율로 한다. Normal 벡터 추정도 가장 작은 고유값에 대응하는 고유벡터를 사용한다. > **추천 자료** > - [3Blue1Brown — Eigenvectors and Eigenvalues](https://www.youtube.com/watch?v=PFDu9oVAE1g) — 고유값의 기하학적 의미를 직관적으로 설명 > - [MIT 18.06 Linear Algebra — Gilbert Strang (YouTube)](https://www.youtube.com/playlist?list=PLE7DDD91010BC51F8) — 선형대수의 널리 알려진 강의. 고유값 분해를 포함한 전체 선형대수를 깊이 있게 다룬다. ### 3.1.3 특이값 분해 (SVD: Singular Value Decomposition) ``` A = UΣV^T ``` - U: 좌측 특이벡터 (m×m 직교행렬) - Σ: 특이값 대각행렬 (m×n) - V: 우측 특이벡터 (n×n 직교행렬) **활용**: 최소자승법 해, 행렬 근사, Fundamental Matrix 계산 SVD는 로보틱스에서 정말 많이 나온다. 왜냐하면 과결정(overdetermined) 시스템의 최소자승 해를 구하는 데 가장 수치적으로 안정적인 방법이기 때문이다. 카메라 캘리브레이션에서 Fundamental Matrix를 구할 때, 포인트 클라우드 정합에서 최적 변환을 구할 때, 전부 SVD를 사용한다. "8-point algorithm"에서 8개 이상의 대응점으로 Fundamental Matrix를 구하는 마지막 단계가 바로 SVD이다. > **추천 자료** > - [Steve Brunton — Singular Value Decomposition (YouTube)](https://www.youtube.com/watch?v=nbBvuuNVfco) — SVD의 수학적 의미와 응용을 명쾌하게 설명하는 워싱턴 대학교 교수의 강의 > - [Linear Algebra and Its Applications (Gilbert Strang)](https://math.mit.edu/~gs/linearalgebra/ila6/indexila6.html) — 선형대수 표준 교재. SVD 챕터가 특히 잘 쓰여 있다. ### 3.2 3D 기하학 (3D Geometry) 3D 기하학은 Spatial AI에서 핵심적인 부분이다. "3D 공간에서 로봇은 어디에 있고, 카메라는 어디를 보고 있으며, 저 물체는 어디에 있는가?"를 수학적으로 표현하는 방법이다. 이 파트를 모르면 SLAM 논문의 첫 페이지부터 막힌다. ### 3.2.1 좌표계 (Coordinate Frames) Spatial AI에서는 여러 좌표계를 오가며 작업한다: **World Frame (W)**: 전역 고정 좌표계 **Camera Frame (C)**: 카메라 중심 좌표계 **Body Frame (B)**: 로봇 중심 좌표계 **IMU Frame (I)**: IMU 센서 좌표계 직접 로봇 시스템을 만들어보면 바로 체감하는데, 실제 로봇 시스템에서는 하나의 데이터가 여러 좌표계를 거쳐야 의미가 있기 때문이다. 예를 들어, "카메라가 본 물체의 위치"는 카메라 좌표계에서 표현되어 있지만, "로봇이 물체에 접근하려면" 그 위치를 로봇 좌표계 또는 월드 좌표계로 변환해야 한다. 센서마다 자기만의 좌표계가 있고, 이들 사이의 변환을 정확히 알아야(extrinsic calibration) 센서 퓨전이 가능하다. **좌표 변환**: ``` p_W = T_WC × p_C ``` T_WC: Camera → World 변환 행렬 (4×4) > **추천 자료** > - [State Estimation for Robotics, Ch.6 — Coordinate Frames (Tim Barfoot) — 무료 PDF](http://asrl.utias.utoronto.ca/~tdb/bib/barfoot_ser17.pdf) — 좌표계 변환을 로보틱스 관점에서 가장 잘 정리한 교재 > - [Stanford CS231A — Camera Models](https://web.stanford.edu/class/cs231a/) — Stanford의 CV 강의에서 카메라 좌표계와 투영 모델을 다루는 부분 ### 3.2.2 회전 표현 (Rotation Representations) 회전을 표현하는 방법이 여러 가지인 이유가 있다. 각 표현이 서로 다른 장단점을 가지기 때문이다. SLAM 최적화에서는 어떤 표현을 쓰느냐에 따라 수렴 속도와 안정성이 달라진다. 이 내용을 모르면 "왜 이 코드에서는 쿼터니언을 쓰고, 저 코드에서는 Rotation Matrix를 쓰는지" 이해할 수 없다. **Rotation Matrix (R)**: - 3×3 직교행렬, det(R) = 1 - R^T = R^(-1) - 9개 파라미터 (6개 제약조건으로 3 DoF) **Euler Angles**: - Roll(φ), Pitch(θ), Yaw(ψ) - 직관적이지만 **Gimbal Lock** 문제 존재 - 순서에 따라 다른 결과 (ZYX, XYZ 등) **Quaternion (q)**: - q = [w, x, y, z], ||q|| = 1 - 4개 파라미터 (1개 제약조건으로 3 DoF) - Gimbal Lock 없음, 보간(Slerp) 용이 - 널리 사용됨 **Axis-Angle**: - 회전축 n과 회전각 θ - 3개 파라미터 (축 방향 2 + 각도 1) - Rodrigues formula로 R과 변환 실전에서의 팁: ROS에서는 Quaternion이 기본 회전 표현이고, OpenCV에서는 Rodrigues 벡터(Axis-Angle)를 주로 사용하며, 최적화 라이브러리(Ceres, GTSAM)에서는 Lie Group 기반 표현(so(3) → SO(3) 매핑)을 사용하는 경우가 많다. 이들 사이의 변환을 자유자재로 할 수 있어야 한다. > **추천 자료** > - [3Blue1Brown — Quaternions and 3D Rotation](https://www.youtube.com/watch?v=zjMuIxRvygQ) — 쿼터니언의 기하학적 의미를 시각화한 영상. 4차원이 왜 3D 회전에 필요한지 직관적으로 이해할 수 있다. > - [State Estimation for Robotics, Ch.7 — Rotation (Tim Barfoot)](http://asrl.utias.utoronto.ca/~tdb/bib/barfoot_ser17.pdf) — 모든 회전 표현과 그들 사이의 변환을 깔끔하게 정리한 교재 > - [Sola — Quaternion Kinematics for the Error-State Kalman Filter (Tech Report)](https://arxiv.org/abs/1711.02508) — VIO/INS 구현 시 쿼터니언 기반 에러 상태 칼만 필터의 수학적 기초. 실전에서 매우 유용한 테크니컬 리포트. > **실습**: [회전 표현과 Gimbal Lock](https://alexjunholee.github.io/robotics-practice/app.html#rotation_gimbal) | [6DoF 포즈 시각화](https://alexjunholee.github.io/robotics-practice/app.html#xyzrpy_6dof) > 오일러 각의 Gimbal Lock 현상과 쿼터니언 회전을 직접 조작하며 비교하고, 6자유도 포즈(x, y, z, roll, pitch, yaw)를 인터랙티브하게 확인할 수 있다. ### 3.2.3 Homogeneous Coordinates 3D 점을 4D로 확장하여 변환을 단일 행렬로 표현: ``` [X, Y, Z, 1]^T (3D 점) T = | R t | (4×4 변환 행렬) | 0 1 | ``` Homogeneous Coordinates를 쓰는 이유: 회전과 이동(translation)을 하나의 행렬 곱으로 표현할 수 있기 때문이다. 일반 좌표에서는 p' = Rp + t (곱셈 + 덧셈)이지만, Homogeneous Coordinates에서는 p' = Tp (곱셈만)로 쓸 수 있다. 여러 변환을 연쇄적으로 적용할 때 행렬을 그냥 곱하면 되므로, 로봇 팔의 관절 변환 같은 체인을 다룰 때 매우 편리하다. **SE(3)**: Special Euclidean Group - 3D 강체 변환의 집합 (회전 + 이동) - 6 DoF (3 회전 + 3 이동) **SO(3)**: Special Orthogonal Group - 3D 회전의 집합 - 3 DoF SE(3)와 SO(3)는 **Lie Group**이다. 최적화를 할 때 "회전 행렬의 제약조건(직교, 행렬식 1)을 만족하면서 업데이트"해야 하는데, Lie Group 이론이 이를 우아하게 해결한다. 대응되는 **Lie Algebra** (se(3), so(3))에서 제약 없이 최적화한 후 Exponential Map으로 다시 Lie Group으로 매핑하는 방식이다. SLAM의 Pose Graph Optimization에서 이 개념이 핵심적으로 사용된다. > **추천 자료** > - [State Estimation for Robotics, Ch.7 (Tim Barfoot) — 무료 PDF](http://asrl.utias.utoronto.ca/~tdb/bib/barfoot_ser17.pdf) — SE(3), SO(3), Lie Group/Algebra를 로보틱스 관점에서 가장 잘 풀어낸 교재. 이 분야를 판다면 꼭 읽자. > - [Sola — A Micro Lie Theory for State Estimation in Robotics (arXiv:1812.01537)](https://arxiv.org/abs/1812.01537) — Lie Group 이론을 로보틱스 상태 추정에 필요한 만큼만 간결하게 정리한 논문. 매우 실용적. ### 3.3 확률 및 통계 (Probability & Statistics) 센서 데이터에는 항상 노이즈가 있고, 로봇의 상태에는 항상 불확실성이 있다. 이 불확실성을 수학적으로 표현하고 다루는 것이 확률과 통계이다. "센서 값이 정확히 3.0m"가 아니라 "3.0m ± 0.05m (95% 신뢰구간)"으로 표현해야 의미가 있고, 이 불확실성을 전파하고 업데이트하는 것이 상태 추정의 기본이다. ### 3.3.1 정규분포 (Gaussian Distribution) ``` p(x) = (1 / √(2πσ²)) × exp(-(x-μ)²/(2σ²)) ``` **다변량 정규분포**: ``` p(x) = N(μ, Σ) ``` - μ: 평균 벡터 - Σ: 공분산 행렬 센서 노이즈, 위치 불확실성 모델링에 널리 사용 정규분포가 이렇게까지 많이 쓰이는 이유: (1) 중심극한정리에 의해 많은 자연현상이 정규분포를 따르고, (2) 정규분포끼리의 연산(곱, 합)이 닫혀 있어 수학적으로 다루기 편하며, (3) 평균과 공분산 두 개의 파라미터만으로 완전히 특성화되어 효율적이다. 칼만 필터가 정규분포를 가정하는 이유가 바로 이 수학적 편의성 때문이다. > **추천 자료** > - [3Blue1Brown — But what is the Central Limit Theorem?](https://www.youtube.com/watch?v=zeJD6dqJ5lo) — 중심극한정리를 시각적으로 설명. 왜 정규분포가 어디에나 나타나는지 직관적으로 이해할 수 있다. > **실습**: [Kalman Filter](https://alexjunholee.github.io/robotics-practice/app.html#kalman_filter) > 칼만 필터의 predict-update 사이클을 인터랙티브하게 조작하며, 정규분포 기반 상태 추정 과정을 확인할 수 있다. **Mahalanobis Distance** 유클리드 거리는 모든 방향을 동등하게 취급한다. 하지만 센서 데이터는 방향에 따라 불확실성이 다르다. 예를 들어 GPS는 수평 방향(수 미터)보다 수직 방향(수십 미터)의 오차가 크다. Mahalanobis 거리는 공분산(covariance)을 고려한 거리이다: ``` d_M = sqrt((x - μ)^T Σ^{-1} (x - μ)) ``` Σ가 단위 행렬이면 유클리드 거리와 같다. Σ가 대각 행렬이면 각 축별로 스케일링된 거리이다. 일반적인 Σ에서는 공분산의 주축 방향으로 거리가 재정의된다. SLAM에서의 활용: 데이터 연관(data association) 시 "이 관측이 이 랜드마크에서 왔는가?"를 판단할 때 Mahalanobis 거리를 쓴다. 유클리드로 가까워도 Mahalanobis로 멀면 (불확실성 방향과 맞지 않으면) 잘못된 연관일 가능성이 높다. (참고: [다크 프로그래머 — 평균, 표준편차, 분산, 그리고 Mahalanobis 거리](https://darkpgmr.tistory.com/41)) ### 3.3.2 베이즈 정리 (Bayes' Rule) ``` P(A|B) = P(B|A) × P(A) / P(B) ``` 베이즈 정리는 상태 추정의 수학적 기반이다. "센서 측정값이 주어졌을 때, 로봇의 실제 상태는 무엇인가?"라는 질문에 답하는 공식이다. 이걸 모르면 칼만 필터, 파티클 필터, Factor Graph 기반 SLAM 전부 이해할 수 없다. **재귀적 상태 추정**: ``` P(x_t | z_{1:t}) ∝ P(z_t | x_t) × P(x_t | z_{1:t-1}) ``` - P(z_t | x_t): Measurement model (관측 모델) — "로봇이 이 위치에 있다면, 센서가 이 값을 출력할 확률은?" - P(x_t | z_{1:t-1}): Prior (이전 상태 기반 예측) — "이전까지의 정보로 볼 때 로봇이 여기 있을 확률은?" 이 재귀적 구조를 이해하면 칼만 필터가 바로 이해된다. 새로운 센서 데이터가 들어올 때마다 기존 믿음(prior)을 업데이트하여 더 정확한 추정(posterior)을 얻는다. 칼만 필터의 predict-update 사이클이 바로 이 구조의 구현이다. > **추천 자료** > - [3Blue1Brown — Bayes' Theorem](https://www.youtube.com/watch?v=HZGCoVF3YvM) — 베이즈 정리를 시각적으로 이해하기 좋은 영상 > - [Probabilistic Robotics (Thrun, Burgard, Fox)](https://www.probabilistic-robotics.org/) — 확률적 로보틱스의 필수 교재. 베이즈 필터, 칼만 필터, 파티클 필터, SLAM까지 확률론적 관점에서 깔끔하게 정리한 교과서다. > - [김기섭 블로그 — Bayesian Filtering 시리즈 (2편)](https://gisbi-kim.github.io/blog/2021/03/09/bayesfiltering-1.html) — 베이즈 필터의 한글 해설. 칼만 필터로 이어지는 기초 ### 3.3.3 MLE와 MAP **MLE (Maximum Likelihood Estimation)**: ``` x* = argmax P(z | x) ``` 데이터가 주어졌을 때 가장 가능성 높은 파라미터 **MAP (Maximum A Posteriori)**: ``` x* = argmax P(x | z) = argmax P(z | x) × P(x) ``` 사전 확률(prior)을 고려한 추정 MLE와 MAP의 차이를 이해하는 것이 중요한 이유: SLAM에서 "관측 데이터만 보고 최적 위치를 구하는 것(MLE)"과 "이전 위치 정보도 함께 고려하여 최적 위치를 구하는 것(MAP)"의 차이이다. 실제 SLAM 시스템에서는 대부분 MAP 추정을 사용한다. Prior를 넣음으로써 노이즈가 심한 관측에도 안정적으로 추정할 수 있기 때문이다. 수학적으로는 MAP의 로그를 취하면 "관측 오차의 제곱합 + 정규화 항"이 되어, 최적화 관점에서는 Regularized Least Squares와 동일한 형태가 된다. > **추천 자료** > - [Probabilistic Robotics, Ch.2 — Recursive State Estimation (Thrun)](https://www.probabilistic-robotics.org/) — MLE, MAP, 베이즈 필터의 관계를 로보틱스 맥락에서 설명 > - [Cyrill Stachniss — Maximum Likelihood and MAP Estimation](https://www.youtube.com/watch?v=XepXtl9YKwc) — MLE와 MAP의 차이를 예시와 함께 명쾌하게 설명 **MLE와 MAP의 직관적 차이** 둘 다 "가장 그럴듯한 파라미터를 찾는다"는 목표는 같지만, 접근이 다르다. - **MLE (Maximum Likelihood)**: "이 데이터가 관측될 확률을 가장 높이는 파라미터는?" — 데이터만 본다. 사전 정보 없음. - **MAP (Maximum A Posteriori)**: "이 데이터가 관측되었을 때, 파라미터의 사후 확률을 가장 높이는 값은?" — 데이터 + 사전 지식(prior)을 결합. 수식으로: MAP = MLE + prior. 가우시안 prior를 쓰면 MAP는 MLE에 L2 정규화를 추가한 것과 같다. 딥러닝에서 weight decay가 MAP의 구현이라고 볼 수 있다. SLAM에서: odometry 측정값의 likelihood와 센서 관측의 likelihood를 곱하고, 이전 상태의 prior를 결합하여 MAP 추정을 한다. Factor graph에서 각 factor가 바로 이 likelihood/prior에 해당한다. (참고: [다크 프로그래머 — 베이즈 정리, ML과 MAP, 그리고 영상처리](https://darkpgmr.tistory.com/62)) ### 3.4 최적화 기초 (Optimization Basics) 최적화는 Spatial AI의 거의 모든 알고리즘의 마지막 단계이다. "이 조건을 만족하는 최적의 값을 찾아라"를 수학적으로 푸는 것이다. SLAM의 Bundle Adjustment, 카메라 캘리브레이션, 딥러닝의 학습 전부 최적화 문제이다. 이 섹션의 내용을 모르면 코드를 돌릴 수는 있어도, 왜 수렴하지 않는지, 왜 결과가 이상한지 디버깅할 수 없다. ### 3.4.1 Least Squares ``` x* = argmin ||Ax - b||² ``` **정규방정식 (Normal Equation)**: ``` x* = (A^T A)^(-1) A^T b ``` 최소자승법은 "노이즈가 있는 여러 측정값에서 가장 적합한 모델 파라미터를 찾는" 가장 기본적인 방법이다. 직선 피팅부터 카메라 캘리브레이션까지, 거의 모든 추정 문제의 출발점이다. > **추천 자료** > - [Cyrill Stachniss — Least Squares for Robotics](https://www.youtube.com/watch?v=khJGXf2OTIw) — 최소자승법을 로보틱스 문제에 적용하는 방법을 구체적으로 설명 > - [김기섭 블로그 — SLAM back-end 시리즈 (3편)](https://gisbi-kim.github.io/blog/2021/03/04/slambackend-1.html) — "SLAM은 Ax=b를 푸는 문제"에서 시작하는 back-end 입문. Factor graph까지 3편 시리즈 > - [김기섭 블로그 — Iterative Optimization 1편](https://gisbi-kim.github.io/blog/2021/03/16/leastsquare-1.html) — 비선형 최적화의 직관적 한글 해설 **최소자승법의 직관** "왜 오차의 제곱을 최소화하는가?" 절댓값이 아니라 제곱인 이유: (1) 미분이 가능하고, (2) 큰 오차에 더 큰 페널티를 주며, (3) 가우시안 노이즈 가정 하에서 Maximum Likelihood Estimation과 동일하다. over-determined system (방정식 수 > 미지수 수)에서 Ax = b를 정확히 만족하는 x는 없다. 대신 ||Ax - b||²를 최소화하는 x를 찾으면, normal equation `A^T A x = A^T b`가 된다. 이것이 최소자승법의 전부다. 주의: `A^T A`가 singular하면 (rank 부족) 유일한 해가 없다. 이때는 pseudo-inverse `x = (A^T A)^{-1} A^T b` 대신 SVD를 써야 수치적으로 안정적이다. (참고: [다크 프로그래머 — 최소자승법 이해와 다양한 활용예](https://darkpgmr.tistory.com/56)) ### 3.4.2 Gradient Descent ``` x_{k+1} = x_k - α × ∇f(x_k) ``` - α: Learning rate - ∇f: Gradient (기울기) Gradient Descent는 딥러닝에서 매일 쓰는 알고리즘이지만, 로보틱스 최적화에서도 기본이 된다. 함수의 기울기 반대 방향으로 조금씩 이동하여 최솟값을 찾는 직관적인 방법이다. 다만 learning rate 설정이 어렵고, local minimum에 빠질 수 있으며, 수렴 속도가 느리다는 한계가 있어서, 로보틱스에서는 보통 더 효율적인 방법(Gauss-Newton, LM)을 사용한다. **Gradient, Jacobian, Hessian의 관계 정리** 혼동하기 쉬운 세 개념을 정리한다: - **Gradient** ∇f: 스칼라 함수 f의 1차 미분. 벡터를 출력한다 (n×1). "어느 방향으로 가야 f가 가장 빠르게 증가하는가"를 알려준다. - **Jacobian** J: 벡터 함수 f: R^n → R^m의 1차 미분. 행렬을 출력한다 (m×n). 각 출력의 각 입력에 대한 편미분. - **Hessian** H: 스칼라 함수 f의 2차 미분. 대칭 행렬 (n×n). 곡률 정보를 담고 있어서 Newton's method에서 사용한다. 관계: ``` 비용 함수 C(x) = ||r(x)||² 일 때: Gradient: ∇C = J^T r (J는 r의 Jacobian) Hessian: H ≈ J^T J (Gauss-Newton 근사: 2차 미분 항 무시) Update: δx = -(J^T J)^{-1} J^T r ``` Gauss-Newton이 J^T J를 Hessian 근사로 쓰는 이유: 정확한 Hessian은 계산이 비싸고, 잔차 r이 작은 영역에서는 2차 항이 무시할 수 있을 만큼 작기 때문이다. (참고: [다크 프로그래머 — Gradient, Jacobian 행렬, Hessian 행렬, Laplacian](https://darkpgmr.tistory.com/132)) ### 3.4.3 Gauss-Newton 비선형 최소자승 문제를 반복적으로 선형화하여 해결: ``` (J^T J) Δx = -J^T r x_{k+1} = x_k + Δx ``` - J: Jacobian 행렬 - r: Residual (잔차) Gauss-Newton이 Gradient Descent보다 로보틱스에서 선호되는 이유: 2차 정보(Hessian의 근사인 J^T J)를 사용하므로 수렴이 훨씬 빠르다. SLAM에서 수천~수만 개의 변수를 최적화할 때, Gradient Descent로는 수렴에 너무 오래 걸리지만 Gauss-Newton으로는 몇 번의 반복만에 수렴할 수 있다. ### 3.4.4 Levenberg-Marquardt (LM) Gauss-Newton과 Gradient Descent의 결합: ``` (J^T J + λI) Δx = -J^T r ``` - λ: Damping factor - λ 작음 → Gauss-Newton (빠른 수렴) - λ 큼 → Gradient Descent (안정적) SLAM의 Bundle Adjustment, Pose Graph Optimization에서 핵심 알고리즘 LM이 실전에서 가장 많이 쓰이는 이유: Gauss-Newton은 초기값이 좋으면 매우 빠르게 수렴하지만, 초기값이 나쁘면 발산할 수 있다. LM은 λ를 자동으로 조절하여, 초기에는 Gradient Descent처럼 안정적으로 시작하고, 해에 가까워지면 Gauss-Newton처럼 빠르게 수렴한다. Ceres Solver, g2o, GTSAM 같은 로보틱스 최적화 라이브러리에서 기본 알고리즘으로 채택하고 있다. > **추천 자료** > - [Cyrill Stachniss — Gauss-Newton and Levenberg-Marquardt for SLAM](https://www.youtube.com/watch?v=C8JK4FN9bFM) — SLAM에서 Gauss-Newton과 LM이 어떻게 사용되는지 단계별로 설명 > - [State Estimation for Robotics, Ch.4 — Nonlinear Optimization (Tim Barfoot) — 무료 PDF](http://asrl.utias.utoronto.ca/~tdb/bib/barfoot_ser17.pdf) — 비선형 최적화를 로보틱스 상태 추정 관점에서 잘 설명한다 > - [Ceres Solver Tutorial](http://ceres-solver.org/tutorial.html) — Google의 비선형 최소자승 최적화 라이브러리. 실제로 LM 알고리즘을 코드로 어떻게 사용하는지 실습할 수 있다. > - [다크 프로그래머 — 최적화 기법의 직관적 이해](https://darkpgmr.tistory.com/149) — Gradient Descent, Newton, LM 등의 기하학적 직관 > - [김기섭 블로그 — SLAM Back-end 공부자료 5개 추천](https://gisbi-kim.github.io/blog/2021/10/03/slam-textbooks.html) — Error-state KF, Factor Graphs, Bundle Adjustment 등 핵심 자료 큐레이션 **LM의 직관: Gauss-Newton과 Gradient Descent 사이의 스위칭** 비선형 최소자승 문제에서 Gauss-Newton은 수렴이 빠르지만 초기값이 나쁘면 발산한다. Gradient Descent는 느리지만 안정적이다. LM은 damping factor λ로 둘 사이를 자동으로 전환한다. - λ가 작으면 → Gauss-Newton에 가까움 (빠른 수렴, 해 근처에서 유리) - λ가 큼 → Gradient Descent에 가까움 (안정적, 해에서 먼 곳에서 유리) update가 비용을 줄이면 λ를 줄이고 (GN 쪽으로), 비용이 늘면 λ를 키운다 (GD 쪽으로). 이 adaptive한 전환이 LM의 핵심이다. Ceres Solver의 기본 solver가 LM인 이유이기도 하다. (참고: [다크 프로그래머 — 함수최적화 기법 정리 (LM 방법 등)](https://darkpgmr.tistory.com/142)) ### 3.5 추천 자료 **교재**: - "Linear Algebra and Its Applications" (Gilbert Strang) — 선형대수의 고전. 직관적 설명과 엄밀한 수학의 균형이 좋다. - "Introduction to Applied Linear Algebra" (Boyd & Vandenberghe) - 무료 PDF — 실용적 관점의 선형대수. Python 예제 포함. - "State Estimation for Robotics" (Tim Barfoot) - 무료 PDF — 이 챕터에서 다룬 모든 수학을 로보틱스 관점에서 가장 포괄적으로 다룬 교재. 3D 기하학, 확률, 최적화를 하나의 맥락에서 배울 수 있다. **온라인 강의**: - MIT 18.06 Linear Algebra (Gilbert Strang) - YouTube — 널리 알려진 선형대수 강의 - Coursera: Mathematics for Machine Learning — 선형대수, 다변수 미적분, PCA를 다룬다. 딥러닝 입문 전 수학이 부족하면 여기서 시작 - 3Blue1Brown: Essence of Linear Algebra — 기하학적 직관을 최우선으로 다루는 시각적 강의 ### 3.6 심화: Lie Group과 Lie Algebra *연구자가 되고 싶다면 여기서부터 읽어라.* 로보틱스에서 가장 자주 마주치는 수학적 난관 중 하나는 "회전을 어떻게 최적화할 것인가"이다. 이 절에서는 Lie group과 Lie algebra를 통해 회전과 강체 변환을 체계적으로 다루는 방법을 설명한다. SLAM 백엔드, Visual-Inertial Odometry, Bundle Adjustment 등 로보틱스의 핵심 알고리즘을 이해하려면 이 내용이 필수다. #### 3.6.1 왜 Lie Group이 필요한가 3.2절에서 회전을 표현하는 여러 방법을 다뤘다. 그런데 이 표현들을 가지고 최적화를 하려고 하면 문제가 생긴다. - **회전 행렬 R**: 3x3이므로 파라미터가 9개인데, 실제 자유도는 3이다. R^T R = I 와 det(R) = 1이라는 제약 조건이 있기 때문이다. 일반적인 unconstrained optimization을 적용하면 업데이트 후 R이 더 이상 유효한 회전 행렬이 아니게 된다. - **쿼터니언**: 4개 파라미터에 정규화 제약(||q|| = 1)이 있다. 업데이트할 때마다 re-normalize해야 하고, 이 과정에서 수치 오류가 누적될 수 있다. - **오일러 각**: Gimbal lock 문제가 있고, 각도 wrapping도 까다롭다. 핵심 문제는 이것이다: 회전은 비선형 manifold 위에 살고 있는데, 우리가 아는 최적화 알고리즘(Gauss-Newton, LM)은 유클리드 공간에서 동작한다. Lie group 이론은 이 간극을 메운다. Manifold 위의 점(회전 행렬) 근처에 접선 공간(Lie algebra)을 정의하고, 이 접선 공간에서 유클리드 최적화를 수행한 뒤, 결과를 다시 manifold 위로 올리는 것이다. 배경 지식: 여기서 말하는 "group"이란, 어떤 연산에 대해 닫힘(closure), 결합법칙(associativity), 항등원(identity), 역원(inverse)이 성립하는 집합이다. 예를 들어 invertible한 n x n 행렬의 집합은 행렬 곱에 대해 group을 이루며, 이를 general linear group GL(n)이라 한다. 그 중 det = 1인 부분군이 special linear group SL(n)이다. Orthogonal group O(n)은 내적을 보존하는 행렬의 집합이고, 여기서 det = 1인 것만 모으면 SO(n) — 즉 회전군이 된다. det = -1인 것들은 반사(reflection)를 포함하며, 이들은 군 연산에 대해 닫혀있지 않으므로 부분군을 형성하지 않는다. #### 3.6.2 SO(3): 3D 회전군 **정의:** ``` SO(3) = { R in R^{3x3} | R^T R = I, det(R) = 1 } ``` SO(3)는 group이다: - **군 연산**: 행렬 곱. 두 회전 R_1, R_2의 합성은 R_1 R_2 (이것도 SO(3)의 원소). - **항등원**: 단위 행렬 I. - **역원**: R^T (= R^{-1}). 직교 행렬이므로 전치가 곧 역행렬이다. - **결합법칙**: 행렬 곱은 결합법칙을 만족한다. 단, 교환법칙은 성립하지 않는다 (R_1 R_2 != R_2 R_1 일반적으로). **Lie algebra so(3):** SO(3)의 Lie algebra는 3x3 반대칭 행렬(skew-symmetric matrix)의 공간이며, 3차원이다. **Hat operator** `[.]x` 는 3차원 벡터를 반대칭 행렬로 변환한다: ``` w = [w1, w2, w3]^T (in R^3) [ 0 -w3 w2 ] [w]x = [ w3 0 -w1 ] in so(3) [ -w2 w1 0 ] ``` 이 행렬은 벡터 외적(cross product)에 대응한다: `[w]x v = w x v` **Vee operator** `(.)v` 는 역변환이다: 반대칭 행렬에서 3차원 벡터를 추출한다. 직관적으로, so(3)의 원소 w는 "회전축 방향"과 "회전 크기"를 하나의 벡터로 인코딩한다. 축-각(axis-angle) 표현과 직접 대응된다. #### 3.6.3 Exponential Map과 Logarithmic Map **Exponential map**: so(3) -> SO(3) Lie algebra의 원소(벡터)를 Lie group의 원소(회전 행렬)로 보내는 사상이다. 이것이 어디서 나오는지 유도해 보자. 시간에 따라 연속적으로 회전하는 행렬 R(t)가 있다고 하자 (R(0) = I). R(t)는 항상 SO(3)에 있으므로 `R(t) R(t)^T = I`이다. 양변을 t로 미분하면: ``` d/dt (R R^T) = R_dot R^T + R R_dot^T = 0 → R_dot R^T = -(R R_dot^T)^T ``` 즉 `R_dot R^T`는 반대칭 행렬(skew-symmetric)이다. 이를 어떤 벡터 w(t)의 hat form으로 쓸 수 있다: ``` R_dot(t) R^T(t) = [w(t)]x → R_dot(t) = [w(t)]x R(t) ``` w가 상수(일정한 각속도)인 경우, 이 미분 방정식의 해는: ``` R(t) = exp([w]x * t) = sum_{n=0}^{inf} ([w]x * t)^n / n! ``` 여기서 `exp([w]x)`는 축 w 방향으로 ||w|| 라디안만큼 회전시키는 행렬이 된다. 구체적으로, theta = ||w||로 두면 **Rodrigues' formula**로 닫힌 형태를 얻는다: ``` exp([w]x) = I + (sin(theta) / theta) [w]x + ((1 - cos(theta)) / theta^2) [w]x^2 ``` 이 공식은 `sin(t)`와 `cos(t)`의 Taylor 전개를 `[w]x`의 거듭제곱에 대입하면 유도된다. `[w]x^3 = -theta^2 [w]x`라는 성질을 이용하면 급수가 sin, cos 항으로 정리된다. theta가 작을 때(|theta| < eps)는 sin(theta)/theta ≈ 1, (1-cos(theta))/theta^2 ≈ 1/2이므로: ``` exp([w]x) ≈ I + [w]x + (1/2)[w]x^2 (1차 근사) ``` 주의: 하나의 회전 행렬 R에 대해 `R = exp([w]x)`를 만족하는 w는 유일하지 않다. ||w|| + 2*pi*k (정수 k)에 대해 같은 R을 준다. 이것이 logarithmic map에서 주의해야 하는 부분이다. **Logarithmic map**: SO(3) -> so(3) 역변환이다. 주어진 회전 행렬 R에서 축-각 벡터 w를 복원한다. ``` theta = arccos((tr(R) - 1) / 2) [w]x = (theta / (2 sin(theta))) (R - R^T) ``` theta = 0 (항등 회전) 이나 theta = pi (180도 회전) 근처에서는 특별한 처리가 필요하다. **직관**: Lie algebra는 group 위의 한 점(보통 항등원 I)에서의 접선 공간(tangent space)이다. "작은 회전"은 접선 공간의 벡터로 표현할 수 있고, exponential map이 이 벡터를 manifold 위의 실제 회전으로 매핑한다. 이것이 최적화에서 핵심이 되는 이유다: 업데이트량 dw를 접선 공간(R^3)에서 계산한 뒤, exp([dw]x)를 현재 회전에 곱해서 manifold 위에서 이동하는 것이다. #### 3.6.4 SE(3): 3D 강체 변환군 로봇의 포즈는 회전뿐 아니라 이동도 포함한다. 이를 다루는 것이 SE(3)이다. **정의:** ``` SE(3) = { T = [ R t ] | R in SO(3), t in R^3 } [ 0 1 ] ``` T는 4x4 homogeneous transformation matrix이다. SE(3)도 group이다: - 군 연산: T_1 T_2 (행렬 곱) - 항등원: 4x4 단위 행렬 - 역원: T^{-1} = [ R^T -R^T t ; 0 1 ] **Lie algebra se(3):** SE(3)의 Lie algebra는 6차원이다. 원소를 **twist** 벡터라 부른다: ``` xi = [rho; w] in R^6 (rho in R^3: 이동 성분, w in R^3: 회전 성분) ``` **Hat operator**는 6차원 벡터를 4x4 행렬로 변환한다: ``` [ [w]x rho ] xi^ = [ 0 0 ] in se(3) (4x4 행렬) ``` **Exponential map**: se(3) -> SE(3) ``` exp(xi^) = [ exp([w]x) J rho ] in SE(3) [ 0 1 ] ``` 여기서 J는 left Jacobian of SO(3)이다: ``` J = I + ((1 - cos(theta)) / theta^2) [w]x + ((theta - sin(theta)) / theta^3) [w]x^2 ``` **핵심**: 6-DoF 포즈(3 회전 + 3 이동)를 6차원 벡터 xi in R^6으로 매개변수화할 수 있다. 제약 조건 없는 6차원 유클리드 공간에서 최적화를 수행하고, exponential map으로 결과를 SE(3) manifold 위로 올릴 수 있다. 이것이 SLAM 최적화에서 Lie group을 쓰는 이유다. > **실습**: [SE(3) Pose Composition](https://alexjunholee.github.io/robotics-practice/app.html#pose_composition_3d) > SE(3) 변환의 합성을 3D로 직접 조작하며, 회전과 이동이 결합된 강체 변환이 어떻게 연쇄되는지 확인할 수 있다. #### 3.6.5 Perturbation Model과 Jacobian Gauss-Newton이나 LM 알고리즘으로 포즈를 최적화할 때, 현재 추정값 T에 작은 변화(perturbation) d_xi를 가하는 방법이 두 가지 있다. **Left perturbation (global frame 기준):** ``` T' = exp(d_xi^) * T ``` **Right perturbation (body frame 기준):** ``` T' = T * exp(d_xi^) ``` 어느 쪽을 쓰든 수학적으로 일관성 있게 유지하면 된다. 문헌마다 convention이 다르니 주의해야 한다. Barfoot의 교재는 left를 주로 쓰고, Strasdat의 Sophus는 right를 기본으로 한다. **Jacobian 계산:** 에러 함수 e(T)가 있을 때, perturbation에 대한 Jacobian은: ``` de/d(d_xi) = lim_{d_xi->0} (e(exp(d_xi^) * T) - e(T)) / d_xi (left perturbation의 경우) ``` 이 Jacobian은 6열짜리 행렬이 된다 (에러 차원 x 6). **왜 이게 중요한가:** 일반적인 최적화에서 업데이트는 `x <- x + dx` (유클리드 덧셈)이다. 하지만 SE(3) 위에서는 덧셈이 정의되지 않는다. 대신: 1. 접선 공간에서 d_xi in R^6을 Gauss-Newton으로 계산한다: `d_xi = -(J^T J)^{-1} J^T e` 2. Manifold 위에서 업데이트한다: `T <- exp(d_xi^) * T` 이렇게 하면 업데이트 후에도 T가 항상 유효한 SE(3) 원소임이 보장된다. 별도의 제약 조건 처리가 필요 없다. **실용적 참고**: g2o, GTSAM, Ceres (with local parameterization / manifold)에서 내부적으로 이 방식을 쓴다. GTSAM의 `Pose3`는 SE(3)를 직접 구현하고, `Pose3::Expmap()`, `Pose3::Logmap()`을 제공한다. Ceres에서는 `LocalParameterization` (또는 최신 API의 `Manifold`)을 통해 같은 개념을 구현한다. #### 3.6.6 Adjoint Representation twist를 다른 좌표계로 변환해야 할 때 Adjoint를 쓴다. SE(3)의 원소 T에 대해, Adjoint 행렬 Ad_T는 6x6 행렬이다: ``` Ad_T = [ R [t]x R ] in R^{6x6} [ 0 R ] ``` twist 변환: ``` xi_a = Ad_{T_ab} * xi_b ``` **실용적 의미**: 센서(예: IMU)가 측정한 속도(angular velocity, linear velocity)는 센서 프레임에서 표현된다. 이를 body 프레임이나 world 프레임으로 변환할 때 Adjoint를 쓴다. 여러 센서를 fusion하는 VIO 시스템에서 좌표계 간 변환이 빈번하게 일어나므로, Adjoint의 의미를 이해하고 있어야 한다. #### 3.6.7 실무에서의 사용 **Sophus (C++)**: Strasdat가 만든 Lie group 라이브러리. SO(3), SE(3)와 그 exponential/logarithmic map, Adjoint 등을 구현한다. ORB-SLAM3, Kimera 등 주요 SLAM 시스템이 사용한다. ```cpp #include
// SE(3) 포즈 초기화 (항등 변환) Sophus::SE3d T_world_body; // se(3) perturbation (6-vector): [translation; rotation] Sophus::SE3d::Tangent delta; delta << 0.01, 0.0, 0.0, 0.0, 0.0, 0.001; // 작은 x-이동 + 작은 z-회전 // Left perturbation update T_world_body = Sophus::SE3d::exp(delta) * T_world_body; // Log map: SE(3) -> se(3) Sophus::SE3d::Tangent xi = T_world_body.log(); ``` **Jaxlie (Python/JAX)**: Brent Yi가 만든 JAX 기반 Lie group 라이브러리. 자동 미분이 가능하므로, Jacobian을 손으로 유도하지 않아도 된다. 연구 프로토타이핑에 유용하다. ```python import jaxlie import jax.numpy as jnp T = jaxlie.SE3.identity() delta = jnp.array([0.01, 0.0, 0.0, 0.0, 0.0, 0.001]) T_updated = jaxlie.SE3.exp(delta) @ T ``` **GTSAM**: `gtsam::Pose3`가 내부적으로 SE(3)를 쓴다. Factor graph 최적화 시 Lie group 위에서의 perturbation을 자동으로 처리한다. > **추천 자료** > - [State Estimation for Robotics, Ch.7-8 (Barfoot)](http://asrl.utias.utoronto.ca/~tdb/bib/barfoot_ser17.pdf) -- Lie group을 로보틱스 상태 추정 관점에서 다루는 핵심 레퍼런스 > - [A micro Lie theory for state estimation in robotics (Sola et al., arXiv:1812.01537)](https://arxiv.org/abs/1812.01537) -- Lie group의 핵심만 20페이지로 요약. 논문 읽기 전에 이것부터 > - [TUM Multiple View Geometry, Ch.2 -- Rigid Body Motion](https://cvg.cit.tum.de/teaching/online/mvg) -- Daniel Cremers 교수의 강의. SO(3), SE(3)를 시각적으로 설명 > - [Sophus GitHub](https://github.com/strasdat/Sophus) -- C++ Lie group 라이브러리. 코드를 읽으면 이해가 빨라진다 > - [정진용 블로그 — SE(3) and SO(3) transformation](https://jinyongjeong.github.io/2016/06/07/se3_so3_transformation/) — SE(3), SO(3) 변환의 한글 정리. GL(3), O(3)부터 체계적으로 설명 ### 3.7 심화: Factor Graph *연구자가 되고 싶다면 여기서부터 읽어라.* SLAM 문제를 체계적으로 정의하고 효율적으로 푸는 프레임워크가 factor graph이다. 현대 SLAM 시스템의 백엔드는 거의 예외 없이 factor graph 기반이다. #### 3.7.1 Factor Graph란 Factor graph는 두 종류의 노드로 구성된 이분 그래프(bipartite graph)이다: - **변수 노드 (variable nodes)**: 추정하고자 하는 상태. 로봇 포즈(x_1, x_2, ...), 랜드마크 위치(l_1, l_2, ...) 등. - **팩터 노드 (factor nodes)**: 변수들 사이의 제약 조건 또는 측정. 각 팩터는 연결된 변수들에 대한 비용 함수를 정의한다. 확률적으로, 전체 사후 분포는 팩터들의 곱으로 분해된다: ``` p(X | Z) proportional to prod_i f_i(X_i) ``` 여기서 X_i는 팩터 f_i에 연결된 변수들의 부분 집합이다. **MAP 추정** = 모든 팩터의 곱을 최대화 = 음의 로그를 취하면 합을 최소화 = **nonlinear least squares** 문제가 된다: ``` X* = argmin_X sum_i ||e_i(X_i)||^2_{Sigma_i} ``` e_i는 에러 함수, Sigma_i는 해당 측정의 공분산(불확실성 가중치)이다. #### 3.7.2 SLAM을 Factor Graph로 표현 SLAM에서 흔히 사용되는 팩터 유형들: - **Prior factor**: 초기 포즈에 대한 사전 정보. 예: "시작점은 원점이다" - **Odometry (pose-pose) factor**: 두 연속 포즈 사이의 상대 변환. IMU preintegration이나 wheel odometry에서 온다. - **Landmark observation factor**: 포즈에서 랜드마크를 관측한 측정. 예: reprojection error. - **Loop closure factor**: 이전에 방문한 장소를 재인식했을 때 추가. 전체 궤적의 drift를 보정하는 핵심. - **IMU preintegration factor**: 두 키프레임 사이의 IMU 측정을 하나의 팩터로 요약. ASCII로 간략히 표현하면: ``` [prior]---x1---[odom]---x2---[odom]---x3 | | [landmark] [landmark] | | l1 l2 x3 ---[loop closure]--- x1 ``` 각 팩터에는 측정값과 공분산(노이즈 모델)이 포함된다. 그래프가 구축되면 Gauss-Newton 또는 LM으로 전체 변수를 동시에 최적화한다. #### 3.7.3 풀이: Variable Elimination과 Bayes Tree Factor graph를 최적화하려면 정규 방정식 `H d = -b`를 풀어야 한다 (H는 Hessian 근사, b는 gradient). 이 시스템의 구조를 이해하는 것이 효율적 풀이의 핵심이다. **Variable elimination**: 변수를 하나씩 소거하는 과정. 이것은 sparse Cholesky factorization과 수학적으로 동등하다. 소거 순서에 따라 fill-in (원래 0이었던 곳이 non-zero가 되는 현상)이 달라지며, 이는 계산 비용에 직접 영향을 미친다. **Variable ordering**: 소거 순서를 최적화하는 것이 중요하다. COLAMD (Column Approximate Minimum Degree) 같은 heuristic이 널리 쓰인다. 직관적으로, 연결이 적은 변수를 먼저 소거하면 fill-in이 적다. **Bayes tree**: Kaess et al. (2012)이 제안한 자료구조로, iSAM2의 핵심이다. Factor graph를 elimination하면 Bayes net이 되고, 이를 tree 구조로 재편하면 Bayes tree가 된다. 새로운 측정이 들어올 때, 영향을 받는 subtree만 re-elimination하면 된다. 이것이 중요한 이유: 실시간 SLAM에서는 매 프레임마다 새로운 팩터가 추가된다. 전체 시스템을 처음부터 다시 풀면 O(n^3)이지만, Bayes tree를 이용한 incremental update는 영향받는 부분만 갱신하므로 실시간 처리가 가능하다. > **추천 자료** > - [Factor Graphs and GTSAM (Dellaert & Kaess)](https://gtsam.org/tutorials/intro.html) -- GTSAM 공식 튜토리얼. Factor graph에서 SLAM으로의 연결을 설명 > - [Factor Graphs for Robot Perception (Dellaert & Kaess, 2017)](https://www.cs.cmu.edu/~kaess/pub/Dellaert17fnt.pdf) -- 100페이지 분량의 종합 레퍼런스 > - [CMU 16-833 Lecture Notes](https://www.cs.cmu.edu/~kaess/teaching/16833/) -- Michael Kaess 교수의 SLAM 강의. Factor graph와 iSAM2를 깊이 다룬다 > **실습**: [Factor Graph 시각화](https://alexjunholee.github.io/robotics-practice/app.html#factor_graph_viz) > Factor graph의 변수 노드와 팩터 노드를 직접 구성하고, 그래프 구조가 최적화에 미치는 영향을 확인할 수 있다. #### 3.7.4 Ceres Solver로 Pose Graph 최적화 구현하기 GTSAM 외에 Google의 Ceres Solver로도 factor graph 기반 최적화를 구현할 수 있다. Ceres는 범용 nonlinear least squares 솔버라서 SLAM에 특화된 기능은 없지만, 그만큼 내부 동작을 직접 이해하기 좋다. 아래는 Ceres 공식 예제인 `pose_graph_3d`를 기반으로 한 분석이다. **Error Term 정의:** 두 포즈 `x_a`, `x_b` 사이의 상대 변환 측정값 `T_ab_measured`가 있을 때, residual은 추정된 상대 변환과 측정값의 차이다. ```cpp class PoseGraph3dErrorTerm { public: PoseGraph3dErrorTerm(Pose3d t_ab_measured, Eigen::Matrix
sqrt_information) : t_ab_measured_(std::move(t_ab_measured)), sqrt_information_(std::move(sqrt_information)) {} template
bool operator()(const T* const p_a_ptr, const T* const q_a_ptr, const T* const p_b_ptr, const T* const q_b_ptr, T* residuals_ptr) const { // 추정된 상대 변환 계산 Eigen::Quaternion
q_a_inverse = q_a.conjugate(); Eigen::Quaternion
q_ab_estimated = q_a_inverse * q_b; Eigen::Matrix
p_ab_estimated = q_a_inverse * (p_b - p_a); // 측정값과의 차이 Eigen::Quaternion
delta_q = t_ab_measured_.q.cast
() * q_ab_estimated.conjugate(); // residual = [position_error; orientation_error] residuals.block<3,1>(0,0) = p_ab_estimated - t_ab_measured_.p.cast
(); residuals.block<3,1>(3,0) = T(2.0) * delta_q.vec(); // information matrix 적용 (covariance의 역) residuals.applyOnTheLeft(sqrt_information_.cast
()); return true; } }; ``` 핵심 포인트: - **template \
**: Ceres 내부에서 residual 값이 필요하면 `T=double`, Jacobian이 필요하면 `T=Jet
`로 자동 전환된다. 이것이 AutoDiff의 원리다. - **sqrt_information**: covariance의 Cholesky decomposition. `information.llt().matrixL()`로 구한다. - **AutoDiffCostFunction 차원**: `
` — residual 6차원, pos_a 3차원, quat_a 4차원, pos_b 3차원, quat_b 4차원. - **SetManifold**: quaternion은 4차원이지만 자유도는 3이므로, `EigenQuaternionManifold`를 지정해서 manifold 위에서 최적화하도록 한다. 이전 API에서는 `LocalParameterization`이었다. **문제 구성:** ```cpp ceres::Problem problem; ceres::LossFunction* loss_function = nullptr; // robust loss 필요시 HuberLoss 등 ceres::Manifold* quaternion_manifold = new EigenQuaternionManifold; for (const auto& constraint : constraints) { ceres::CostFunction* cost_function = PoseGraph3dErrorTerm::Create(constraint.t_be, sqrt_information); problem.AddResidualBlock(cost_function, loss_function, pose_begin.p.data(), pose_begin.q.coeffs().data(), pose_end.p.data(), pose_end.q.coeffs().data()); problem.SetManifold(pose_begin.q.coeffs().data(), quaternion_manifold); problem.SetManifold(pose_end.q.coeffs().data(), quaternion_manifold); } // 첫 번째 포즈 고정 (gauge freedom 제거) problem.SetParameterBlockConstant(poses.begin()->second.p.data()); problem.SetParameterBlockConstant(poses.begin()->second.q.coeffs().data()); ``` **풀이:** ```cpp ceres::Solver::Options options; options.max_num_iterations = 200; options.linear_solver_type = ceres::SPARSE_NORMAL_CHOLESKY; ceres::Solver::Summary summary; ceres::Solve(options, &problem, &summary); ``` `SPARSE_NORMAL_CHOLESKY`는 pose graph처럼 sparse한 문제에 적합하다. 변수가 많아지면 `SPARSE_SCHUR`도 고려할 수 있다. GTSAM vs Ceres 비교: - **GTSAM**: SLAM에 특화. `BetweenFactor`, `PriorFactor` 등 미리 정의된 팩터 제공. iSAM2로 incremental 풀이 가능. Lie group manifold를 기본 지원. - **Ceres**: 범용 솔버. 모든 것을 직접 정의해야 하지만, 그만큼 유연하다. Jacobian 검증(`GradientChecker`)이 편리. 대규모 BA(Bundle Adjustment)에서는 Ceres가 더 빠른 경우도 있다. > **추천 자료** > - [Ceres Solver 공식 pose_graph_3d 예제](https://ceres-solver.googlesource.com/ceres-solver/+/master/examples/slam/pose_graph_3d/) -- 위 코드의 전체 버전 > - [Ceres Solver Tutorial](http://ceres-solver.org/tutorial.html) -- AutoDiff, Manifold 개념 설명 > - [정진용 블로그 — Ceres Solver Tutorial](https://jinyongjeong.github.io/2023/07/22/Ceres_tutorial/) — Ceres Solver 발표자료와 GitHub 실습 코드. 비선형 최적화 입문에 적합 ### 3.8 심화: Robust Estimation *연구자가 되고 싶다면 여기서부터 읽어라.* 현실 세계의 데이터는 깨끗하지 않다. 잘못된 데이터 연관(false match), 동적 물체, 센서 고장 -- 이런 것들이 outlier를 만들고, outlier는 최적화 결과를 심각하게 왜곡한다. Robust estimation은 이런 상황에서도 합리적인 추정을 하기 위한 기법들이다. #### 3.8.1 왜 필요한가 Standard least squares는 에러의 제곱을 최소화한다: `rho(r) = r^2`. 이 함수는 큰 잔차(residual)에 큰 가중치를 주기 때문에, 하나의 outlier가 전체 해를 끌고 갈 수 있다. SLAM에서의 구체적 사례: - 잘못된 loop closure 하나가 전체 지도를 뒤틀어 버린다 - Visual feature matching에서의 false positive가 BA 결과를 망친다 - 동적 물체(사람, 차)에 붙은 feature가 정적 장면 가정을 위반한다 #### 3.8.2 M-Estimator M-estimator는 `rho(r) = r^2` 대신 다른 비용 함수 rho를 사용하여 outlier의 영향을 줄인다. | M-Estimator | rho(r) | 특성 | |---|---|---| | **L2 (표준)** | r^2 | Outlier에 취약 | | **Huber** | r^2 (abs(r) <= k), 2k*abs(r) - k^2 (abs(r) > k) | 작은 잔차는 L2, 큰 잔차는 L1. 가장 널리 쓰임 | | **Cauchy** | c^2 * log(1 + (r/c)^2) | Huber보다 outlier 억제가 강함 | | **Geman-McClure** | r^2 / (1 + r^2) | 극단적 outlier를 사실상 무시 | 선택 기준: Huber가 대부분의 경우 안전한 기본 선택이다. Outlier 비율이 높거나 극단적인 경우 Cauchy나 Geman-McClure를 고려한다. 파라미터(k 또는 c)는 잔차의 통계적 분포에 맞춰 튜닝해야 한다. 실무적으로, Ceres Solver에서는 `ceres::HuberLoss`, `ceres::CauchyLoss` 등을 cost function에 감싸서 적용한다. GTSAM에서는 `gtsam::noiseModel::mEstimator::Huber`를 쓴다. > **실습**: [M-Estimator 비교](https://alexjunholee.github.io/robotics-practice/app.html#m_estimator) > L2, Huber, Cauchy, Geman-McClure 등 다양한 비용 함수가 outlier에 어떻게 반응하는지 인터랙티브하게 비교할 수 있다. #### 3.8.3 RANSAC와 변종 RANSAC (Random Sample Consensus)은 outlier가 포함된 데이터에서 모델을 피팅하는 반복적 알고리즘이다. M-estimator와 달리, 데이터를 inlier/outlier로 명시적으로 분류한다. **기본 RANSAC 알고리즘:** 1. 최소 샘플을 무작위로 선택 2. 해당 샘플로 모델을 피팅 3. 전체 데이터에서 inlier 수를 계산 (threshold 이내의 잔차를 가진 점) 4. 반복 -> 가장 많은 inlier를 가진 모델을 선택 5. 최종적으로 모든 inlier를 사용해 모델을 re-fit **변종들:** - **PROSAC (Progressive SAmple Consensus)**: matching score 등 사전 품질 정보를 활용하여 좋은 샘플을 먼저 시도. RANSAC보다 빠르게 수렴한다. - **Lo-RANSAC (Locally Optimized RANSAC)**: 좋은 모델을 찾을 때마다 로컬 최적화 단계를 추가. 정확도가 향상된다. - **MAGSAC / MAGSAC++**: 노이즈 스케일 sigma를 자동 추정하며, 고정된 inlier threshold가 필요 없다. marginalization 기반으로 inlier/outlier 구분을 soft하게 처리한다. 트레이드오프: - RANSAC: 단순하고 구현이 쉽지만, 파라미터(threshold, 반복 횟수)에 민감 - PROSAC: 사전 정보가 있을 때 빠르지만, 사전 정보의 품질에 의존 - Lo-RANSAC: 더 정확하지만 더 느림 - MAGSAC++: 파라미터 프리에 가까우나, 계산 비용이 높음 OpenCV의 `cv::findHomography`, `cv::findFundamentalMat` 등에서 `cv::USAC_MAGSAC` 플래그로 MAGSAC++를 사용할 수 있다. > **추천 자료** > - [State Estimation for Robotics, Ch.5 (Barfoot)](http://asrl.utias.utoronto.ca/~tdb/bib/barfoot_ser17.pdf) -- 바이어스, 대응 문제, outlier를 다루는 실전적 챕터 > - [Hartley & Zisserman, Ch.4 -- Estimation: 2D Projective Transforms](https://www.robots.ox.ac.uk/~vgg/hzbook/) -- RANSAC의 원본 설명과 robust estimation 이론 > - [다크 프로그래머 — RANSAC의 이해와 영상처리 활용](https://darkpgmr.tistory.com/61) — RANSAC의 원리, threshold 설정, 반복 횟수 계산을 한글로 설명 > - [정진용 블로그 — Bundle Adjustment의 Jacobian 계산](https://jinyongjeong.github.io/2020/03/01/Jacobian_of_BA/) — BA의 reprojection error Jacobian을 Lie algebra와 quaternion으로 유도. 손필기 수식 포함 > **실습**: [RANSAC 시각화](https://alexjunholee.github.io/robotics-practice/app.html#ransac) > Outlier가 포함된 데이터에서 RANSAC이 inlier/outlier를 분류하고 모델을 피팅하는 과정을 단계별로 확인할 수 있다. ### 3.9 심화: 정보 이론 기초 *연구자가 되고 싶다면 여기서부터 읽어라.* Active SLAM, exploration, 불확실성 기반 의사결정에서 정보 이론 개념이 쓰인다. 여기서는 핵심만 짧게 다룬다. **Shannon entropy**: 확률 변수 X의 불확실성을 측정한다. ``` H(X) = -sum p(x) log p(x) ``` Entropy가 높을수록 불확실성이 크다. 가우시안 분포의 경우 공분산이 클수록 entropy가 높다. **KL divergence (Kullback-Leibler divergence)**: 두 확률 분포 p와 q 사이의 "차이"를 측정한다. ``` D_KL(p || q) = sum p(x) log(p(x) / q(x)) ``` 비대칭이다: D_KL(p||q) != D_KL(q||p). "p라고 생각했는데 실제로 q일 때의 정보 손실"로 해석할 수 있다. **Mutual information**: Y를 관측하면 X에 대해 얼마나 알게 되는가를 측정한다. ``` I(X; Y) = H(X) - H(X|Y) ``` H(X)는 Y를 관측하기 전 X의 불확실성, H(X|Y)는 관측 후 불확실성. 그 차이가 Y가 X에 대해 제공하는 정보량이다. **Active SLAM 응용**: 로봇이 다음에 어디로 갈지 결정할 때, "이 행동을 취하면 지도/포즈의 불확실성이 얼마나 줄어드는가?"를 mutual information으로 계량화할 수 있다. Expected information gain이 가장 큰 행동을 선택하는 것이 정보 이론 기반 탐색의 핵심 아이디어다. ``` a* = argmax_a I(X; Z_a) = argmax_a [ H(Z_a) - H(Z_a | X) ] ``` 여기서 a는 행동(action), Z_a는 그 행동을 통해 얻을 관측, X는 환경 상태이다. > **추천 자료** > - [Elements of Information Theory (Cover & Thomas)](https://onlinelibrary.wiley.com/doi/book/10.1002/047174882X) -- 정보 이론 교과서 > - [Placed: An exploration planner using information gain (2022)](https://arxiv.org/abs/2206.05193) -- Active SLAM에서 정보 이론 활용 예시 > **기술 흐름: 로보틱스 수학 및 최적화** > - **~2005**: 칼만 필터(EKF) 중심의 상태 추정. 선형 근사 기반, 소규모 문제에 적합. 실시간 처리가 어려워 문제 크기에 제약이 있었다. > - **2006~2015**: Factor Graph 기반 최적화(iSAM, g2o, GTSAM) 등장. 스파스 행렬 구조를 활용하여 대규모 SLAM 문제를 효율적으로 풀 수 있게 됨. Lie Group/Algebra가 SLAM 커뮤니티에서 표준 도구로 자리잡음. > - **2016~2020**: 실시간 대규모 최적화 실용화. 증분적 최적화(incremental optimization)로 매 프레임 실시간 업데이트 가능. Ceres Solver가 산업계 표준으로 자리잡음. > - **2021~**: Differentiable Programming 시대. PyTorch/JAX의 자동 미분(Auto-Diff)을 활용한 End-to-End 최적화. NeRF, 3D Gaussian Splatting 등 미분 가능 렌더링의 등장으로, 기존에 손으로 유도하던 Jacobian을 자동 미분으로 대체. Theseus(Meta) 같은 미분 가능 최적화 라이브러리 등장. > - **지금 주목할 것**: 고전적 수학(Lie Group, 확률, 최적화)은 여전히 필수이지만, Differentiable Programming이 최적화 문제 접근 방식을 바꾸고 있다. "Jacobian을 손으로 유도"하는 시대에서 "자동 미분으로 전체 파이프라인을 미분"하는 시대로 전환 중이다. 그러나 자동 미분이 내부에서 무엇을 하는지 이해하려면 여기서 다룬 수학적 기초가 필요하다. 기초 없이 도구만 사용하면 디버깅할 수 없다. --- ## 4. 기구학 & 메카트로닉스 (Kinematics & Mechatronics) 로봇 팔 하나를 책상 위에 올려놓았다고 하자. 모터 6개에 각각 어떤 각도를 줘야 손끝이 커피잔에 닿는가? 이 질문에 답하는 학문이 기구학이다. 그리고 그 모터를 실제로 돌리고, 센서를 읽고, 제어 루프를 1kHz로 돌리는 현실의 문제가 메카트로닉스이다. 이 장에서는 수학부터 시작해서 실제 하드웨어 선정과 통신 프로토콜까지 다룬다. 수식이 좀 나오지만, 결국 목적은 "로봇을 실제로 움직이는 것"이다. --- ### 4.1 왜 기구학을 배우는가 로봇 매니퓰레이터는 여러 개의 관절(joint)과 링크(link)로 구성된다. 우리가 원하는 것은 끝단(end-effector)의 위치와 자세(pose)이다. 하지만 우리가 직접 제어하는 것은 각 관절의 각도(또는 변위)이다. 이 둘 사이의 관계를 수학적으로 기술하는 것이 **기구학(Kinematics)**이다. - **순기구학 (Forward Kinematics, FK)**: 관절 각도 → 끝단 위치/자세 - **역기구학 (Inverse Kinematics, IK)**: 끝단 위치/자세 → 관절 각도 동역학(Dynamics)과 다르다. 기구학은 힘과 질량을 고려하지 않는다. 순수하게 기하학적 관계만 다룬다. "어디에 있는가"의 문제이지, "어떤 힘이 필요한가"의 문제가 아니다. 동역학은 다음 장에서 다룬다. 기구학을 모르면 다음 상황에서 막힌다: - 로봇 팔 경로 계획 (motion planning) - 텔레오퍼레이션 (원격 조종 시 마스터-슬레이브 매핑) - 캘리브레이션 (실제 로봇과 모델 사이 오차 보정) - 충돌 회피 (각 링크가 공간 어디에 있는지 알아야 피한다) --- ### 4.2 순기구학 (Forward Kinematics) #### 4.2.1 동차 변환 행렬 (Homogeneous Transformation Matrix) 기구학의 기본 도구는 4×4 동차 변환 행렬이다: ``` T = | R p | | 0 1 | ``` 여기서 R은 3×3 회전 행렬, p는 3×1 위치 벡터이다. 이 행렬 하나로 강체의 위치와 자세를 동시에 표현할 수 있고, 여러 변환을 행렬 곱으로 연쇄(chain)할 수 있다는 점이 핵심이다. 두 프레임 사이의 변환 T_01이 있고, 또 다른 변환 T_12가 있으면: ``` T_02 = T_01 * T_12 ``` 이것이 순기구학의 본질이다. 베이스에서 끝단까지 각 관절의 변환을 순서대로 곱하면 된다. #### 4.2.2 DH Parameters (Denavit-Hartenberg) 1955년 Denavit와 Hartenberg가 제안한 방법이다. 70년이 지났지만 여전히 산업계의 표준이다. 4개의 파라미터로 인접한 두 링크 사이의 관계를 정의한다: | 파라미터 | 의미 | |---------|------| | **a_i** (link length) | x_i 축을 따른 z_{i-1}에서 z_i까지의 거리 | | **α_i** (link twist) | z_{i-1}에서 z_i까지 x_i 축 기준 회전 각도 | | **d_i** (link offset) | z_{i-1} 축을 따른 x_{i-1}에서 x_i까지의 거리 | | **θ_i** (joint angle) | x_{i-1}에서 x_i까지 z_{i-1} 축 기준 회전 각도 | 회전 관절(revolute joint)에서는 θ_i가 변수이고, 나머지 3개는 상수이다. 직선 관절(prismatic joint)에서는 d_i가 변수이다. 각 관절의 변환 행렬: ``` T_i = Rot_z(θ_i) * Trans_z(d_i) * Trans_x(a_i) * Rot_x(α_i) = | cos(θ) -sin(θ)cos(α) sin(θ)sin(α) a*cos(θ) | | sin(θ) cos(θ)cos(α) -cos(θ)sin(α) a*sin(θ) | | 0 sin(α) cos(α) d | | 0 0 0 1 | ``` 주의: DH convention에는 "standard"와 "modified (Craig convention)" 두 가지가 있다. Craig 교과서를 쓴다면 modified DH를 보게 되고, 많은 다른 교재는 standard DH를 사용한다. 둘은 프레임 부착 방식이 다르다. 혼용하면 결과가 틀리니 어떤 convention을 쓰는지 항상 명시해야 한다. #### 4.2.3 예제: 2-link Planar Arm의 FK 가장 간단한 예제부터 하자. 평면 위의 2-링크 로봇 팔이다. ``` q1 q2 O────────O────────O → end-effector (base) L1 L2 ``` DH 테이블 (standard convention): | Link | a | α | d | θ | |------|------|-----|-----|------| | 1 | L1 | 0 | 0 | θ_1 | | 2 | L2 | 0 | 0 | θ_2 | 끝단 위치는 단순히 삼각함수로 유도된다: ``` x = L1*cos(θ_1) + L2*cos(θ_1 + θ_2) y = L1*sin(θ_1) + L2*sin(θ_1 + θ_2) ``` Python으로 구현하면: ```python import numpy as np def fk_2link(theta1, theta2, L1=1.0, L2=1.0): """2-link planar arm의 순기구학.""" x = L1 * np.cos(theta1) + L2 * np.cos(theta1 + theta2) y = L1 * np.sin(theta1) + L2 * np.sin(theta1 + theta2) phi = theta1 + theta2 # 끝단의 절대 방향 return x, y, phi # θ_1=30°, θ_2=45°, 링크 길이 각각 1m x, y, phi = fk_2link(np.radians(30), np.radians(45)) print(f"End-effector position: ({x:.3f}, {y:.3f}), orientation: {np.degrees(phi):.1f}°") # 출력: End-effector position: (0.259, 1.366), orientation: 75.0° ``` 이게 지나치게 단순해 보인다면 정상이다. 실제 6축 로봇 팔의 FK도 원리는 같다. 4×4 행렬을 6번 곱하면 될 뿐이다. #### 4.2.4 Product of Exponentials (PoE) DH 파라미터의 대안으로, Lie group/Lie algebra에 기반한 PoE (Product of Exponentials) 방법이 있다. Lynch & Park의 "Modern Robotics"에서 채택한 방법이다. 핵심 아이디어: 각 관절을 twist(나선 운동)로 표현하고, 행렬 지수(matrix exponential)를 통해 변환을 계산한다. ``` T(θ) = e^{[S_1]θ_1} * e^{[S_2]θ_2} * ... * e^{[S_n]θ_n} * M ``` 여기서: - S_i는 i번째 관절의 screw axis (6×1 벡터) - [S_i]는 S_i의 4×4 skew-symmetric matrix 표현 (se(3) 원소) - M은 모든 관절이 영 위치(home configuration)일 때의 끝단 자세 - θ_i는 관절 변수 **DH vs PoE 비교:** | 항목 | DH | PoE | |------|-----|-----| | 프레임 부착 | 각 링크에 프레임 필요 | 기준 프레임과 끝단 프레임만 필요 | | Convention 혼동 | standard vs modified 주의 | 없음 (space form vs body form 구분은 있음) | | 수학적 기반 | 행렬 곱 | Lie group, 행렬 지수 | | 특이점 분석 | 별도 처리 필요 | 자연스럽게 통합 | | 산업계 채택 | 매우 높음 | 학계 중심, 점점 확산 | | 교재 | Craig, Siciliano | Lynch & Park | 실무적 조언: DH 파라미터는 반드시 알아야 한다. URDF(로봇 기술 파일)에 들어가는 파라미터가 결국 DH 기반이고, 산업용 로봇 매뉴얼은 모두 DH 테이블을 제공한다. PoE는 이론적으로 더 깔끔하고 연구에서 선호되지만, 현장에서 DH를 모르면 곤란하다. 둘 다 익혀라. ```python # robotics-toolbox-python으로 DH 기반 FK 예제 (Puma 560) import roboticstoolbox as rtb puma = rtb.models.DH.Puma560() q = [0, -np.pi/4, np.pi/4, 0, np.pi/6, 0] # 6개 관절 각도 T = puma.fkine(q) print(T) # 4x4 SE(3) 동차 변환 행렬 출력 print(f"Position: {T.t}") # 끝단 위치 print(f"RPY angles: {T.rpy()}") # Roll-Pitch-Yaw ``` > **추천 자료** > - Lynch & Park, *Modern Robotics*, Chapter 4 — PoE 설명이 가장 잘 되어 있는 교재. 무료 PDF와 Coursera 강의 제공: https://modernrobotics.org > - Craig, *Introduction to Robotics*, Chapter 3 — DH 파라미터의 정석. Modified DH convention 사용. > - Peter Corke, *Robotics, Vision and Control* — Python 코드와 함께 FK를 실습할 수 있다: https://github.com/petercorke/robotics-toolbox-python --- ### 4.3 역기구학 (Inverse Kinematics) FK는 쉽다. 행렬 곱이면 된다. 문제는 IK이다. "끝단을 (x, y, z)에 놓고 싶은데, 관절 각도를 각각 얼마로 해야 하는가?" 이 문제가 어려운 이유: 1. **비선형 방정식** — 삼각함수가 얽혀 있다 2. **다중 해** — 같은 끝단 위치에 도달하는 관절 각도 조합이 여러 개일 수 있다 (elbow-up, elbow-down 등) 3. **해가 없을 수도 있다** — workspace 밖의 점은 도달 불가 4. **무한히 많은 해** — 자유도가 남으면 (redundant manipulator) 해가 무한대 #### 4.3.1 Analytical IK (해석적 방법) 닫힌 형태(closed-form)의 해를 구하는 방법이다. 가능한 경우 가장 빠르고 정확하다. **2-link planar arm의 IK:** 목표 위치 (x, y)가 주어졌을 때: ``` cos(θ_2) = (x² + y² - L1² - L2²) / (2 * L1 * L2) θ_2 = atan2(±√(1 - cos²(θ_2)), cos(θ_2)) θ_1 = atan2(y, x) - atan2(L2*sin(θ_2), L1 + L2*cos(θ_2)) ``` ±에서 보듯이 해가 두 개다 (elbow-up, elbow-down). 이것이 IK의 본질적 어려움이다. ```python def ik_2link(x, y, L1=1.0, L2=1.0, elbow_up=True): """2-link planar arm의 역기구학. 해가 없으면 None 반환.""" d_sq = x**2 + y**2 # 도달 가능 여부 체크 if d_sq > (L1 + L2)**2 or d_sq < (L1 - L2)**2: return None cos_q2 = (d_sq - L1**2 - L2**2) / (2 * L1 * L2) cos_q2 = np.clip(cos_q2, -1.0, 1.0) # 수치 안전 if elbow_up: q2 = np.arctan2(np.sqrt(1 - cos_q2**2), cos_q2) else: q2 = np.arctan2(-np.sqrt(1 - cos_q2**2), cos_q2) q1 = np.arctan2(y, x) - np.arctan2(L2 * np.sin(q2), L1 + L2 * np.cos(q2)) return q1, q2 # 검증: FK → IK → FK target_x, target_y = 1.2, 0.8 result = ik_2link(target_x, target_y) if result: q1, q2 = result x_check, y_check, _ = fk_2link(q1, q2) print(f"Target: ({target_x}, {target_y})") print(f"IK solution: q1={np.degrees(q1):.2f}°, q2={np.degrees(q2):.2f}°") print(f"FK check: ({x_check:.6f}, {y_check:.6f})") print(f"Error: {np.sqrt((x_check-target_x)**2 + (y_check-target_y)**2):.2e}") ``` **6R 매니퓰레이터의 해석적 IK:** 6축 로봇 중 Pieper의 조건을 만족하는 구조 — 마지막 3축이 한 점에서 만나는(spherical wrist) 경우 — 는 해석적으로 풀 수 있다. 대부분의 산업용 6축 로봇(UR, KUKA, ABB 등)이 이 구조이다. 이 경우 위치 문제(처음 3축)와 자세 문제(마지막 3축)를 분리하여 풀 수 있다. 최대 8개의 해가 존재하며, 관절 제한(joint limits)과 이전 관절 각도에 가까운 해를 선택하는 것이 일반적이다. #### 4.3.2 Numerical IK (수치적 방법) 해석적 해가 불가능한 경우 (복잡한 구조, 7축 이상, 비표준 구조) 수치적으로 풀어야 한다. 본질적으로 반복적 최적화 문제이다. **Jacobian Pseudo-Inverse 방법:** ``` Δq = J†(q) * Δx ``` 여기서 J†는 자코비안의 pseudo-inverse이다. 이를 반복하여 목표에 수렴한다. ```python def numerical_ik_2link(target_x, target_y, L1=1.0, L2=1.0, max_iter=100, tol=1e-6): """Jacobian pseudo-inverse 기반 수치적 IK.""" # 초기 추정값 (랜덤 또는 현재 관절 각도) q = np.array([0.5, 0.5]) for i in range(max_iter): # 현재 FK x = L1 * np.cos(q[0]) + L2 * np.cos(q[0] + q[1]) y = L1 * np.sin(q[0]) + L2 * np.sin(q[0] + q[1]) # 오차 error = np.array([target_x - x, target_y - y]) if np.linalg.norm(error) < tol: print(f"수렴: {i+1}회 반복") return q # 자코비안 J = np.array([ [-L1*np.sin(q[0]) - L2*np.sin(q[0]+q[1]), -L2*np.sin(q[0]+q[1])], [ L1*np.cos(q[0]) + L2*np.cos(q[0]+q[1]), L2*np.cos(q[0]+q[1])] ]) # Pseudo-inverse로 관절 각도 업데이트 dq = np.linalg.pinv(J) @ error q += dq print("수렴 실패") return q ``` **Damped Least Squares (DLS, Levenberg-Marquardt):** Pseudo-inverse의 문제는 특이점 근처에서 관절 속도가 폭발한다는 것이다. DLS는 damping factor λ를 추가하여 이를 완화한다: ``` Δq = J^T (J * J^T + λ²I)^{-1} * Δx ``` λ가 크면 특이점 근처에서 안정적이지만 수렴이 느리고, λ가 작으면 pseudo-inverse에 가까워진다. 적응적으로 λ를 조절하는 방법(Nakamura & Hanafusa, 1986)이 실무에서 많이 쓰인다. #### 4.3.3 특이점 (Singularity) 자코비안의 rank가 부족해지는 관절 배치를 특이점(singularity)이라 한다. 특이점에서는: 1. **특정 방향으로 끝단을 움직일 수 없다** — 자유도 상실 2. **미소 이동에 관절 속도가 무한대** — 실제 모터는 이를 따라갈 수 없다 3. **IK 해가 불연속** — 경로 추종 시 갑작스러운 관절 점프 2-link arm의 특이점은 간단하다: θ_2 = 0 (팔이 완전히 펴진 경우) 또는 θ_2 = π (완전히 접힌 경우). 이때 끝단은 반지름 방향으로만 움직일 수 있고, 접선 방향 속도는 낼 수 없다. 6축 로봇의 대표적 특이점: - **Wrist singularity**: 축 4와 6이 정렬됨 (q5 ≈ 0) - **Shoulder singularity**: 끝단이 축 1 위에 위치 - **Elbow singularity**: 팔이 완전히 펴짐 실무 대처법: - 특이점 근처를 피하는 경로 계획 - DLS 방법으로 특이점 통과 시 속도 제한 - Redundancy(여분의 자유도) 활용 #### 4.3.4 IK 솔버들 직접 IK를 구현할 일은 드물다. 검증된 솔버를 사용하는 것이 현명하다. | 솔버 | 방법 | 특징 | |------|------|------| | **KDL** | Numerical (Newton-Raphson) | ROS 기본, 느림, 특이점 취약 | | **IKFast** (OpenRAVE) | Analytical (코드 생성) | 특정 구조에 대해 C++ 코드 자동 생성. 빠름 | | **TRAC-IK** | KDL + SQP 듀얼 | KDL보다 성공률 높음, ROS 패키지 존재 | | **MoveIt2 IK** | 위 솔버들을 통합 | ROS2 생태계, 충돌 회피 통합 | | **pinocchio** | PoE 기반 | 현대적, 빠름, 미분 가능 (differentiable) | ```python # TRAC-IK가 KDL보다 나은 이유: 시간 내 해를 찾을 확률 # 벤치마크 결과 (Beeson & Ames, 2015): # KDL: 해 성공률 ~50-70% (시간 제한 5ms 기준) # TRAC-IK: 해 성공률 ~95-99% (동일 조건) ``` > **추천 자료** > - Beeson & Ames, "TRAC-IK: An Open-Source Library for Improved Solving of Generic Inverse Kinematics" (2015): https://traclabs.com/projects/trac-ik/ > - MoveIt2 IK 문서: https://moveit.picknik.ai/main/doc/concepts/inverse_kinematics.html > - Pinocchio (rigid body dynamics library): https://github.com/stack-of-tasks/pinocchio --- ### 4.4 자코비안 (Jacobian) 자코비안은 기구학에서 가장 많이 쓰이는 도구 중 하나이다. FK가 "위치"의 문제라면, 자코비안은 "속도"의 문제이다. #### 4.4.1 관절 속도 → 끝단 속도 끝단 속도(선속도 v, 각속도 ω)와 관절 속도 q̇의 관계: ``` ẋ = J(q) * q̇ 여기서 ẋ = [v; ω] ∈ ℝ^6 (6축의 경우) q̇ ∈ ℝ^n J(q) ∈ ℝ^{6×n} ``` n < 6이면 under-actuated, n = 6이면 fully-actuated, n > 6이면 redundant이다. #### 4.4.2 힘/토크 관계 (Duality) 자코비안의 전치(transpose)는 끝단 힘을 관절 토크로 매핑한다: ``` τ = J^T(q) * F ``` 여기서 τ는 관절 토크, F는 끝단에 작용하는 힘/모멘트이다. 이것이 **정역학적 이중성(static duality)**이다. 속도와 힘이 자코비안과 그 전치를 통해 쌍대 관계를 이룬다. 파워 보존 원리에서 자연스럽게 유도된다: ``` P = F^T * ẋ = F^T * J * q̇ = (J^T * F)^T * q̇ = τ^T * q̇ ``` 이 관계는 힘 제어(force control)에서 핵심적이다. 끝단에 원하는 힘 F를 가하려면, 각 관절에 τ = J^T * F의 토크를 인가하면 된다. #### 4.4.3 Manipulability Ellipsoid 자코비안은 로봇이 현재 자세에서 "얼마나 잘 움직일 수 있는지"도 알려준다. ``` manipulability index = √det(J * J^T) ``` 이 값이 0이면 특이점이다. 값이 클수록 모든 방향으로 고르게 움직일 수 있다. J * J^T의 고유값(eigenvalue)과 고유벡터(eigenvector)로 타원체(ellipsoid)를 그릴 수 있다. 고유값이 크면 그 방향으로 빠르게 움직일 수 있고, 작으면 느리다. 고유값이 모두 비슷하면 등방적(isotropic)이고, 차이가 크면 비등방적이다. ```python import roboticstoolbox as rtb import numpy as np # Puma 560의 자코비안과 manipulability puma = rtb.models.DH.Puma560() q = [0, -np.pi/4, np.pi/4, 0, np.pi/6, 0] J = puma.jacob0(q) # 6x6 자코비안 (기저 프레임 기준) # Manipulability index m = np.sqrt(np.linalg.det(J @ J.T)) print(f"Manipulability index: {m:.4f}") # 속도 타원체의 주축 (고유값 분석) JJT = J[:3, :] @ J[:3, :].T # 선속도 부분만 eigenvalues, eigenvectors = np.linalg.eigh(JJT) print(f"Velocity ellipsoid semi-axes: {np.sqrt(eigenvalues)}") # Condition number: 등방성 지표 (1에 가까울수록 좋다) sigma = np.linalg.svd(J, compute_uv=False) cond = sigma[0] / sigma[-1] print(f"Condition number: {cond:.2f}") # cond가 1이면 완벽한 등방성, 무한대면 특이점 ``` #### 4.4.4 실용 코드: 자코비안 기반 속도 제어 ```python import numpy as np def jacobian_velocity_control(robot_fk, robot_jacob, q_current, desired_twist, dt=0.001): """ 자코비안 기반 분해 속도 제어 (resolved rate control). Args: robot_fk: FK 함수 (q -> SE3) robot_jacob: 자코비안 함수 (q -> 6xn matrix) q_current: 현재 관절 각도 desired_twist: 원하는 끝단 속도 [vx, vy, vz, wx, wy, wz] dt: 제어 주기 Returns: q_new: 새 관절 각도 """ J = robot_jacob(q_current) # Damped least squares lambda_dls = 0.01 n = J.shape[1] JJT = J @ J.T J_dls = J.T @ np.linalg.inv(JJT + lambda_dls**2 * np.eye(JJT.shape[0])) q_dot = J_dls @ desired_twist # 관절 속도 제한 (실제 로봇에서 필수) max_qdot = 2.0 # rad/s scale = np.max(np.abs(q_dot)) / max_qdot if scale > 1.0: q_dot /= scale q_new = q_current + q_dot * dt return q_new ``` > **추천 자료** > - Siciliano et al., *Robotics: Modelling, Planning and Control*, Chapter 3 — 자코비안의 가장 포괄적인 설명 > - Corke, *Robotics, Vision and Control*, Chapter 8 — 코드 예제와 시각화 포함: https://petercorke.com/rvc/ > - robotics-toolbox-python 문서: https://github.com/petercorke/robotics-toolbox-python --- ### 4.5 메카트로닉스 기초 수학은 여기까지다. 이제 현실로 돌아오자. 관절 각도를 "정한다"고 해서 로봇이 움직이는 것이 아니다. 모터가 있어야 하고, 센서가 있어야 하고, 그 사이를 연결하는 전자 회로와 통신이 있어야 한다. 이것이 메카트로닉스이다. #### 4.5.1 액추에이터 **DC 모터:** 가장 기본적인 액추에이터. 전압을 가하면 회전한다. 토크는 전류에 비례하고 (τ = K_t * i), 역기전력은 속도에 비례한다 (V_emf = K_e * ω). 제어가 쉽고 가격이 저렴하지만, 브러시 마모가 있다. **BLDC (Brushless DC) 모터:** 브러시 없이 전자적으로 전류를 전환한다. 수명이 길고, 토크 밀도가 높고, 효율이 좋다. 현대 로봇의 표준이라 할 수 있다. FOC (Field-Oriented Control)로 제어하면 토크 리플(ripple)을 최소화할 수 있다. **서보 모터 (Dynamixel 시리즈):** 모터 + 감속기 + 엔코더 + 컨트롤러가 일체형으로 패키징된 것이다. Robotis의 Dynamixel 시리즈가 연구용으로 가장 널리 쓰인다. | 모델 | 토크 (Nm) | 통신 | 용도 | |------|-----------|------|------| | XL330 | 0.5 | TTL | 소형 그리퍼, SO-ARM100 등 | | XM540 | 10.0 | RS-485 | 중형 로봇 팔 | | PH54 | 44.7 | RS-485 | 대형 매니퓰레이터, 모바일 로봇 | Dynamixel의 장점: 데이지 체인 연결, 위치/속도/토크 제어 모드, PID 게인 조절 가능, 가격 대비 성능이 좋다. 단점: 통신 속도 한계, 고급 제어를 하려면 커스텀 펌웨어가 필요한 경우가 있다. **Quasi-Direct Drive (QDD):** MIT Mini Cheetah(2019)에서 유명해진 방식이다. 핵심 아이디어는 단순하다: **감속비를 낮추는 것.** 일반적인 로봇 관절: 감속비 100:1 이상 (harmonic drive) QDD: 감속비 6:1 ~ 10:1 (유성기어 또는 belt) 낮은 감속비의 장점: - **높은 백드라이버빌리티(backdrivability)**: 외력이 가해졌을 때 관절이 자연스럽게 따라간다. 충돌 시 안전하고, 힘 제어가 용이하다. - **높은 투명도(transparency)**: 토크 센서 없이도 모터 전류만으로 끝단 힘을 추정할 수 있다. - **높은 대역폭**: 감속기의 마찰과 탄성이 적어 빠른 토크 응답이 가능하다. 단점: 동일 크기 대비 출력 토크가 낮다. 큰 토크가 필요하면 더 큰 모터를 써야 한다. QDD를 사용하는 최근 시스템들: - MIT Mini Cheetah / Cheetah 3 - ALOHA (low-cost bimanual teleop) - Unitree 로봇 시리즈 ``` # QDD vs 전통적 감속기의 토크 제어 비교 # # 전통적 (감속비 100:1, harmonic drive): # 반사 관성 (reflected inertia) = N² × I_motor # → 모터 관성 0.001 kg·m² × 100² = 10 kg·m² # → 끝단에서 느끼는 관성이 매우 크다 # → 정밀한 힘 제어가 어렵다 # # QDD (감속비 8:1): # 반사 관성 = 8² × 0.01 = 0.64 kg·m² # → 15배 이상 가볍다 # → 힘 제어가 훨씬 쉽다 ``` **감속기 종류:** | 종류 | 감속비 | 백래시 | 효율 | 가격 | 용도 | |------|--------|--------|------|------|------| | Planetary | 3~100:1 | 중간 | 85-95% | 저렴 | 범용, QDD에 적합 | | Harmonic Drive | 30~320:1 | 매우 낮음 | 65-85% | 비쌈 | 산업용 로봇, 정밀 | | Cycloidal | 6~120:1 | 낮음 | 85-93% | 중간 | 최근 대안으로 부상 | **액추에이터 선정 기준:** 로봇 관절의 액추에이터를 선정할 때 고려해야 할 것들: 1. **필요 토크 (torque)**: 정적 토크 (자세 유지) + 동적 토크 (가속). 안전 계수 2~3배. 2. **필요 속도 (speed)**: 관절의 최대 각속도. 감속비를 고려하여 모터 RPM 결정. 3. **백드라이버빌리티**: 협동 로봇이나 힘 제어가 필요하면 QDD, 아니면 harmonic drive. 4. **크기와 무게**: 로봇 링크에 장착해야 하므로 물리적 제약 존재. 5. **열 (thermal)**: 연속 토크 사양 확인. 피크 토크는 짧은 시간만 가능. ```python # 간단한 액추에이터 선정 계산 예시 import numpy as np # 목표: 1kg 물체를 팔 끝에서 들어올리기 (팔 길이 0.5m) m_payload = 1.0 # kg m_link = 0.5 # 링크 자체 무게 L = 0.5 # m g = 9.81 # m/s² # 최악의 경우 토크 (수평으로 뻗었을 때) tau_static = (m_payload * L + m_link * L/2) * g print(f"정적 토크: {tau_static:.2f} Nm") # 가속 토크 (최대 각가속도 10 rad/s²) alpha_max = 10.0 # rad/s² I_total = m_payload * L**2 + m_link * (L/2)**2 # 관성 모멘트 (단순화) tau_dynamic = I_total * alpha_max print(f"동적 토크: {tau_dynamic:.2f} Nm") # 총 필요 토크 (안전 계수 2) tau_required = (tau_static + tau_dynamic) * 2.0 print(f"필요 토크 (안전 계수 2): {tau_required:.2f} Nm") # 최대 각속도 → 모터 RPM omega_max = 3.0 # rad/s (관절) gear_ratio = 8 # QDD motor_rpm = omega_max * gear_ratio * 60 / (2 * np.pi) print(f"모터 필요 RPM: {motor_rpm:.0f}") ``` > **추천 자료** > - Katz, "A Low Cost Modular Actuator for Dynamic Robots" (MIT, 2018) — QDD의 핵심 논문: https://dspace.mit.edu/handle/1721.1/118671 > - Dynamixel 제품 라인업 및 문서: https://emanual.robotis.com/ > - Seok et al., "Design Principles for Energy-Efficient Legged Locomotion and Implementation on the MIT Cheetah Robot" (2015) #### 4.5.2 센서 인터페이싱 **엔코더 (Encoder):** 관절 각도를 측정하는 가장 기본적인 센서이다. *Incremental encoder*: A, B 두 채널의 펄스를 세어 상대적 회전량을 측정한다. 전원이 꺼지면 위치를 잊는다 (homing 필요). 가격이 저렴하고, 분해능이 높다 (10,000 PPR 이상도 흔함). *Absolute encoder*: 현재 위치를 절대값으로 출력한다. 전원을 켜자마자 위치를 안다. Multi-turn absolute encoder는 여러 바퀴를 기억한다. 가격이 비싸지만 homing 불필요. 산업용 로봇에서 표준. ``` 분해능 계산 예시: Incremental encoder, 4096 PPR, quadrature decoding (x4) → 분해능 = 360° / (4096 × 4) = 0.022° ≈ 0.38 mrad → 감속비 100:1 관절 → 출력 분해능 0.0038 mrad ``` **토크 센서:** 관절 토크 또는 끝단 힘을 직접 측정한다. 스트레인 게이지(strain gauge) 기반이 대부분이다. *관절 토크 센서 (Joint Torque Sensor, JTS)*: 감속기 출력 측에 장착. KUKA LBR iiwa가 7개 관절 모두에 JTS를 장착하여 힘 제어의 기준을 세웠다. *힘/토크 센서 (F/T Sensor)*: 끝단에 장착하여 6축(Fx, Fy, Fz, Tx, Ty, Tz)을 측정. ATI Industrial Automation의 센서가 연구용 표준이다. 가격이 비싸다 ($3,000~$20,000). **관성 센서 (IMU):** 이미 2장에서 다루었으므로 간략히만 언급한다. 가속도계 + 자이로스코프 + (자력계). 모바일 로봇이나 legged robot의 몸체 자세 추정에 사용. 매니퓰레이터에서는 링크별 IMU를 달아 진동 감쇠에 활용하기도 한다. #### 4.5.3 통신 프로토콜 센서와 액추에이터를 마이크로컨트롤러/PC에 연결하는 방법이다. 로봇 시스템에서 통신은 생각보다 많은 문제를 일으킨다. 지연(latency)이 크면 제어가 불안정해지고, 대역폭이 부족하면 데이터가 누락된다. **기초 프로토콜:** | 프로토콜 | 배선 | 속도 | 거리 | 특징 | |---------|------|------|------|------| | **UART** | 2선 (TX, RX) | ~1 Mbps | ~15m | 가장 단순, 1:1 통신 | | **SPI** | 4선 (MOSI, MISO, SCK, CS) | ~50 Mbps | ~1m (PCB 내) | 빠름, 다수 슬레이브는 CS 추가 | | **I2C** | 2선 (SDA, SCL) | 100k~3.4 Mbps | ~1m | 주소 기반, 센서 연결에 편리 | 이 셋은 마이크로컨트롤러 수준의 기초이다. 로봇 시스템에서는 더 강건한 프로토콜이 필요하다. **CAN Bus:** 자동차 산업에서 시작하여 로봇에서도 표준으로 자리잡았다. 차동 신호(differential signaling)로 노이즈에 강하고, 멀티마스터 구조에 우선순위 기반 중재(arbitration)를 지원한다. - 속도: 최대 1 Mbps (CAN 2.0), 5 Mbps (CAN FD) - 거리: 최대 1km (125 kbps에서) - 토폴로지: 버스 (데이지 체인 가능) 로봇에서의 활용: 모터 드라이버와 메인 컨트롤러 사이 통신. MIT Cheetah, 많은 legged robot이 CAN을 사용한다. ```cpp // CAN bus를 통한 모터 명령 전송 예시 (pseudo-code, STM32 HAL) #include "can.h" struct MotorCommand { float position; // rad float velocity; // rad/s float torque; // Nm float kp; // position gain float kd; // velocity gain }; void send_motor_command(CAN_HandleTypeDef* hcan, uint8_t motor_id, MotorCommand cmd) { CAN_TxHeaderTypeDef header; header.StdId = motor_id; // 각 모터에 고유 CAN ID header.DLC = 8; // 8 bytes (CAN 2.0 기본) header.RTR = CAN_RTR_DATA; // 부동소수점을 정수로 패킹 (로봇 모터 드라이버의 일반적 방식) uint8_t data[8]; int16_t pos_int = (int16_t)(cmd.position / 0.001f); // 0.001 rad 단위 int16_t vel_int = (int16_t)(cmd.velocity / 0.01f); // 0.01 rad/s 단위 int16_t tau_int = (int16_t)(cmd.torque / 0.01f); // 0.01 Nm 단위 int16_t kp_int = (int16_t)(cmd.kp / 0.01f); data[0] = pos_int >> 8; data[1] = pos_int & 0xFF; data[2] = vel_int >> 8; data[3] = vel_int & 0xFF; data[4] = tau_int >> 8; data[5] = tau_int & 0xFF; data[6] = kp_int >> 8; data[7] = kp_int & 0xFF; uint32_t mailbox; HAL_CAN_AddTxMessage(hcan, &header, data, &mailbox); } ``` **EtherCAT:** 산업용 실시간 이더넷 프로토콜이다. 일반 이더넷 하드웨어를 사용하면서 마이크로초 단위의 결정적(deterministic) 통신을 제공한다. 왜 로봇에서 쓰는가: - **속도**: 100 Mbps, 수십~수백 개 노드를 마이크로초 주기로 동기화 - **결정론적 타이밍**: 패킷 지연이 일정 → 실시간 제어에 적합 - **처리 방식**: 마스터가 보낸 프레임을 각 슬레이브가 on-the-fly로 읽고 쓴다 (프레임이 지나가면서 처리). 대역폭 효율이 극히 높다. KUKA, Beckhoff, 그리고 최근의 많은 연구용 로봇 플랫폼이 EtherCAT을 사용한다. 단점: 전용 마스터 소프트웨어 필요 (SOEM, IgH EtherCAT Master 등), 설정이 복잡하다. 취미 수준에서는 과도한 선택이다. **RS-485 / Dynamixel Protocol:** Dynamixel 서보의 통신 방식이다. RS-485는 차동 신호 기반의 시리얼 통신으로, 최대 1 Mbps, 여러 장치를 데이지 체인으로 연결할 수 있다. ```python # Dynamixel SDK를 이용한 서보 제어 예시 from dynamixel_sdk import * PROTOCOL_VERSION = 2.0 BAUDRATE = 1000000 DEVICENAME = '/dev/ttyUSB0' DXL_ID = 1 # 포트 열기 port = PortHandler(DEVICENAME) packet = PacketHandler(PROTOCOL_VERSION) port.openPort() port.setBaudRate(BAUDRATE) # 토크 활성화 ADDR_TORQUE_ENABLE = 64 packet.write1ByteTxRx(port, DXL_ID, ADDR_TORQUE_ENABLE, 1) # 목표 위치로 이동 (단위: 0~4095, 0~360도) ADDR_GOAL_POSITION = 116 goal_position = 2048 # 중앙 (180도) packet.write4ByteTxRx(port, DXL_ID, ADDR_GOAL_POSITION, goal_position) # 현재 위치 읽기 ADDR_PRESENT_POSITION = 132 pos, _, _ = packet.read4ByteTxRx(port, DXL_ID, ADDR_PRESENT_POSITION) print(f"현재 위치: {pos} (= {pos * 360 / 4096:.1f}°)") ``` #### 4.5.4 실시간 시스템 로봇 제어에서 "실시간(real-time)"은 "빠른"이 아니라 **"정해진 시간 안에 반드시 완료되는"**을 의미한다. 1kHz 제어 루프라면, 매 1ms마다 센서 읽기 → 제어 계산 → 모터 명령 전송이 완료되어야 한다. 한 번이라도 지연되면 로봇이 불안정해질 수 있다. **RTOS (Real-Time Operating System):** | RTOS | 특징 | 용도 | |------|------|------| | **FreeRTOS** | 경량, 마이크로컨트롤러용, 무료 | STM32, ESP32 등 | | **Zephyr** | 최신, 다양한 하드웨어 지원, Linux Foundation | IoT, 로봇 임베디드 | | **VxWorks** | 상용, NASA도 사용 | 항공우주, 산업용 | 마이크로컨트롤러에서 직접 모터를 제어할 때는 RTOS를 쓴다. 태스크 우선순위를 설정하여 제어 루프가 다른 태스크에 밀리지 않도록 한다. **PREEMPT_RT Linux:** 문제: ROS2는 Linux에서 돌아간다. 그런데 일반 Linux 커널은 실시간이 아니다. 스케줄러가 제어 스레드를 아무 때나 중단시킬 수 있고, 수 밀리초의 지연이 발생할 수 있다. 해결: PREEMPT_RT 패치를 적용한 Linux 커널. 커널 대부분의 코드 경로를 선점(preemptible)으로 만들어서 실시간에 가까운 성능을 제공한다. 설정 방법 (개략): ```bash # 1. PREEMPT_RT 패치가 적용된 커널 설치 (Ubuntu 예시) sudo apt install linux-image-rt-amd64 # Debian/Ubuntu # 2. GRUB에서 RT 커널로 부팅 설정 # 3. 제어 스레드에 실시간 우선순위 부여 sudo chrt -f 99 ./my_robot_controller # 4. CPU isolation (선택적이지만 권장) # /etc/default/grub에 isolcpus=2,3 추가 # → CPU 2, 3을 일반 프로세스에서 격리 # → 제어 스레드를 이 CPU에 고정(affinity) # 5. 성능 확인 sudo cyclictest -m -p 99 -t 1 -n # 최대 지연(max latency)이 50μs 이하면 양호 ``` **왜 1kHz인가:** 로봇 제어 루프의 표준 주기가 1kHz (1ms)인 이유: 1. **임피던스/힘 제어**: 기계적 공진 주파수보다 충분히 높은 제어 주파수가 필요하다. 대부분의 로봇 팔은 수~수십 Hz의 고유 진동수를 가지므로, 안정적 제어를 위해 적어도 10배 이상 (→ 수백 Hz~1kHz)이 필요하다. 2. **Nyquist 정리**: 100Hz의 동적 현상을 제어하려면 최소 200Hz 샘플링이 필요하고, 실제로는 5~10배 (→ 1kHz) 정도가 바람직하다. 3. **통신 대역폭**: CAN bus 1 Mbps로 10개 모터를 1kHz로 제어하면 꽉 찬다. 그 이상은 EtherCAT이 필요하다. 4. **관행**: MIT Cheetah가 CAN + 1kHz로 인상적인 결과를 보여준 이후 QDD + 1kHz가 일종의 표준이 되었다. 고주파 제어 (5~10kHz)가 필요한 경우: 매우 가벼운 로봇 (낮은 관성), 고속 충돌 대응, 일부 촉각 제어. 이 경우 EtherCAT이나 FPGA 기반 제어가 필요하다. > **추천 자료** > - FreeRTOS 공식 문서: https://www.freertos.org/ > - PREEMPT_RT Wiki: https://wiki.linuxfoundation.org/realtime/start > - Dynamixel SDK: https://github.com/ROBOTIS-GIT/DynamixelSDK > - IgH EtherCAT Master (Linux용 오픈소스): https://etherlab.org/en/ethercat/ > - SOEM (Simple Open EtherCAT Master): https://github.com/OpenEtherCATsociety/SOEM --- ### 4.6 심화: Workspace Analysis와 최적 설계 *연구자가 되고 싶다면 여기서부터 읽어라.* 기구학은 "주어진 로봇을 어떻게 움직이나"의 문제이기도 하지만, "어떤 로봇을 설계해야 하나"의 문제이기도 하다. 이 절은 설계 최적화와 관련된 고급 주제를 다룬다. #### 4.6.1 Reachable Workspace vs Dexterous Workspace **Reachable workspace**: 끝단이 적어도 하나의 자세(orientation)로 도달할 수 있는 모든 점의 집합. "어디까지 손이 닿는가." **Dexterous workspace**: 끝단이 임의의 자세로 도달할 수 있는 점의 집합. "어디서 자유롭게 움직일 수 있는가." 당연히 reachable workspace의 부분집합이고, 보통 훨씬 작다. 6-DOF 로봇의 경우, dexterous workspace는 상당히 제한적일 수 있다. 이것이 7-DOF 로봇이 존재하는 이유 중 하나이다. Workspace 분석은 Monte Carlo 방법으로 수행할 수 있다: 관절 공간을 무작위로 샘플링하고, FK로 끝단 위치를 계산하여 점구름(point cloud)을 만든다. ```python import numpy as np import roboticstoolbox as rtb # Puma 560의 workspace 시각화 (Monte Carlo) puma = rtb.models.DH.Puma560() n_samples = 50000 positions = [] for _ in range(n_samples): # 각 관절의 범위 내에서 무작위 샘플링 q = puma.random_q() T = puma.fkine(q) positions.append(T.t) # [x, y, z] positions = np.array(positions) # 시각화 (matplotlib) import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D fig = plt.figure(figsize=(10, 8)) ax = fig.add_subplot(111, projection='3d') ax.scatter(positions[:, 0], positions[:, 1], positions[:, 2], s=0.1, alpha=0.1, c='blue') ax.set_xlabel('X (m)') ax.set_ylabel('Y (m)') ax.set_zlabel('Z (m)') ax.set_title('Puma 560 Reachable Workspace') plt.savefig('workspace.png', dpi=150) ``` #### 4.6.2 Condition Number와 Isotropy 자코비안의 condition number (κ)는 로봇이 특정 자세에서 얼마나 "잘" 움직일 수 있는지의 지표이다. ``` κ(J) = σ_max / σ_min ``` σ_max, σ_min은 자코비안의 최대/최소 특이값(singular value)이다. - κ = 1: 완벽한 등방성 (isotropic). 모든 방향으로 균일하게 움직인다. 실현 불가능하지만 이상적. - κ → ∞: 특이점. 한 방향으로는 전혀 움직이지 못한다. 로봇 설계 시 작업 영역 전체에 걸쳐 condition number를 최소화하는 것이 목표가 될 수 있다. 이를 **kinematic optimization** 또는 **optimal design**이라 한다. 주의: 자코비안의 condition number를 계산할 때, 선속도(m/s)와 각속도(rad/s)의 단위가 다르므로 직접 비교하면 의미가 없다. 특성 길이(characteristic length)로 정규화하거나, 선속도와 각속도를 별도로 분석해야 한다. 이 문제는 로봇 기구학 최적화에서 오래된 논쟁거리이다. #### 4.6.3 Redundancy Resolution (7-DOF Arms) 7-DOF 로봇 팔 (Kinova Gen3, KUKA LBR iiwa, Franka Emika Panda 등)은 6-DOF 작업 공간에 비해 자유도가 1개 남는다. 이 여분의 자유도를 **kinematic redundancy**라 한다. 같은 끝단 자세를 유지하면서 팔 전체의 형태(configuration)를 바꿀 수 있다. 사람 팔이 주먹의 위치를 고정한 채 팔꿈치를 올리거나 내리는 것과 같다. 이 자유도를 활용하는 전략: 1. **특이점 회피**: 자코비안의 manipulability를 최대화하는 방향으로 여분 자유도 사용 2. **관절 제한 회피**: 관절이 한계에 가까워지면 여분 자유도로 중앙 위치 복귀 3. **장애물 회피**: 팔꿈치가 장애물과 충돌하지 않도록 형태 조정 4. **에너지 최적화**: 토크를 최소화하는 자세 선택 수학적으로, 여분 자유도는 자코비안의 null space에 해당한다: ``` q̇ = J† * ẋ + (I - J† * J) * q̇_0 ``` 첫째 항은 끝단 속도를 달성하는 최소 norm 관절 속도이다. 둘째 항 (I - J†J)은 null space projector로, 끝단 속도에 영향을 주지 않으면서 관절을 움직인다. q̇_0는 2차 목적(예: manipulability 최대화)의 그래디언트이다. ```python def redundancy_resolution(J, x_dot, q, q_center, k_null=0.5): """ 7-DOF 로봇의 redundancy resolution. Args: J: 6x7 자코비안 x_dot: 6x1 원하는 끝단 속도 q: 7x1 현재 관절 각도 q_center: 7x1 관절 중앙값 (null space 목표) k_null: null space 게인 Returns: q_dot: 7x1 관절 속도 """ # Damped pseudo-inverse lam = 0.01 J_pinv = J.T @ np.linalg.inv(J @ J.T + lam**2 * np.eye(6)) # 1차 목적: 끝단 속도 추종 q_dot_primary = J_pinv @ x_dot # 2차 목적: 관절 중앙으로 복귀 (null space) null_projector = np.eye(7) - J_pinv @ J q_dot_null = null_projector @ (k_null * (q_center - q)) return q_dot_primary + q_dot_null ``` > **추천 자료** > - Siciliano et al., *Robotics: Modelling, Planning and Control*, Chapter 3.9 — Redundancy resolution 상세 설명 > - Nakamura, "Advanced Robotics: Redundancy and Optimization" (1991) — 고전 > - Dietrich et al., "An Overview of Null Space Projections for Redundant, Torque-Controlled Robots" (2015) > - Franka Emika 연구 인터페이스: https://frankaemika.github.io/docs/ --- ### 4.7 추천 자료 종합 정리이다. 기구학과 메카트로닉스를 진지하게 공부하려면, 교재 하나를 처음부터 끝까지 풀어보는 것이 가장 효과적이다. **교재:** - **Craig, "Introduction to Robotics: Mechanics and Planning"** — DH 파라미터와 기구학의 정석. Modified DH convention을 사용한다. 학부 수준에서 가장 적합하다. - **Lynch & Park, "Modern Robotics: Mechanics, Planning, and Control"** — PoE 기반. 무료 PDF와 Coursera 강의를 제공하여 접근성이 뛰어나다. 수학적으로 더 깔끔하지만 처음 보면 어렵다. https://modernrobotics.org - **Corke, "Robotics, Vision and Control"** — MATLAB/Python 코드와 함께 기구학을 실습할 수 있다. robotics-toolbox-python은 이 책의 동반 라이브러리이다. 3판은 Python 기반. https://petercorke.com/rvc/ - **Siciliano et al., "Robotics: Modelling, Planning and Control"** — 가장 포괄적인 대학원 교과서. 기구학, 동역학, 제어를 모두 다룬다. 두껍지만 그만큼 빠짐없다. **온라인 강의:** - Modern Robotics, Coursera (Northwestern University): https://www.coursera.org/specializations/modernrobotics - Introduction to Robotics, Stanford CS223A (Khatib): https://see.stanford.edu/Course/CS223A **소프트웨어/라이브러리:** - robotics-toolbox-python: https://github.com/petercorke/robotics-toolbox-python - Pinocchio (고속 동역학, 미분 가능 기구학): https://github.com/stack-of-tasks/pinocchio - MoveIt2 (ROS2 모션 플래닝): https://moveit.picknik.ai/ - Drake (시뮬레이션 + 최적화 + 제어): https://drake.mit.edu/ --- ### 기술 흐름 ``` 1955 ── DH 파라미터 제안 (Denavit & Hartenberg) 1969 ── Stanford Arm (최초의 전기식 컴퓨터 제어 로봇 팔) 1985 ── Product of Exponentials 정형화 1998 ── Harmonic Drive 로봇 적용 확산 2019 ── MIT Mini Cheetah: QDD 액추에이터 2019 ── MoveIt2 (ROS2 기반 모션 플래닝 프레임워크) 2023 ── ALOHA: 저비용 양팔 텔레오퍼레이션 플랫폼 2024 ── SO-ARM100: 오픈소스 5축 로봇 팔 ($200 이하) ``` --- *다음 장에서는 이 기구학 위에 힘과 질량을 얹는다: 동역학(Dynamics)과 제어(Control). 관절 각도를 "원하는 값으로 보내는" 것이 아니라, "원하는 토크를 가하는" 관점으로 전환된다.* --- ## 5. 강체 역학 & 동역학 (Rigid Body Dynamics) --- ### 5.1 왜 동역학을 배우는가 기구학(kinematics)이 "로봇이 **어디로** 움직이는가"를 다룬다면, 동역학(dynamics)은 "**어떤 힘으로** 움직이는가"를 다룬다. 기구학만으로 로봇을 제어할 수 있는 경우도 있다. 느리게 움직이는 산업용 매니퓰레이터가 그렇다. 관절 속도가 충분히 낮으면 관성력과 코리올리 힘이 무시할 만하고, PID 제어기가 나머지를 알아서 잡아준다. 그런데 다음과 같은 상황에서는 동역학 없이 버틸 수 없다: - **고속 매니퓰레이션**: 산업 현장에서 cycle time을 줄이려면 로봇을 빠르게 움직여야 한다. 빠르게 움직이면 관성력, 원심력, 코리올리 힘이 커진다. 이걸 무시하면 경로 추종 오차가 커지고, 최악의 경우 관절 모터가 포화(saturation)된다. - **보행 로봇 (legged robots)**: 두 발이든 네 발이든, 지면과의 접촉력을 관리하면서 넘어지지 않아야 한다. 이건 순수하게 동역학 문제다. - **시뮬레이션**: 물리 시뮬레이터는 힘/토크를 받아서 가속도를 계산하고, 이를 적분하여 다음 상태를 구한다. 동역학 모델이 곧 시뮬레이터의 핵심이다. - **최적 제어 (optimal control)**: 에너지를 최소화하거나 시간을 최소화하는 궤적을 찾으려면 동역학 모델이 constraints로 들어간다. - **충돌/접촉 처리**: 물체를 잡거나(grasp), 밀거나(push), 던지는(throw) 작업은 접촉 역학 없이 불가능하다. 정리하면, 기구학은 로봇의 "기하학"이고, 동역학은 로봇의 "물리학"이다. 기하학만으로 세상을 이해할 수 없듯이, 기구학만으로 로봇을 완전히 제어할 수 없다. > **추천 자료** > - Featherstone, *Rigid Body Dynamics Algorithms*, Chapter 1 — 동역학이 왜 필요한지를 간결하게 설명한다. > - Russ Tedrake, *Underactuated Robotics* Ch.1 (https://underactuated.csail.mit.edu/) — 동역학 기반 제어가 왜 기구학 기반보다 강력한지 직관적으로 보여준다. --- ### 5.2 뉴턴-오일러 역학 (Newton-Euler Formulation) #### 기본 원리 뉴턴 역학의 핵심은 두 가지다: **병진 운동 (translational motion):** ``` F = ma ``` 물체에 작용하는 합력 F는 질량 m과 질량중심(CoM) 가속도 a의 곱이다. **회전 운동 (rotational motion):** ``` τ = Iα + ω × (Iω) ``` 물체에 작용하는 합토크 τ는 관성 텐서 I와 각가속도 α의 곱에, 자이로스코픽 항 ω × (Iω)를 더한 것이다. 2D에서는 뒤의 항이 사라져서 τ = Iα로 단순해지지만, 3D에서는 반드시 포함해야 한다. 이걸 빠뜨리면 시뮬레이션에서 로봇이 귀신 같은 거동을 보인다. #### Recursive Newton-Euler Algorithm (RNEA) 직렬 매니퓰레이터(serial manipulator)의 역동역학(inverse dynamics)을 푸는 가장 효율적인 방법이다. 핵심 아이디어는 간단하다: **Forward pass (base → end-effector):** 각 링크의 속도와 가속도를 순방향으로 전파한다. 링크 i의 속도는 링크 i-1의 속도에 관절 i의 기여분을 더한 것이다. **Backward pass (end-effector → base):** 각 링크에 작용하는 힘과 토크를 역방향으로 전파한다. 뉴턴-오일러 방정식으로 링크 i에 필요한 합력/합토크를 구하고, 이를 관절 i의 토크로 변환한다. 왜 재귀적(recursive)으로 푸는가? 단일 강체의 동역학은 O(1)이다. n개의 링크를 순차적으로 처리하면 O(n)이다. 반면 라그랑주 방정식을 직접 풀면 M(q) 행렬 계산에 O(n^3)이 걸린다. 관절 수가 많은 로봇(예: humanoid의 30+ DOF)에서 이 차이는 실시간 제어 가능 여부를 결정한다. RNEA의 의사 코드를 정리하면 다음과 같다: ``` RNEA(model, q, q̇, q̈): # Forward pass: i = 1, 2, ..., n for i = 1 to n: v[i] = v[i-1] + S[i] * q̇[i] # 관절축 방향 속도 추가 a[i] = a[i-1] + S[i] * q̈[i] + v[i] × (S[i] * q̇[i]) f[i] = I[i] * a[i] + v[i] × (I[i] * v[i]) # Newton-Euler # Backward pass: i = n, n-1, ..., 1 for i = n downto 1: τ[i] = S[i]^T * f[i] # 관절 토크 추출 f[parent(i)] += f[i] # 부모 링크로 전파 return τ ``` 여기서 S[i]는 관절 i의 motion subspace matrix (관절 축 방향), v[i]는 링크 i의 공간 속도, I[i]는 링크 i의 공간 관성이다. 이 표기는 Featherstone의 spatial vector 표기를 따른 것인데, 5.7절에서 더 자세히 다룬다. #### 실제 코드: Pinocchio Pinocchio는 RNEA를 포함한 다양한 동역학 알고리즘을 구현한 C++/Python 라이브러리이다. 다음은 RNEA로 역동역학을 계산하는 예시다: ```python import pinocchio as pin import numpy as np # URDF에서 모델 로드 model = pin.buildModelFromUrdf("robot.urdf") data = model.createData() # 현재 상태 설정 q = pin.randomConfiguration(model) # 관절 위치 v = np.random.randn(model.nv) # 관절 속도 a = np.random.randn(model.nv) # 관절 가속도 # RNEA: (q, v, a) → τ tau = pin.rnea(model, data, q, v, a) print("Joint torques:", tau) # 중력 토크만 계산 (v=0, a=0) tau_g = pin.rnea(model, data, q, np.zeros(model.nv), np.zeros(model.nv)) print("Gravity compensation torques:", tau_g) ``` 중력 보상(gravity compensation)은 RNEA에서 v=0, a=0을 넣으면 바로 나온다. 이것만으로도 로봇이 중력에 의해 처지지 않게 할 수 있다. 실무에서 가장 먼저 구현하는 제어기 중 하나이다. Drake에서의 동일한 계산: ```python from pydrake.multibody.plant import MultibodyPlant from pydrake.multibody.parsing import Parser import numpy as np plant = MultibodyPlant(time_step=0.0) Parser(plant).AddModels("robot.urdf") plant.Finalize() context = plant.CreateDefaultContext() q = np.random.randn(plant.num_positions()) v = np.random.randn(plant.num_velocities()) vdot = np.random.randn(plant.num_velocities()) plant.SetPositions(context, q) plant.SetVelocities(context, v) # Inverse dynamics: vdot → τ tau = plant.CalcInverseDynamics(context, vdot, MultibodyForces(plant)) ``` > **추천 자료** > - Featherstone, *Rigid Body Dynamics Algorithms*, Chapter 5 — RNEA의 원본 설명 > - Pinocchio documentation (https://github.com/stack-of-tasks/pinocchio) — 실무에서 RNEA를 가장 쉽게 써볼 수 있는 라이브러리 > - Luh, Walker, Paul (1980), "On-Line Computational Scheme for Mechanical Manipulators" — RNEA의 원 논문 --- ### 5.3 라그랑주 역학 (Lagrangian Mechanics) #### Lagrangian이란 라그랑주 역학은 힘과 토크를 직접 다루는 대신, 에너지를 통해 운동 방정식을 유도한다. **Lagrangian** L은 다음과 같이 정의된다: ``` L(q, q̇) = T(q, q̇) - V(q) ``` 여기서 T는 시스템의 운동에너지(kinetic energy), V는 위치에너지(potential energy)이다. **Euler-Lagrange 방정식:** ``` d/dt (∂L/∂q̇_i) - ∂L/∂q_i = τ_i ``` 각 일반화 좌표(generalized coordinate) q_i에 대해 이 방정식을 세우면, 시스템의 운동 방정식이 나온다. 이것의 장점은 좌표계 선택의 자유도다. 뉴턴 역학에서는 각 링크의 질량중심 위치와 자세를 월드 프레임에서 표현하고, 구속 조건(constraint)을 관리해야 한다. 라그랑주 역학에서는 관절 각도를 일반화 좌표로 선택하면 구속 조건이 자동으로 사라진다. #### Manipulator Equation n-DOF 직렬 매니퓰레이터의 Euler-Lagrange 방정식을 정리하면 다음과 같은 표준 형태가 나온다: ``` M(q)q̈ + C(q, q̇)q̇ + g(q) = τ ``` 각 항의 의미: - **M(q)**: 질량/관성 행렬 (mass/inertia matrix). n×n 대칭 양정치(symmetric positive definite) 행렬이다. 로봇의 자세 q에 따라 달라진다 — 팔을 쭉 펴면 관성이 커지고, 접으면 작아지는 것과 같은 원리다. - **C(q, q̇)q̇**: 코리올리 및 원심력 항 (Coriolis and centrifugal terms). 관절들이 동시에 움직일 때 발생하는 관성 커플링이다. 느리게 움직이면 무시해도 되지만, 빠르게 움직이면 이 항이 크다. - **g(q)**: 중력 벡터 (gravity vector). 로봇이 중력장에 있을 때 각 관절에 작용하는 중력 토크이다. - **τ**: 관절 토크 벡터. 모터가 내는 힘이다. 마찰(friction)은 보통 별도로 모델링하여 더한다. 이 방정식이 로보틱스 동역학의 핵심이다. 제어, 시뮬레이션, 궤적 최적화 전부 이 방정식에서 출발한다. #### 2-Link Planar Arm 예제 2-link planar arm은 동역학 입문에서 빠지지 않는 예제이다. 노트에 직접 유도해보는 것을 강력히 권한다 — 한 번 해보면 n-DOF 경우의 구조가 명확해진다. 설정: - 링크 길이: l_1, l_2 - 링크 질량: m_1, m_2 (질량이 링크 끝에 집중된다고 가정 — point mass) - 관절 각도: q_1, q_2 (base에서부터) - 중력: g (아래 방향) **운동에너지 T:** 링크 1 끝점의 위치: ``` x_1 = l_1 cos(q_1) y_1 = l_1 sin(q_1) ``` 링크 2 끝점의 위치: ``` x_2 = l_1 cos(q_1) + l_2 cos(q_1 + q_2) y_2 = l_1 sin(q_1) + l_2 sin(q_1 + q_2) ``` 각 질량의 속도를 구하고 T = (1/2)m_1 v_1^2 + (1/2)m_2 v_2^2 을 전개하면: ``` T = (1/2)(m_1 + m_2) l_1^2 q̇_1^2 + (1/2) m_2 l_2^2 (q̇_1 + q̇_2)^2 + m_2 l_1 l_2 cos(q_2) q̇_1 (q̇_1 + q̇_2) ``` **위치에너지 V:** ``` V = m_1 g l_1 sin(q_1) + m_2 g [l_1 sin(q_1) + l_2 sin(q_1 + q_2)] ``` **M(q) 행렬:** ``` M(q) = [ (m_1+m_2)l_1^2 + m_2 l_2^2 + 2 m_2 l_1 l_2 cos(q_2) m_2 l_2^2 + m_2 l_1 l_2 cos(q_2) ] [ m_2 l_2^2 + m_2 l_1 l_2 cos(q_2) m_2 l_2^2 ] ``` M(q)가 q_2에 의존하는 것에 주목하라. q_2 = 0이면 팔이 쭉 펴진 상태이고, 관성이 최대다. q_2 = π이면 팔이 접힌 상태이고, 관성이 최소다. **C(q, q̇) 행렬:** ``` C(q, q̇) = [ -m_2 l_1 l_2 sin(q_2) q̇_2 -m_2 l_1 l_2 sin(q_2)(q̇_1 + q̇_2) ] [ m_2 l_1 l_2 sin(q_2) q̇_1 0 ] ``` C 행렬의 유도 방법은 여러 가지가 있다(Christoffel symbols 등). 가장 체계적인 방법은 Christoffel symbols를 쓰는 것이지만, 2-link의 경우 Euler-Lagrange 방정식에서 직접 항을 정리하는 편이 빠르다. **g(q) 벡터:** ``` g(q) = [ (m_1 + m_2) g l_1 cos(q_1) + m_2 g l_2 cos(q_1 + q_2) ] [ m_2 g l_2 cos(q_1 + q_2) ] ``` 이것을 SymPy로 검증하는 코드: ```python import sympy as sp q1, q2, dq1, dq2, ddq1, ddq2 = sp.symbols('q1 q2 dq1 dq2 ddq1 ddq2') m1, m2, l1, l2, g = sp.symbols('m1 m2 l1 l2 g', positive=True) # 위치 x1 = l1 * sp.cos(q1) y1 = l1 * sp.sin(q1) x2 = x1 + l2 * sp.cos(q1 + q2) y2 = y1 + l2 * sp.sin(q1 + q2) # 속도 (chain rule) vx1 = sp.diff(x1, q1) * dq1 vy1 = sp.diff(y1, q1) * dq1 vx2 = sp.diff(x2, q1) * dq1 + sp.diff(x2, q2) * dq2 vy2 = sp.diff(y2, q1) * dq1 + sp.diff(y2, q2) * dq2 # 운동에너지 T = sp.Rational(1,2)*m1*(vx1**2 + vy1**2) + sp.Rational(1,2)*m2*(vx2**2 + vy2**2) T = sp.trigsimp(sp.expand(T)) # 위치에너지 V = m1*g*y1 + m2*g*y2 # Lagrangian L = T - V # Euler-Lagrange equations # d/dt(∂L/∂q̇_i) - ∂L/∂q_i = τ_i # 여기서 d/dt는 q1, q2에 대한 시간 미분을 포함해야 하므로 치환이 필요하다. # 간단하게 M, C, g를 추출하는 것은 교재를 참고하라. print("T =", T) print("V =", V) ``` 이 코드를 직접 실행해보면 위에서 손으로 유도한 결과와 일치하는 것을 확인할 수 있다. SymPy가 trigsimp을 적용하면 깔끔한 형태가 나온다. > **추천 자료** > - Murray, Li, Sastry, *A Mathematical Introduction to Robotic Manipulation*, Ch. 4 (https://www.cds.caltech.edu/~murray/mlswiki/) — 라그랑주 역학을 로보틱스 맥락에서 가장 엄밀하게 다룬 교재. 무료 PDF 제공. > - Spong, Hutchinson, Vidyasagar, *Robot Modeling and Control*, Ch. 6-7 — 학부 수준에서 가장 접근하기 쉬운 설명 > - Craig, *Introduction to Robotics*, Ch. 6 — 2-link arm 예제가 상세히 나와 있다 --- ### 5.4 뉴턴-오일러 vs 라그랑주 이 둘은 같은 물리를 다른 관점에서 보는 것이다. 최종 결과(운동 방정식)는 동일하다. 차이는 유도 과정과 계산 효율에 있다. | 항목 | 뉴턴-오일러 (RNEA) | 라그랑주 | |------|-------------------|---------| | 관점 | 힘/토크 (force-based) | 에너지 (energy-based) | | 계산 복잡도 | O(n) | O(n^3) (M 행렬 직접 계산 시) | | 유도 난이도 | 재귀적이라 n이 커져도 같은 패턴 | n이 커지면 편미분이 폭발 | | 물리적 직관 | 각 링크의 힘/토크를 직접 볼 수 있음 | 에너지 보존/변환을 볼 수 있음 | | 주 용도 | 실시간 제어, 시뮬레이션 | 모델 유도, 에너지 기반 분석, Lyapunov 안정성 | | 구속력 | 명시적으로 계산 가능 | 일반화 좌표 사용 시 자동으로 소거 | 실무에서의 일반적인 워크플로우: 1. **모델 유도**: 라그랑주 역학으로 manipulator equation의 구조를 이해한다. 2. **수치 계산**: RNEA(또는 ABA)로 실시간 계산한다. 3. **제어기 설계**: manipulator equation의 구조 (M, C, g)를 이용한 computed torque control, passivity-based control 등을 설계한다. 4. **코드 구현**: Pinocchio나 Drake가 내부적으로 RNEA/ABA를 사용하므로, 라이브러리를 호출하면 된다. 결국 둘 다 알아야 한다. 라그랑주를 모르면 제어 이론을 이해할 수 없고, 뉴턴-오일러를 모르면 실시간 구현을 할 수 없다. > **추천 자료** > - Featherstone, *Rigid Body Dynamics Algorithms*, Ch. 3 — 두 formulation의 관계를 명확히 설명 > - Siciliano et al., *Robotics: Modelling, Planning and Control*, Ch. 7 — 두 방법으로 같은 로봇의 동역학을 유도하는 비교 예제 --- ### 5.5 Forward Dynamics vs Inverse Dynamics 동역학에는 두 가지 "방향"이 있다: **Inverse Dynamics (역동역학):** ``` 주어진 것: q, q̇, q̈ 구하는 것: τ ``` "이 궤적을 따라가려면 모터가 얼마의 토크를 내야 하는가?"에 답한다. 제어에서 주로 사용한다. Computed torque control의 핵심이다. **Forward Dynamics (순동역학):** ``` 주어진 것: q, q̇, τ 구하는 것: q̈ ``` "이 토크를 가하면 로봇이 어떻게 가속하는가?"에 답한다. 시뮬레이션의 핵심이다. 시뮬레이터의 매 time step에서 이 계산이 수행된다. 수학적으로 forward dynamics는 manipulator equation에서 q̈을 풀어내는 것이다: ``` q̈ = M(q)^{-1} [τ - C(q, q̇)q̇ - g(q)] ``` 단순히 M(q)의 역행렬을 구하면 O(n^3)이다. 이건 관절 수가 많으면 느리다. #### Articulated Body Algorithm (ABA) Featherstone이 제안한 ABA는 forward dynamics를 O(n)에 계산한다. RNEA가 inverse dynamics의 O(n) 알고리즘이듯, ABA는 forward dynamics의 O(n) 알고리즘이다. ABA의 핵심 아이디어: 각 링크를 "articulated body"로 보고, 해당 서브트리의 관성을 재귀적으로 합산한다. M 행렬을 명시적으로 구성하지 않고도 q̈을 직접 계산할 수 있다. ``` ABA(model, q, q̇, τ): # Pass 1 (forward): 속도 전파 for i = 1 to n: v[i] = v[parent(i)] + S[i] * q̇[i] c[i] = v[i] × (S[i] * q̇[i]) # Coriolis acceleration # Pass 2 (backward): articulated body inertia 계산 for i = n downto 1: I_A[i] = I[i] # spatial inertia p_A[i] = v[i] × (I[i] * v[i]) - f_ext[i] # bias force # 자식 링크들의 기여를 합산 (생략) # 관절 가속도의 중간값 계산 # Pass 3 (forward): 가속도 전파 for i = 1 to n: q̈[i] = ... # articulated body inertia를 이용해 계산 a[i] = a[parent(i)] + S[i] * q̈[i] + c[i] return q̈ ``` 실제 구현은 상당히 복잡하다. 직접 구현하기보다 Pinocchio나 Drake를 사용하는 것이 현명하다. #### 시뮬레이터에서의 역할 시뮬레이터마다 사용하는 알고리즘이 다르다: - **MuJoCo**: forward dynamics에 자체 알고리즘을 사용한다. 접촉까지 포함한 통합 solver가 특징이다. 내부적으로 sparse factorization을 활용하며, 분기형(branching) 구조에 특화되어 있다. - **Drake**: MultibodyPlant에서 ABA를 사용한다. 접촉은 별도의 solver(time-stepping, hydroelastic 등)로 처리한다. - **Bullet (PyBullet)**: Featherstone ABA를 기반으로 하되, 접촉은 sequential impulse solver를 사용한다. 코드로 보면: ```python # Pinocchio: forward dynamics (ABA) import pinocchio as pin import numpy as np model = pin.buildModelFromUrdf("robot.urdf") data = model.createData() q = pin.randomConfiguration(model) v = np.random.randn(model.nv) tau = np.random.randn(model.nv) # ABA: (q, v, τ) → q̈ qdd = pin.aba(model, data, q, v, tau) print("Joint accelerations:", qdd) # 검증: RNEA로 역계산 tau_check = pin.rnea(model, data, q, v, qdd) print("Torque error:", np.linalg.norm(tau - tau_check)) # ≈ 0 ``` RNEA와 ABA는 서로 역연산 관계이다. RNEA(q, v, ABA(q, v, τ)) ≈ τ 가 성립한다 (부동소수점 오차 이내). > **추천 자료** > - Featherstone, *Rigid Body Dynamics Algorithms*, Ch. 7 — ABA의 원본 설명 > - MuJoCo documentation: Computation (https://mujoco.readthedocs.io/en/latest/computation/) — MuJoCo의 동역학 파이프라인 설명 > - Drake MultibodyPlant tutorial (https://drake.mit.edu/doxygen_cxx/classdrake_1_1multibody_1_1_multibody_plant.html) — Drake에서의 동역학 계산 API --- ### 5.6 접촉 역학 (Contact Dynamics) 로봇이 환경과 접촉하는 순간, 동역학은 한 단계 더 복잡해진다. 자유 공간에서의 동역학은 ODE(ordinary differential equation)로 깔끔하게 표현되지만, 접촉이 들어가면 부등식 구속 조건과 불연속성이 생긴다. #### Rigid Contact vs Compliant Contact 접촉을 모델링하는 두 가지 큰 틀이 있다: **Rigid contact (경성 접촉):** - 물체가 서로 관통하지 않는다는 구속 조건을 직접 부과한다. - 접촉력은 구속 조건의 Lagrange multiplier로 나온다. - 수학적으로 깔끔하지만, 수치적으로 어렵다 — 접촉/비접촉 전환 시 불연속성이 생기고, 이를 처리하기 위해 LCP(Linear Complementarity Problem)나 NCP(Nonlinear Complementarity Problem)를 풀어야 한다. - Drake의 time-stepping 방식이 이 계열이다. **Compliant contact (연성 접촉):** - 접촉면에 가상의 스프링-댐퍼를 놓는다. 관통 깊이에 비례하는 반발력을 생성한다. - 수치적으로 안정적이고 구현이 쉽다. - 스프링 강성(stiffness)을 높이면 rigid contact에 가까워지지만, 수치 적분의 time step을 줄여야 한다 (stiff ODE). - MuJoCo의 기본 접촉 모델이 이 계열이다. #### Coulomb Friction Model 접촉이 있으면 마찰(friction)이 따라온다. 가장 기본적인 마찰 모델은 Coulomb friction이다: ``` |f_t| ≤ μ f_n (static friction: 정지 마찰) |f_t| = μ f_n, f_t ∥ -v_t (sliding friction: 운동 마찰) ``` 여기서 f_t는 접선 방향 마찰력, f_n은 법선 방향 수직항력, μ는 마찰 계수, v_t는 접선 방향 상대 속도이다. 이 모델의 문제점: - 정지 마찰에서 운동 마찰로의 전환이 불연속이다. - 3D에서 마찰 원뿔(friction cone)은 비선형이다. 이를 선형화하면 friction pyramid이 되는데, 정확도가 떨어진다. - Painleve's paradox: 특정 조건에서 rigid contact + Coulomb friction의 해가 존재하지 않거나 유일하지 않는 경우가 있다. #### Contact-Rich Manipulation이 어려운 이유 물체를 잡고, 돌리고, 끼우는 작업(peg-in-hole, in-hand manipulation 등)은 왜 그렇게 어려운가? 1. **Hybrid dynamics**: 접촉 모드가 수시로 바뀐다 (contact/no-contact, stick/slip). 각 모드마다 동역학이 다르고, 모드 전환의 시점을 예측하기 어렵다. 2. **Discontinuous dynamics**: 모드 전환 시 상태가 불연속적으로 변할 수 있다 (충격, impact). 3. **Sensitivity to parameters**: 마찰 계수, 접촉 강성 등의 정확한 값을 모르면 시뮬레이션과 실제의 괴리(sim-to-real gap)가 크다. 4. **Combinatorial complexity**: n개의 접촉점이 있으면 가능한 접촉 모드 조합이 3^n개다 (contact/separation, stick/slip 각 방향). #### 시뮬레이터마다 접촉 처리가 다른 이유 접촉 역학에 "정답"이 없기 때문이다. 각 시뮬레이터는 정확도, 속도, 안정성 사이의 트레이드오프를 다르게 선택한다: - **MuJoCo**: compliant contact + convex optimization. 빠르고 안정적이지만, 물리적으로 완벽하지는 않다. 특히 관통이 허용되는데, 이를 "soft contact"의 일부로 받아들인다. RL 환경으로 인기가 많은 이유 중 하나가 이 안정성이다. - **Drake**: rigid contact + time-stepping (Stewart-Trinkle) 또는 hydroelastic contact. 물리적으로 더 엄밀하지만, 계산 비용이 높을 수 있다. Hydroelastic contact은 접촉면의 압력 분포까지 계산한다. - **Bullet**: velocity-level LCP + sequential impulse. 게임/VR에서 출발한 엔진이라 속도에 최적화되어 있으나, 정밀한 접촉이 필요한 로보틱스 작업에서는 한계가 있다. - **DART**: LCP 기반의 rigid contact. 학술적으로 엄밀한 구현이지만, MuJoCo나 Drake에 비해 사용자 기반이 작다. 어떤 시뮬레이터를 쓸지는 연구 목적에 따라 다르다. RL로 locomotion을 학습하겠다면 MuJoCo가 표준이다. 접촉이 중요한 manipulation 연구라면 Drake나 MuJoCo 중 택하는데, 최근에는 MuJoCo도 접촉 품질이 많이 좋아졌다. ```python # MuJoCo에서 접촉 정보 접근 import mujoco import numpy as np model = mujoco.MjModel.from_xml_path("scene.xml") data = mujoco.MjData(model) mujoco.mj_step(model, data) # 접촉점 개수 n_contacts = data.ncon print(f"Number of contacts: {n_contacts}") # 각 접촉의 정보 for i in range(n_contacts): contact = data.contact[i] print(f"Contact {i}:") print(f" Position: {contact.pos}") print(f" Normal: {contact.frame[:3]}") # 접촉 법선 print(f" Penetration depth: {contact.dist}") print(f" Geom pair: ({contact.geom1}, {contact.geom2})") ``` > **추천 자료** > - Stewart, "Rigid-Body Dynamics with Friction and Impact", SIAM Review 2000 — 접촉 역학의 수학적 기초 > - Todorov, "Convex and analytically-invertible dynamics with contacts and constraints", ICRA 2014 — MuJoCo의 접촉 모델 논문 > - [Todorov et al., "MuJoCo: A Physics Engine for Model-Based Control" (IROS 2012)](https://ieeexplore.ieee.org/document/6386109) — 접촉 기반 제어 시뮬레이션의 표준. 볼록 접촉 모델과 velocity stepping 소개 > - Drake의 접촉 모델 documentation (https://drake.mit.edu/doxygen_cxx/group__hydroelastic__user__guide.html) — Hydroelastic contact 설명 > - Russ Tedrake, *Underactuated Robotics*, Ch. "Contact" (https://underactuated.csail.mit.edu/) — 접촉 역학 개론 --- ### 5.7 심화: Featherstone 알고리즘과 Spatial Algebra *연구자가 되고 싶다면 여기서부터 읽어라.* 여기서부터는 대학원 수준이다. Featherstone의 spatial vector algebra는 동역학 알고리즘을 간결하고 효율적으로 표현하기 위한 수학적 틀이다. #### Spatial Vectors (6D Vectors) 3D 공간에서 강체의 운동은 병진(3 DOF) + 회전(3 DOF) = 6 DOF이다. 이를 하나의 6D 벡터로 통합한 것이 spatial vector이다. **Motion vector (spatial velocity, twist):** ``` v = [ω; v_O] ``` 위 3개는 각속도(ω), 아래 3개는 기준점 O에서의 선속도(v_O)이다. **Force vector (spatial force, wrench):** ``` f = [n_O; f] ``` 위 3개는 기준점 O 주위의 모멘트(n_O), 아래 3개는 힘(f)이다. 이 표기의 핵심적인 장점: spatial velocity와 spatial force의 내적이 곧 power(일률)이다. ``` P = f^T v = n_O · ω + f · v_O ``` 이것은 우연이 아니라, spatial vector가 이 성질을 갖도록 설계된 것이다. #### Spatial Inertia 6×6 spatial inertia matrix는 질량, 질량중심 위치, 회전 관성을 하나의 행렬에 통합한다: ``` I_sp = [ I_cm + m·[c]×[c]×^T m·[c]× ] [ m·[c]×^T m·1 ] ``` 여기서 m은 질량, c는 질량중심까지의 벡터, I_cm은 질량중심 주위의 회전 관성, [c]×는 c의 skew-symmetric matrix이다. Spatial inertia의 장점: - 여러 강체의 관성을 합칠 때 그냥 더하면 된다: I_composite = I_1 + I_2 + ... - 좌표 변환이 congruence transform 하나로 끝난다: I_B = X^T I_A X #### RNEA와 ABA의 Spatial Vector 표현 5.2절과 5.5절에서 보인 의사 코드가 사실 spatial vector 표기였다. S[i]는 관절 i의 motion subspace (revolute 관절이면 [e_z; 0], prismatic이면 [0; e_z]), v[i]는 spatial velocity, f[i]는 spatial force이다. Spatial vector를 쓰면 회전 관절이든 직동 관절이든 같은 코드로 처리할 수 있다. 이것이 Pinocchio, Drake 등의 라이브러리가 내부적으로 spatial algebra를 사용하는 이유이다. #### Pinocchio에서 Spatial Quantities 접근 ```python import pinocchio as pin import numpy as np model = pin.buildModelFromUrdf("robot.urdf") data = model.createData() q = pin.randomConfiguration(model) v = np.random.randn(model.nv) # Forward kinematics + velocity pin.forwardKinematics(model, data, q, v) # 각 프레임의 spatial velocity for i in range(model.njoints): # 월드 프레임 기준 spatial velocity v_world = pin.getVelocity(model, data, i, pin.ReferenceFrame.WORLD) print(f"Joint {i} spatial velocity (world): {v_world}") # Composite Rigid Body Algorithm (CRBA): M(q) 계산 M = pin.crba(model, data, q) print("Mass matrix M(q):\n", data.M) # Centroidal momentum matrix pin.computeCentroidalMap(model, data, q) Ag = data.Ag # 6 x nv matrix # h = Ag @ v 가 centroidal momentum (선운동량 + 각운동량) ``` C++에서 Pinocchio를 사용할 때: ```cpp #include
#include
#include
pinocchio::Model model; pinocchio::urdf::buildModel("robot.urdf", model); pinocchio::Data data(model); Eigen::VectorXd q = pinocchio::randomConfiguration(model); Eigen::VectorXd v = Eigen::VectorXd::Random(model.nv); Eigen::VectorXd tau = Eigen::VectorXd::Random(model.nv); // Inverse dynamics (RNEA) Eigen::VectorXd tau_id = pinocchio::rnea(model, data, q, v, Eigen::VectorXd::Zero(model.nv)); // Forward dynamics (ABA) Eigen::VectorXd qdd = pinocchio::aba(model, data, q, v, tau); ``` Pinocchio의 C++ API는 Eigen 기반이며, Python API와 거의 동일한 인터페이스를 제공한다. 실시간 제어에서는 C++ API를 써야 한다 — Python의 오버헤드가 kHz 급 제어 루프에서는 무시할 수 없다. > **추천 자료** > - Featherstone, *Rigid Body Dynamics Algorithms*, Ch. 2 — Spatial vector algebra의 원본 설명. 이 분야를 연구하려면 이 장을 반드시 읽어야 한다. > - Featherstone, "A Beginner's Guide to 6-D Vectors" (IEEE Robotics & Automation Magazine, 2010) — 교과서보다 접근하기 쉬운 소개 논문 > - Pinocchio GitHub (https://github.com/stack-of-tasks/pinocchio) — 소스 코드 자체가 spatial algebra의 좋은 구현 예시이다 --- ### 5.8 심화: 부유 베이스 시스템 (Floating Base) *연구자가 되고 싶다면 여기서부터 읽어라.* 산업용 매니퓰레이터는 base가 바닥에 볼트로 고정되어 있다. 그런데 보행 로봇, 드론, 수중 로봇은 base 자체가 움직인다. 이 경우 base의 위치와 자세가 추가적인 자유도가 되며, 동역학의 구조가 근본적으로 달라진다. #### 부유 베이스의 Configuration 고정 base 로봇의 configuration은 q ∈ R^n이다. 부유 base 로봇의 configuration은: ``` q = [q_base; q_joints] ``` q_base는 SE(3)의 원소이다 — 위치(3) + 자세(3, 또는 quaternion으로 4). 그래서 Pinocchio에서는 q의 차원(nq)과 v의 차원(nv)이 다를 수 있다 (quaternion을 쓰면 nq = nv + 1). 이것이 미묘하게 중요하다. q와 v가 같은 벡터 공간에 살지 않기 때문에, 적분이나 차분을 할 때 단순히 q += v*dt 를 하면 안 된다. Pinocchio에서는 `pin.integrate(model, q, v*dt)`를 써야 한다. #### Underactuated Systems 부유 base 시스템의 핵심적인 특성: **underactuation**. Base에는 직접 구동기(actuator)가 없다. 보행 로봇은 발이 지면을 밀어야 base가 움직이고, 드론은 프로펠러의 추력으로 base를 움직인다. Manipulator equation을 base와 joints로 나누면: ``` [ M_bb M_bj ] [ a_base ] [ C_b ] [ g_b ] [ 0 ] [ J_c^T ] [ M_jb M_jj ] [ q̈_joints] + [ C_j ] + [ g_j ] = [ τ_j ] + [ J_c^T ] λ ``` 왼쪽 위의 `0`이 핵심이다 — base에는 관절 토크가 없다. λ는 접촉력이고, J_c는 접촉 Jacobian이다. Base는 오직 접촉력과 중력을 통해서만 가속할 수 있다. 이 구속 조건이 locomotion 제어를 본질적으로 어렵게 만든다. 고정 base 매니퓰레이터는 원하는 관절 토크를 그냥 모터에 명령하면 되지만, 보행 로봇은 적절한 접촉력을 만들어내야 base를 원하는 대로 움직일 수 있다. #### Centroidal Dynamics 전체 시스템의 운동량(momentum)을 질량중심(center of mass, CoM)에서 표현한 것이 centroidal dynamics이다: **선운동량 (linear momentum):** ``` p = m v_CoM = Σ m_i v_i ``` **각운동량 (angular momentum about CoM):** ``` L = Σ (r_i - r_CoM) × (m_i v_i) + I_i ω_i ``` **Centroidal momentum의 시간 미분:** ``` ṗ = m g + Σ f_contact L̇ = Σ (r_contact - r_CoM) × f_contact ``` 이것이 locomotion 제어에서 왜 중요한가: 1. **CoM 역학이 balance를 결정한다.** 로봇이 넘어지지 않으려면 CoM의 궤적이 지지 영역(support polygon) 위에 있어야 한다 (ZMP 조건). 더 정확히는 centroidal momentum가 적절히 조절되어야 한다. 2. **차원 축소.** n-DOF 보행 로봇의 전체 동역학은 n차원이지만, centroidal dynamics는 6차원(선운동량 3 + 각운동량 3)이다. 이 6차원 공간에서 먼저 원하는 운동량 궤적을 계획하고, 그 다음 전체 관절 수준으로 분해하는 것이 일반적인 접근법이다. 3. **접촉력 계획과 직접 연결된다.** 위의 식에서 보듯이, centroidal momentum의 변화율은 외력(접촉력 + 중력)에 의해서만 결정된다. 어떤 접촉력 패턴이 원하는 운동량 궤적을 만들어내는지 계획하는 것이 locomotion의 핵심 문제이다. ```python # Pinocchio에서 centroidal dynamics 계산 import pinocchio as pin import numpy as np model = pin.buildModelFromUrdf("humanoid.urdf", pin.JointModelFreeFlyer()) data = model.createData() q = pin.randomConfiguration(model) v = np.random.randn(model.nv) # Centroidal momentum pin.computeCentroidalMomentum(model, data, q, v) h = data.hg # 6D centroidal momentum [angular; linear] print("Angular momentum:", h.angular) print("Linear momentum:", h.linear) # Centroidal momentum matrix: h = A_g(q) * v pin.computeCentroidalMap(model, data, q) Ag = data.Ag # 6 x nv h_check = Ag @ v print("Centroidal momentum (via Ag):", h_check) # CoM 위치 및 속도 pin.centerOfMass(model, data, q, v) print("CoM position:", data.com[0]) print("CoM velocity:", data.vcom[0]) ``` #### Centroidal Dynamics 기반 Locomotion 제어의 구조 현대적인 보행 로봇 제어 파이프라인의 전형적인 구조는 다음과 같다: ``` [Contact Schedule] → [Centroidal Trajectory Optimization] → [Whole-Body Control] → [Joint Torques] 1단계: 언제 어떤 발이 땅에 닿는지 결정 (gait pattern) 2단계: Centroidal dynamics를 만족하는 CoM 궤적 + 접촉력 계획 3단계: Centroidal 목표를 달성하면서 관절 수준의 여러 제약을 만족하는 토크 계산 4단계: 모터에 토크 명령 ``` 이 구조는 보행 로봇에 한정되지 않고, 부유 base를 가진 모든 시스템에 적용 가능하다. 드론의 trajectory optimization, 수중 로봇의 제어에도 유사한 구조가 사용된다. > **추천 자료** > - Orin et al., "Centroidal Dynamics of a Humanoid Robot", Autonomous Robots 2013 — Centroidal dynamics를 로보틱스에 도입한 핵심 논문 > - Wensing et al., "Optimization-Based Control for Dynamic Legged Locomotion", 2023 — 최신 locomotion 제어 survey > - Russ Tedrake, *Underactuated Robotics*, Ch. "Walking" (https://underactuated.csail.mit.edu/) — Underactuated 시스템과 보행의 관계 > - Carpentier, Mansard, "Pinocchio: fast forward and inverse dynamics for poly-articulated systems" (https://github.com/stack-of-tasks/pinocchio) — Pinocchio의 centroidal dynamics 구현 --- ### 5.9 추천 자료 이 분야를 공부할 때 어디서 시작할지가 가장 중요하다. 추천 순서는 배경지식에 따라 다르다. **학부 3-4학년, 동역학 입문:** > - Spong, Hutchinson, Vidyasagar, *Robot Modeling and Control* — 학부 수준에서 가장 접근하기 쉬운 교재. Manipulator equation 유도가 상세하다. > - Craig, *Introduction to Robotics: Mechanics and Control* — 산업 현장 관점. 실용적이지만 수학적 깊이는 좀 얕다. **대학원, 수학적으로 엄밀한 이해가 필요할 때:** > - Murray, Li, Sastry, *A Mathematical Introduction to Robotic Manipulation* (https://www.cds.caltech.edu/~murray/mlswiki/) — Lie group/algebra 관점에서의 동역학. 무료 PDF 제공. 수학적으로 가장 엄밀하지만, 처음 읽으면 어렵다. > - Featherstone, *Rigid Body Dynamics Algorithms* — 동역학 알고리즘의 핵심 참고서. Spatial vector algebra, RNEA, ABA, composite rigid body algorithm 등 모든 핵심 알고리즘이 여기 있다. 대학원생이라면 반드시 읽어야 한다. **동역학 + 제어 통합:** > - Russ Tedrake, *Underactuated Robotics* (https://underactuated.csail.mit.edu/) — 동역학 모델을 제어와 최적화에 어떻게 활용하는지를 다룬다. MIT OCW에 강의 영상도 있다. 무료. **라이브러리 & 도구:** > - Pinocchio (https://github.com/stack-of-tasks/pinocchio) — 순수 동역학 계산용 C++/Python 라이브러리. RNEA, ABA, CRBA, centroidal dynamics, analytical derivatives 등을 지원한다. CasADi/JAX를 통한 autodiff도 가능하다 (Pinocchio 3.x). > - Drake (https://drake.mit.edu/) — 시뮬레이션 + 최적화 + 제어를 통합한 프레임워크. MultibodyPlant가 동역학 엔진이다. Mathematical programming 인터페이스가 강력하여 trajectory optimization에 특히 유용하다. > - MuJoCo (https://mujoco.org/) — DeepMind가 관리하는 물리 시뮬레이터. 접촉 처리가 빠르고 안정적이다. RL 연구의 사실상 표준 시뮬레이터. > - PyBullet (https://pybullet.org/) — Bullet Physics의 Python 인터페이스. 진입 장벽이 낮아 교육용으로 적합하지만, 접촉 물리의 정밀도는 MuJoCo나 Drake에 미치지 못한다. --- ### 기술 흐름 ``` 1687 ── Newton의 운동 법칙 (Principia Mathematica) 1788 ── Lagrange의 해석역학 (Mécanique Analytique) 1965 ── Uicker의 동역학 방정식 (symbolic, 비효율적) 1980 ── Luh, Walker, Paul의 Newton-Euler 재귀 알고리즘 (RNEA, O(n)) 1983 ── Featherstone의 Articulated Body Algorithm (ABA, O(n) forward dynamics) 1987 ── Featherstone의 Spatial Vector Algebra 체계 정립 2000 ── Stewart의 rigid contact dynamics 수학적 정리 (SIAM Review) 2004 ── ODE (Open Dynamics Engine) — 초기 오픈소스 물리 엔진 2012 ── MuJoCo 공개 (Todorov, Erez, Tassa) 2015 ── Bullet Physics 2.x → PyBullet 인터페이스 2016 ── Pinocchio 1.0 공개 (LAAS-CNRS) 2022 ── Drake 1.0 (MIT → Toyota Research Institute) 2021 ── MuJoCo 오픈소스화 (DeepMind 인수 후) 2022 ── MuJoCo 2.3: implicit integration, elliptic friction cone 2023 ── Pinocchio 3.0: CasADi/JAX autodiff 지원 2023 ── MuJoCo 3.0: MJX (JAX backend for GPU parallelism) ``` --- ### 정리 이 장에서 다룬 내용을 한 문장으로 요약하면: **동역학은 힘과 운동의 관계를 다루며, 로봇을 제어하고 시뮬레이션하는 데 필수적이다.** 실무적으로 기억해야 할 것: 1. Manipulator equation `M(q)q̈ + C(q,q̇)q̇ + g(q) = τ`는 모든 것의 출발점이다. 2. 역동역학(τ 계산)에는 RNEA, 순동역학(q̈ 계산)에는 ABA를 쓴다. 둘 다 O(n)이다. 3. 접촉이 들어가면 문제가 급격히 어려워진다. 시뮬레이터 선택이 중요하다. 4. 부유 base 시스템에서는 centroidal dynamics가 핵심 도구이다. 5. 직접 구현하지 말고 Pinocchio나 Drake를 써라. 단, 이 라이브러리들이 내부적으로 무엇을 계산하는지는 이해하고 있어야 한다. 다음 장에서는 이 동역학 모델을 기반으로 한 제어 기법 — computed torque control, operational space control, whole-body control — 을 다룬다. --- ## 6. 제어 이론 (Control Theory) 로봇이 세상을 인식하는 것과 세상에 실제로 영향을 미치는 것은 완전히 다른 문제다. 인식 파이프라인이 아무리 정교해도, 모터에 보내는 전류 하나를 잘못 계산하면 로봇은 넘어지거나 물건을 부수거나 사람을 다치게 한다. 제어 이론은 "원하는 동작을 실제로 실현하는 방법"에 대한 학문이다. --- ### 6.1 왜 제어를 배우는가 인식(Perception)이 "세상을 이해하는 것"이라면, 제어(Control)는 "세상에 영향을 미치는 것"이다. 둘 다 없으면 로봇이 아니라 그냥 센서 덩어리 또는 모터 덩어리다. 제어를 배워야 하는 이유는 단순하다: - **모터는 생각보다 멍청하다.** "관절을 30도로 보내라"라고 명령하면, 모터는 그냥 최대 전류를 때려넣고 30도를 지나쳐서 진동한다. 이걸 부드럽게 원하는 위치에 도달시키는 것이 제어다. - **외란(disturbance)은 항상 존재한다.** 바닥이 미끄럽거나, 바람이 불거나, 페이로드 무게가 예상과 다르거나. 센서-액추에이터 루프를 닫아서(feedback) 이런 불확실성에 대응해야 한다. - **안전이 걸려 있다.** 산업용 로봇 팔이 사람 옆에서 작업하는데, 힘 제어가 없으면 사람 팔뼈가 부러진다. 과장이 아니다. 제어 이론의 범위는 넓다. 이 장에서는 로보틱스에서 실제로 가장 많이 쓰이는 것들을 중심으로, PID부터 MPC, 임피던스 제어, 그리고 연구 수준의 Whole-Body Control까지 다룬다. 한 가지 미리 말해두자면, 제어 이론은 수학이 많이 나온다. 선형대수와 미분방정식에 익숙하지 않다면, 이 장을 읽기 전에 최소한 행렬 연산과 고유값(eigenvalue) 개념은 복습하고 오는 것을 권한다. --- ### 6.2 PID 제어 PID(Proportional-Integral-Derivative)는 1922년 Minorsky가 선박 조타 시스템을 위해 제안한 이래로, 100년이 넘게 산업 현장에서 가장 널리 쓰이는 제어기이다. 세상 모든 제어 엔지니어가 처음 배우는 것이고, 은퇴할 때까지 쓰는 것이다. #### 기본 구조 오차 e(t) = r(t) - y(t)로 정의한다. r(t)는 목표값(reference), y(t)는 현재 출력이다. ``` u(t) = Kp * e(t) + Ki * integral(e(τ)dτ, 0, t) + Kd * de(t)/dt ``` 각 항의 역할: - **P (Proportional)**: 현재 오차에 비례하여 제어 입력을 생성한다. Kp가 크면 반응이 빠르지만, 오버슈트가 커지고 진동이 발생한다. P 항만으로는 정상상태 오차(steady-state error)가 남는다. 목표값 근처에서 오차가 작아지면 제어 입력도 작아지기 때문이다. - **I (Integral)**: 오차의 누적값에 비례한다. 정상상태 오차를 제거하는 역할을 한다. 중력이나 마찰 같은 상수 외란이 있을 때 필수적이다. 다만 과도하면 wind-up 현상이 발생한다. 오차가 오랫동안 누적되어 제어 입력이 포화(saturation)된 상태에서, 목표에 도달한 후에도 누적된 적분값 때문에 큰 오버슈트가 생기는 문제다. 실무에서는 anti-windup 로직을 반드시 구현해야 한다. - **D (Derivative)**: 오차의 변화율에 비례한다. 오차가 빠르게 줄어들고 있으면 제어 입력을 줄여서 오버슈트를 억제한다. 일종의 "브레이크" 역할이다. 문제는 미분이 노이즈에 극도로 민감하다는 것이다. 센서 노이즈가 있는 실제 시스템에서는 D 항에 저역통과 필터(low-pass filter)를 걸어야 한다. 그래서 현장에서는 D 항을 아예 안 쓰고 PI 제어만 하는 경우도 많다. #### Python 구현 ```python class PIDController: """이산시간 PID 제어기. Anti-windup 포함.""" def __init__(self, kp: float, ki: float, kd: float, dt: float, output_limit: tuple[float, float] = (-float('inf'), float('inf')), d_filter_coeff: float = 0.1): self.kp = kp self.ki = ki self.kd = kd self.dt = dt self.output_limit = output_limit self.d_filter_coeff = d_filter_coeff # D항 저역통과 필터 계수 self.integral = 0.0 self.prev_error = 0.0 self.prev_d_filtered = 0.0 def compute(self, error: float) -> float: # Proportional p_term = self.kp * error # Integral (trapezoidal integration) self.integral += 0.5 * (error + self.prev_error) * self.dt i_term = self.ki * self.integral # Derivative (with low-pass filter) d_raw = (error - self.prev_error) / self.dt d_filtered = (self.d_filter_coeff * d_raw + (1.0 - self.d_filter_coeff) * self.prev_d_filtered) d_term = self.kd * d_filtered # 제어 출력 output = p_term + i_term + d_term # Output saturation + anti-windup (clamping) lo, hi = self.output_limit if output > hi: output = hi # Anti-windup: 포화 시 적분값 역산 self.integral -= 0.5 * (error + self.prev_error) * self.dt elif output < lo: output = lo self.integral -= 0.5 * (error + self.prev_error) * self.dt self.prev_error = error self.prev_d_filtered = d_filtered return output # 사용 예시: 1-DOF 위치 제어 import numpy as np dt = 0.001 # 1kHz 제어 주기 pid = PIDController(kp=100.0, ki=10.0, kd=5.0, dt=dt, output_limit=(-50.0, 50.0)) position = 0.0 velocity = 0.0 mass = 1.0 target = 1.0 positions = [] for step in range(5000): error = target - position force = pid.compute(error) # 간단한 1차 동역학: F = ma, 감쇠 포함 acceleration = (force - 0.5 * velocity) / mass velocity += acceleration * dt position += velocity * dt positions.append(position) ``` #### 튜닝 방법 **Ziegler-Nichols 방법**: 고전적 튜닝법이다. Ki = 0, Kd = 0으로 놓고 Kp를 올려가면서 시스템이 지속 진동(sustained oscillation)하는 임계 이득 Ku와 진동 주기 Tu를 구한다. 그리고 다음 표에 따라 게인을 설정한다. ``` PID: Kp = 0.6 * Ku, Ki = 2 * Kp / Tu, Kd = Kp * Tu / 8 PI: Kp = 0.45 * Ku, Ki = 1.2 * Kp / Tu P: Kp = 0.5 * Ku ``` 솔직히 Ziegler-Nichols로 튜닝하면 오버슈트가 꽤 크게 나온다. 시작점으로는 괜찮지만, 그 후에 수동 미세조정이 필수다. **실무에서의 경험적 튜닝**: 현실에서는 대부분 이렇게 한다. 1. D, I를 0으로 놓는다. 2. P를 올린다. 시스템이 빠르게 반응하되 진동하지 않는 선에서 멈춘다. 3. 정상상태 오차가 있으면 I를 조금씩 올린다. Wind-up 조심. 4. 오버슈트가 크면 D를 조금 넣는다. 노이즈 필터 확인. 이 과정을 "느낌으로 한다"고 하면 교수님이 싫어하겠지만, 현장에서는 대부분 이렇게 한다. 시스템 모델이 정확하면 시뮬레이션에서 먼저 튜닝하고 실기에 적용하는 것이 훨씬 효율적이다. #### PID의 한계 PID는 강력하지만 분명한 한계가 있다: - **SISO(Single-Input Single-Output) 전용이다.** 6축 로봇 팔처럼 관절 간 커플링이 있는 시스템에서는 각 관절에 독립적으로 PID를 걸면 성능이 떨어진다. 한 관절의 움직임이 다른 관절에 외란으로 작용하기 때문이다. - **비선형 시스템에 약하다.** PID는 근본적으로 선형 제어기다. 로봇 동역학은 비선형이다. 작동점(operating point) 근처에서만 잘 동작한다. - **제약 조건을 처리할 수 없다.** 토크 제한, 관절 각도 제한, 속도 제한 같은 물리적 제약을 PID 구조 안에서 명시적으로 다룰 방법이 없다. - **미래를 예측하지 않는다.** 현재 오차만 보고 반응한다. Feedforward가 없으면 추종 성능이 제한된다. 그럼에도 PID가 100년째 쓰이는 이유는 간단하다: **구현이 쉽고, 이해하기 쉽고, 왠만한 시스템에서 "적당히" 동작한다.** 제어 대상이 단순하고 성능 요구가 극단적이지 않으면 PID로 충분하다. 산업용 로봇의 각 관절 서보 제어는 지금도 PID 기반이 대부분이다. --- ### 6.3 상태공간 표현 (State-Space Representation) PID는 입력-출력 관계만 본다. 시스템 "내부"에서 무슨 일이 일어나는지는 모른다. 상태공간 표현은 시스템의 내부 상태를 명시적으로 기술하는 방법이다. #### 기본 형태 연속시간 선형 시스템: ``` x_dot(t) = A * x(t) + B * u(t) (상태 방정식) y(t) = C * x(t) + D * u(t) (출력 방정식) ``` - x(t): 상태 벡터 (n x 1). 시스템을 완전히 기술하는 데 필요한 최소 변수 집합. - u(t): 입력 벡터 (m x 1). 제어 입력. - y(t): 출력 벡터 (p x 1). 측정 가능한 출력. - A: 시스템 행렬 (n x n). 시스템의 고유 동특성을 결정한다. - B: 입력 행렬 (n x m). 입력이 상태에 미치는 영향. - C: 출력 행렬 (p x n). 상태에서 출력으로의 매핑. - D: 직접 전달 행렬 (p x m). 대부분의 물리 시스템에서 0이다. 예를 들어, 질량-스프링-댐퍼 시스템 (m * x_ddot + c * x_dot + k * x = F)에서 상태를 x1 = 위치, x2 = 속도로 잡으면: ``` A = [[0, 1], [-k/m, -c/m]] B = [[0], [1/m]] C = [[1, 0]] (위치만 측정) D = [[0]] ``` #### 전달함수와의 관계 전달함수 G(s) = C * (sI - A)^(-1) * B + D 이다. 전달함수는 SISO 시스템에서 편리하지만, MIMO(Multi-Input Multi-Output) 시스템에서는 상태공간이 훨씬 자연스럽다. 로봇은 거의 항상 MIMO 시스템이므로, 상태공간 표현이 표준이다. #### 가제어성 (Controllability) 시스템이 가제어(controllable)하다는 것은, 임의의 초기 상태에서 임의의 최종 상태로 유한 시간 내에 이동할 수 있다는 것이다. 가제어성 행렬: ``` C_ctrl = [B, A*B, A^2*B, ..., A^(n-1)*B] ``` 이 행렬의 rank가 n이면 가제어이다. rank가 n보다 작으면, 제어 입력으로 도달할 수 없는 상태가 존재한다는 뜻이다. 그런 시스템에 LQR을 적용하면 안 된다. #### 가관측성 (Observability) 시스템이 가관측(observable)하다는 것은, 출력 y(t)를 관찰하여 초기 상태 x(0)를 유일하게 결정할 수 있다는 것이다. 가관측성 행렬: ``` O = [C; C*A; C*A^2; ...; C*A^(n-1)] ``` rank가 n이면 가관측이다. 가관측하지 않으면 상태 추정(observer, Kalman filter)이 제대로 동작하지 않는다. #### 왜 PID에서 상태공간으로 넘어가야 하는가 PID로 각 관절을 독립적으로 제어하면, 관절 간 동적 커플링을 무시하게 된다. 2-DOF 로봇 팔만 해도 한 관절이 빠르게 움직이면 다른 관절에 원심력과 코리올리 힘이 작용한다. 이걸 외란으로 처리하면 PID의 I 항이 열심히 보상하겠지만, 응답이 느리고 성능이 나쁘다. 상태공간에서는 시스템 전체를 하나의 모델로 기술하고, 모든 상태 변수를 동시에 고려하여 제어 입력을 계산한다. 이것이 다음 절의 LQR과 MPC의 기반이 된다. ```python import numpy as np from scipy import signal import control # pip install control # 도립진자(inverted pendulum) 상태공간 모델 # 상태: [x, x_dot, theta, theta_dot] # x: 카트 위치, theta: 진자 각도 (수직에서) M = 1.0 # 카트 질량 (kg) m = 0.1 # 진자 질량 (kg) l = 0.5 # 진자 길이 (m) g = 9.81 # 중력 (m/s^2) # 선형화된 상태공간 행렬 (theta ≈ 0 근처) A = np.array([ [0, 1, 0, 0], [0, 0, -m * g / M, 0], [0, 0, 0, 1], [0, 0, (M + m) * g / (M * l), 0] ]) B = np.array([[0], [1 / M], [0], [-1 / (M * l)]]) C = np.array([[1, 0, 0, 0], [0, 0, 1, 0]]) # 카트 위치와 진자 각도 측정 D = np.zeros((2, 1)) # 가제어성 확인 ctrb_matrix = control.ctrb(A, B) print(f"가제어성 행렬 rank: {np.linalg.matrix_rank(ctrb_matrix)}") # 4 = 가제어 # 가관측성 확인 obsv_matrix = control.obsv(A, C) print(f"가관측성 행렬 rank: {np.linalg.matrix_rank(obsv_matrix)}") # 4 = 가관측 # 시스템 극점 (eigenvalues of A) eigenvalues = np.linalg.eigvals(A) print(f"시스템 극점: {eigenvalues}") # 양의 실수부를 가진 극점이 있으면 → 불안정 시스템 (도립진자가 그렇다) ``` --- ### 6.4 LQR (Linear Quadratic Regulator) PID가 "경험과 튜닝"에 의존한다면, LQR은 "최적화"에 기반한 제어기이다. 주어진 비용 함수를 최소화하는 제어 입력을 해석적으로 구할 수 있다. #### 비용 함수 ``` J = integral_0^inf (x(t)^T * Q * x(t) + u(t)^T * R * u(t)) dt ``` - Q (n x n, 양의 반정치): 상태 오차에 대한 페널티. "상태가 0에서 벗어나는 것이 얼마나 싫은가." - R (m x m, 양정치): 제어 입력에 대한 페널티. "제어 에너지를 얼마나 아끼고 싶은가." Q를 크게 하면 상태가 빠르게 0으로 수렴하지만 제어 입력이 커진다. R을 크게 하면 제어 입력이 작아지지만 상태 수렴이 느려진다. 이것이 LQR의 본질적인 트레이드오프다. #### Q, R 행렬 튜닝 실용적인 방법: Q와 R을 대각 행렬로 놓고, 각 대각 원소를 해당 상태/입력의 허용 범위의 역수 제곱으로 설정한다. ``` Q_ii = 1 / (허용 가능한 x_i의 최대값)^2 R_jj = 1 / (허용 가능한 u_j의 최대값)^2 ``` 예: 카트 위치가 0.5m 이내, 진자 각도가 0.1rad 이내, 힘이 20N 이내를 원한다면: ``` Q = diag(1/0.5^2, 0, 1/0.1^2, 0) = diag(4, 0, 100, 0) R = [1/20^2] = [0.0025] ``` 이것은 출발점일 뿐이다. 이후 시뮬레이션을 돌려가며 조정한다. #### Algebraic Riccati Equation (ARE) LQR의 최적 게인 K는 다음 Algebraic Riccati Equation의 해 P로부터 구한다: ``` A^T * P + P * A - P * B * R^(-1) * B^T * P + Q = 0 ``` 최적 상태 피드백 게인: K = R^(-1) * B^T * P 제어 법칙: u(t) = -K * x(t) 이 결과의 핵심은, 폐루프 시스템 (A - BK)의 모든 고유값이 좌반면에 놓인다는 것이 보장된다는 점이다. 즉, 안정성이 수학적으로 증명된다. #### Python 구현 ```python import numpy as np from scipy.linalg import solve_continuous_are # 앞 절의 도립진자 모델 사용 M, m, l, g = 1.0, 0.1, 0.5, 9.81 A = np.array([ [0, 1, 0, 0], [0, 0, -m * g / M, 0], [0, 0, 0, 1], [0, 0, (M + m) * g / (M * l), 0] ]) B = np.array([[0], [1 / M], [0], [-1 / (M * l)]]) # 비용 함수 가중치 Q = np.diag([4.0, 0.0, 100.0, 0.0]) # 위치, 속도, 각도, 각속도 R = np.array([[0.0025]]) # ARE 풀기 P = solve_continuous_are(A, B, Q, R) # 최적 게인 계산 K = np.linalg.inv(R) @ B.T @ P print(f"LQR 게인 K: {K}") # 폐루프 극점 확인 A_cl = A - B @ K eigenvalues_cl = np.linalg.eigvals(A_cl) print(f"폐루프 극점: {eigenvalues_cl}") # 모든 실수부가 음수 → 안정 def simulate_lqr(A, B, K, x0, dt=0.001, t_final=5.0): """LQR 폐루프 시뮬레이션 (Euler 적분).""" n_steps = int(t_final / dt) n = A.shape[0] x_history = np.zeros((n_steps, n)) u_history = np.zeros((n_steps, 1)) x = x0.copy() for i in range(n_steps): u = -K @ x x_history[i] = x.flatten() u_history[i] = u.flatten() x_dot = A @ x + B @ u x = x + x_dot * dt return x_history, u_history # 초기 조건: 진자가 10도 기울어진 상태 x0 = np.array([[0.0], [0.0], [np.radians(10)], [0.0]]) x_hist, u_hist = simulate_lqr(A, B, K, x0) # x_hist[:, 2]가 0으로 수렴하면 성공 print(f"최종 진자 각도: {np.degrees(x_hist[-1, 2]):.4f} deg") ``` #### LQR의 한계 - **선형 모델이 필요하다.** 비선형 시스템은 작동점 근처에서 선형화해야 한다. 작동점에서 멀어지면 성능이 급격히 떨어진다. - **제약 조건을 명시적으로 처리할 수 없다.** 토크 제한, 속도 제한 같은 물리적 제약을 비용 함수에 넣을 수 없다. 제어 입력이 포화되면 최적성이 깨진다. - **전체 상태를 알아야 한다.** u = -Kx이므로 모든 상태 변수를 측정하거나 추정(observer)해야 한다. - **추종(tracking) 문제에 그대로 적용 불가.** 기본 LQR은 regulator, 즉 상태를 0으로 보내는 문제만 풀 수 있다. 시변 목표를 추종하려면 확장이 필요하다. 이런 한계를 극복하기 위해 MPC가 등장한다. --- ### 6.5 MPC (Model Predictive Control) MPC(Model Predictive Control)는 매 제어 주기마다 유한 구간(finite horizon) 최적화 문제를 풀어 제어 입력을 계산하는 방법이다. 2020년대 로보틱스에서 가장 널리 쓰이는 제어 기법 중 하나다. #### 기본 개념 매 time step k에서 다음을 수행한다: 1. 현재 상태 x(k)를 측정 또는 추정한다. 2. 모델을 이용하여 N step 앞까지 미래를 예측한다. 3. 비용 함수를 최소화하는 입력 시퀀스 {u(k), u(k+1), ..., u(k+N-1)}을 구한다. 이때 제약 조건을 명시적으로 반영한다. 4. 첫 번째 입력 u(k)만 실제로 적용하고, 나머지는 버린다. 5. 다음 time step에서 1로 돌아간다. 이것을 "receding horizon" 전략이라 한다. 매번 최적화를 새로 풀기 때문에, 모델 오차나 외란에 대한 피드백 효과가 자연스럽게 생긴다. #### 왜 로보틱스에서 MPC가 대세인가 - **제약 조건 처리**: 토크 제한, 관절 각도 제한, 속도 제한, 충돌 회피 등을 최적화 문제의 제약 조건으로 직접 넣을 수 있다. PID나 LQR로는 불가능하다. - **비선형 모델 사용 가능**: Nonlinear MPC에서는 비선형 동역학 모델을 그대로 쓸 수 있다. - **미래 예측**: 단순히 현재 오차에 반응하는 것이 아니라, 미래 궤적을 예측하여 능동적으로 대응한다. 보행 로봇이 다음 발을 내딛기 전에 미리 무게 중심을 이동시키는 것이 이 원리다. - **다목적 최적화**: 비용 함수에 여러 목표를 동시에 넣을 수 있다. "목표 궤적을 추종하면서 에너지를 아끼고 토크 제한을 지켜라." #### Linear MPC vs Nonlinear MPC **Linear MPC**: 선형 모델(x(k+1) = A*x(k) + B*u(k))을 사용하고, 비용 함수가 이차(quadratic), 제약이 선형이면 문제가 QP(Quadratic Program)가 된다. QP는 볼록(convex) 최적화이므로 전역 최적해를 빠르게 구할 수 있다. 실시간 제어에 적합하다. **Nonlinear MPC (NMPC)**: 비선형 동역학 모델을 사용한다. 문제가 비볼록(non-convex)이 되어 풀기 어렵고, 전역 최적해를 보장하지 못한다. 하지만 로봇 동역학을 정확히 반영할 수 있으므로 성능이 좋다. CasADi + IPOPT 조합이 표준 도구다. 실무에서의 선택: 시스템이 충분히 선형에 가깝거나 제어 주기가 매우 짧아야 하면 Linear MPC, 비선형성이 크고 제어 주기에 여유가 있으면 NMPC. #### 실시간성 문제 MPC의 최대 난관은 매 제어 주기마다 최적화를 풀어야 한다는 것이다. 보행 로봇이 1kHz로 제어된다면, 1ms 안에 QP를 풀어야 한다. 주요 QP solver: - **OSQP** (https://osqp.org/): operator splitting 기반, sparse QP에 강하다. 대부분의 Linear MPC에서 첫 번째 선택. - **qpOASES**: active-set 기반, warm-starting이 가능하여 연속적인 QP 풀이에 효율적. - **ECOS/Clarabel**: second-order cone programming까지 처리 가능. NMPC의 경우: - **CasADi** + **IPOPT**: 자동 미분 + interior-point method. NMPC 구현의 사실상 표준. - **acados** (https://docs.acados.org/): CasADi 기반이지만 실시간성에 최적화됨. C 코드 생성 가능. **solver 속도가 곧 제어 주기를 결정한다.** solver가 5ms 걸리면 200Hz가 한계다. 이것이 MPC 엔지니어가 solver를 매우 신경 쓰는 이유다. #### Linear MPC Python 예시 ```python import numpy as np from scipy import sparse import osqp def linear_mpc(A, B, Q, R, Q_f, x0, N, x_min, x_max, u_min, u_max): """ Linear MPC: QP로 변환하여 OSQP로 풀기. A, B: 이산시간 시스템 행렬 Q: 상태 비용 (stage) R: 입력 비용 Q_f: 종단 비용 (terminal) x0: 현재 상태 N: 예측 구간 (horizon) x_min, x_max: 상태 제약 u_min, u_max: 입력 제약 """ n = A.shape[0] # 상태 차원 m = B.shape[1] # 입력 차원 # 결정 변수: z = [x(0), x(1), ..., x(N), u(0), ..., u(N-1)] n_var = (N + 1) * n + N * m # --- 비용 함수 행렬 (P, q) --- # min 0.5 * z^T P z + q^T z P_blocks = [sparse.kron(sparse.eye(N), Q)] # x(0) ~ x(N-1) P_blocks.append(Q_f) # x(N) terminal cost P_blocks.append(sparse.kron(sparse.eye(N), R)) # u(0) ~ u(N-1) P = sparse.block_diag(P_blocks, format='csc') q = np.zeros(n_var) # --- 등식 제약: 동역학 --- # x(k+1) = A*x(k) + B*u(k) # → A*x(k) + B*u(k) - x(k+1) = 0 Ax_eq = sparse.kron(sparse.eye(N + 1), -sparse.eye(n)) Au_shift = sparse.kron(sparse.eye(N, N + 1, 1), sparse.eye(n)) # 수정: 좌하단에 A 추가 for i in range(N): row_start = (i + 1) * n col_start = i * n Ax_eq[row_start:row_start + n, col_start:col_start + n] = A Bu_eq = sparse.lil_matrix(((N + 1) * n, N * m)) for i in range(N): Bu_eq[(i + 1) * n:(i + 2) * n, i * m:(i + 1) * m] = B Bu_eq = sparse.csc_matrix(Bu_eq) A_eq = sparse.hstack([Ax_eq, Bu_eq], format='csc') l_eq = np.zeros((N + 1) * n) l_eq[:n] = -x0.flatten() # 초기 조건 u_eq = l_eq.copy() # --- 부등식 제약: 상태 및 입력 범위 --- A_ineq = sparse.eye(n_var, format='csc') l_ineq = np.concatenate([ np.tile(x_min, N + 1), np.tile(u_min, N) ]) u_ineq = np.concatenate([ np.tile(x_max, N + 1), np.tile(u_max, N) ]) # --- 전체 제약 결합 --- A_total = sparse.vstack([A_eq, A_ineq], format='csc') l_total = np.concatenate([l_eq, l_ineq]) u_total = np.concatenate([u_eq, u_ineq]) # --- OSQP 풀기 --- solver = osqp.OSQP() solver.setup(P, q, A_total, l_total, u_total, warm_starting=True, verbose=False, eps_abs=1e-6, eps_rel=1e-6) result = solver.solve() if result.info.status != 'solved': print(f"MPC 풀이 실패: {result.info.status}") return None, None # 첫 번째 입력만 반환 u_opt = result.x[(N + 1) * n:(N + 1) * n + m] x_pred = result.x[:(N + 1) * n].reshape(N + 1, n) return u_opt, x_pred # 사용 예시: 2차원 더블 인티그레이터 dt = 0.1 A_d = np.array([[1, dt], [0, 1]]) # 이산시간 B_d = np.array([[0.5 * dt**2], [dt]]) n, m_ctrl = 2, 1 Q_mpc = sparse.diags([10.0, 1.0]) R_mpc = sparse.diags([0.1]) Q_f_mpc = sparse.diags([100.0, 10.0]) # terminal cost 크게 x0 = np.array([5.0, 0.0]) # 초기 위치 5m, 속도 0 N_horizon = 20 x_min_val = np.array([-10.0, -5.0]) x_max_val = np.array([10.0, 5.0]) u_min_val = np.array([-1.0]) # 힘 제한 u_max_val = np.array([1.0]) u_opt, x_pred = linear_mpc( A_d, B_d, Q_mpc, R_mpc, Q_f_mpc, x0, N_horizon, x_min_val, x_max_val, u_min_val, u_max_val ) print(f"최적 제어 입력: {u_opt}") print(f"예측 궤적 (위치): {x_pred[:5, 0]}") ``` #### 산업 사례 - **Boston Dynamics Atlas (2019~)**: MPC + Whole-Body Control 조합. 비선형 MPC로 접촉 시퀀스를 예측하고, WBC로 관절 토크를 실시간 분배한다. - **Unitree H1/G1 (2023~)**: 학습 기반 정책(reinforcement learning)이 high-level 명령을 생성하고, MPC가 low-level 궤적 추종을 담당하는 하이브리드 구조. - **Figure 01 (2024)**: LLM이 태스크 레벨 계획을 세우고, MPC가 manipulation 궤적을 최적화한다. 제어와 AI의 결합 사례. --- ### 6.6 임피던스/어드미턴스 제어 (Impedance/Admittance Control) 지금까지 다룬 제어 기법들은 주로 "위치를 원하는 곳에 보내는 것"에 집중했다. 하지만 로봇이 환경과 물리적으로 접촉하는 순간, 위치 제어만으로는 부족해진다. #### 위치 제어 vs 힘 제어 vs 임피던스 제어 - **위치 제어(Position Control)**: 목표 위치를 추종한다. 환경이 없거나 매우 강성(rigid)인 환경에서 적합하다. 하지만 로봇 팔이 테이블 위의 컵을 집으려는데, 테이블 높이가 1mm만 달라도 — 위치 제어기는 모르고 밀어 넣으려 하므로 — 과도한 힘이 발생한다. - **힘 제어(Force Control)**: 목표 힘을 추종한다. 연마, 조립 같은 접촉 태스크에서 필요하다. 그러나 순수 힘 제어는 비접촉 상태에서 불안정하다. 힘 센서 노이즈에도 민감하다. - **임피던스 제어(Impedance Control)**: 위치와 힘의 관계를 제어한다. 로봇이 가상의 스프링-댐퍼 시스템처럼 행동하도록 만든다. 환경과 접촉하면 자연스럽게 힘이 발생하고, 비접촉 상태에서는 위치 제어처럼 동작한다. #### 가상 스프링-댐퍼 모델 임피던스 제어의 핵심 아이디어: ``` F = M_d * (x_ddot_d - x_ddot) + D_d * (x_dot_d - x_dot) + K_d * (x_d - x) ``` 또는 관성 항을 무시한 간소화 버전: ``` F = K_d * (x_d - x) + D_d * (x_dot_d - x_dot) ``` - K_d: 가상 강성(virtual stiffness). 크면 위치 추종이 정확하지만, 접촉 시 힘이 크다. - D_d: 가상 감쇠(virtual damping). 진동을 억제한다. - M_d: 가상 관성(virtual inertia). 보통 조정하기 어려워서 관성 항은 생략하는 경우가 많다. 핵심은 K_d와 D_d를 태스크에 맞게 조정하는 것이다: - 유리잔을 집을 때: K_d 낮게 (부드럽게), D_d 높게 (안정적으로) - 볼트를 조일 때: K_d 높게 (정밀하게) - 사람과 협업할 때: K_d 매우 낮게 (안전하게) ```python import numpy as np class ImpedanceController: """카르테시안 공간 임피던스 제어기 (1-DOF 간소화).""" def __init__(self, k_d: float, d_d: float, m_d: float = 0.0): self.k_d = k_d # 가상 강성 (N/m) self.d_d = d_d # 가상 감쇠 (N*s/m) self.m_d = m_d # 가상 관성 (kg) def compute_force(self, x_d, x, x_dot_d, x_dot, x_ddot_d=0.0, x_ddot=0.0) -> float: """목표 임피던스 관계에 따른 힘 계산.""" f = (self.k_d * (x_d - x) + self.d_d * (x_dot_d - x_dot) + self.m_d * (x_ddot_d - x_ddot)) return f # 시뮬레이션: 로봇이 벽에 접근하여 접촉 dt = 0.001 controller = ImpedanceController(k_d=500.0, d_d=50.0) # 로봇 + 환경 robot_mass = 2.0 position = 0.0 velocity = 0.0 target_position = 0.15 # 목표 위치 wall_position = 0.10 # 벽 위치 (목표보다 가까움) wall_stiffness = 10000.0 # 벽의 강성 positions = [] forces = [] contact_forces = [] for step in range(10000): # 환경 접촉력 if position > wall_position: f_env = -wall_stiffness * (position - wall_position) else: f_env = 0.0 # 임피던스 제어 출력 f_ctrl = controller.compute_force( x_d=target_position, x=position, x_dot_d=0.0, x_dot=velocity ) # 동역학 acceleration = (f_ctrl + f_env) / robot_mass velocity += acceleration * dt position += velocity * dt positions.append(position) forces.append(f_ctrl) contact_forces.append(-f_env) # 결과: position은 wall_position 근처에서 안정화 # 벽을 부수지 않고, 적절한 접촉력으로 밀고 있다 print(f"최종 위치: {positions[-1]:.4f} m (벽: {wall_position} m)") print(f"최종 접촉력: {contact_forces[-1]:.2f} N") # 순수 위치 제어였으면 벽에 10000 N/m * 0.05 m = 500 N을 때렸을 것이다 ``` #### Admittance Control 임피던스 제어가 "위치 편차 → 힘 출력"이라면, 어드미턴스 제어는 반대다: "힘 입력 → 위치 출력." ``` x_d_new = x_d + (1 / K_d) * F_ext + (1 / D_d) * F_ext_dot ``` 좀 더 정확히, 외력 F_ext가 측정되면 이를 가상 임피던스 모델에 넣어서 목표 위치를 수정하고, 그 수정된 목표를 기존 (강성이 높은) 위치 제어기에 전달한다. 산업용 로봇에서 어드미턴스 제어가 많이 쓰이는 이유: 산업용 로봇은 이미 매우 정밀한 위치 제어기가 내장되어 있고, 대부분 외부에서 토크 명령을 직접 줄 수 없다. 그래서 힘 센서(F/T sensor)로 외력을 측정하고, 위치 명령을 수정하는 어드미턴스 방식이 더 실용적이다. 반면 연구용 토크 제어 가능 로봇(Franka Emika Panda 등)에서는 임피던스 제어가 더 자연스럽다. --- ### 6.7 심화: Whole-Body Control *연구자가 되고 싶다면 여기서부터 읽어라.* 휴머노이드 로봇이나 사족 보행 로봇은 관절이 수십 개이고, 여러 개의 접촉점(발, 손)을 동시에 관리해야 하며, 균형도 유지해야 한다. 이런 시스템에서 "각 관절에 PID를 걸어라"는 것은 사실상 의미가 없다. 전신(whole-body) 레벨에서 통합적으로 제어해야 한다. #### Task-space vs Joint-space - **Joint-space control**: 관절 각도 q를 직접 제어한다. 간단하지만 태스크 수준의 목표(end-effector 위치, 무게중심 위치)를 달성하려면 역기구학(inverse kinematics)을 먼저 풀어야 한다. - **Task-space control**: 태스크 좌표(카르테시안 위치, 방향)에서 직접 제어한다. 태스크 목표를 자연스럽게 기술할 수 있다. 관절 공간으로의 매핑은 제어기 내부에서 처리한다. #### Operational Space Control (Khatib, 1987) Khatib의 Operational Space Framework는 task-space 제어의 기초다. 핵심 아이디어: 태스크 공간에서의 동역학을 직접 유도한다. 조인트 공간 동역학: ``` M(q) * q_ddot + C(q, q_dot) * q_dot + g(q) = tau + J^T * F_ext ``` 태스크 공간으로 변환: ``` Lambda(q) * x_ddot + mu(q, q_dot) * x_dot + p(q) = F + F_ext ``` 여기서 Lambda = (J * M^(-1) * J^T)^(-1)은 태스크 공간 관성 행렬이다. 태스크 공간에서 원하는 가속도 x_ddot_d를 달성하기 위한 관절 토크: ``` tau = J^T * Lambda * x_ddot_d + C * q_dot + g(q) ``` 이 프레임워크 위에 임피던스 제어를 결합하면, 태스크 공간에서 원하는 동적 행동(impedance)을 구현할 수 있다. #### QP 기반 Whole-Body Control 현대적 WBC는 매 제어 주기에 QP(Quadratic Program)를 풀어 여러 태스크를 동시에 처리한다. 기본 구조: ``` minimize || J_task * q_ddot - x_ddot_d ||^2 (태스크 추종) subject to M(q)*q_ddot + h(q,q_dot) = S^T*tau + J_c^T*F_c (동역학) F_c ∈ friction cone (접촉력 제약) tau_min ≤ tau ≤ tau_max (토크 제한) ``` 여기서: - J_task: 태스크 자코비안 - J_c: 접촉 자코비안 - F_c: 접촉력 - S: selection matrix (underactuated 자유도 제거) **다중 태스크 우선순위**: 실제 로봇에서는 여러 태스크가 충돌한다. 예를 들어 "오른손을 목표 위치에 보내라" + "균형을 유지하라" + "관절 한계를 지켜라". 이때 태스크에 우선순위를 부여한다: 1. 최고 우선순위: 접촉 제약 (발이 바닥에 붙어 있어야 한다), 관절 한계 2. 높은 우선순위: 균형 유지 (CoM 제어) 3. 중간 우선순위: end-effector 위치 제어 4. 낮은 우선순위: 자세 유지 (null-space) 이것을 strict hierarchy로 구현하려면 null-space projection을 쓰거나, 각 우선순위 레벨의 QP를 순차적으로 푼다 (hierarchical QP). 또는 soft priority로 가중치를 다르게 두어 하나의 QP로 합칠 수도 있다. #### Contact-Consistent Control 보행 로봇에서 중요한 점은 접촉력이 물리적으로 타당해야 한다는 것이다: - **단방향 접촉(unilateral contact)**: 발이 바닥을 당길 수 없다. F_z >= 0. - **마찰 원뿔(friction cone)**: 접선력이 수직력 x 마찰계수보다 작아야 한다. sqrt(F_x^2 + F_y^2) <= mu * F_z. - **ZMP/CoP 제약**: 압력 중심(Center of Pressure)이 지지 다각형(support polygon) 안에 있어야 넘어지지 않는다. 이 모든 제약을 QP에 넣으면, 물리적으로 실현 가능한 제어 입력을 얻을 수 있다. 마찰 원뿔은 원래 비선형(second-order cone)이지만, 다면체로 근사(linearized friction cone)하면 QP로 풀 수 있다. ```python import numpy as np def linearized_friction_cone(mu, n_edges=8): """ 마찰 원뿔의 다면체 근사. 반환: A_cone * F <= 0 형태의 제약 행렬. F = [fx, fy, fz]^T """ A_rows = [] for i in range(n_edges): theta = 2 * np.pi * i / n_edges # mu * fz >= cos(theta)*fx + sin(theta)*fy # → cos(theta)*fx + sin(theta)*fy - mu*fz <= 0 row = [np.cos(theta), np.sin(theta), -mu] A_rows.append(row) # fz >= 0 → -fz <= 0 A_rows.append([0, 0, -1]) return np.array(A_rows) # 마찰계수 0.7, 8각형 근사 A_friction = linearized_friction_cone(mu=0.7) print(f"마찰 원뿔 제약 행렬 shape: {A_friction.shape}") # (9, 3) → 9개의 선형 부등식으로 3D 마찰 원뿔을 근사 ``` --- ### 6.8 심화: Lyapunov 안정성과 적응 제어 *연구자가 되고 싶다면 여기서부터 읽어라.* 제어기를 설계했으면, "이 제어기가 시스템을 정말 안정하게 만드는가?"를 증명해야 한다. 시뮬레이션에서 잘 되는 것과 수학적으로 안정성이 보장되는 것은 전혀 다른 문제다. Lyapunov 이론은 이 증명의 핵심 도구다. #### Lyapunov 안정성 비선형 시스템 x_dot = f(x)에서 원점이 평형점이라 하자 (f(0) = 0). Lyapunov의 직접 방법(direct method): 함수 V(x)가 다음을 만족하면 원점은 안정하다. 1. V(0) = 0 2. V(x) > 0 for all x != 0 (양정치) 3. V_dot(x) = dV/dx * f(x) <= 0 (비증가) V_dot(x) < 0이면 점근적 안정(asymptotically stable), 즉 시간이 지남에 따라 상태가 원점으로 수렴한다. 물리적 직관: V(x)를 에너지로 생각하면 된다. 에너지가 항상 양수이고, 시간에 따라 감소하면, 시스템은 에너지가 최소인 평형점으로 수렴한다. 어려운 점: V(x)를 찾는 것이다. 일반적인 방법론이 없다. 기계 시스템에서는 역학적 에너지(운동에너지 + 위치에너지)가 자연스러운 Lyapunov 함수 후보이다. 선형 시스템에서는 V(x) = x^T * P * x (P는 ARE의 해)가 Lyapunov 함수가 된다. LQR의 안정성 증명이 여기서 나온다. #### 적응 제어 (Adaptive Control) 모델 파라미터가 정확히 알려져 있지 않을 때 쓴다. 예를 들어 로봇 팔에 실린 페이로드의 무게를 모른다거나, 마찰 계수가 시간에 따라 변한다거나. 기본 아이디어: 제어기 내에 파라미터 추정기(estimator)를 내장하고, 제어와 추정을 동시에 수행한다. 로봇 동역학은 다음과 같이 파라미터에 대해 선형인 형태로 쓸 수 있다: ``` M(q)*q_ddot + C(q,q_dot)*q_dot + g(q) = Y(q, q_dot, q_ddot) * theta ``` 여기서 Y는 regressor matrix이고, theta는 동역학 파라미터 벡터(질량, 관성, 마찰 등)이다. 적응 제어 법칙: ``` tau = Y * theta_hat - K_d * s theta_hat_dot = -Gamma * Y^T * s ``` 여기서 s는 sliding variable, theta_hat은 파라미터 추정값, Gamma는 적응 게인 행렬이다. Lyapunov 함수를 적절히 잡으면 (V = 0.5*s^T*M*s + 0.5*theta_tilde^T*Gamma^(-1)*theta_tilde), V_dot <= 0을 보일 수 있고, 추종 오차가 0으로 수렴함을 증명할 수 있다. theta_hat이 실제 theta로 수렴하는 것은 보장되지 않는다는 점에 주의. 추종 오차만 수렴한다. #### Robust Control 모델 불확실성이 있지만 그 범위(bound)는 아는 경우에 쓴다. - **H-infinity control**: 최악의 외란에 대한 성능을 최적화한다. "어떤 외란이 들어오든 출력 오차가 이 이하로 유지된다"는 보장을 준다. 수학이 무겁고 (Riccati 부등식, LMI), 보수적인 경향이 있다. 1990년대에 산업적으로 많이 적용되었다. - **Sliding Mode Control**: 상태를 슬라이딩 면(sliding surface)으로 유한 시간 내에 끌어온 뒤, 슬라이딩 면 위에서 원하는 동특성을 따르게 한다. 모델 불확실성에 매우 강건하다. 문제는 chattering: 슬라이딩 면 근처에서 고주파 스위칭이 발생하여 액추에이터에 부담을 준다. Boundary layer approach나 higher-order sliding mode로 완화한다. #### 언제 쓰는가, 언제 안 쓰는가 | 상황 | 추천 | 비추천 | |------|------|--------| | 모델이 정확하고 선형성 충분 | LQR, MPC | 적응 제어 (과설계) | | 파라미터 불확실성이 큼 | 적응 제어 | PID만으로 버티기 | | 불확실성 범위를 알고 있음 | Robust control (H-inf) | 적응 제어 (불필요) | | 안전 인증이 필요함 | Lyapunov 기반 증명 | "시뮬레이션에서 됐으니까 OK" | | 빠르게 프로토타입 | PID + feedforward | 처음부터 H-infinity | 솔직히, 논문을 쓰는 것이 아니라면 적응 제어나 sliding mode를 실제 시스템에 쓰는 일은 많지 않다. MPC가 충분히 강력하고 직관적이기 때문이다. 하지만 "왜 이 제어기가 안정한가?"를 설명해야 할 때, Lyapunov 이론은 피할 수 없다. 특히 안전이 중요한 시스템(의료 로봇, 자율주행)에서는 수학적 안정성 증명이 필수다. --- ### 6.9 추천 자료 > **Åström & Murray, "Feedback Systems: An Introduction for Scientists and Engineers"** > https://fbswiki.org/ > 무료 PDF 제공. 제어 이론 입문서로 가장 적합하다. 수학이 과하지 않으면서 핵심을 정확히 짚는다. PID부터 상태공간, 주파수 응답까지. 학부생이라면 이 책부터 시작하라. > **Steve Brunton, "Control Bootcamp" (YouTube)** > https://www.youtube.com/playlist?list=PLMrJAkhIeNNR20Mz-VpzgfQs5zrYi085m > 상태공간, 가제어성, 가관측성, LQR을 직관적으로 설명한다. 영상 하나가 15분 내외로 짧고 밀도가 높다. 교과서를 읽기 전에 먼저 보면 이해가 훨씬 빠르다. > **Slotine & Li, "Applied Nonlinear Control"** > 비선형 제어, Lyapunov 안정성, 적응 제어의 표준 교재. 6.8절의 내용을 본격적으로 공부하려면 이 책이다. 절판이지만 PDF가 돌아다닌다 (알아서 찾아라). > **Russ Tedrake, "Underactuated Robotics" (MIT OCW)** > https://underactuated.csail.mit.edu/ > 무료 온라인 교재 + 강의. MPC, trajectory optimization, 그리고 제어와 계획(planning)의 연결을 깊이 있게 다룬다. Drake 라이브러리의 이론적 배경이기도 하다. > **python-control library** > https://python-control.readthedocs.io/ > Python으로 제어 시스템을 분석하고 설계하는 라이브러리. MATLAB Control System Toolbox의 Python 대안. Bode plot, root locus, state-space 분석 등을 지원한다. > **CasADi** > https://web.casadi.org/ > Nonlinear MPC 구현의 사실상 표준 도구. 자동 미분(automatic differentiation)과 다양한 NLP solver (IPOPT, SNOPT)를 지원한다. Python, MATLAB, C++ 인터페이스 제공. > **OSQP (Operator Splitting Quadratic Program)** > https://osqp.org/ > Linear MPC용 QP solver. 빠르고, robust하고, 코드 생성(code generation)이 가능하여 임베디드 시스템에 배포할 수 있다. C 구현 기반으로 Python, MATLAB, Julia 등 다양한 바인딩을 제공한다. > **주요 논문** > - [Hogan, "Impedance Control: An Approach to Manipulation" (ASME JDSMC 1985)](https://doi.org/10.1115/1.3140702) — 임피던스 제어의 원논문. 위치 제어와 힘 제어를 통합하는 프레임워크 제시 > - [Khatib, "A Unified Approach for Motion and Force Control of Robot Manipulators: The Operational Space Formulation" (IEEE RA 1987)](https://doi.org/10.1109/JRA.1987.1087068) — Operational Space Control의 원논문. Task-space 동역학 유도와 제어의 기초 > - [Khazoom et al., "Tailoring Solution Accuracy for Fast Whole-Body MPC" (RA-L 2024, arXiv:2407.10789)](https://arxiv.org/abs/2407.10789) — 실시간 whole-body MPC의 최신 접근 --- ### 기술 흐름 ``` 1922 ── PID 제어 개념 정립 (Minorsky) 1960 ── 상태공간 이론 (Kalman) 1960 ── LQR (Kalman) 1985 ── Impedance Control 개념 (Hogan) 1987 ── Operational Space Control (Khatib) 1990s ─ Robust control (H-infinity) 산업 적용 2004 ── 실시간 MPC 실용화 시작 2019 ── Boston Dynamics Atlas: MPC + WBC 2023 ── Unitree H1/G1: 학습 기반 + MPC 하이브리드 2024 ── Figure 01: LLM + MPC + manipulation ``` --- 이 장에서 다룬 내용은 제어 이론의 전체 중 로보틱스에서 실제로 쓰이는 핵심 부분이다. 각 기법의 수학적 세부사항은 추천 자료를 통해 공부하기 바란다. 한 가지 조언하자면, 제어 이론은 시뮬레이션 없이 이해하기 어렵다. 이 장의 코드를 직접 실행하고, 파라미터를 바꿔가며 시스템 응답이 어떻게 달라지는지 관찰하는 것이 가장 효과적인 학습 방법이다. 교과서를 세 번 읽는 것보다 시뮬레이션을 한 번 돌리는 것이 낫다. --- ## 7. 모션 플래닝 & 궤적 최적화 (Motion Planning & Trajectory Optimization) 로봇이 A에서 B로 가려면 경로가 필요하다. 단순히 직선으로 가면 되지 않냐고 생각할 수 있지만, 장애물이 있고, 관절 한계가 있고, 동역학 제약이 있다. 이 모든 조건을 만족하는 경로를 찾는 것이 모션 플래닝이고, 그 경로를 시간에 따라 최적으로 추종하는 것이 궤적 최적화다. --- ### 7.1 왜 모션 플래닝을 배우는가 6축 로봇 팔에게 "저 컵을 집어라"라고 명령했다고 하자. IK로 목표 관절 각도를 구했다. 그런데 현재 자세에서 목표 자세로 관절을 직선으로 보간(interpolation)하면, 팔이 테이블을 관통하거나 자기 몸체에 충돌할 수 있다. 관절 공간에서의 직선이 작업 공간에서의 직선이 아니기 때문이다. 모션 플래닝은 다음 질문에 답한다: - 충돌 없이 목표에 도달하는 경로가 존재하는가? - 존재한다면, 가장 짧은/빠른/부드러운 경로는 무엇인가? - 동역학 제약(토크 한계, 속도 한계)을 만족하면서 그 경로를 따라갈 수 있는가? --- ### 7.2 Configuration Space (C-space) 모션 플래닝의 핵심 개념이다. 로봇의 모든 가능한 상태를 하나의 공간으로 표현한다. **Joint space = Configuration space**: n-DOF 로봇의 configuration은 q = (q1, q2, ..., qn)이다. 이 q가 살고 있는 n차원 공간이 C-space이다. **C-space obstacle**: 작업 공간(3D)의 장애물을 C-space로 변환한 것이다. C-space에서 장애물 영역에 속하는 configuration은 충돌 상태이다. 왜 C-space에서 생각해야 하는가: 로봇은 점이 아니다. 3D 공간에서 로봇의 모든 링크가 장애물과 충돌하지 않는지 확인하려면, 각 configuration에서 FK를 계산하고 충돌 검사를 해야 한다. C-space에서는 로봇을 "점"으로 취급할 수 있고, 장애물을 피하는 문제가 점의 경로 찾기 문제로 환원된다. 문제는 C-space obstacle의 정확한 형태를 계산하는 것이 일반적으로 어렵다는 것이다. 그래서 실무에서는 C-space obstacle을 명시적으로 구하지 않고, 특정 configuration에서의 충돌 여부를 검사하는 collision checker를 사용한다. --- ### 7.3 그래프 탐색 기반 플래닝 가장 고전적인 접근이다. C-space를 이산화(discretize)하고 그래프 탐색 알고리즘으로 경로를 찾는다. #### Dijkstra 알고리즘 가중 그래프에서 최단 경로를 찾는다. 모든 간선을 탐색하므로 최적해를 보장한다. 시간 복잡도 O((V + E) log V). #### A* 알고리즘 Dijkstra에 휴리스틱을 추가한 것이다. 목표까지의 추정 거리(heuristic)를 이용하여 탐색 방향을 유도한다. 휴리스틱이 admissible(실제 거리 이하)이면 최적해를 보장하면서 Dijkstra보다 빠르다. ```python import heapq import numpy as np def astar_2d(grid, start, goal): """2D 격자에서의 A* 경로 탐색. grid: 0=free, 1=obstacle """ rows, cols = grid.shape open_set = [(0, start)] # (f_score, node) came_from = {} g_score = {start: 0} def heuristic(a, b): return abs(a[0] - b[0]) + abs(a[1] - b[1]) # Manhattan distance neighbors = [(-1,0), (1,0), (0,-1), (0,1), (-1,-1), (-1,1), (1,-1), (1,1)] while open_set: f, current = heapq.heappop(open_set) if current == goal: # 경로 복원 path = [current] while current in came_from: current = came_from[current] path.append(current) return path[::-1] for dx, dy in neighbors: neighbor = (current[0] + dx, current[1] + dy) if (0 <= neighbor[0] < rows and 0 <= neighbor[1] < cols and grid[neighbor] == 0): cost = np.sqrt(dx**2 + dy**2) tentative_g = g_score[current] + cost if tentative_g < g_score.get(neighbor, float('inf')): came_from[neighbor] = current g_score[neighbor] = tentative_g f_score = tentative_g + heuristic(neighbor, goal) heapq.heappush(open_set, (f_score, neighbor)) return None # 경로 없음 ``` #### 장단점 격자 기반 플래닝은 **완전성(completeness)**을 보장한다 — 해가 존재하면 반드시 찾는다. 하지만 **차원의 저주(curse of dimensionality)**에 시달린다. 6-DOF 로봇 팔의 C-space를 각 축 100개로 이산화하면 100^6 = 10^12개의 셀이 된다. 사실상 불가능하다. 이 문제를 해결하기 위해 샘플링 기반 플래너가 등장했다. --- ### 7.4 샘플링 기반 플래너 C-space를 이산화하지 않고, 무작위로 샘플링하여 경로를 탐색한다. 고차원 C-space에서 실용적인 유일한 방법이다. #### RRT (Rapidly-exploring Random Tree) LaValle (1998)이 제안한 알고리즘이다. 핵심 아이디어는 단순하다: ``` 1. 시작점에서 트리를 초기화한다. 2. C-space에서 무작위 점 q_rand를 샘플링한다. 3. 트리에서 q_rand에 가장 가까운 노드 q_near를 찾는다. 4. q_near에서 q_rand 방향으로 step_size만큼 확장하여 q_new를 만든다. 5. q_near → q_new 경로가 충돌하지 않으면 트리에 추가한다. 6. q_new가 목표 근처면 종료. 아니면 2로 돌아간다. ``` ```python import numpy as np class RRT: def __init__(self, start, goal, obstacle_fn, bounds, step_size=0.3, max_iter=5000): self.start = np.array(start) self.goal = np.array(goal) self.obstacle_fn = obstacle_fn # config -> bool (True if collision) self.bounds = np.array(bounds) # [[min_q1, max_q1], ...] self.step_size = step_size self.max_iter = max_iter self.nodes = [self.start] self.parents = {0: -1} def sample_random(self): # 10% 확률로 goal을 샘플링 (goal bias) if np.random.random() < 0.1: return self.goal return np.random.uniform(self.bounds[:, 0], self.bounds[:, 1]) def nearest(self, q): dists = [np.linalg.norm(node - q) for node in self.nodes] return np.argmin(dists) def steer(self, q_near, q_rand): direction = q_rand - q_near dist = np.linalg.norm(direction) if dist < self.step_size: return q_rand return q_near + (direction / dist) * self.step_size def collision_free(self, q1, q2, n_checks=10): for t in np.linspace(0, 1, n_checks): q = q1 + t * (q2 - q1) if self.obstacle_fn(q): return False return True def plan(self): for i in range(self.max_iter): q_rand = self.sample_random() idx_near = self.nearest(q_rand) q_near = self.nodes[idx_near] q_new = self.steer(q_near, q_rand) if self.collision_free(q_near, q_new): idx_new = len(self.nodes) self.nodes.append(q_new) self.parents[idx_new] = idx_near if np.linalg.norm(q_new - self.goal) < self.step_size: # 경로 복원 path = [q_new] idx = idx_new while self.parents[idx] != -1: idx = self.parents[idx] path.append(self.nodes[idx]) return path[::-1] return None # 실패 ``` #### RRT* (Optimal RRT) Karaman & Frazzoli (2011)이 제안. RRT는 해를 찾지만 최적이 아니다. RRT*는 새 노드 추가 시 근처 노드들과의 re-wiring을 수행하여 점근적 최적성(asymptotic optimality)을 보장한다. 샘플 수가 무한대로 가면 최적 경로에 수렴한다. 실무적으로 RRT*는 RRT보다 좋은 경로를 찾지만, 수렴이 느리다. 시간 제한이 있는 실시간 상황에서는 RRT-Connect가 더 실용적인 경우가 많다. #### PRM (Probabilistic Roadmap) Kavraki et al. (1996)이 제안. RRT가 single-query(한 번에 하나의 start-goal 쌍)인 반면, PRM은 multi-query에 적합하다. 1단계 (offline): C-space에 많은 점을 샘플링하고, 가까운 점들을 충돌 없는 간선으로 연결하여 로드맵(graph)을 구축한다. 2단계 (online): start와 goal을 로드맵에 연결하고, 그래프 탐색(A* 등)으로 경로를 찾는다. 같은 환경에서 여러 경로 쿼리가 필요한 경우(예: 산업용 로봇 셀) PRM이 효율적이다. #### RRT-Connect Kuffner & LaValle (2000)이 제안. 시작점과 목표점에서 동시에 트리를 성장시키고, 두 트리가 만나면 경로를 연결한다. 실무에서 가장 많이 쓰이는 변종이다. MoveIt2의 기본 플래너도 RRT-Connect(OMPL 내장)이다. #### OMPL 라이브러리 Open Motion Planning Library (https://ompl.kavrakilab.org/). Kavraki Lab (Rice University)에서 개발한 C++ 라이브러리로, RRT, RRT*, RRT-Connect, PRM, EST, KPIECE 등 수십 가지 샘플링 기반 플래너를 제공한다. OMPL 자체는 충돌 검사를 하지 않는다. State validity checker를 사용자가 제공해야 한다. MoveIt2는 OMPL + FCL(Flexible Collision Library)를 결합하여 완전한 모션 플래닝 파이프라인을 구성한다. ```python # MoveIt2에서 OMPL 기반 모션 플래닝 (ROS2 Python API, 간략화) from moveit_py import MoveItPy moveit = MoveItPy(node_name="motion_planner") arm = moveit.get_planning_component("manipulator") # 목표 설정 arm.set_goal_state(configuration_name="home") # 플래닝 (OMPL RRT-Connect가 기본) plan_result = arm.plan() if plan_result: # 실행 arm.execute() ``` > **추천 자료** > - [LaValle, "Planning Algorithms"](http://lavalle.pl/planning/) — 무료 온라인 교재. 모션 플래닝의 표준 교재 > - [OMPL](https://ompl.kavrakilab.org/) — 오픈소스 모션 플래닝 라이브러리 > - [MoveIt2 Tutorials](https://moveit.picknik.ai/) — ROS2 기반 실전 모션 플래닝 가이드 --- ### 7.5 궤적 최적화 (Trajectory Optimization) 샘플링 기반 플래너는 "충돌 없는 경로"를 찾아준다. 하지만 그 경로는: - 울퉁불퉁하다 (random sampling이므로) - 동역학을 무시한다 (기구학적 경로만 제공) - 시간 정보가 없다 (어떤 속도로 따라가야 하는지 모른다) 궤적 최적화는 이 한계를 보완한다. 비용 함수(시간, 에너지, 부드러움)를 최소화하면서, 동역학 제약, 충돌 회피, 관절 한계를 모두 만족하는 궤적을 찾는다. #### Direct Collocation 궤적을 시간 구간으로 나누고, 각 구간의 상태와 입력을 결정 변수(decision variable)로 둔다. 동역학 방정식은 등식 제약(equality constraint)으로 처리한다. ``` minimize Σ_k L(x_k, u_k) * dt (비용) subject to x_{k+1} = f(x_k, u_k) for all k (동역학) g(x_k) <= 0 for all k (부등식 제약: 충돌, 관절 한계) x_0 = x_init (초기 조건) x_N = x_goal (종단 조건) ``` 이것을 하나의 큰 nonlinear program(NLP)으로 만들고, IPOPT 같은 솔버로 푼다. 장점: 동역학과 제약을 동시에 처리, 부드러운 궤적 단점: 초기 추측(initial guess)에 민감, 비볼록이므로 지역 최적해에 빠질 수 있음 #### Direct Shooting 상태를 결정 변수에서 제거하고, 입력 시퀀스 {u_0, u_1, ..., u_{N-1}}만을 결정 변수로 둔다. 상태는 동역학 시뮬레이션으로 계산한다. collocation보다 결정 변수가 적지만, 시뮬레이션이 불안정하면 (예: 도립진자) 최적화도 불안정해진다. #### CHOMP (Covariant Hamiltonian Optimization for Motion Planning) Ratliff et al. (2009)이 제안. 초기 궤적(보통 직선 보간)에서 시작하여, 충돌 비용 + 부드러움 비용의 gradient를 따라 궤적을 반복적으로 개선한다. 공변 gradient(covariant gradient)를 사용하여 업데이트가 부드럽다. 장점: 직관적, 기존 궤적을 점진적으로 개선 단점: 좁은 통로(narrow passage)를 통과하기 어려움, 지역 최적해 #### TrajOpt Schulman et al. (2014)이 제안. Sequential convex optimization 기반이다. 매 반복에서 비선형 문제를 선형/이차 근사로 바꿔서 QP로 풀고, trust region으로 수렴을 보장한다. 충돌 회피를 signed distance function으로 처리하여 연속적인 gradient를 제공한다. #### CasADi를 이용한 Trajectory Optimization CasADi는 symbolic computation + automatic differentiation + NLP solver 연결을 제공하는 프레임워크다. Trajectory optimization의 사실상 표준 도구이다. ```python import casadi as ca import numpy as np # 간단한 예: 1D 더블 인티그레이터의 시간 최적 궤적 # x = [position, velocity], u = force # x_dot = [velocity, force/mass] N = 50 # 구간 수 dt = 0.1 # 시간 간격 mass = 1.0 opti = ca.Opti() # 결정 변수 X = opti.variable(2, N + 1) # 상태 궤적 U = opti.variable(1, N) # 입력 궤적 # 비용: 에너지 최소화 + 시간 패널티 cost = 0 for k in range(N): cost += U[0, k]**2 * dt # 에너지 opti.minimize(cost) # 동역학 제약 (Euler integration) for k in range(N): x_next = X[:, k] + ca.vertcat(X[1, k], U[0, k] / mass) * dt opti.subject_to(X[:, k + 1] == x_next) # 경계 조건 opti.subject_to(X[:, 0] == ca.vertcat(0, 0)) # 시작: 위치 0, 속도 0 opti.subject_to(X[:, N] == ca.vertcat(1, 0)) # 종료: 위치 1, 속도 0 # 입력 제약 opti.subject_to(opti.bounded(-5.0, U, 5.0)) # 상태 제약 (속도 제한) opti.subject_to(opti.bounded(-2.0, X[1, :], 2.0)) # 솔버 설정 opti.solver('ipopt', {'print_time': False}, {'print_level': 0}) sol = opti.solve() x_opt = sol.value(X) u_opt = sol.value(U) print(f"최적 궤적 - 최종 위치: {x_opt[0, -1]:.4f}") print(f"최대 힘: {np.max(np.abs(u_opt)):.4f} N") ``` > **추천 자료** > - [Matthew Kelly, "An Introduction to Trajectory Optimization" (SIAM Review 2017)](https://www.matthewpeterkelly.com/research/MatthewKelly_IntroTrajectoryOptimization_SIAM_Review_2017.pdf) — collocation과 shooting을 비교하는 좋은 튜토리얼 > - [CasADi](https://web.casadi.org/) — NLP 구현 표준 도구 > - [Drake Trajectory Optimization](https://drake.mit.edu/) — direct collocation 예제 포함 --- ### 7.6 MoveIt2: 실전 모션 플래닝 MoveIt2는 ROS2 기반의 모션 플래닝 프레임워크이다. 산업/연구 양쪽에서 가장 널리 쓰이는 로봇 팔 플래닝 도구다. **아키텍처:** - **Planning Scene**: 로봇 + 환경(장애물)의 3D 모델 관리. 충돌 검사의 기반. - **Planning Pipeline**: OMPL 등 플래너 호출 → 경로 검증 → 시간 매개변수화(time parameterization) - **Move Group Interface**: 사용자 API. 목표 설정, 플래닝, 실행을 추상화. **OMPL 통합**: MoveIt2는 OMPL을 기본 플래닝 백엔드로 사용한다. `ompl_planning.yaml`에서 플래너 종류와 파라미터를 설정한다. ```yaml # ompl_planning.yaml 예시 manipulator: planner_configs: - RRTConnectkConfigDefault - RRTstarkConfigDefault - PRMkConfigDefault default_planner_config: RRTConnectkConfigDefault projection_evaluator: joints(joint1, joint2) longest_valid_segment_fraction: 0.01 ``` **Pick-and-Place 파이프라인:** 1. 물체 인식 (Perception) → 물체의 6-DoF 포즈 추정 2. Grasp planning → 잡을 위치/자세 결정 3. Approach trajectory → 물체 위 접근점까지 모션 플래닝 4. Grasp → 그리퍼 닫기 5. Retreat trajectory → 물체를 들어올림 6. Place trajectory → 놓을 위치까지 모션 플래닝 7. Release → 그리퍼 열기 각 단계에서 MoveIt2가 충돌 회피와 관절 한계를 자동으로 처리한다. --- ### 7.7 심화: Optimization-Based Planning *연구자가 되고 싶다면 여기서부터 읽어라.* #### Constrained Nonlinear Optimization 실제 로봇의 궤적 최적화는 대부분 constrained NLP이다: ``` minimize Σ L(x_k, u_k) + Φ(x_N) subject to x_{k+1} = f(x_k, u_k) (동역학) h(x_k, u_k) = 0 (등식 제약) g(x_k, u_k) <= 0 (부등식 제약: 충돌, 토크 한계 등) ``` IPOPT(Interior Point Optimizer)가 이 문제를 푸는 표준 솔버다. CasADi에서 IPOPT를 기본으로 사용한다. #### Contact-Implicit Trajectory Optimization 접촉 모드(어디가 닿아 있고 어디가 떨어져 있는지)를 미리 지정하지 않고, 최적화가 자동으로 결정하게 하는 방법이다. 걷기, 잡기 같은 접촉 전환이 필요한 태스크에서 유용하다. 접촉력을 결정 변수에 포함하고, 상보성 조건(complementarity constraint)을 추가한다: ``` F_n >= 0 (접촉력은 당기지 못함) d >= 0 (물체가 바닥 아래로 못 감) F_n * d = 0 (떨어져 있으면 힘 0, 닿아 있으면 거리 0) ``` 이 문제는 수학적으로 MPCC(Mathematical Program with Complementarity Constraints)이고, 풀기 어렵다. Relaxation 기법이나 smoothed contact model을 쓴다. Drake의 `ContactImplicitDirectCollocation`이 이 방법을 구현한다. #### 실시간 Re-planning과 MPC의 연결 정적 환경에서 한 번 계획하면 끝이지만, 동적 환경에서는 실시간으로 재계획(re-plan)해야 한다. 이것이 궤적 최적화와 MPC의 연결점이다. MPC를 짧은 horizon의 trajectory optimization으로 볼 수 있다. 매 제어 주기마다 짧은 구간의 궤적을 최적화하고, 첫 입력만 적용한 뒤 다시 최적화한다. 이전 장의 MPC가 정확히 이것이다. 차이점: 모션 플래닝의 trajectory optimization은 보통 오프라인으로 전체 궤적을 한 번에 계산하고, MPC는 온라인으로 짧은 구간을 반복 계산한다. --- ### 7.8 심화: Task and Motion Planning (TAMP) *연구자가 되고 싶다면 여기서부터 읽어라.* "컵을 선반 위에 놓아라"라는 명령을 수행하려면: 1. 컵이 어디 있는지 인식 2. 컵을 잡을 수 있는 grasp pose 결정 3. 접근 → 잡기 → 들기 → 이동 → 놓기 순서 계획 4. 각 단계의 모션 플래닝 1-3은 **symbolic planning** (어떤 순서로 어떤 action을 할지), 4는 **motion planning** (구체적으로 어떤 궤적으로 움직일지). TAMP는 이 둘을 결합한다. #### PDDLStream MIT에서 개발한 TAMP 프레임워크. PDDL(Planning Domain Definition Language)로 symbolic action을 정의하고, stream을 통해 연속적 파라미터(grasp pose, placement pose)를 생성한다. #### LLM 기반 Task Planning 최근에는 LLM이 symbolic planner를 대체하는 시도가 활발하다: - **SayCan** (Google, 2022): LLM이 가능한 action들의 자연어 설명을 평가하고, affordance model이 현재 상태에서 실행 가능한 action을 필터링한다. 둘의 곱으로 다음 action을 선택한다. - **Code as Policies** (Google, 2023): LLM이 직접 로봇 제어 코드를 생성한다. 자연어 명령 → Python 코드 → 로봇 실행. - **Inner Monologue** (Google, 2023): LLM + 환경 피드백의 반복적 대화로 태스크를 완수한다. 현실적 한계: LLM 기반 TAMP는 아직 실험 단계이다. 복잡한 기하학적 제약(좁은 공간에서의 조작, 정밀 조립)은 LLM이 처리하기 어렵고, 결국 전통적 motion planner가 필요하다. LLM은 high-level 계획, motion planner는 low-level 실행이라는 역할 분담이 현실적이다. --- ### 7.9 추천 자료 > **LaValle, "Planning Algorithms"** > http://lavalle.pl/planning/ > 무료 온라인. 모션 플래닝의 가장 포괄적인 교과서. RRT의 원저자가 쓴 책이니 당연히 좋다. > **Russ Tedrake, "Underactuated Robotics" Ch.10: Trajectory Optimization** > https://underactuated.csail.mit.edu/trajopt.html > Drake를 이용한 trajectory optimization 실습. 코드와 이론이 함께 제공된다. > **Matthew Kelly, "An Introduction to Trajectory Optimization" (SIAM Review 2017)** > https://www.matthewpeterkelly.com/research/MatthewKelly_IntroTrajectoryOptimization_SIAM_Review_2017.pdf > Direct collocation과 shooting을 비교하는 좋은 튜토리얼. 예제 코드도 제공. > **OMPL** > https://ompl.kavrakilab.org/ > 오픈소스 모션 플래닝 라이브러리. RRT, RRT*, PRM 등 수십 가지 알고리즘 구현. > **MoveIt2 Tutorials** > https://moveit.picknik.ai/ > ROS2 기반 실전 모션 플래닝. Pick-and-place부터 고급 설정까지. > **Drake** > https://drake.mit.edu/ > Trajectory optimization + 시뮬레이션 통합 프레임워크. Contact-implicit 지원. > **CasADi** > https://web.casadi.org/ > Nonlinear trajectory optimization 구현 표준 도구. > **추가 논문** > - [Garrett et al., "Integrated Task and Motion Planning" (2021, arXiv:2010.01083)](https://arxiv.org/abs/2010.01083) — TAMP의 표준 서베이 논문 > - [Janner et al., "Planning with Diffusion for Flexible Behavior Synthesis" (ICML 2022, arXiv:2205.09991)](https://arxiv.org/abs/2205.09991) — trajectory-level diffusion 기반 planning의 시작 --- ### 기술 흐름 ``` 1979 ── Visibility graph 기반 path planning 1996 ── PRM (Kavraki et al.) — 샘플링 기반 플래닝의 시작 1998 ── RRT (LaValle) — single-query 플래닝의 표준 2000 ── RRT-Connect (Kuffner & LaValle) — 실무에서 가장 많이 쓰이는 변종 2009 ── CHOMP (Ratliff et al.) — gradient 기반 궤적 최적화 2011 ── RRT* (Karaman & Frazzoli) — 점근적 최적성 보장 2012 ── OMPL 1.0 공개 — 샘플링 기반 플래너 통합 라이브러리 2014 ── TrajOpt (Schulman et al.) — sequential convex optimization 2019 ── MoveIt2 (ROS2) — 산업/연구 표준 프레임워크 2022 ── SayCan (Google) — LLM + motion planning 2023 ── Contact-implicit trajectory optimization 실용화 2024 ── LLM 기반 TAMP 연구 확산 ``` --- ## 8. 로봇 러닝 (Robot Learning) 로봇 러닝은 로봇이 명시적 프로그래밍 대신 데이터와 경험으로부터 행동을 학습하는 분야다. 이 챕터에서는 강화학습 기초부터 sim-to-real transfer, 모방학습, 그리고 최근 foundation model 기반 접근까지 다룬다. --- ### 8.1 왜 로봇 러닝을 배우는가 **전통적 방법이 잘 되는 영역** PID 제어, MPC, RRT 같은 전통적 제어/플래닝은 dynamics 모델이 정확하고, 환경이 정형화되어 있을 때 매우 잘 동작한다. 산업용 로봇 팔이 정해진 위치의 부품을 집어 조립하는 작업이 대표적이다. 모델이 정확하면 최적 제어 이론이 수학적으로 보장하는 성능을 얻을 수 있다. 학습 기반 방법이 이걸 이기기는 쉽지 않다. **전통적 방법이 힘든 영역** 문제는 현실 세계가 깔끔하지 않다는 점이다. - **모델링이 어려운 dynamics**: 천, 로프, 유체 같은 deformable object의 물리 모델을 정확히 세우는 건 현실적으로 불가능에 가깝다. - **복잡한 접촉(contact)**: 물체를 손으로 돌리거나 끼워 맞추는 작업은 접촉 모드가 수시로 바뀐다. 접촉 역학을 정확히 모델링하는 것은 아직 열린 문제다. - **비정형 환경**: 가정집 부엌, 재난 현장 등 미리 모델링할 수 없는 환경에서의 동작. 어떤 물체가 어디에 있을지 알 수 없다. 이런 상황에서 학습 기반 접근은 데이터로부터 직접 입력-출력 관계를 근사하므로, 명시적 모델 없이도 동작할 수 있다. **하지만 만능은 아니다** 학습 기반 방법의 한계를 명확히 알아야 한다. - **데이터 효율(sample efficiency)**: 강화학습은 수백만 스텝의 상호작용이 필요한 경우가 많다. 실제 로봇에서 이 데이터를 모으는 건 시간과 비용 면에서 비현실적이다. - **안전성(safety)**: 학습 중 로봇이 자기 자신이나 주변 환경을 파손할 수 있다. 탐색(exploration)이 본질적으로 위험하다. - **일반화(generalization)**: 학습한 조건과 조금만 달라져도 성능이 급락하는 경우가 흔하다. 결론적으로, 전통적 방법으로 풀 수 있으면 전통적 방법을 쓰는 게 낫다. 학습은 전통적 방법이 한계에 부딪히는 문제에 적용하는 도구다. 둘을 적절히 조합하는 것이 실무에서 가장 현실적인 접근이다. --- ### 8.2 강화학습 기초 (RL Basics) #### MDP (Markov Decision Process) 강화학습의 수학적 프레임워크는 MDP다. 구성 요소는 다음과 같다. - **State (s)**: 환경의 현재 상태. 로봇의 관절 각도, 속도, 물체 위치 등. - **Action (a)**: 에이전트가 취하는 행동. 관절 토크, 목표 관절 각도 등. - **Reward (r)**: 행동의 결과로 받는 스칼라 보상 신호. r = R(s, a). - **Transition (T)**: 상태 전이 확률. T(s'|s, a). 현재 상태에서 행동을 취했을 때 다음 상태의 분포. - **Discount factor (γ)**: 미래 보상의 할인율. 0 < γ ≤ 1. 보통 0.99 정도를 쓴다. 목표는 cumulative discounted reward를 최대화하는 policy π(a|s)를 찾는 것이다. ``` J(π) = E[ Σ_{t=0}^{∞} γ^t · r_t ] ``` Markov property는 "다음 상태는 현재 상태와 행동에만 의존한다"는 가정이다. 이전 히스토리 전체를 볼 필요가 없다는 뜻인데, 실제 로봇에서는 이 가정이 깨지는 경우도 있다 (부분 관측, 즉 POMDP 상황). 이 경우 observation history를 state로 사용하거나 recurrent policy를 쓴다. #### Policy Gradient 직관 Policy gradient의 핵심 아이디어는 단순하다. 1. 현재 policy로 여러 trajectory를 수집한다. 2. 높은 return을 받은 trajectory에서의 action 확률을 올린다. 3. 낮은 return을 받은 trajectory에서의 action 확률을 내린다. 수식으로 쓰면: ``` ∇J(θ) = E[ Σ_t ∇log π_θ(a_t|s_t) · A_t ] ``` A_t는 advantage function으로, 해당 action이 평균 대비 얼마나 좋았는지를 나타낸다. 이 gradient를 따라 파라미터 θ를 업데이트한다. 직관적으로, `log π(a|s)`의 gradient는 action a의 확률을 올리는 방향이고, 여기에 advantage를 곱해서 좋은 action은 더 자주, 나쁜 action은 덜 자주 선택하도록 만든다. #### Value Function, Q-function - **Value function V^π(s)**: state s에서 policy π를 따랐을 때 기대되는 cumulative reward. - **Q-function Q^π(s, a)**: state s에서 action a를 취하고 이후 π를 따랐을 때의 기대 cumulative reward. - **Advantage A^π(s, a) = Q^π(s, a) - V^π(s)**: action a가 평균 대비 얼마나 좋은지. Value function을 따로 학습해두면 variance를 줄일 수 있다. 대부분의 현대 RL 알고리즘은 policy network와 value network를 함께 학습하는 actor-critic 구조를 사용한다. #### On-policy vs Off-policy - **On-policy**: 현재 policy가 수집한 데이터로만 학습한다. 데이터를 한번 쓰고 버린다. PPO가 대표적이다. 안정적이지만 sample efficiency가 낮다. - **Off-policy**: 과거 policy가 수집한 데이터도 재사용한다 (replay buffer). SAC, TD3가 대표적이다. sample efficient하지만 학습이 불안정할 수 있다. 로봇에서는 데이터 수집 비용이 크기 때문에 off-policy 방법의 sample efficiency가 매력적이다. 하지만 시뮬레이션에서 대규모 병렬 환경을 돌릴 수 있다면 on-policy(PPO)도 충분히 경쟁력이 있다. --- ### 8.3 주요 RL 알고리즘 #### PPO (Proximal Policy Optimization) PPO는 현재 가장 널리 쓰이는 on-policy 알고리즘이다. 핵심 아이디어는 policy update의 크기를 제한하는 것이다. 이전 policy 대비 너무 크게 바뀌면 clipping으로 잘라낸다. ``` L_CLIP(θ) = E[ min( r_t(θ) · A_t, clip(r_t(θ), 1-ε, 1+ε) · A_t ) ] ``` 여기서 r_t(θ) = π_θ(a_t|s_t) / π_θ_old(a_t|s_t)는 probability ratio, ε는 보통 0.2를 쓴다. PPO가 인기 있는 이유는 구현이 비교적 간단하고, 하이퍼파라미터에 둔감하며, 안정적으로 학습된다는 점이다. NVIDIA Isaac Lab 등 대규모 병렬 시뮬레이션과 결합하면 수천 개 환경에서 동시에 데이터를 수집할 수 있어서 sample efficiency 문제를 물량으로 해결할 수 있다. #### SAC (Soft Actor-Critic) SAC는 off-policy 알고리즘으로, entropy regularization을 추가한 것이 특징이다. 보상을 최대화하면서 동시에 policy의 entropy를 최대화한다. 즉, 가능한 한 다양한 action을 시도하도록 유도한다. ``` J(π) = E[ Σ_t γ^t ( r_t + α · H(π(·|s_t)) ) ] ``` α는 temperature parameter로, entropy와 reward 사이의 균형을 조절한다. 자동으로 α를 조절하는 방법도 있다. 장점은 연속 action space에서 sample efficient하다는 점이다. Replay buffer를 사용하므로 한번 수집한 데이터를 여러 번 재사용할 수 있다. 실제 로봇에서 직접 학습할 때 PPO보다 유리한 경우가 많다. #### TD3 (Twin Delayed DDPG) TD3는 DDPG의 개선 버전으로, SAC와 비슷한 off-policy 알고리즘이다. 핵심 개선점 세 가지: 1. **Twin Q-networks**: Q-function 두 개를 학습하고 작은 값을 사용하여 overestimation bias를 줄인다. 2. **Delayed policy update**: critic을 여러 번 업데이트한 후에 policy를 한번 업데이트한다. 3. **Target policy smoothing**: target action에 노이즈를 추가한다. SAC와 성능이 비슷하지만, entropy tuning이 필요 없어서 하이퍼파라미터가 약간 적다. 다만 탐색(exploration)이 SAC보다 약할 수 있다. #### 알고리즘 선택 가이드 | 상황 | 추천 알고리즘 | 이유 | |------|-------------|------| | 시뮬레이션, GPU 병렬화 가능 | PPO | 병렬 환경으로 sample efficiency 보상 가능 | | 실제 로봇, 데이터 적음 | SAC | off-policy, sample efficient | | 연속 action space, 안정성 중시 | SAC 또는 TD3 | 둘 다 연속 공간에 강함 | | 이산 action space | PPO 또는 DQN | SAC는 연속 공간 전용 | | 처음 시작하는 프로젝트 | PPO | 튜닝이 쉽고, 디버깅이 용이 | #### Stable-Baselines3 코드 예시 PPO로 MuJoCo Ant 환경을 학습하는 기본 코드다. ```python import gymnasium as gym from stable_baselines3 import PPO from stable_baselines3.common.env_util import make_vec_env from stable_baselines3.common.evaluation import evaluate_policy # 병렬 환경 생성 (8개) vec_env = make_vec_env("Ant-v4", n_envs=8) # PPO 에이전트 생성 model = PPO( "MlpPolicy", vec_env, learning_rate=3e-4, n_steps=2048, # 한 번의 rollout에서 수집할 스텝 수 batch_size=64, n_epochs=10, # 수집한 데이터로 몇 epoch 학습할지 gamma=0.99, gae_lambda=0.95, # GAE (Generalized Advantage Estimation) clip_range=0.2, verbose=1, tensorboard_log="./ppo_ant_tb/", ) # 학습 (총 2M 스텝) model.learn(total_timesteps=2_000_000) # 평가 eval_env = gym.make("Ant-v4") mean_reward, std_reward = evaluate_policy(model, eval_env, n_eval_episodes=20) print(f"Mean reward: {mean_reward:.1f} +/- {std_reward:.1f}") # 모델 저장/로드 model.save("ppo_ant") loaded_model = PPO.load("ppo_ant") ``` SAC 예시도 구조는 비슷하다. ```python from stable_baselines3 import SAC model = SAC( "MlpPolicy", "Ant-v4", learning_rate=3e-4, buffer_size=1_000_000, # replay buffer 크기 learning_starts=10_000, # 이 스텝 이후부터 학습 시작 batch_size=256, tau=0.005, # target network soft update rate gamma=0.99, verbose=1, ) model.learn(total_timesteps=1_000_000) ``` > 실전 팁: Stable-Baselines3는 빠르게 프로토타이핑하기에 좋다. 하지만 알고리즘 내부를 이해하고 싶으면 CleanRL을 권장한다. 모든 알고리즘이 단일 파일로 구현되어 있어서 코드를 따라 읽기에 좋다. --- ### 8.4 시뮬레이션 환경 로봇 RL에서 시뮬레이션은 선택이 아니라 필수다. 실제 로봇에서 수백만 스텝의 데이터를 수집하는 건 비현실적이기 때문이다. 주요 시뮬레이터를 정리한다. #### MuJoCo (Multi-Joint dynamics with Contact) DeepMind가 인수한 후 2022년에 오픈소스로 공개했다. Contact physics 모델이 정확하고 빠르다. RL 연구에서 사실상 표준 벤치마크 역할을 한다. - 장점: 접촉 시뮬레이션 품질, 안정적인 수치 적분, gymnasium과의 통합 - 단점: 기본 엔진은 CPU 기반. MuJoCo 3.0+에서 MJX (JAX backend)를 통해 GPU 병렬화 가능하지만, Isaac Lab 대비 생태계가 작음 - 용도: 단일 환경 벤치마크, 알고리즘 연구, 소규모 실험 #### Isaac Lab (NVIDIA) NVIDIA Isaac Sim 위에 구축된 로봇 학습 프레임워크다. 핵심 장점은 GPU 병렬 시뮬레이션으로, 수천 개의 환경을 동시에 실행할 수 있다. - 장점: 수천~수만 환경 병렬 실행, 사실적 렌더링, sensor 시뮬레이션 - 단점: NVIDIA GPU 필수, 설치/설정이 복잡, 학습 곡선이 가파름 - 용도: 대규모 locomotion 학습, sim-to-real 파이프라인, manipulation 연구 #### PyBullet 입문용으로 적합한 오픈소스 물리 엔진이다. pip install만으로 설치 가능하다. - 장점: 설치 간편, 문서 풍부, 커뮤니티 활발 - 단점: 물리 정확도가 MuJoCo보다 낮음, 속도 느림 - 용도: 교육, 프로토타이핑, 간단한 실험 #### Brax Google에서 개발한 JAX 기반 물리 엔진이다. JAX의 JIT 컴파일과 자동 미분을 활용한다. - 장점: GPU/TPU에서 초고속 실행, 미분 가능한 시뮬레이션 - 단점: 물리 정확도 제한, 복잡한 접촉 시나리오에 약함 - 용도: 빠른 알고리즘 반복 실험, differentiable physics 연구 #### 환경 비교표 | 시뮬레이터 | 물리 정확도 | 속도 | GPU 병렬화 | 설치 난이도 | 주 용도 | |-----------|-----------|------|-----------|-----------|---------| | MuJoCo | 높음 | 보통 | MJX로 가능 | 쉬움 | 알고리즘 벤치마크 | | Isaac Lab | 높음 | 매우 빠름 | 수천~만 | 어려움 | 대규모 로봇 학습 | | PyBullet | 보통 | 느림 | 불가 | 매우 쉬움 | 입문/교육 | | Brax | 낮음 | 매우 빠름 | 가능 | 보통 | 빠른 반복 실험 | 시작하는 단계라면 MuJoCo + Gymnasium 조합을 권장한다. 대규모 실험이 필요해지면 Isaac Lab으로 넘어가는 것이 일반적인 경로다. --- ### 8.5 Sim-to-Real Transfer 시뮬레이션에서 학습한 policy를 실제 로봇에 적용하는 것을 sim-to-real transfer라 한다. 이론적으로는 시뮬레이션에서 충분히 학습하고 실제 로봇에 배포하면 끝이지만, 현실은 그렇지 않다. #### Reality Gap 시뮬레이션과 현실 사이에는 차이(gap)가 존재한다. - **물리 파라미터 차이**: 마찰 계수, 질량, 관성 모멘트 등이 시뮬레이션과 다르다. - **센서 노이즈**: 실제 센서는 노이즈, 지연, 드리프트가 있다. - **액추에이터 모델링 오차**: 모터의 비선형성, 기어 백래시, 컴플라이언스 등. - **접촉 모델 차이**: 시뮬레이션의 접촉 모델은 현실의 근사에 불과하다. 시뮬레이션에서 reward 10,000을 찍어도 실제 로봇에서 쓰러지는 건 흔한 일이다. #### Domain Randomization 가장 널리 쓰이는 방법이다. 아이디어는 시뮬레이션의 물리 파라미터를 랜덤하게 변화시켜서, policy가 특정 파라미터에 의존하지 않고 robust하게 학습되도록 하는 것이다. 랜덤화하는 대표적인 파라미터들: - 마찰 계수: 0.5 ~ 1.5 사이에서 uniform 샘플링 - 물체 질량: 기본값의 0.8 ~ 1.2배 - 관절 damping: 기본값의 0.5 ~ 2.0배 - 센서 노이즈: Gaussian noise 추가 - 액추에이터 강도(strength): 기본값의 0.8 ~ 1.2배 - 통신 지연: 0 ~ 2 스텝 랜덤 지연 ```python # Isaac Lab 스타일의 domain randomization 설정 예시 (pseudo-code) class RandomizationConfig: # 에피소드 시작마다 랜덤화 friction_range = (0.5, 1.5) mass_scale_range = (0.8, 1.2) joint_damping_scale_range = (0.5, 2.0) # 매 스텝마다 적용 obs_noise_std = 0.05 # observation에 Gaussian noise action_delay_steps = (0, 2) # action 적용 지연 push_force_range = (-5.0, 5.0) # 외부 교란 (N) def randomize_env(env, config): """에피소드 시작 시 호출.""" import numpy as np friction = np.random.uniform(*config.friction_range) mass_scale = np.random.uniform(*config.mass_scale_range) damping_scale = np.random.uniform(*config.joint_damping_scale_range) env.set_friction(friction) env.scale_mass(mass_scale) env.scale_joint_damping(damping_scale) def add_obs_noise(obs, config): """매 스텝 observation에 노이즈 추가.""" import numpy as np noise = np.random.normal(0, config.obs_noise_std, size=obs.shape) return obs + noise ``` 충분히 넓은 범위로 랜덤화하면, 현실은 그 범위 안에 포함될 가능성이 높다. 대신 policy 성능의 상한은 내려간다. 특정 파라미터에 최적화된 policy보다 성능이 낮을 수밖에 없다. #### System Identification (Sys-ID) Domain randomization과 반대 방향의 접근이다. 실제 로봇의 물리 파라미터를 가능한 정확하게 측정/추정해서 시뮬레이션에 반영한다. 방법: - 직접 측정: 전자저울로 질량 측정, 마찰 계수 실험 측정 - 파라미터 최적화: 실제 로봇의 trajectory와 시뮬레이션 trajectory의 차이를 최소화하는 파라미터를 찾음 - 온라인 적응: 실제 운용 중에 파라미터를 지속적으로 추정/업데이트 Sys-ID는 domain randomization과 같이 쓰는 경우가 많다. Sys-ID로 대략적인 파라미터를 잡고, 나머지 불확실성은 domain randomization으로 커버하는 방식이다. #### Teacher-Student 구조 시뮬레이션에서는 접근할 수 있지만 현실에서는 접근할 수 없는 정보(privileged information)를 활용하는 방법이다. 2단계로 학습한다. 1. **Teacher 학습**: 시뮬레이션에서 privileged information (정확한 지형 높이, 정확한 마찰 계수, 물체의 정확한 위치 등)을 state에 포함하여 policy를 학습한다. 정보가 많으므로 학습이 쉽다. 2. **Student 학습**: 실제 로봇에서 사용 가능한 observation (IMU, 관절 encoder, 카메라 등)만으로 teacher의 행동을 모방하도록 학습한다. 이 방식은 ANYmal 사족보행 로봇의 locomotion 연구에서 큰 성공을 거뒀다. Teacher는 정확한 지형 높이맵을 알지만, student는 proprioception history만으로 teacher와 비슷한 행동을 학습한다. #### 실제 사례 **ANYmal 보행 (ETH Zurich / Robotic Systems Lab)** - PPO + domain randomization + teacher-student로 사족보행 학습 - 시뮬레이션에서 수십억 스텝 학습 후 실제 로봇에 zero-shot transfer - 계단, 자갈, 경사면 등 다양한 지형에서 robust하게 보행 - 핵심: 대규모 domain randomization + privileged learning + proprioception history **Dexterous Hand Manipulation (OpenAI, NVIDIA 등)** - Rubik's cube를 Shadow Hand로 풀기 (OpenAI, 2019) - 대규모 domain randomization이 핵심: 수백 개의 물리 파라미터를 동시에 랜덤화 - 시뮬레이션에서 약 13,000년 분량의 경험으로 학습 - 현실에서의 성공률은 시뮬레이션 대비 상당히 낮았지만, 학습 기반 접근의 가능성을 보여줌 --- ### 8.6 모방 학습 (Imitation Learning) 강화학습은 reward 함수를 설계해야 하고, 학습에 많은 데이터가 필요하다. 반면 모방 학습은 전문가(사람)의 시연 데이터로부터 직접 policy를 학습한다. "보고 배우기"에 해당한다. #### Behavioral Cloning (BC) 가장 단순한 모방 학습이다. 전문가의 (observation, action) 쌍을 수집하고, 지도학습(supervised learning)으로 policy를 학습한다. ```python import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset class BCPolicy(nn.Module): def __init__(self, obs_dim, act_dim, hidden_dim=256): super().__init__() self.net = nn.Sequential( nn.Linear(obs_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, act_dim), ) def forward(self, obs): return self.net(obs) # 전문가 데이터 로드 (NumPy -> Tensor) # expert_obs: (N, obs_dim), expert_act: (N, act_dim) dataset = TensorDataset( torch.FloatTensor(expert_obs), torch.FloatTensor(expert_act), ) loader = DataLoader(dataset, batch_size=256, shuffle=True) policy = BCPolicy(obs_dim=48, act_dim=7) optimizer = torch.optim.Adam(policy.parameters(), lr=1e-3) loss_fn = nn.MSELoss() # 학습 for epoch in range(100): total_loss = 0.0 for obs_batch, act_batch in loader: pred_act = policy(obs_batch) loss = loss_fn(pred_act, act_batch) optimizer.zero_grad() loss.backward() optimizer.step() total_loss += loss.item() if (epoch + 1) % 10 == 0: print(f"Epoch {epoch+1}, Loss: {total_loss/len(loader):.4f}") ``` **Compounding error 문제**: BC의 근본적 한계다. 학습된 policy가 조금이라도 전문가 trajectory에서 벗어나면, 학습 데이터에 없는 상태에 도달하게 된다. 거기서의 행동은 예측 불가능하고, 더 벗어나게 되고, 에러가 누적된다. 시간에 따라 에러가 기하급수적으로 커질 수 있다. #### DAgger (Dataset Aggregation) DAgger는 compounding error를 해결하기 위한 방법이다. 1. 초기 전문가 데이터로 BC policy를 학습한다. 2. 학습된 policy를 실행하여 새로운 trajectory를 수집한다. 3. 이 trajectory의 각 state에서 전문가가 어떤 action을 취할지 레이블링한다. 4. 새 데이터를 기존 데이터에 추가하고 다시 학습한다. 5. 2-4를 반복한다. 핵심은 "policy가 실제로 방문하는 state"에서의 전문가 action을 학습 데이터에 포함시키는 것이다. 이론적으로 DAgger는 no-regret guarantee를 가진다. 단점은 전문가가 반복적으로 레이블링해야 한다는 점이다. 사람이 일일이 correction을 해줘야 하므로 노동 집약적이다. #### ACT (Action Chunking with Transformers) Stanford의 ALOHA 프로젝트에서 제안한 방법이다. 핵심 아이디어 두 가지: 1. **Action chunking**: 한 번에 하나의 action을 예측하는 대신, 미래 k 스텝의 action sequence를 한 번에 예측한다. 이렇게 하면 temporal correlation을 잡을 수 있고, compounding error를 줄인다. 2. **CVAE (Conditional Variational Autoencoder)**: action의 다봉(multimodal) 분포를 모델링한다. 같은 상황에서도 여러 유효한 행동이 있을 수 있는데, 단순 MSE loss로는 이걸 평균내버려서 어중간한 action이 나온다. 구조는 Transformer encoder-decoder를 사용하며, 입력으로 joint position과 카메라 이미지를 받는다. #### Diffusion Policy CMU의 Chi et al. (2023)이 제안한 방법으로, diffusion model을 action 생성에 적용한다. 기존 BC가 unimodal Gaussian으로 action을 모델링하는 반면, diffusion policy는 denoising 과정을 통해 임의의 복잡한 action 분포를 표현할 수 있다. 특히 다봉 분포(multimodal distribution)를 잘 다룬다. ``` # Diffusion Policy의 action 생성 과정 (pseudo-code) # 1. 순수 noise에서 시작 action = torch.randn(batch_size, horizon, action_dim) # 2. K번의 denoising step for k in reversed(range(K)): # 현재 observation 조건 하에 noise 예측 predicted_noise = noise_pred_net(action, k, obs_encoding) # noise 제거 (DDPM 또는 DDIM scheduler 사용) action = scheduler.step(predicted_noise, k, action) # 3. 최종 action sequence 출력 ``` Diffusion policy는 ACT와 함께 현재 manipulation 분야에서 가장 활발히 연구되는 모방학습 방법이다. #### 데이터 수집 방법 모방 학습의 성능은 데이터 품질에 결정적으로 의존한다. 주요 데이터 수집 방법: - **Teleoperation**: 사람이 원격으로 로봇을 조종한다. ALOHA는 leader-follower 구조를 사용했고, 비교적 저렴하게 양팔 조작 데이터를 수집할 수 있다. - **VR controller**: VR 컨트롤러로 end-effector 위치/자세를 지정한다. 직관적이지만 contact-rich 작업에서는 힘 피드백이 부족할 수 있다. - **Kinesthetic teaching**: 로봇 팔을 직접 잡고 움직인다. 가장 직관적이지만, 로봇 크기가 크거나 무거우면 어렵다. - **Space mouse**: 6-DoF 입력 장치. 한 손으로 조작 가능. 정밀 작업에 유용하다. 데이터 양은 보통 수십~수백 개의 시연으로 시작한다. Diffusion policy 논문에서는 약 100~200개의 시연으로 유의미한 성능을 보였다. 당연히 더 많을수록 좋지만, 수집 비용과의 trade-off가 있다. --- ### 8.7 심화: Foundation Models for Robot Control *연구자가 되고 싶다면 여기서부터 읽어라.* LLM과 VLM의 성공에 영감을 받아, 로봇 분야에서도 대규모 사전학습 모델(foundation model)을 만들려는 시도가 활발하다. 아이디어는 대량의 로봇 데이터로 범용 정책(generalist policy)을 학습하고, 새로운 로봇이나 태스크에 빠르게 적응하는 것이다. #### RT-1, RT-2 (Google DeepMind) **RT-1 (2022)**: 13만 개의 로봇 시연 데이터(약 17개월 수집)로 학습한 Transformer 기반 policy. 이미지와 자연어 명령을 입력으로 받아 action을 출력한다. 700개 이상의 태스크를 하나의 모델로 수행. **RT-2 (2023)**: VLM (Vision-Language Model)을 직접 action 출력으로 fine-tuning한 것. PaLM-E나 PaLI-X를 base model로 사용. 핵심 발견은 웹 스케일 사전학습 지식이 로봇 제어에도 전이된다는 점이다. 학습 데이터에 없던 물체에 대해서도 어느 정도 일반화가 가능했다. #### Octo UC Berkeley 등에서 개발한 오픈소스 범용 로봇 정책이다. Open X-Embodiment 데이터셋(다양한 로봇, 다양한 기관에서 수집한 데이터)으로 학습했다. Diffusion 기반 action head를 사용하며, 새로운 로봇에 fine-tuning할 수 있도록 설계되었다. #### pi0 (Physical Intelligence) 2024년에 공개된 diffusion 기반 범용 로봇 정책이다. VLM을 backbone으로 사용하고, flow matching으로 action을 생성한다. 다양한 manipulation 태스크에서 state-of-the-art 성능을 보였으며, 빨래 접기 같은 복잡한 장시간(long-horizon) 태스크에서도 동작했다. #### OpenVLA 오픈소스 VLA (Vision-Language-Action) 모델이다. 7B 파라미터의 VLM을 fine-tuning하여 action token을 출력하도록 학습했다. 누구나 접근 가능한 오픈소스라는 점이 핵심 기여다. #### 현실적 평가 Foundation model for robotics는 아직 초기 단계다. 솔직히 말하면: - 특정 태스크에서는 해당 태스크에 특화된 전통적 방법이나 task-specific 학습이 더 나은 경우가 많다. - 대규모 로봇 데이터 수집 비용이 매우 크다. 인터넷 텍스트/이미지 데이터와는 규모가 다르다. - 안전성 보장이 없다. Foundation model의 행동을 예측하기 어렵다. - 추론 속도(inference latency)가 실시간 제어에 충분하지 않을 수 있다. #### 연구 방향 - **Data scaling**: Open X-Embodiment처럼 여러 기관의 데이터를 합치는 시도. 데이터가 많을수록 일반화가 좋아지는지 검증 중. - **Cross-embodiment transfer**: 한 로봇에서 학습한 정책을 다른 로봇에 전이하는 연구. 서로 다른 action space를 어떻게 통일할 것인가가 핵심 문제. - **Efficient fine-tuning**: LoRA 같은 parameter-efficient fine-tuning으로 새 태스크에 빠르게 적응. - **Action representation**: action을 어떻게 토큰화/표현할 것인가. 이산화, 연속 분포, diffusion 등 다양한 접근이 경쟁 중. --- ### 8.8 심화: Reward Design과 Safe RL *연구자가 되고 싶다면 여기서부터 읽어라.* RL의 성패는 reward 함수 설계에 달려 있다고 해도 과언이 아니다. 그리고 실제 로봇에 RL을 적용하려면 안전성 문제를 반드시 다뤄야 한다. #### Reward Shaping **Sparse reward의 문제**: "목표에 도달하면 +1, 아니면 0" 같은 sparse reward는 정의하기 쉽지만, agent가 우연히 보상을 받기까지 무작위 탐색을 해야 한다. state-action 공간이 크면 사실상 학습이 안 된다. **Dense reward**: 중간 과정에 대한 보상을 추가한다. 예를 들어 물체 잡기 태스크에서: ```python def compute_reward(gripper_pos, object_pos, target_pos, is_grasped): # 1. 그리퍼를 물체에 가까이 가져가기 dist_to_object = np.linalg.norm(gripper_pos - object_pos) reaching_reward = -1.0 * dist_to_object # 2. 물체를 잡았으면 보너스 grasp_reward = 5.0 if is_grasped else 0.0 # 3. 물체를 목표 위치에 가까이 if is_grasped: dist_to_target = np.linalg.norm(object_pos - target_pos) place_reward = -1.0 * dist_to_target else: place_reward = 0.0 # 4. 목표 도달 보너스 success_reward = 10.0 if (is_grasped and np.linalg.norm(object_pos - target_pos) < 0.05) else 0.0 return reaching_reward + grasp_reward + place_reward + success_reward ``` **Curriculum learning**: 쉬운 태스크에서 시작해 점차 어려운 태스크로 넘어가는 방법이다. 예를 들어 locomotion에서 처음에는 평지에서 걷기, 다음에 작은 장애물, 그 다음 계단 순으로 난이도를 올린다. 이렇게 하면 sparse reward 상황에서도 학습 초기에 agent가 성공 경험을 쌓을 수 있다. #### Reward Hacking Agent가 reward를 최대화하되, 설계자가 의도하지 않은 방식으로 행동하는 현상이다. 대표적인 예시: - 로봇 팔이 물체를 "옮기라"고 했는데 물체를 밀어서 목표 위치로 보내기 (잡지 않음) - 보행 로봇이 "빠르게 이동하라"고 했는데 넘어지면서 미끄러지기 - 점프를 학습하라고 했는데 비정상적으로 긴 형태로 진화 (형태 최적화와 결합 시) 대응 방법: - reward 함수를 반복적으로 수정하고 학습된 행동을 검토한다 (사실상 trial-and-error). - 원치 않는 행동에 대한 penalty term을 추가한다. - 비디오를 보면서 정성적으로 검토한다. 자동화하기 어려운 부분이다. #### Constrained RL Safety constraint를 명시적으로 다루는 RL이다. 일반적인 RL이 reward를 최대화하는 것이라면, constrained RL은 reward를 최대화하되 cost를 일정 한도 이하로 유지한다. ``` max_π E[ Σ γ^t r_t ] subject to E[ Σ γ^t c_t ] ≤ d ``` c_t는 cost (예: 관절 토크 한계 초과, 장애물 충돌), d는 허용 한도다. 대표적인 알고리즘으로 CPO (Constrained Policy Optimization), PCPO, Lagrangian relaxation 기반 방법 등이 있다. 실제 로봇에서는 하드웨어 보호를 위해 토크 제한, 관절 각도 제한 등을 constraint로 넣는 것이 현실적이다. #### Human-in-the-loop RL 사람의 피드백을 reward 신호로 사용하는 접근이다. LLM의 RLHF (RL from Human Feedback)와 같은 아이디어를 로보틱스에 적용한 것이다. 방법: 1. 로봇의 행동 쌍을 보여주고 사람이 선호도를 표시한다 (A가 B보다 나음). 2. 선호도 데이터로 reward model을 학습한다. 3. 학습된 reward model로 RL을 수행한다. 수치적으로 reward를 정의하기 어려운 태스크 (예: "자연스럽게 걷기", "조심스럽게 물건 놓기")에서 유용하다. 단점은 사람의 시간이 많이 든다는 점과, reward model이 부정확할 수 있다는 점이다. --- ### 8.9 추천 자료 > **Sutton & Barto, "Reinforcement Learning: An Introduction" (2nd edition)** > http://incompleteideas.net/book/the-book-2nd.html > RL의 필수 교재. 무료 PDF 제공. MDP부터 policy gradient까지 기초를 다지려면 반드시 읽어야 한다. 전부 읽을 시간이 없으면 Ch.1-6, Ch.13을 우선으로. > **Sergey Levine, CS285: Deep Reinforcement Learning** > https://rail.eecs.berkeley.edu/deeprlcourse/ > 로봇 RL에 초점을 맞춘 대학원 수준 강의. 강의 영상과 슬라이드 모두 공개되어 있다. 이 챕터의 대부분의 주제를 더 깊이 다룬다. > **Stable-Baselines3** > https://stable-baselines3.readthedocs.io/ > PyTorch 기반 RL 알고리즘 라이브러리. PPO, SAC, TD3 등 주요 알고리즘이 구현되어 있다. 빠른 프로토타이핑에 적합. > **CleanRL** > https://github.com/vwxyzjn/cleanrl > 단일 파일 RL 구현 모음. 한 파일에 알고리즘 전체가 들어 있어서 코드를 따라가며 공부하기에 좋다. 알고리즘 내부를 이해하고 싶으면 SB3보다 이쪽을 권장한다. > **Isaac Lab** > https://isaac-sim.github.io/IsaacLab/ > NVIDIA의 GPU 병렬 로봇 시뮬레이션 프레임워크. 대규모 locomotion, manipulation 학습에 사실상 표준이 되어가고 있다. > **LeRobot (HuggingFace)** > https://github.com/huggingface/lerobot > 모방학습과 로봇 학습을 위한 프레임워크. ACT, Diffusion Policy 등의 구현이 포함되어 있다. 데이터셋도 함께 제공한다. > **robomimic** > https://robomimic.github.io/ > 모방학습 알고리즘 벤치마크. BC, BC-RNN, HBC 등 다양한 모방학습 방법을 동일 조건에서 비교할 수 있다. > **추가 논문** > - [Andrychowicz et al., "Hindsight Experience Replay" (NeurIPS 2017, arXiv:1707.01495)](https://arxiv.org/abs/1707.01495) — sparse reward 문제 해결의 핵심. 실패한 trajectory를 성공으로 재레이블링 > - [Hafner et al., "Mastering Diverse Domains through World Models" (DreamerV3, arXiv:2301.04104)](https://arxiv.org/abs/2301.04104) — 단일 하이퍼파라미터로 150+ 도메인 학습. World model 기반 RL의 현 시점 대표적 성과 > - [Chi et al., "Universal Manipulation Interface" (UMI, RSS 2024, arXiv:2402.10329)](https://arxiv.org/abs/2402.10329) — 핸드헬드 그리퍼로 데이터 수집, 다양한 로봇에 zero-shot 배포 > - [Fu et al., "Mobile ALOHA" (CoRL 2024, arXiv:2401.02117)](https://arxiv.org/abs/2401.02117) — 모바일 베이스 + 양팔 텔레오퍼레이션. co-training으로 성공률 대폭 향상 --- ### 기술 흐름 ``` 1992 ── REINFORCE algorithm (Williams) 최초의 policy gradient 방법. 수렴 증명은 있으나 variance가 높다. 2013 ── DQN (Mnih et al., Atari) Deep RL의 시작. replay buffer + target network로 안정적 학습. 2015 ── TRPO (Schulman et al.) trust region 제약으로 안정적 policy update. 이론은 좋으나 구현이 복잡. 2017 ── PPO (Schulman et al.) TRPO의 실용적 대안. clipping으로 간단하게 구현. 사실상 표준. 2018 ── SAC (Haarnoja et al.) entropy regularization + off-policy. 연속 공간에서 sample efficient. 2019 ── ANYmal: sim-to-real locomotion (ETH Zurich) 대규모 domain randomization으로 사족보행 sim-to-real 성공. 2020 ── DAgger 실전 적용 확산 모방학습의 실용성 입증. 다양한 로봇 플랫폼에 적용. 2022 ── RT-1 (Google) 대규모 로봇 데이터 + Transformer. 다중 태스크 범용 정책. 2023 ── ACT/ALOHA (Stanford), Diffusion Policy (CMU) 모방학습의 새로운 표준. action chunking과 diffusion으로 성능 향상. 2023 ── RT-2 (Google) VLM을 직접 action 생성에 사용. 웹 지식의 로봇 전이. 2024 ── Octo, OpenVLA, pi0 오픈소스 범용 정책 모델 등장. Cross-embodiment 학습 시작. 2025 ── Cross-embodiment learning 연구 확산 서로 다른 로봇 간 정책 전이. 데이터 스케일링 법칙 검증 중. ``` --- ## 9. 컴퓨터 비전 기초 (Computer Vision Fundamentals) 로봇이 세상을 "보는" 방법을 이해하려면, 컴퓨터 비전의 기초부터 탄탄히 쌓아야 한다. 이 챕터에서 다루는 내용은 단순히 이미지를 예쁘게 처리하는 게 아니다. 로봇이 카메라로 들어오는 원시 데이터를 의미 있는 정보로 바꾸는 모든 과정의 뿌리가 여기에 있다. SLAM을 하든, 물체를 집든, 자율주행을 하든 — 이 챕터의 개념들이 안 잡혀 있으면 "왜 안 되지?"에서 한참을 헤매게 된다. --- ### 9.1 이미지 처리 기초 카메라에서 들어오는 raw 이미지는 노이즈도 많고 정보가 정리되어 있지 않다. 어떤 알고리즘이든 그 위에서 동작하려면, 먼저 이미지를 "깔끔하게" 만들어야 한다. 필터링, 에지 검출, 형태학적 연산 — 이것들이 전처리의 기본 도구이고, 이걸 모르면 후속 파이프라인에서 왜 결과가 이상한지 원인을 잡을 수 없다. ### 9.1.1 OpenCV 소개 **OpenCV (Open Source Computer Vision Library)**는 가장 널리 사용되는 CV 라이브러리이다. OpenCV 없이는 컴퓨터 비전에서 "Hello World"를 치는 것 자체가 불가능하다. 논문의 알고리즘을 직접 구현하든, 빠르게 프로토타입을 만들든, OpenCV는 거의 항상 거치게 되는 도구이다. C++/Python 바인딩이 모두 있어서 연구에서 프로덕션까지 커버한다. **설치**: ```bash pip install opencv-python opencv-contrib-python ``` **기본 사용**: ```python import cv2 import numpy as np # 이미지 읽기 img = cv2.imread('image.jpg') # 그레이스케일 변환 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 이미지 표시 cv2.imshow('Image', img) cv2.waitKey(0) cv2.destroyAllWindows() ``` **주의**: OpenCV는 BGR 순서를 사용 (RGB 아님). 이거 모르면 Matplotlib이나 다른 라이브러리와 섞어 쓸 때 색이 뒤집혀서 한참 헤맨다. `cv2.cvtColor(img, cv2.COLOR_BGR2RGB)`를 습관처럼 쓰자. > **추천 자료** > - [OpenCV 공식 튜토리얼](https://docs.opencv.org/4.x/d9/df8/tutorial_root.html) — Python/C++ 예제가 잘 정리되어 있다 > - [First Principles of Computer Vision](https://www.youtube.com/channel/UCf0WB91t8Ky6AuYcQV0CcLw) — Columbia의 Shree Nayar 교수의 채널. 이미지 처리 원리를 직관적으로 설명한다 > - [Szeliski, "Computer Vision: Algorithms and Applications"](https://szeliski.org/Book/) — 무료 PDF 제공. CV 분야의 표준 교과서 ### 9.1.2 필터링 (Filtering) 이미지에서 원하는 정보를 뽑아내거나, 원치 않는 노이즈를 제거하는 가장 기본적인 도구가 필터링이다. 필터링을 모르면 에지 검출 결과가 지저분해도 원인을 모르고, segmentation 전처리에서 왜 blur를 거치는지 감이 안 온다. **Blur (흐림)**: ```python # Gaussian Blur blurred = cv2.GaussianBlur(img, (5, 5), 0) # Median Blur (노이즈 제거에 효과적) median = cv2.medianBlur(img, 5) ``` **Edge Detection (에지 검출)**: ```python # Canny Edge Detection edges = cv2.Canny(gray, threshold1=50, threshold2=150) # Sobel Operator sobel_x = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3) sobel_y = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3) ``` 에지는 이미지에서 가장 정보량이 많은 부분이다. 물체의 윤곽, 구조, 경계 — 사람이 물체를 인식할 때 가장 먼저 보는 것도 에지이다. Canny는 가장 널리 쓰이는 에지 검출기인데, threshold 값에 따라 결과가 크게 달라지므로 직접 파라미터를 바꿔가며 실험해보는 것을 추천한다. > **추천 자료** > - [First Principles of Computer Vision — Edge Detection](https://www.youtube.com/playlist?list=PL2zRqk16wsdoCCLpouGuRbcJFBVVJlvgr) — 에지 검출의 수학적 원리를 시각적으로 설명 > - [OpenCV 필터링 튜토리얼](https://docs.opencv.org/4.x/d4/d13/tutorial_py_filtering.html) — 코드와 함께 바로 따라할 수 있다 > **실습**: [Canny Edge Detection](https://alexjunholee.github.io/robotics-practice/app.html#canny_edge) > Canny 에지 검출기의 threshold 파라미터를 실시간으로 조절하며 결과 변화를 확인할 수 있다. > **실습**: [Convolution 시각화](https://alexjunholee.github.io/robotics-practice/app.html#convolution) > 다양한 커널을 이미지에 적용하며 convolution 연산이 어떻게 필터링을 수행하는지 직관적으로 이해할 수 있다. ### 9.1.3 Morphology 이진 이미지(binary image)를 다룰 때 필수적인 도구이다. 예를 들어, segmentation 결과에서 작은 노이즈 점들을 제거하거나, 끊어진 영역을 이어 붙이거나 할 때 morphology를 쓴다. 이걸 모르면 이진화 결과를 후처리할 때 막막하다. ```python kernel = np.ones((5, 5), np.uint8) # Erosion (침식) eroded = cv2.erode(binary_img, kernel, iterations=1) # Dilation (팽창) dilated = cv2.dilate(binary_img, kernel, iterations=1) # Opening (침식 → 팽창): 노이즈 제거 opening = cv2.morphologyEx(binary_img, cv2.MORPH_OPEN, kernel) # Closing (팽창 → 침식): 구멍 채우기 closing = cv2.morphologyEx(binary_img, cv2.MORPH_CLOSE, kernel) ``` Opening과 Closing의 순서를 헷갈리는 사람이 많은데 — Opening은 "먼저 깎고(erosion) 다시 키우는(dilation)" 것이라 작은 돌기나 노이즈가 사라지고, Closing은 "먼저 키우고 다시 깎는" 것이라 작은 구멍이 메워진다. 직관적으로 기억하자. > **추천 자료** > - [OpenCV Morphological Operations](https://docs.opencv.org/4.x/d9/d61/tutorial_py_morphological_ops.html) — 시각적 예제와 함께 설명 > - [First Principles of Computer Vision — Binary Image Processing](https://www.youtube.com/watch?v=IcBzsP-fvPo) — 형태학적 연산의 원리 --- ### 9.2 카메라 모델 (Camera Model) 로봇에게 카메라는 눈이다. 그런데 그 눈이 세상을 어떻게 "해석"하는지 모르면, 카메라에서 나온 2D 이미지로 3D 세상을 이해하는 건 불가능하다. SLAM, 3D reconstruction, visual servoing — 이 모든 것의 출발점이 카메라 모델이다. 선형대수를 배웠다면 여기서 행렬이 어떻게 쓰이는지 체감할 수 있을 것이다. ### 9.2.1 Pinhole Model 이상적인 카메라 모델로, 3D 점을 2D 이미지로 투영한다. 로봇이 카메라로 본 픽셀 좌표 (u, v)에서 "이게 실제 세상에서 어디에 있는 점이지?"를 역으로 계산하려면, 이 투영 관계를 정확히 알아야 한다. 이 관계를 수식으로 표현한 것이 바로 Pinhole Model이다. **투영 방정식**: ``` [u] [f_x 0 c_x] [X/Z] [v] = [0 f_y c_y] [Y/Z] [1] [0 0 1 ] [ 1 ] ``` **Intrinsic Parameters (내부 파라미터)**: - f_x, f_y: Focal length (픽셀 단위) - c_x, c_y: Principal point (이미지 중심) - Intrinsic Matrix K (3×3) **Extrinsic Parameters (외부 파라미터)**: - R: 회전 행렬 (3×3) - t: 이동 벡터 (3×1) - World → Camera 변환 선형대수 시간에 배운 행렬 곱이 바로 여기서 써먹어진다. K는 카메라의 "렌즈 특성"을, [R|t]는 카메라가 세상에서 "어디에 어떤 방향으로 놓여있는지"를 나타낸다. 이 둘을 곱하면 3D 점이 2D 픽셀로 매핑된다. > **추천 자료** > - [Stanford CS231A — Camera Models](https://web.stanford.edu/class/cs231a/) — 기하 기반 CV의 핵심 강의 > - [First Principles of CV — Camera and Imaging](https://www.youtube.com/playlist?list=PL2zRqk16wsdoYzrWStQ2SQHXXS2K6ofd4) — Pinhole부터 실제 렌즈까지 차근차근 설명 > - [Szeliski Ch.2 — Image Formation](https://szeliski.org/Book/) — 카메라 모델의 수학적 기초 > - [정진용 블로그 — Camera Models and Distortion (Perspective, Fisheye, Omni)](https://jinyongjeong.github.io/2020/06/15/Camera_and_distortion_model/) — Perspective, Equidistant, Omni 카메라 모델 비교 정리 > - [정진용 블로그 — OpenCV Camera model 정리](https://jinyongjeong.github.io/2020/06/19/SLAM-Opencv-Camera-model-%EC%A0%95%EB%A6%AC/) — OpenCV의 핀홀/어안 카메라 모델 구현 기준 정리 > **실습**: [Camera Projection](https://alexjunholee.github.io/robotics-practice/app.html#camera_projection) > 3D 공간의 점이 카메라 내부/외부 파라미터에 의해 2D 이미지로 투영되는 과정을 인터랙티브하게 확인할 수 있다. ### 9.2.2 Distortion Models 실제 렌즈는 왜곡이 발생한다. 왜곡 보정을 빠뜨리면 바로 문제가 보인다. 실제 카메라로 찍은 이미지는 Pinhole Model이 가정하는 것처럼 깔끔하지 않다. 특히 광각 렌즈나 fisheye 렌즈를 쓰면, 직선이 곡선으로 보이는 왜곡이 심하다. 이 왜곡을 보정하지 않으면 SLAM의 정확도가 뚝 떨어지고, 3D reconstruction 결과가 찌그러진다. 카메라 렌즈는 완벽한 핀홀이 아니다. 렌즈를 통과하면서 빛이 휘어지고, 이 휘어짐이 이미지에 왜곡으로 나타난다. **Radial distortion** (방사 왜곡): 이미지 중심에서 멀어질수록 심해진다. 파라미터 k1, k2, k3로 모델링한다. k1 < 0이면 barrel distortion (직선이 바깥으로 볼록), k1 > 0이면 pincushion distortion (직선이 안쪽으로 오목). 대부분의 렌즈는 barrel distortion을 가진다. 광각 렌즈일수록 심하다. **Tangential distortion** (접선 왜곡): 렌즈가 이미지 센서와 완벽하게 평행하지 않을 때 발생한다. 파라미터 p1, p2. 보통 radial보다 영향이 작지만, 저가 카메라에서는 무시할 수 없다. **왜곡 보정**: ```python # 단순 보정 (매 프레임 계산 — 느림) undistorted = cv2.undistort(distorted, K, dist_coeffs) # 보정 맵 미리 계산 후 재사용 (빠름 — SLAM 파이프라인 표준) map1, map2 = cv2.initUndistortRectifyMap(K, dist_coeffs, None, K, (w, h), cv2.CV_32FC1) undistorted = cv2.remap(distorted, map1, map2, cv2.INTER_LINEAR) ``` `cv2.undistort()`는 매 프레임마다 호출하면 느리다. `initUndistortRectifyMap()`으로 맵을 미리 계산해두고 `cv2.remap()`으로 적용하는 것이 실시간 시스템에서의 표준이다. **어안 렌즈 (Fisheye)**: 일반 핀홀 왜곡 모델로는 보정이 안 된다. 어안 렌즈는 빛의 입사각 θ에 대해 왜곡을 모델링한다 (equidistant model: r = f·θ). OpenCV의 `cv2.fisheye` 모듈을 별도로 사용해야 한다. 혼동하면 보정 결과가 오히려 나빠지니 주의. (참고: [다크 프로그래머 — 카메라 왜곡보정](https://darkpgmr.tistory.com/31), [정진용 블로그 — Camera Models and Distortion](https://jinyongjeong.github.io/2020/06/15/Camera_and_distortion_model/)) > **추천 자료** > - [OpenCV Camera Calibration and 3D Reconstruction](https://docs.opencv.org/4.x/d9/d0c/group__calib3d.html) — 왜곡 모델의 수식이 잘 정리되어 있다 > - [First Principles of CV — Lens Distortion](https://www.youtube.com/watch?v=gRbS6VT2KUI) — 왜곡이 왜 생기는지 물리적 직관 설명 > **실습**: [Lens Distortion 시각화](https://alexjunholee.github.io/robotics-practice/app.html#lens_distortion) > Radial/Tangential 왜곡 파라미터를 조절하며 이미지가 어떻게 변형되는지 직접 확인할 수 있다. ### 9.2.3 캘리브레이션 (Calibration) 카메라의 내부/외부 파라미터를 추정하는 과정이다. 앞서 배운 K (intrinsic matrix)와 왜곡 계수를 실제로 알아내야 카메라 모델을 쓸 수 있다. 캘리브레이션이 부정확하면 그 위에 쌓는 모든 것 — SLAM, 스테레오 깊이 추정, hand-eye calibration — 전부 정확도가 떨어진다. "garbage in, garbage out"의 대표적인 사례이다. **체커보드 방식**: ```python # 체커보드 코너 검출 ret, corners = cv2.findChessboardCorners(gray, (9, 6), None) # 코너 정밀화 corners = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria) # 캘리브레이션 ret, K, dist, rvecs, tvecs = cv2.calibrateCamera( object_points, image_points, gray.shape[::-1], None, None ) ``` 팁: 캘리브레이션 퀄리티를 높이려면 (1) 다양한 각도에서 20장 이상 촬영하고, (2) 체커보드가 이미지 전체를 고르게 커버하게 하고, (3) reprojection error가 0.5 픽셀 이하인지 확인하자. **캘리브레이션이 하는 일을 직관적으로 이해하기** 카메라 캘리브레이션은 결국 "이 카메라가 3D 세상을 2D 이미지로 어떻게 변환하는지"의 파라미터를 알아내는 것이다. 체커보드 패턴을 여러 각도에서 촬영하면, 체커보드의 3D 좌표(알고 있음)와 이미지의 2D 좌표(검출함)의 대응 쌍이 수십~수백 개 생긴다. 이 대응 쌍으로부터: 1. **Intrinsic parameters** (fx, fy, cx, cy): 렌즈의 초점거리와 이미지 중심. 카메라 고유 속성이므로 한 번 구하면 렌즈를 바꾸지 않는 한 변하지 않는다. 2. **Distortion coefficients** (k1, k2, p1, p2, k3): 렌즈의 왜곡 정도. 저가 렌즈일수록 크다. 3. **Extrinsic parameters** (R, t): 각 촬영 위치에서의 카메라 자세. 캘리브레이션 자체에서는 부산물이지만, hand-eye calibration 등에서 별도로 쓰인다. 체커보드를 최소 10장 이상, 다양한 각도와 거리에서 촬영해야 한다. 한쪽으로 치우치면 해당 영역의 왜곡만 보정되고 나머지는 부정확하다. 이미지 전체에 걸쳐 체커보드가 고르게 분포하도록 촬영하는 것이 핵심이다. reprojection error가 0.5 픽셀 이하면 양호, 0.1 이하면 매우 좋다. 1.0 이상이면 촬영을 다시 하거나 outlier 이미지를 제거해야 한다. (참고: [다크 프로그래머 — 카메라 캘리브레이션](https://darkpgmr.tistory.com/32)) **Kalibr**: 멀티 카메라, Camera-IMU 캘리브레이션 도구 - ROS 기반 - AprilTag 보드 사용 - 시간 오프셋까지 추정 > **추천 자료** > - [OpenCV 카메라 캘리브레이션 튜토리얼](https://docs.opencv.org/4.x/dc/dbb/tutorial_py_calibration.html) — 체커보드 캘리브레이션 step-by-step > - [Kalibr 공식 Wiki](https://github.com/ethz-asl/kalibr/wiki) — Camera-IMU 캘리브레이션의 사실상 표준 도구 > - [Zhang, "A Flexible New Technique for Camera Calibration" (2000)](https://www.microsoft.com/en-us/research/publication/a-flexible-new-technique-for-camera-calibration/) — 현재 OpenCV 캘리브레이션의 기반이 되는 논문 --- ### 9.3 특징점 (Features) 이미지에서 구별 가능한 점(keypoint)과 그 주변을 설명하는 벡터(descriptor)이다. SLAM을 이해하려면 특징점을 먼저 알아야 한다. 로봇이 카메라를 움직이면서 "지금 보는 장면이 아까 봤던 그곳인지"를 판단하려면, 이미지 간에 "같은 점"을 찾아야 한다. 특징점은 그 "같은 점"을 안정적으로 찾기 위한 핵심 도구이다. SLAM, Visual Odometry, Object Recognition 등 거의 모든 시각 기반 로보틱스 알고리즘이 특징점에 의존한다. ### 9.3.1 Keypoint Detection **Harris Corner**: - 코너 검출의 고전적 방법 - 속도 느림, 스케일 변화에 취약 **FAST (Features from Accelerated Segment Test)**: - 매우 빠른 코너 검출 - 실시간 시스템에 적합 - 스케일 불변 아님 **ORB (Oriented FAST and Rotated BRIEF)**: - FAST 검출 + BRIEF 디스크립터 + 방향 정보 - 특허 무료 - 실시간 SLAM에서 널리 사용 ORB는 ORB-SLAM 시리즈의 핵심이다. 특허 무료라서 상업적으로도 자유롭게 쓸 수 있고, 속도가 빨라 실시간 시스템에 적합하다. 로보틱스에서 가장 먼저 접하게 될 feature이다. **SIFT (Scale-Invariant Feature Transform)**: - 스케일, 회전 불변 - 높은 반복성 - 계산 비용 높음 (과거 특허 문제, 현재 해제) SIFT는 2004년 David Lowe가 발표한 알고리즘으로, 컴퓨터 비전 역사에서 가장 영향력 있는 논문 중 하나이다. 스케일과 회전에 불변하는 특징점을 추출하는 원리를 이해하면, 이후 나온 SURF, ORB 등이 SIFT를 어떻게 개선했는지 자연스럽게 이해된다. **SuperPoint** (딥러닝 기반): - Self-supervised 학습 - 높은 반복성과 정확도 - GPU 필요 > **추천 자료** > - [Lowe, "Distinctive Image Features from Scale-Invariant Keypoints" (2004)](https://www.cs.ubc.ca/~lowe/papers/ijcv04.pdf) — SIFT 원논문. 한 번쯤은 읽어볼 가치가 있다 > - [Rublee et al., "ORB: An efficient alternative to SIFT or SURF" (2011)](https://ieeexplore.ieee.org/document/6126544) — ORB 원논문 > - [First Principles of CV — Feature Detection](https://www.youtube.com/playlist?list=PL2zRqk16wsdqXEMpHrc4Qnb5rA1Cylrhx) — 특징점 검출의 원리를 시각적으로 > - [DeTone et al., "SuperPoint: Self-Supervised Interest Point Detection and Description" (2018)](https://arxiv.org/abs/1712.07629) — 딥러닝 기반 특징점의 시작 > - [다크 프로그래머 — 영상 특징점(keypoint) 추출방법](https://darkpgmr.tistory.com/131) — SIFT, HOG, Haar, Ferns, LBP, MCT 등 특징점 비교 정리 ### 9.3.2 Descriptor Keypoint를 찾았으면, 그 주변을 "어떻게 설명할 것인가"가 descriptor이다. 두 이미지에서 같은 물리적 점을 찾으려면, 그 점 주변의 패턴을 숫자로 표현해서 비교해야 한다. **BRIEF (Binary Robust Independent Elementary Features)**: - 이진 디스크립터 (0 or 1) - 빠른 매칭 (Hamming distance) - 회전 불변 아님 **ORB Descriptor**: - BRIEF + 방향 정보 - 256비트 이진 벡터 이진 디스크립터의 장점은 매칭 속도이다. 두 디스크립터 간 거리를 Hamming distance (XOR 연산)로 계산하기 때문에 SIFT의 유클리드 거리 비교보다 훨씬 빠르다. 임베디드 시스템에서 이 차이는 크다. **SuperGlue** (딥러닝 기반): - Graph Neural Network 기반 매칭 - 반복 패턴, 적은 텍스처에서도 강건 - LightGlue: 경량화 버전 > **추천 자료** > - [Sarlin et al., "SuperGlue: Learning Feature Matching with Graph Neural Networks" (2020)](https://arxiv.org/abs/1911.11763) — 딥러닝 기반 매칭의 대표작 > - [OpenCV Feature Matching 튜토리얼](https://docs.opencv.org/4.x/dc/dc3/tutorial_py_matcher.html) — BFMatcher, FLANN 사용법 ### 9.3.3 Feature Matching SLAM에서 카메라가 움직일 때, 이전 프레임과 현재 프레임에서 같은 점을 찾아야 한다. 그게 바로 feature matching이고, 이걸 제대로 이해 못 하면 왜 SLAM이 tracking lost를 뱉는지 감이 안 온다. ```python # ORB 특징점 및 디스크립터 추출 orb = cv2.ORB_create() kp1, des1 = orb.detectAndCompute(img1, None) kp2, des2 = orb.detectAndCompute(img2, None) # BFMatcher (Brute-Force) bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True) matches = bf.match(des1, des2) # Ratio Test (Lowe's ratio) bf = cv2.BFMatcher(cv2.NORM_HAMMING) matches = bf.knnMatch(des1, des2, k=2) good = [m for m, n in matches if m.distance < 0.75 * n.distance] ``` Lowe's ratio test가 핵심이다. kNN으로 가장 가까운 2개의 매치를 찾고, 1등과 2등의 거리 비가 일정 threshold 이하인 것만 "좋은 매치"로 남긴다. 이렇게 하면 애매한 매치(1등과 2등 거리가 비슷한)를 걸러낼 수 있다. 0.75라는 값은 Lowe가 원논문에서 제안한 것인데, 상황에 따라 0.6~0.8 사이에서 조절하면 된다. > **추천 자료** > - [OpenCV Feature Matching](https://docs.opencv.org/4.x/dc/dc3/tutorial_py_matcher.html) — BFMatcher, FLANN, Ratio Test 예제 > - [Computerphile — SIFT Features](https://www.youtube.com/watch?v=ram-jbLJjFg) — 특징점 매칭의 직관적 설명 > **실습**: [Feature Matching](https://alexjunholee.github.io/robotics-practice/app.html#feature_matching) > 두 이미지 간 특징점 매칭과 Lowe's ratio test 적용 과정을 인터랙티브하게 실험할 수 있다. --- ### 9.4 Epipolar Geometry 두 카메라 시점 사이의 기하학적 관계를 다룬다. 두 장의 사진에서 같은 물체를 봤을 때, 카메라가 어떻게 움직였는지(상대 자세)를 알아내고, 나아가 3D 구조를 복원하는 것이 목표이다. Visual Odometry와 SfM (Structure from Motion)의 수학적 기반이 바로 여기에 있다. 선형대수에서 배운 SVD, eigenvalue 분해 등이 직접 활용되는 부분이기도 하다. ### 9.4.1 Essential Matrix (E) **정의**: 캘리브레이션된 카메라 쌍의 상대 자세를 인코딩 ``` x2^T E x1 = 0 ``` - x1, x2: 정규화된 이미지 좌표 - E = [t]_× R (t의 skew-symmetric 행렬 × R) **5-point 알고리즘**: 최소 5쌍의 대응점으로 E 추정 (RANSAC과 함께 사용) Essential Matrix에서 R과 t를 분해(decompose)하면 두 카메라 간의 상대 회전과 이동을 알 수 있다. 이것이 Visual Odometry의 핵심 원리이다. > **실습**: [Epipolar Geometry 시각화](https://alexjunholee.github.io/robotics-practice/app.html#epipolar) > 두 카메라 시점 간의 에피폴라 선과 에피폴을 인터랙티브하게 확인하며, Essential/Fundamental Matrix의 기하학적 의미를 이해할 수 있다. ### 9.4.2 Fundamental Matrix (F) **정의**: 캘리브레이션되지 않은 카메라 쌍의 관계 ``` p2^T F p1 = 0 ``` - p1, p2: 픽셀 좌표 - F = K2^(-T) E K1^(-1) **8-point 알고리즘**: 최소 8쌍의 대응점으로 F 추정 E와 F의 관계를 정리하면: F는 "픽셀 좌표에서 바로 쓸 수 있는" 버전이고, E는 "카메라 내부 파라미터를 이미 알고 있을 때 쓰는" 버전이다. 캘리브레이션을 했다면 E를, 안 했다면 F를 쓴다. ### 9.4.3 Triangulation 두 시점에서 동일 점을 관측했을 때, 3D 위치를 계산한다. 우리 눈이 양안시로 깊이를 느끼는 것과 같은 원리이다. 두 카메라(또는 움직인 하나의 카메라)에서 같은 점을 관측하면, 기하학적으로 그 점의 3D 위치를 계산할 수 있다. ```python # OpenCV triangulation points_4d = cv2.triangulatePoints(P1, P2, pts1, pts2) points_3d = points_4d[:3] / points_4d[3] # Homogeneous → Cartesian ``` 주의: baseline (두 카메라 간 거리)이 너무 작으면 삼각측량 정확도가 떨어지고, 너무 크면 같은 점을 양쪽에서 동시에 관측하기 어려워진다. 이 trade-off를 잘 이해해야 한다. > **추천 자료** > - [Stanford CS231A — Epipolar Geometry](https://web.stanford.edu/class/cs231a/) — 수학적 유도가 잘 정리된 강의 자료 > - [Hartley & Zisserman, "Multiple View Geometry in Computer Vision"](https://www.robots.ox.ac.uk/~vgg/hzbook/) — 다중 시점 기하학의 핵심 교재. 깊이 들어가려면 필독 > - [First Principles of CV — Stereo Vision](https://www.youtube.com/playlist?list=PL2zRqk16wsdoYzrWStQ2SQHXXS2K6ofd4) — Epipolar geometry를 직관적으로 설명 > - [다크 프로그래머 — 영상 Geometry 시리즈 (7편: 좌표계~Epipolar)](https://darkpgmr.tistory.com/77) — 좌표계, Homogeneous, 2D/3D 변환, Homography, Imaging, Epipolar Geometry를 한글로 체계적 정리 > **실습**: [Homography 시각화](https://alexjunholee.github.io/robotics-practice/app.html#homography) > 평면 간 호모그래피 변환을 인터랙티브하게 조작하며, 4개의 대응점으로 투영 변환이 어떻게 결정되는지 확인할 수 있다. --- ### 9.5 Optical Flow 연속 프레임 간 픽셀 이동을 추정한다. 로봇이 카메라로 세상을 보면서 움직일 때, "각 픽셀이 다음 프레임에서 어디로 갔는지"를 아는 것은 매우 유용하다. Visual Odometry에서 카메라 자세 추정, 동적 물체 감지, 로봇 충돌 회피 등에 직접 활용된다. Feature matching이 sparse한 점만 다루는 반면, dense optical flow는 모든 픽셀의 움직임을 추정한다는 차이가 있다. ### 9.5.1 Lucas-Kanade Method - Sparse optical flow (특정 점들만) - 밝기 불변 가정 - 작은 움직임 가정 ```python # 광류 계산 p1, status, err = cv2.calcOpticalFlowPyrLK( prev_gray, curr_gray, p0, None, **lk_params ) ``` "PyrLK"에서 "Pyr"는 Pyramid를 뜻한다. 이미지 피라미드를 사용해서 큰 움직임도 잡을 수 있게 한 것이다. Lucas-Kanade의 "작은 움직임 가정"을 극복하기 위한 기법이다. ### 9.5.2 Dense Optical Flow - 모든 픽셀의 움직임 계산 - Farneback, RAFT (딥러닝) ```python # Farneback dense flow flow = cv2.calcOpticalFlowFarneback(prev_gray, curr_gray, None, 0.5, 3, 15, 3, 5, 1.2, 0) ``` 최근에는 RAFT (Recurrent All-Pairs Field Transforms)가 dense optical flow의 사실상 표준이 되었다. 딥러닝 기반이지만 정확도가 크게 높아서, 정확성이 중요한 경우에는 RAFT를 쓰는 것이 일반적이다. > **추천 자료** > - [First Principles of CV — Optical Flow](https://www.youtube.com/playlist?list=PL2zRqk16wsdp8KbDfHKvPYNGF2L-zQASc) — 광류의 수학적 원리 > - [Teed & Deng, "RAFT: Recurrent All-Pairs Field Transforms for Optical Flow" (2020)](https://arxiv.org/abs/2003.12039) — 딥러닝 기반 optical flow의 대표작 > - [Huang et al., "FlowFormer: A Transformer Architecture for Optical Flow" (ECCV 2022, arXiv:2203.16194)](https://arxiv.org/abs/2203.16194) — Transformer 기반 optical flow > - [OpenCV Optical Flow 튜토리얼](https://docs.opencv.org/4.x/d4/dee/tutorial_optical_flow.html) — Lucas-Kanade, Farneback 코드 예제 > **실습**: [Optical Flow 시각화](https://alexjunholee.github.io/robotics-practice/app.html#optical_flow) > Lucas-Kanade와 Dense Optical Flow 알고리즘의 동작을 인터랙티브하게 비교하며 픽셀 이동 추정 과정을 확인할 수 있다. --- ### 9.6 심화: PnP 문제 *연구자가 되고 싶다면 여기서부터 읽어라.* **Perspective-n-Point (PnP)**은 3D 공간의 점과 2D 이미지 상의 대응점이 주어졌을 때, 카메라의 포즈(회전 R + 이동 t)를 추정하는 문제이다. SLAM에서 매 프레임 카메라 tracking이 곧 PnP 문제이며, AR에서 마커 기반 위치 추정도 PnP로 풀린다. **문제 정의**: n개의 3D-2D 대응 {(X_i, x_i)}가 주어졌을 때, 카메라 외부 파라미터 [R|t]를 추정한다. $$x_i = K [R | t] X_i$$ 여기서 K는 카메라 내부 파라미터(intrinsics)이다. **P3P (3-Point Problem)**: - 최소 3개의 대응점으로 풀 수 있다. - 3점으로 최대 4개의 해가 나온다. 4번째 점을 사용해 disambiguation한다. - RANSAC과 결합하여 outlier에 robust하게 풀 수 있다. **EPnP (Efficient PnP)**: - O(n) 복잡도로, 대응점이 많을 때 효율적이다. - 3D 점들을 4개의 가상 제어점(virtual control points)으로 표현하고, 이 제어점의 카메라 좌표를 추정하는 방식이다. - 많은 점이 있는 경우 P3P+RANSAC보다 빠르고 안정적이다. **실무 사용법**: ```python import cv2 import numpy as np # 3D 월드 좌표 (n x 3) object_points = np.array([...], dtype=np.float64) # 대응하는 2D 이미지 좌표 (n x 2) image_points = np.array([...], dtype=np.float64) # 카메라 내부 파라미터 camera_matrix = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]], dtype=np.float64) dist_coeffs = np.zeros(4) # 기본 PnP (iterative, 초기값 필요 없음) success, rvec, tvec = cv2.solvePnP( object_points, image_points, camera_matrix, dist_coeffs, flags=cv2.SOLVEPNP_EPNP ) # RANSAC 버전 — outlier가 있을 때 필수 success, rvec, tvec, inliers = cv2.solvePnPRansac( object_points, image_points, camera_matrix, dist_coeffs, iterationsCount=1000, reprojectionError=3.0 ) ``` **SLAM과의 연결**: Visual SLAM에서 매 프레임마다 수행하는 과정은 다음과 같다. 1. 이전 프레임에서 삼각측량(triangulation)으로 3D 맵 포인트를 만든다. 2. 새 프레임에서 해당 맵 포인트의 2D 재투영(reprojection)을 예측한다. 3. 실제 관측된 2D 키포인트와 매칭한다. 4. 이 3D-2D 대응으로 PnP를 풀어 새 프레임의 카메라 포즈를 구한다. ORB-SLAM3의 Tracking 단계가 정확히 이 과정이다. > **추천 자료** > - [Lepetit et al., "EPnP: An Accurate O(n) Solution to the PnP Problem" (2009)](https://doi.org/10.1007/s11263-008-0152-6) — EPnP 원논문 > - [OpenCV solvePnP 문서](https://docs.opencv.org/4.x/d5/d1f/calib3d_solvePnP.html) — 다양한 PnP 알고리즘 flag 설명 > - [Multiple View Geometry — Ch. 7](https://www.robots.ox.ac.uk/~vgg/hzbook/) — PnP 문제의 수학적 배경 **solvePnP 실전 팁** OpenCV의 `cv2.solvePnP()`는 3D-2D 대응점으로 카메라 포즈를 추정한다. 반환하는 `rvec`은 Rodrigues 벡터(축-각 표현)이다. ```python # rvec → 회전 행렬 변환 R, _ = cv2.Rodrigues(rvec) # 카메라의 월드 좌표 위치 camera_position = -R.T @ tvec ``` 주의할 점: - `solvePnP`의 결과는 **세계→카메라 변환**이다. 카메라의 세계 좌표 위치를 구하려면 역변환을 해야 한다. - 최소 4점이 필요하지만, 점이 많을수록 노이즈에 강건하다. RANSAC 버전인 `cv2.solvePnPRansac()`을 쓰면 outlier를 자동으로 걸러준다. - `flags` 파라미터로 알고리즘을 선택할 수 있다: `cv2.SOLVEPNP_ITERATIVE` (기본, LM), `cv2.SOLVEPNP_P3P` (최소 3점), `cv2.SOLVEPNP_EPNP` (빠르고 안정적, 많은 점에 적합). Rodrigues 벡터의 방향이 회전축, 크기(norm)가 회전 각도이다. 3장의 Lie algebra so(3)에서 다룬 축-각 표현과 정확히 같다. `cv2.Rodrigues()`는 exp/log map의 구현이라고 보면 된다. (참고: [다크 프로그래머 — solvePnP 함수 사용법과 Rodrigues 표현법](https://darkpgmr.tistory.com/99)) --- ### 9.7 심화: RANSAC 변종 *연구자가 되고 싶다면 여기서부터 읽어라.* 3장의 robust estimation에서 RANSAC을 소개했다. 실제 연구에서는 vanilla RANSAC을 그대로 쓰는 경우가 드물다. 수렴 속도와 정확도를 개선한 여러 변종이 존재하며, 어떤 것을 쓰느냐에 따라 결과가 크게 달라질 수 있다. **주요 변종**: | 방법 | 핵심 아이디어 | 특징 | |------|-------------|------| | **Lo-RANSAC** | inlier로 로컬 최적화 수행 | vanilla보다 적은 iteration으로 수렴 | | **PROSAC** | 매칭 신뢰도 순으로 샘플링 | 좋은 매칭부터 먼저 시도하여 수렴 가속 | | **MAGSAC++** | σ(inlier threshold)를 marginalization | threshold 설정 불필요, 자동 적응 | **Lo-RANSAC (Locally Optimized RANSAC)**: - 좋은 모델을 찾으면, 그 모델의 inlier들로 다시 모델을 추정(local optimization)한다. - 단순한 아이디어지만 효과가 크다. 특히 inlier ratio가 낮을 때 유용하다. **PROSAC (Progressive Sample Consensus)**: - 매칭 스코어가 높은 대응점부터 우선적으로 샘플링한다. - 좋은 매칭이 앞에 많으면 초기 iteration에서 바로 좋은 모델을 찾는다. **MAGSAC++ (Marginalizing Sample Consensus)**: - 가장 골치 아픈 하이퍼파라미터인 inlier threshold σ를 marginalize한다. - Threshold를 고정하지 않고 여러 σ 값에 대해 적분하므로, 수동 튜닝이 거의 필요 없다. - 현재 OpenCV에서 권장하는 방법이다. **OpenCV에서 MAGSAC++ 사용**: ```python import cv2 # Fundamental matrix 추정에 MAGSAC++ 사용 F, mask = cv2.findFundamentalMat( pts1, pts2, method=cv2.USAC_MAGSAC, ransacReprojThreshold=1.0, confidence=0.999, maxIters=10000 ) # Homography 추정에도 동일하게 적용 가능 H, mask = cv2.findHomography( src_pts, dst_pts, method=cv2.USAC_MAGSAC, ransacReprojThreshold=3.0 ) ``` **실무 팁**: - Iteration 수: `confidence` 파라미터로 제어한다. 0.999면 "99.9% 확률로 올바른 모델을 찾겠다"는 의미. inlier ratio가 낮을수록 필요한 iteration이 기하급수적으로 증가한다. - Threshold: MAGSAC++를 쓰면 threshold에 덜 민감하지만, 초기값은 여전히 줘야 한다. Fundamental matrix는 1.0~3.0 pixel, Homography는 3.0~5.0 pixel이 일반적이다. - 속도가 중요하면 PROSAC, 정확도가 중요하면 MAGSAC++를 쓴다. > **추천 자료** > - [Barath et al., "MAGSAC++, a Fast, Reliable and Accurate Robust Estimator" (2020)](https://arxiv.org/abs/1912.05909) — MAGSAC++ 원논문 > - [OpenCV USAC 문서](https://docs.opencv.org/4.x/d1/df1/md__build_4rdparty_ippicv_ippicv_lnx_doc_USAC.html) — OpenCV의 universal RANSAC 프레임워크 > - [Chum & Matas, "Matching with PROSAC" (2005)](https://doi.org/10.1109/CVPR.2005.221) — PROSAC 원논문 --- ### 9.8 심화: 학습 기반 특징 매칭 *연구자가 되고 싶다면 여기서부터 읽어라.* 9.3에서 다룬 ORB, SIFT 같은 hand-crafted feature는 수십 년간 잘 작동해왔지만, 반복 패턴, 텍스처 부족, 극단적 조명 변화 등에서 실패한다. 2018년 이후 딥러닝 기반 특징 추출과 매칭이 고전 방법을 넘어서기 시작했다. **파이프라인 발전 과정**: ``` SuperPoint (2018) → SuperGlue (2020) → LightGlue (2023) [키포인트 검출+기술] [그래프 신경망 매칭] [경량화된 매칭] ``` **SuperPoint**: - 자기지도 학습으로 키포인트 검출기와 디스크립터를 동시에 학습한다. - Homographic adaptation: 합성 변환을 적용하고 역변환하여 pseudo ground truth를 생성한다. - 반복 패턴에 강하고, 고전 방법 대비 repeatability가 높다. **SuperGlue**: - 두 이미지의 키포인트를 그래프로 보고, attention mechanism으로 매칭한다. - Self-attention으로 같은 이미지 내 키포인트 관계를 학습하고, cross-attention으로 두 이미지 간 매칭을 수행한다. - Sinkhorn algorithm으로 최적 할당(optimal assignment) 문제를 푼다. - 정확도는 매우 높지만 속도가 느리다 (GPU 필수). **LightGlue**: - SuperGlue의 경량 버전. Adaptive early stopping으로 쉬운 이미지 쌍은 빨리, 어려운 쌍은 더 많은 layer를 통과시킨다. - SuperGlue 대비 속도가 수 배 빠르면서 정확도는 유사하다. **LoFTR (Detector-Free Local Feature Matching)**: - 키포인트 검출 단계 자체를 제거한다. 이미지 전체에서 dense matching을 수행한다. - Transformer 기반으로 coarse-to-fine 매칭을 한다. - 텍스처가 부족한 영역에서도 매칭이 가능하다는 것이 가장 큰 장점이다. - 단점: 속도가 느리고 GPU 메모리를 많이 사용한다. **고전 방법 vs 학습 기반 비교**: | 항목 | ORB/SIFT | SuperPoint+LightGlue | LoFTR | |------|----------|---------------------|-------| | 속도 (CPU) | 빠름 | 느림 | 매우 느림 | | 속도 (GPU) | 해당 없음 | 보통 | 느림 | | 텍스처 부족 영역 | 실패 | 보통 | 강함 | | 반복 패턴 | 약함 | 강함 | 강함 | | GPU 의존성 | 없음 | 높음 | 매우 높음 | | 실시간 로봇 적용 | 용이 | 조건부 가능 | 어려움 | **코드 예시 — LightGlue (kornia 사용)**: ```python import kornia from kornia.feature import LightGlueMatcher, KeyNetAffNetHardNet # 특징 추출기 + 매칭기 구성 extractor = KeyNetAffNetHardNet(num_features=2048).eval() matcher = LightGlueMatcher("keynetaffnethardnet").eval() # GPU로 이동 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") extractor = extractor.to(device) matcher = matcher.to(device) # 이미지 로드 (kornia 형식: B x C x H x W, 0-1 범위) img0 = kornia.io.load_image(path0).unsqueeze(0).to(device) img1 = kornia.io.load_image(path1).unsqueeze(0).to(device) # 특징 추출 with torch.no_grad(): feats0 = extractor(img0) feats1 = extractor(img1) # 매칭 dists, match_idxs = matcher(feats0["descriptors"], feats1["descriptors"]) ``` 실무에서는 GPU가 있으면 SuperPoint+LightGlue 조합이 가장 밸런스가 좋다. GPU 없이 embedded에서 돌려야 하면 여전히 ORB가 현실적이다. > **추천 자료** > - [DeTone et al., "SuperPoint: Self-Supervised Interest Point Detection and Description" (2018)](https://arxiv.org/abs/1712.07629) — SuperPoint 원논문 > - [Lindenberger et al., "LightGlue: Local Feature Matching at Light Speed" (2023)](https://arxiv.org/abs/2306.13643) — LightGlue 원논문 > - [Sun et al., "LoFTR: Detector-Free Local Feature Matching with Transformers" (2021)](https://arxiv.org/abs/2104.00680) — LoFTR 원논문 --- > **기술 흐름: 컴퓨터 비전 기초 (Classical Methods)** > - **~2004**: 고전적 특징점의 시대. Harris Corner (1988), SIFT (2004) 등 hand-crafted feature가 주류. 수학적으로 정교하지만 계산이 무거움 > - **2006~2011**: 실시간을 위한 경량화. SURF (2006), FAST (2006), BRIEF (2010), ORB (2011) 등이 등장. 특허 문제와 속도 문제를 해결하면서 실시간 SLAM이 가능해짐 > - **2015~2019**: 딥러닝의 침투. SuperPoint (2018), SuperGlue (2020) 등 학습 기반 특징점이 고전 방법의 성능을 넘어서기 시작 > - **2020~**: Geometry + Learning의 융합. LoFTR (2021) 같은 detector-free matching, LightGlue (2023) 같은 경량 학습 매칭이 등장. 고전 기하학은 여전히 SLAM/VO의 백엔드에서 핵심 > - **지금 주목할 것**: 고전 기하학(epipolar geometry, triangulation)은 절대 사라지지 않는다. 딥러닝이 front-end(특징점 추출, 매칭)를 대체하는 추세이지만, back-end의 수학은 그대로이다. 둘 다 알아야 진짜 실력이다 --- ## 10. 딥러닝 기반 인식 (Deep Learning for Perception) 이 챕터는 로봇이 "무엇을 보고 있는지"를 이해하는 핵심 기술을 다룬다. 앞서 배운 고전 CV가 "이미지를 어떻게 처리하고, 기하학적 관계를 어떻게 추출하는가"에 집중했다면, 여기서는 "이미지 안에 뭐가 있는지"를 인식하는 데 초점을 맞춘다. 물체 탐지, 분류, 분할 — 로봇이 "저기 빨간 컵이 있으니 집어" 같은 명령을 수행하려면, 이 기술들이 필수이다. --- ### 10.1 프레임워크 선택 어떤 딥러닝 프레임워크를 쓸지는 생각보다 중요한 결정이다. 연구 코드를 읽고 재현하려면 그 코드가 쓰는 프레임워크를 알아야 하고, 자기 모델을 만들려면 하나는 제대로 알아야 한다. ### 10.1.1 PyTorch (권장) **장점**: - 직관적인 동적 그래프 (eager execution) - 디버깅 용이 - 연구 커뮤니티에서 표준 - 풍부한 사전학습 모델 (torchvision, timm) 현실적인 이유가 있다. 2024년 기준 NeurIPS, CVPR, ICLR 등 주요 학회에서 발표되는 논문의 코드 공개 중 약 80% 이상이 PyTorch이다. 최신 논문을 읽고 코드를 돌려보고 수정하려면, PyTorch를 모르면 사실상 진행이 안 된다. **설치**: ```bash # CUDA 12.1 버전 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121 ``` **기본 사용**: ```python import torch import torch.nn as nn # 텐서 생성 x = torch.randn(32, 3, 224, 224) # (batch, channel, height, width) # GPU 사용 device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') x = x.to(device) ``` > **추천 자료** > - [PyTorch 공식 튜토리얼](https://pytorch.org/tutorials/) — 입문부터 고급까지 체계적으로 정리되어 있다 > - [d2l.ai (Dive into Deep Learning)](https://d2l.ai/) — 인터랙티브 교과서. PyTorch 코드와 수학이 함께 나온다 > - [Andrej Karpathy — Neural Networks: Zero to Hero](https://www.youtube.com/playlist?list=PLAqhIrjkxbuWI23v9cThsA9GvCAUhRvKZ) — 전 Tesla AI Director가 신경망을 밑바닥부터 설명 ### 10.1.2 TensorFlow / JAX **TensorFlow**: 프로덕션 배포에 강점, TF Lite 모바일 지원 **JAX**: 고성능 연산, 함수형 프로그래밍, 연구용 대부분의 최신 연구 코드가 PyTorch로 공개되므로, **PyTorch 학습을 권장**한다. 단, TensorFlow Lite는 로봇의 엣지 디바이스(Jetson, 라즈베리파이 등)에 모델을 배포할 때 여전히 많이 쓰이고, JAX는 Google DeepMind 계열 연구에서 많이 사용되므로 존재는 알아두자. > **추천 자료** > - [TensorFlow 공식 가이드](https://www.tensorflow.org/guide) — TFLite 변환까지 커버 > - [JAX 공식 문서](https://jax.readthedocs.io/) — 함수형 딥러닝 프레임워크 --- ### 10.2 딥러닝 기초 개념 이 섹션의 개념들을 모르면 "왜 딥러닝이 이미지 인식에서 고전 방법을 압도하는가"를 이해할 수 없다. CNN의 구조를 모르면 ResNet이 왜 중요한지 모르고, Transformer를 모르면 ViT와 DETR이 왜 기존 접근을 대체하는지 이해할 수 없다. ### 10.2.1 CNN (Convolutional Neural Network) 이미지에서 공간적 특징을 추출하는 핵심 구조이다. CNN은 "이미지의 지역적 패턴(에지, 코너, 텍스처)을 자동으로 학습"하는 구조이다. 앞서 배운 고전 CV에서는 SIFT, ORB 같은 특징을 사람이 설계(hand-craft)했지만, CNN은 데이터에서 자동으로 최적의 특징을 배운다. 딥러닝의 전환점이 여기다. **주요 구성 요소**: - **Convolution Layer**: 필터로 특징 추출 - **Pooling Layer**: 공간 크기 축소 (Max, Average) - **Activation**: nonlinearity 도입 (ReLU, GELU) - **Batch Normalization**: 학습 안정화 ```python # 간단한 CNN 블록 class ConvBlock(nn.Module): def __init__(self, in_ch, out_ch): super().__init__() self.conv = nn.Conv2d(in_ch, out_ch, kernel_size=3, padding=1) self.bn = nn.BatchNorm2d(out_ch) self.relu = nn.ReLU(inplace=True) def forward(self, x): return self.relu(self.bn(self.conv(x))) ``` Convolution은 선형대수적으로 보면 "필터(kernel)와 이미지 패치의 내적"이다. 수업에서 배운 행렬 곱이 여기서 바로 쓰인다. `kernel_size=3, padding=1`이면 출력 크기가 입력과 같게 유지된다 — 이 패턴은 매우 자주 나온다. > **추천 자료** > - [Stanford CS231n — Convolutional Neural Networks for Visual Recognition](https://www.youtube.com/playlist?list=PLoROMvodv4rMFqRtEuo6SGjY4XbRIVRd4) — CNN을 이해하기 위한 대표 강의. 보는 걸 권한다 > - [d2l.ai — CNN 챕터](https://d2l.ai/chapter_convolutional-neural-networks/index.html) — 코드와 수학 설명이 동시에 > - [3Blue1Brown — But what is a Neural Network?](https://www.youtube.com/watch?v=aircAruvnKk) — 신경망의 직관적 이해 ### 10.2.2 Attention & Transformer **Self-Attention**: 시퀀스 내 모든 위치 간의 관계를 학습 ``` Attention(Q, K, V) = softmax(QK^T / √d_k) V ``` CNN과의 차이를 알면 왜 Transformer가 뜨는지 바로 이해된다. CNN은 "가까운 픽셀끼리만" 정보를 교환하지만 (local receptive field), Transformer의 Self-Attention은 "이미지의 어떤 부분이든 서로 참조"할 수 있다 (global attention). 이 덕분에 물체의 전체적인 맥락을 파악하는 데 유리하다. 2020년 이후 비전 분야에서 Transformer가 CNN을 빠르게 대체하는 추세이다. **Vision Transformer (ViT)**: - 이미지를 패치로 분할 → 시퀀스로 변환 - Transformer encoder 적용 - 최근 비전 태스크에서 CNN을 대체하는 추세 ViT의 아이디어는 의외로 단순하다: 이미지를 16x16 같은 고정 크기 패치로 자르고, 각 패치를 마치 NLP에서 "단어"처럼 취급해서 Transformer에 넣는 것이다. 이 단순한 아이디어가 대규모 데이터에서 CNN을 넘어서는 성능을 보여줬다. > **추천 자료** > - [Vaswani et al., "Attention Is All You Need" (2017)](https://arxiv.org/abs/1706.03762) — Transformer 원논문. 모든 것의 시작 > - [Dosovitskiy et al., "An Image is Worth 16x16 Words" (2020)](https://arxiv.org/abs/2010.11929) — ViT 원논문 > - [Yannic Kilcher — Vision Transformer 설명](https://www.youtube.com/watch?v=TrdevFK_am4) — 논문을 쉽게 풀어서 설명 > - [Andrej Karpathy — Let's build GPT from scratch](https://www.youtube.com/watch?v=kCc8FmEb1nY) — Transformer 구현을 밑바닥부터. NLP지만 ViT 이해에 직결된다 --- ### 10.3 Image Classification 분류(classification)는 "이 이미지에 뭐가 있는가?"라는 가장 기본적인 질문이다. 복잡한 detection, segmentation 모델도 내부적으로 분류기를 포함하고 있으므로, 분류 모델을 이해하는 것이 기본 중의 기본이다. 또한, 사전학습된 분류 모델의 backbone (ResNet, ViT 등)은 다른 태스크의 feature extractor로 널리 재사용된다. **대표 모델**: | 모델 | 특징 | 용도 | | --- | --- | --- | | ResNet | Residual connection, 안정적 학습 | 백본 네트워크 | | EfficientNet | Compound scaling, 효율적 | 모바일, 효율성 중시 | | ViT | Transformer 기반 | 대규모 데이터, 고성능 | | ConvNeXt | CNN의 현대화 | ViT와 경쟁 | ResNet의 Residual Connection은 "입력을 출력에 더해주는" 단순한 아이디어인데, 이 하나가 수십 층 깊은 네트워크의 학습을 가능하게 만들었다. 2015년 발표 이후 거의 모든 딥러닝 아키텍처의 기본 빌딩 블록이 되었다. **사전학습 모델 사용**: ```python import torchvision.models as models # Pretrained ResNet50 model = models.resnet50(weights='IMAGENET1K_V2') # Feature extractor로 사용 model.fc = nn.Identity() # 마지막 FC 제거 features = model(x) # (batch, 2048) ``` 이 패턴은 매우 자주 쓰인다. ImageNet으로 사전학습된 모델의 마지막 분류 레이어만 제거하고, 그 전까지의 출력을 "feature"로 사용하는 것이다. 이를 transfer learning이라 하고, 로보틱스에서 새로운 물체를 인식시킬 때 거의 항상 이 방식을 쓴다. > **추천 자료** > - [He et al., "Deep Residual Learning for Image Recognition" (2015)](https://arxiv.org/abs/1512.03385) — ResNet 원논문. 딥러닝 역사상 가장 많이 인용된 논문 중 하나 > - [Papers With Code — Image Classification](https://paperswithcode.com/task/image-classification) — 최신 벤치마크와 SOTA 모델 확인 > - [timm (PyTorch Image Models) 라이브러리](https://github.com/huggingface/pytorch-image-models) — 수백 개의 사전학습 모델을 한 줄로 로드. 실무에서 매우 유용 > - [Stanford CS231n — Training Neural Networks](https://www.youtube.com/playlist?list=PLoROMvodv4rMFqRtEuo6SGjY4XbRIVRd4) — 학습 기법과 트릭 --- ### 10.4 Object Detection 로봇이 "저 테이블 위에 컵이 어디 있지?"를 알려면, 단순 분류가 아니라 "어디에 뭐가 있는지"를 알아야 한다. 그것이 object detection이다. Bounding box로 물체의 위치와 클래스를 동시에 예측하는 태스크이며, 로봇 manipulation, 자율주행, 감시 시스템 등 거의 모든 응용에서 쓰인다. ### 10.4.1 Two-Stage Detectors **Faster R-CNN**: 1. Region Proposal Network (RPN): 후보 영역 제안 2. ROI Pooling: 각 영역에서 특징 추출 3. Classification + Bounding Box Regression 장점: 높은 정확도 단점: 느린 속도 Faster R-CNN은 two-stage detection의 대표작이고, 정확도가 중요한 경우(예: 산업용 검사)에서 여전히 쓰인다. "먼저 후보를 뽑고, 그 후보를 정밀 분석한다"는 구조는 직관적이고, 이후 Mask R-CNN 등으로 확장되었다. ### 10.4.2 One-Stage Detectors **YOLO (You Only Look Once)**: - 이미지를 그리드로 나누고 한 번에 예측 - 실시간 처리 가능 (30+ FPS) - 버전: YOLOv5, YOLOv8, YOLOv11 (Ultralytics) YOLO는 이름 그대로 "한 번만 본다" — two-stage처럼 후보를 먼저 뽑지 않고, 이미지 전체를 한 번에 처리해서 모든 물체를 탐지한다. 로봇 시스템에서 실시간성이 중요할 때 가장 먼저 고려하는 모델이다. Ultralytics의 YOLOv8/v11은 설치와 사용이 매우 간편해서 프로토타이핑에 최적이다. ```python from ultralytics import YOLO # 모델 로드 및 추론 model = YOLO('yolov8n.pt') # nano 모델 results = model('image.jpg') # 결과 시각화 results[0].show() ``` **SSD (Single Shot Detector)**: - 다양한 스케일의 feature map에서 예측 - YOLO보다 작은 객체 탐지에 유리 > **추천 자료** > - [Redmon et al., "You Only Look Once: Unified, Real-Time Object Detection" (2016)](https://arxiv.org/abs/1506.02640) — YOLO 원논문. 간결하고 읽기 좋다 > - [Ultralytics YOLOv8 문서](https://docs.ultralytics.com/) — 설치부터 커스텀 학습까지 잘 정리 > - [Papers With Code — Object Detection](https://paperswithcode.com/task/object-detection) — 최신 SOTA 확인 > - [다크 프로그래머 — precision, recall의 이해](https://darkpgmr.tistory.com/162) — detection 평가 지표를 직관적으로 설명 ### 10.4.3 Transformer-based **DETR (Detection Transformer)**: - Object Query를 사용한 set prediction - NMS (Non-Maximum Suppression) 불필요 - 학습이 느리지만, 깔끔한 구조 DETR은 detection을 "집합 예측 문제"로 재정의했다. 기존 방법들이 수천 개의 anchor box를 만들고 NMS로 중복을 제거하는 복잡한 파이프라인을 쓴 반면, DETR은 Transformer로 end-to-end 학습이 가능하다. 구조가 깔끔해서 이후 많은 후속 연구(Deformable DETR, DINO, Co-DETR 등)로 이어졌다. > **추천 자료** > - [Carion et al., "End-to-End Object Detection with Transformers" (2020)](https://arxiv.org/abs/2005.12872) — DETR 원논문 > - [Yannic Kilcher — DETR 설명](https://www.youtube.com/watch?v=T35ba_VXkMY) — 논문을 잘 풀어서 설명 > - [HuggingFace — Object Detection 가이드](https://huggingface.co/docs/transformers/tasks/object_detection) — Transformers 라이브러리로 DETR 사용하기 > - [Zhao et al., "DETRs Beat YOLOs on Real-time Object Detection" (RT-DETR, CVPR 2024, arXiv:2304.08069)](https://arxiv.org/abs/2304.08069) — DETR 계열 최초로 YOLO급 실시간 속도 달성. 실시간 detection의 새로운 방향 > - [Cheng et al., "YOLO-World: Real-Time Open-Vocabulary Object Detection" (CVPR 2024, arXiv:2401.17270)](https://arxiv.org/abs/2401.17270) — YOLO에 텍스트 프롬프트 기반 open-vocabulary detection 추가. 로보틱스에서 임의 물체 탐지에 실용적 --- ### 10.5 Semantic Segmentation 픽셀 단위로 클래스를 예측하는 태스크이다. 로봇 manipulation을 생각하면 차이가 바로 보인다. detection은 bounding box로 대략적인 위치만 알려주지만, segmentation은 물체의 정확한 경계를 알려준다. 로봇이 물체를 잡으려면 bounding box가 아니라 정확한 윤곽이 필요하고, 자율주행에서 도로/인도/차선을 구분하려면 픽셀 단위의 분류가 필수이다. **대표 모델**: | 모델 | 특징 | | --- | --- | | FCN | 최초의 end-to-end segmentation | | U-Net | Encoder-Decoder 구조, 의료 영상에서 시작 | | DeepLab v3+ | Atrous convolution, 다중 스케일 | | SegFormer | Transformer 기반, 경량 디코더 | U-Net의 Encoder-Decoder + Skip Connection 구조는 segmentation의 기본 패턴이 되었다. Encoder에서 특징을 추출하면서 해상도를 줄이고, Decoder에서 다시 해상도를 복원하면서 Skip Connection으로 세부 정보를 보충한다. 이 패턴은 depth estimation, image generation 등 다른 태스크에서도 광범위하게 사용된다. ```python # Segmentation 모델 사용 (transformers 라이브러리) from transformers import SegformerForSemanticSegmentation model = SegformerForSemanticSegmentation.from_pretrained( "nvidia/segformer-b0-finetuned-ade-512-512" ) ``` > **추천 자료** > - [Papers With Code — Semantic Segmentation](https://paperswithcode.com/task/semantic-segmentation) — 최신 벤치마크 > - [HuggingFace — Image Segmentation](https://huggingface.co/docs/transformers/tasks/semantic_segmentation) — SegFormer 등 사용법 > - [Two Minute Papers — Semantic Segmentation 관련 영상들](https://www.youtube.com/@TwoMinutePapers) — 최신 연구를 2분으로 요약 --- ### 10.6 Instance & Panoptic Segmentation **Instance Segmentation**: 각 객체 인스턴스를 구분 - Mask R-CNN: Faster R-CNN + Mask 브랜치 **Panoptic Segmentation**: Semantic + Instance 통합 - "Things" (객체): 인스턴스 구분 - "Stuff" (배경): 인스턴스 구분 없음 한 단계 더 생각해 보면, semantic segmentation은 "여기가 의자 영역"이라고만 알려주지, "의자가 3개 있는데 각각 어디까지인지"는 구분하지 못한다. 로봇이 "왼쪽 의자를 집어"라는 명령을 수행하려면, instance segmentation이 필요하다. Panoptic segmentation은 이 둘을 통합한 것으로, 장면 전체를 완전하게 이해하는 데 쓰인다. > **추천 자료** > - [He et al., "Mask R-CNN" (2017)](https://arxiv.org/abs/1703.06870) — Instance segmentation의 대표작 > - [Detectron2](https://github.com/facebookresearch/detectron2) — Meta의 detection/segmentation 프레임워크. Mask R-CNN 등을 쉽게 사용 가능 --- ### 10.7 Depth Estimation 단일 이미지에서 깊이를 예측하는 태스크이다. 스테레오 카메라나 LiDAR 없이 단안(monocular) 카메라 하나로 깊이 정보를 얻을 수 있다면, 하드웨어 비용과 무게를 크게 줄일 수 있다. 특히 드론이나 소형 로봇처럼 payload가 제한적인 시스템에서 매우 유용하다. 최근 foundation model 수준의 일반화 성능을 보여주는 모델들이 나오면서 실용성이 크게 높아졌다. **대표 모델**: - **MiDaS**: 다양한 데이터셋 학습, 범용성 - **Depth Anything**: Foundation model 수준의 일반화 - **ZoeDepth**: 메트릭 깊이 추정 ```python # Depth Anything 사용 from transformers import pipeline pipe = pipeline("depth-estimation", model="LiheYoung/depth-anything-base-hf") result = pipe("image.jpg") depth = result['depth'] ``` 주의할 점: MiDaS와 Depth Anything은 기본적으로 **상대적 깊이(relative depth)**를 추정한다. 즉, "A가 B보다 가깝다"는 알 수 있지만, "A까지 정확히 몇 미터"인지는 알 수 없다. 메트릭 깊이가 필요하면 ZoeDepth나 Depth Anything V2의 metric 버전을 사용해야 한다. > **추천 자료** > - [Yang et al., "Depth Anything: Unleashing the Power of Large-Scale Unlabeled Data" (2024)](https://arxiv.org/abs/2401.10891) — Depth Anything 원논문 > - [Godard et al., "Digging Into Self-Supervised Monocular Depth Estimation" (Monodepth2, ICCV 2019, arXiv:1806.01260)](https://arxiv.org/abs/1806.01260) — self-supervised depth의 기준선 > - [HuggingFace — Monocular Depth Estimation](https://huggingface.co/docs/transformers/tasks/monocular_depth_estimation) — 바로 돌려볼 수 있는 코드 > - [Papers With Code — Monocular Depth Estimation](https://paperswithcode.com/task/monocular-depth-estimation) — 최신 벤치마크 확인 --- ### 10.8 심화: 학습 레시피 *연구자가 되고 싶다면 여기서부터 읽어라.* 모델 아키텍처만 알면 연구할 수 있을 것 같지만, 실제로 "학습이 잘 되게 하는 것"이 전체 연구 시간의 절반 이상을 차지한다. 같은 모델이라도 learning rate 하나 잘못 잡으면 수렴하지 않고, augmentation 하나 추가하면 정확도가 몇 % 뛸 수 있다. 이 절에서는 실무에서 반복적으로 쓰이는 학습 테크닉을 정리한다. **Learning Rate Schedule**: - **Cosine Annealing with Warm-up**: 가장 널리 쓰이는 스케줄. 초기 몇 epoch 동안 learning rate를 0에서 목표값까지 선형으로 올리고(warm-up), 이후 cosine 곡선으로 감쇠한다. $$\eta_t = \eta_{min} + \frac{1}{2}(\eta_{max} - \eta_{min})\left(1 + \cos\left(\frac{t \cdot \pi}{T}\right)\right)$$ - **OneCycleLR**: learning rate를 한 번 올렸다가 내리는 정책. Super-convergence를 달성할 수 있어서 적은 epoch으로 빠르게 수렴한다. ```python import torch.optim as optim # Cosine Annealing with Warm-up (manual) optimizer = optim.AdamW(model.parameters(), lr=1e-4, weight_decay=0.05) scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=100) # OneCycleLR scheduler = optim.lr_scheduler.OneCycleLR( optimizer, max_lr=1e-3, total_steps=len(dataloader) * num_epochs, pct_start=0.1 # 처음 10%를 warm-up에 사용 ) ``` **Data Augmentation**: | 기법 | 설명 | 주 용도 | |------|------|---------| | **RandAugment** | N개의 변환을 magnitude M으로 랜덤 적용 | 분류 전반 | | **CutMix** | 이미지 영역을 다른 이미지로 대체, 라벨도 비율 혼합 | 분류 | | **MixUp** | 두 이미지와 라벨을 선형 보간 | 분류 | | **Mosaic** | 4개 이미지를 하나로 합성 | Detection (YOLO 계열) | ```python import torchvision.transforms.v2 as T # RandAugment transform = T.Compose([ T.RandAugment(num_ops=2, magnitude=9), T.ToTensor(), T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) ``` **Regularization**: - **Label Smoothing**: hard label (0 또는 1) 대신 soft label (0.1, 0.9 등)을 사용. overconfidence를 방지한다. `nn.CrossEntropyLoss(label_smoothing=0.1)` - **Stochastic Depth**: 학습 시 일부 layer를 랜덤으로 건너뛴다. ResNet 계열에서 overfitting 방지에 효과적이다. - **Weight Decay**: optimizer에서 `weight_decay=0.01~0.05` 설정. AdamW에서는 decoupled weight decay를 사용한다. **Gradient Clipping**: gradient가 폭발하는 것을 방지한다. Transformer 학습에서는 거의 필수이다. ```python torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) ``` **Loss Curve로 문제 진단**: | 패턴 | 진단 | 대응 | |------|------|------| | train loss 감소, val loss 증가 | Overfitting | augmentation 추가, dropout/weight decay 증가, 데이터 확보 | | train loss 높은 상태 정체 | Underfitting | 모델 크기 증가, learning rate 조정, augmentation 감소 | | train loss 진동이 심함 | Learning rate 과다 | learning rate 감소 | | train loss NaN 발생 | Gradient explosion | gradient clipping, learning rate 대폭 감소, 데이터 검증 | | val loss 초반에 급감 후 완전 정체 | Learning rate 부족 또는 스케줄 문제 | warm-up 추가, cosine schedule 적용 | **Distributed Training -- PyTorch DDP 기본**: 모델이 커지면 GPU 1개로는 시간이 부족하다. DistributedDataParallel (DDP)은 여러 GPU에 모델을 복제하고 gradient를 동기화하는 가장 기본적인 병렬 학습 방법이다. ```python # DDP 최소 구조 (torchrun으로 실행) import torch.distributed as dist from torch.nn.parallel import DistributedDataParallel as DDP dist.init_process_group("nccl") local_rank = int(os.environ["LOCAL_RANK"]) model = model.to(local_rank) model = DDP(model, device_ids=[local_rank]) # 실행: torchrun --nproc_per_node=4 train.py ``` > **추천 자료** > - [Goyal et al., "Accurate, Large Minibatch SGD" (2017)](https://arxiv.org/abs/1706.02677) -- 대규모 학습의 learning rate scaling rule > - [PyTorch DDP 튜토리얼](https://pytorch.org/tutorials/intermediate/ddp_tutorial.html) -- 분산 학습 공식 가이드 > - [Wightman et al., "ResNet strikes back" (2021)](https://arxiv.org/abs/2110.00476) -- 학습 레시피의 중요성을 보여주는 논문. 같은 ResNet으로 학습 기법만 바꿔 정확도를 크게 향상 > **실습**: [Data Augmentation 시각화](https://alexjunholee.github.io/robotics-practice/app.html#data_augmentation) > RandAugment, CutMix, MixUp 등 다양한 augmentation 기법이 이미지를 어떻게 변형하는지 인터랙티브하게 확인할 수 있다. > **실습**: [Learning Rate Schedule 시각화](https://alexjunholee.github.io/robotics-practice/app.html#lr_schedule) > Cosine Annealing, OneCycleLR 등 다양한 learning rate schedule의 곡선을 비교하며 하이퍼파라미터의 영향을 확인할 수 있다. --- ### 10.9 심화: 자기지도 학습과 대조 학습 *연구자가 되고 싶다면 여기서부터 읽어라.* 로보틱스 데이터는 라벨이 부족하다. 로봇이 수집하는 이미지는 수천, 수만 장이지만, 이것에 일일이 바운딩 박스나 세그멘테이션 마스크를 다는 것은 비현실적이다. 자기지도 학습(self-supervised learning)은 라벨 없이 데이터 자체에서 학습 신호를 만들어내는 방법이다. **Contrastive Learning**: 핵심 아이디어는 단순하다: 같은 이미지의 다른 augmentation은 가깝게(positive pair), 다른 이미지는 멀게(negative pair) 임베딩 공간에 배치한다. - **SimCLR**: 같은 이미지에 서로 다른 augmentation을 적용하여 positive pair를 만든다. 배치 내 다른 이미지들이 negative pair. 큰 배치 사이즈가 필요하다. - **MoCo (Momentum Contrast)**: momentum encoder와 queue를 사용해서 큰 배치 없이도 많은 negative를 확보한다. **InfoNCE Loss**: $$\mathcal{L} = -\log \frac{\exp(\text{sim}(z_i, z_j) / \tau)}{\sum_{k=1}^{2N} \mathbb{1}_{[k \neq i]} \exp(\text{sim}(z_i, z_k) / \tau)}$$ 여기서 sim은 코사인 유사도, τ는 temperature이다. 분자는 positive pair의 유사도를 크게 하고, 분모는 negative pair와 구분하도록 학습한다. **Masked Image Modeling -- MAE**: ViT 기반으로, 이미지 패치의 75%를 랜덤 마스킹하고 나머지 25%에서 마스킹된 부분을 복원하는 방식이다. NLP의 BERT가 단어를 마스킹하고 복원하는 것과 같은 원리이다. - 마스킹 비율이 75%나 되는 이유: 이미지는 텍스트보다 redundancy가 크기 때문에, 높은 마스킹 비율이 더 어려운 과제를 만들어 좋은 표현을 학습시킨다. - 인코더는 visible 패치만 처리하므로 학습이 효율적이다 (75% 연산량 감소). **DINOv2와의 연결**: DINOv2는 self-distillation 방식으로 학습한다. Teacher-student 구조이되, teacher는 student의 EMA(exponential moving average)이다. - **Self-distillation**: student와 teacher가 같은 구조. teacher의 weight는 student weight의 EMA. - **Centering + Sharpening**: teacher output에 centering(평균 빼기)과 sharpening(낮은 temperature)을 적용하여 mode collapse를 방지한다. - 결과물인 DINOv2 feature는 별도 학습 없이도 ImageNet k-NN 83.0%, ADE20K linear probe 82.0% 등 supervised 방법에 필적하는 성능을 보인다. **실무 -- HuggingFace에서 self-supervised backbone fine-tune**: ```python from transformers import AutoModel, AutoImageProcessor import torch.nn as nn # DINOv2 backbone 로드 processor = AutoImageProcessor.from_pretrained("facebook/dinov2-base") backbone = AutoModel.from_pretrained("facebook/dinov2-base") # Backbone freeze 후 classification head만 학습 for param in backbone.parameters(): param.requires_grad = False class MyClassifier(nn.Module): def __init__(self, backbone, num_classes): super().__init__() self.backbone = backbone self.head = nn.Linear(768, num_classes) # DINOv2-base dim = 768 def forward(self, pixel_values): features = self.backbone(pixel_values).last_hidden_state[:, 0] # CLS token return self.head(features) ``` > **추천 자료** > - [Chen et al., "A Simple Framework for Contrastive Learning of Visual Representations (SimCLR)" (2020)](https://arxiv.org/abs/2002.05709) -- Contrastive learning의 대표작 > - [He et al., "Masked Autoencoders Are Scalable Vision Learners" (2022)](https://arxiv.org/abs/2111.06377) -- MAE 원논문 > - [Oquab et al., "DINOv2: Learning Robust Visual Features without Supervision" (2024)](https://arxiv.org/abs/2304.07193) -- DINOv2 원논문 --- ### 10.10 심화: Knowledge Distillation *연구자가 되고 싶다면 여기서부터 읽어라.* 큰 모델(teacher)의 "지식"을 작은 모델(student)에 전달하는 기법이다. 로보틱스에서 특히 중요한 이유는, VFM 같은 거대 모델을 edge device에서 돌려야 하기 때문이다. SAM을 Jetson에서 실시간으로 돌리고 싶다면, distillation이 거의 유일한 방법이다. **Teacher-Student 구조**: Teacher 모델(큰 모델, 이미 학습됨)의 출력을 student 모델(작은 모델)이 모방하도록 학습한다. Hard label(정답)보다 teacher의 soft prediction이 더 많은 정보를 담고 있다는 것이 핵심 통찰이다. 예를 들어, "고양이" 이미지에 대해 hard label은 [1, 0, 0]이지만, teacher의 soft prediction은 [0.85, 0.10, 0.05]일 수 있다. 이 soft prediction에는 "고양이와 개가 어느 정도 유사하다"는 정보가 담겨 있고, student는 이 정보까지 학습한다. **Soft Targets와 Temperature Scaling**: $$\mathcal{L}_{KD} = \text{KL}\left(\sigma\left(\frac{z_t}{\tau}\right) \| \sigma\left(\frac{z_s}{\tau}\right)\right)$$ 여기서 z_t, z_s는 각각 teacher, student의 logits이고, τ는 temperature이다. τ > 1이면 probability distribution이 더 "부드러워"져서 클래스 간 관계 정보가 더 많이 전달된다. 일반적으로 τ = 3~5를 사용한다. 전체 loss는 hard label loss와 distillation loss의 가중 합이다: $$\mathcal{L} = \alpha \cdot \mathcal{L}_{CE}(y, \sigma(z_s)) + (1 - \alpha) \cdot \tau^2 \cdot \mathcal{L}_{KD}$$ τ^2를 곱하는 이유: temperature scaling 때문에 gradient 크기가 1/τ^2로 줄어드는 것을 보상한다. **Feature-based Distillation (FitNets)**: Logit뿐 아니라 중간 layer의 feature map도 teacher와 유사하게 만든다. $$\mathcal{L}_{feat} = \|f_t(x) - r(f_s(x))\|^2$$ 여기서 r은 student feature 차원을 teacher 차원에 맞추는 projection layer이다. Logit distillation만으로는 전달하기 어려운 중간 표현까지 학습시킬 수 있다. **VFM 경량화 응용**: | Teacher | Student | 방법 | |---------|---------|------| | SAM (ViT-H) | MobileSAM | Image encoder를 경량 ViT로 교체, distillation | | SAM (ViT-H) | FastSAM | YOLO 아키텍처로 전체 파이프라인 대체 | | DINOv2-giant | DINOv2-small | 같은 구조의 작은 버전으로 distillation | ```python import torch import torch.nn.functional as F def distillation_loss(student_logits, teacher_logits, labels, temperature=4.0, alpha=0.5): # Soft target loss (KL divergence) soft_loss = F.kl_div( F.log_softmax(student_logits / temperature, dim=-1), F.softmax(teacher_logits / temperature, dim=-1), reduction="batchmean" ) * (temperature ** 2) # Hard target loss hard_loss = F.cross_entropy(student_logits, labels) return alpha * hard_loss + (1 - alpha) * soft_loss ``` > **추천 자료** > - [Hinton et al., "Distilling the Knowledge in a Neural Network" (2015)](https://arxiv.org/abs/1503.02531) -- Knowledge distillation 원논문 > - [Zhang et al., "Faster Segment Anything (MobileSAM)" (2023)](https://arxiv.org/abs/2306.14289) -- SAM distillation 사례 > - [Romero et al., "FitNets: Hints for Thin Deep Nets" (2015)](https://arxiv.org/abs/1412.6550) -- Feature-based distillation 원논문 --- ### 10.11 심화: 도메인 적응 *연구자가 되고 싶다면 여기서부터 읽어라.* 시뮬레이션에서 학습한 모델을 실제 로봇에 배포하면, 성능이 급격히 떨어진다. 실내 데이터로 학습해서 실외에 배포해도 마찬가지이다. 이 문제를 **domain shift**라 하고, 이를 해결하는 연구가 domain adaptation이다. 로보틱스에서는 sim-to-real gap 문제와 직결된다. **문제 정의**: - Source domain D_s (라벨 있음): 시뮬레이션 데이터 또는 기존 데이터셋 - Target domain D_t (라벨 없음 또는 소량): 실제 배포 환경 - 목표: D_s에서 학습한 모델이 D_t에서도 잘 작동하도록 한다. **Domain Randomization**: 가장 단순하지만 효과적인 접근법이다. 시뮬레이터에서 학습 데이터를 생성할 때, 환경 파라미터를 극단적으로 랜덤화한다. - 텍스처: 벽, 바닥, 물체의 텍스처를 매 에피소드마다 랜덤 변경 - 조명: 위치, 색상, 강도를 랜덤 변경 - 카메라 파라미터: focal length, 위치, 각도에 noise 추가 - 물리 파라미터: 마찰 계수, 질량, 관성 등을 범위 내에서 랜덤 설정 아이디어: 충분히 다양한 시뮬레이션 환경을 보면, 실제 환경도 "또 하나의 변종"으로 처리될 수 있다. **Adversarial Domain Adaptation**: Domain discriminator를 도입하여, feature extractor가 source와 target을 구분할 수 없는 domain-invariant feature를 학습하도록 만든다. ``` Input --> Feature Extractor --> [Task Classifier] --> Task Loss \--> [Domain Discriminator] --> Domain Loss (GRL) ``` - Gradient Reversal Layer (GRL): domain discriminator의 gradient를 반전시켜서, feature extractor가 domain을 구분하지 못하는 방향으로 학습한다. - Task classifier는 source domain에서 정상적으로 학습한다. - 결과적으로 feature extractor는 task에 유용하면서도 domain에 불변인 표현을 학습한다. $$\mathcal{L} = \mathcal{L}_{task}(D_s) - \lambda \cdot \mathcal{L}_{domain}(D_s, D_t)$$ 마이너스 부호가 핵심이다. Domain loss를 "최대화"하는 방향으로 feature extractor를 학습시킨다 (GAN과 유사한 적대적 학습). **Test-Time Adaptation (TTA)**: 배포 후에도 모델이 새로운 환경에 적응하는 방법이다. 학습 데이터에 접근하지 않고, 추론 시 들어오는 데이터만으로 모델을 조정한다. - **TENT**: batch normalization의 affine parameter를 entropy minimization으로 조정한다. - **CoTTA**: continual TTA. 시간에 따라 distribution이 변하는 경우에도 적응한다. ```python # TENT 핵심 아이디어 (간략화) model.eval() # BN의 affine parameter만 학습 가능하게 for m in model.modules(): if isinstance(m, nn.BatchNorm2d): m.requires_grad_(True) m.track_running_stats = False # batch statistics 사용 optimizer = optim.SGD(model.parameters(), lr=1e-4) # 추론 시 adaptation for batch in test_loader: output = model(batch) loss = entropy(output) # prediction entropy 최소화 loss.backward() optimizer.step() optimizer.zero_grad() ``` **Sim-to-Real Gap과의 연결**: 실제 로보틱스에서는 이 기법들을 조합한다: 1. 시뮬레이터에서 domain randomization으로 다양한 데이터를 생성한다. 2. 실제 환경의 소량 데이터로 adversarial adaptation을 수행한다. 3. 배포 후 TTA로 환경 변화에 지속 적응한다. 이 파이프라인이 현재 sim-to-real transfer의 표준 접근법이다. > **추천 자료** > - [Tobin et al., "Domain Randomization for Transferring Deep Neural Networks from Simulation to the Real World" (2017)](https://arxiv.org/abs/1703.06907) -- Domain randomization 원논문 > - [Ganin et al., "Domain-Adversarial Training of Neural Networks" (2016)](https://arxiv.org/abs/1505.07818) -- Adversarial domain adaptation 원논문 (GRL 제안) > - [Wang et al., "TENT: Fully Test-Time Adaptation by Entropy Minimization" (2021)](https://arxiv.org/abs/2006.10726) -- TTA 대표작 > - [Wen et al., "FoundationPose: Unified 6D Pose Estimation and Tracking of Novel Objects" (CVPR 2024, arXiv:2312.08344)](https://arxiv.org/abs/2312.08344) — 새로운 물체의 6D pose 추정. CAD 모델 또는 참조 이미지 몇 장으로 동작 --- > **기술 흐름: 딥러닝 기반 인식 (Deep Learning for Perception)** > - **2012**: AlexNet이 ImageNet 대회에서 기존 방법을 큰 차이로 이기며 우승. "딥러닝 혁명"의 시작. hand-crafted feature의 시대가 끝나기 시작 > - **2014~2015**: VGGNet, GoogLeNet, ResNet 등장. 특히 ResNet (2015)의 residual connection은 수백 층의 네트워크를 학습 가능하게 만듬. 이 시기에 Faster R-CNN (2015), YOLO (2016)로 실시간 object detection이 가능해짐 > - **2017**: "Attention Is All You Need" — Transformer 발표. 원래 NLP용이었지만, 이후 비전까지 확장 > - **2020~2021**: ViT (Vision Transformer) 등장. 이미지를 패치 시퀀스로 처리하는 새 패러다임. DETR로 detection에도 Transformer 적용. Swin Transformer가 다양한 비전 태스크에서 SOTA 달성 > - **2022~**: ConvNeXt가 "CNN도 아직 죽지 않았다"를 보여줌. Segment Anything(SAM)이 segmentation을 foundation model로 끌어올림. Spatial AI로의 확장 — depth estimation, 3D scene understanding이 딥러닝의 다음 전장 > - **지금 주목할 것**: 단일 태스크 모델에서 foundation model로의 전환. DINOv2 하나로 detection, segmentation, depth feature를 모두 추출하는 파이프라인이 실험적으로 등장하고 있다 --- ## 11. Vision Foundation Models (VFM) 이 챕터는 컴퓨터 비전의 가장 최신 패러다임인 Foundation Model을 다룬다. 앞서 배운 딥러닝 모델들이 "특정 데이터셋에서 특정 태스크를 잘 하도록 학습"된 specialist였다면, foundation model은 "거대한 데이터로 범용적 시각 능력을 학습"한 generalist이다. 이 차이를 이해하면, 2023년 이후 ICRA/IROS에서 VFM 기반 인식 논문이 빠르게 늘어난 이유를 알 수 있다. --- ### 11.1 Foundation Model이란? **Foundation Model**은 대규모 데이터로 사전학습되어 다양한 downstream task에 적용 가능한 모델이다. 기존 접근을 생각해 보자. "새로운 환경 → 데이터 수집 → 라벨링 → 학습"의 사이클을 반복해야 했다. 공장이 바뀌면, 로봇이 인식해야 할 물체가 바뀌면, 처음부터 다시 해야 했다. Foundation model은 이 사이클을 끊는다. 한 번 학습된 모델이 **본 적 없는 물체, 본 적 없는 환경**에서도 작동한다. 로봇의 범용성(generalization) 문제를 푸는 가장 유력한 접근이다. **특징**: - **Scale**: 수억~수십억 파라미터 - **Pretraining**: 대규모 데이터 (수억 이미지) - **Zero-shot / Few-shot**: 학습 없이 또는 적은 예제로 새 태스크 수행 - **Transfer**: 다양한 도메인으로 전이 **왜 중요한가?** - 새로운 환경에서도 일반화 능력 - 특정 데이터셋에 대한 annotation 없이도 사용 가능 - 연구실의 Global Module에서 핵심 역할 여기서 "Scale Law"라는 개념이 중요하다. 모델 크기, 데이터 크기, 연산량을 키우면 성능이 power law로 향상된다는 경험적 법칙이다. GPT, CLIP, SAM 등 최근의 대형 모델들이 모두 이 법칙을 활용한다. 즉, "더 크면 더 좋다"가 일정 범위에서 성립한다 (Kaplan et al., 2020; Zhai et al., 2022 for ViT). 단, Hoffmann et al. (2022, Chinchilla)이 보여줬듯 모델 크기만 키우는 것보다 데이터와 연산량의 균형이 더 중요하다. > **추천 자료** > - [Bommasani et al., "On the Opportunities and Risks of Foundation Models" (2021)](https://arxiv.org/abs/2108.07258) — Foundation Model이라는 용어를 정의한 Stanford 보고서 > - [Two Minute Papers — Foundation Models 관련 영상들](https://www.youtube.com/@TwoMinutePapers) — 최신 VFM 연구를 빠르게 따라잡기 > - [HuggingFace Model Hub](https://huggingface.co/models) — 수천 개의 사전학습 모델을 바로 사용 가능 --- ### 11.2 주요 VFM 이 섹션에서는 로보틱스에서 가장 많이 활용되는 Vision Foundation Model들을 다룬다. 각 모델이 어떤 문제를 풀고, 왜 로보틱스에서 중요하며, 어떻게 사용하는지를 중심으로 살펴보자. ### 11.2.1 DINOv2 **Self-Supervised Vision Transformer**로, 라벨 없이 이미지에서 풍부한 특징을 학습한다. DINOv2는 "라벨 없이도" 범용적인 시각 특징을 학습한다. 이 특징은 분류, 분할, 매칭 등 다양한 태스크에 그대로 사용할 수 있다. 특히 로보틱스에서는 DINOv2의 dense feature가 텍스처 없는 영역에서도 안정적인 매칭을 제공하여, SLAM이나 Visual Odometry에서 textureless 환경의 tracking failure rate를 줄이는 데 쓰이고 있다. **특징**: - Contrastive learning + Self-distillation - 다양한 태스크에서 우수한 전이 성능 - Dense visual features 제공 **활용**: - Image retrieval - Semantic segmentation (linear probe) - Feature matching for SLAM/VO - 3D reconstruction의 feature backbone ```python import torch from transformers import AutoModel, AutoImageProcessor processor = AutoImageProcessor.from_pretrained('facebook/dinov2-base') model = AutoModel.from_pretrained('facebook/dinov2-base') inputs = processor(images=image, return_tensors="pt") outputs = model(**inputs) features = outputs.last_hidden_state # (1, num_patches+1, 768) ``` `last_hidden_state`에서 첫 번째 토큰은 [CLS] 토큰으로 이미지 전체의 요약이고, 나머지는 각 패치의 feature이다. [CLS]는 분류에, 패치 features는 dense prediction (segmentation, matching 등)에 쓰인다. > **추천 자료** > - [Oquab et al., "DINOv2: Learning Robust Visual Features without Supervision" (2023)](https://arxiv.org/abs/2304.07193) — DINOv2 원논문 > - [DINOv2 GitHub](https://github.com/facebookresearch/dinov2) — 공식 코드 및 사전학습 모델 > - [HuggingFace — DINOv2](https://huggingface.co/docs/transformers/model_doc/dinov2) — HuggingFace에서 바로 사용 > - [Yannic Kilcher — DINO 설명](https://www.youtube.com/watch?v=h3ij3F3cPIk) — Self-distillation의 핵심 아이디어를 설명 (DINOv1 기반이지만 DINOv2 이해에 필수) ### 11.2.2 SAM (Segment Anything Model) **Promptable Segmentation**: 점, 박스, 텍스트 등의 프롬프트로 어떤 객체든 분할한다. 기존 segmentation 모델은 학습에 사용된 클래스만 분할할 수 있었다. "의자, 테이블, 사람"으로 학습하면 "컵"은 분할 못 한다. SAM은 1.1B개의 마스크로 학습되어, **본 적 없는 어떤 물체든** 분할할 수 있다. 로봇이 새로운 환경에서 처음 보는 물체를 조작해야 할 때, SAM 이후로 segmentation 접근 방식이 달라졌다. **구성**: - Image Encoder: ViT로 이미지 임베딩 - Prompt Encoder: 포인트, 박스, 마스크 등 - Mask Decoder: 경량 디코더로 마스크 생성 **SAM2**: 비디오 지원, 더 빠른 속도 SAM2는 단일 이미지 뿐 아니라 비디오에서도 동작한다. 첫 프레임에서 포인트/박스로 물체를 지정하면, 이후 프레임에서 자동으로 추적하며 분할한다. 이는 로봇이 실시간으로 물체를 추적하며 조작하는 시나리오에 직접 적용할 수 있다. ```python from segment_anything import sam_model_registry, SamPredictor sam = sam_model_registry["vit_h"](checkpoint="sam_vit_h.pth") predictor = SamPredictor(sam) predictor.set_image(image) masks, scores, logits = predictor.predict( point_coords=np.array([[500, 375]]), point_labels=np.array([1]), # 1: foreground multimask_output=True, ) ``` 팁: `multimask_output=True`로 하면 3개의 마스크 후보가 나온다 (전체 물체, 부분, 더 작은 부분). `scores`로 가장 적합한 마스크를 선택하면 된다. > **추천 자료** > - [Kirillov et al., "Segment Anything" (2023)](https://arxiv.org/abs/2304.02643) — SAM 원논문 > - [Ravi et al., "SAM 2: Segment Anything in Images and Videos" (2024)](https://arxiv.org/abs/2408.00714) — SAM2 원논문. 비디오 segmentation으로 확장 > - [Segment Anything GitHub](https://github.com/facebookresearch/segment-anything) — 공식 코드 > - [Two Minute Papers — Segment Anything](https://www.youtube.com/watch?v=sEkgJMj5DjM) — SAM의 임팩트를 빠르게 이해 > - [HuggingFace — SAM](https://huggingface.co/docs/transformers/model_doc/sam) — HuggingFace에서 바로 사용 > **실습**: [SAM2 Interactive Segmentation](https://alexjunholee.github.io/robotics-practice/app.html#hf_sam) > SAM2 모델을 직접 사용하여 이미지에서 프롬프트 기반 세그멘테이션을 체험할 수 있다 (HuggingFace Space). ### 11.2.3 CLIP **Vision-Language Model**: 이미지와 텍스트를 공유 임베딩 공간에 매핑한다. CLIP 이전에는 "이미지를 분류하려면 미리 정한 클래스 목록이 필요"했다. CLIP은 이미지와 텍스트를 같은 공간에 놓으므로, **임의의 텍스트로** 이미지를 검색/분류할 수 있다. "red mug on a wooden table" 같은 자연어로 로봇에게 목표 물체를 지시할 수 있게 된 것이다. open-vocabulary의 시작이고, 로봇이 자연어를 이해하는 기반이 된다. **학습**: 4억 쌍의 이미지-텍스트 데이터로 contrastive learning **활용**: - Zero-shot image classification - Image-text retrieval - Open-vocabulary detection의 기반 ```python import clip import torch model, preprocess = clip.load("ViT-B/32", device="cuda") image = preprocess(Image.open("image.jpg")).unsqueeze(0).to("cuda") text = clip.tokenize(["a dog", "a cat", "a car"]).to("cuda") with torch.no_grad(): image_features = model.encode_image(image) text_features = model.encode_text(text) similarity = (image_features @ text_features.T).softmax(dim=-1) print(similarity) # 각 텍스트와의 유사도 ``` 여기서 `@`는 행렬 곱(dot product)이다. 이미지 feature와 텍스트 feature의 코사인 유사도를 계산하는 것인데, "이미지와 각 텍스트가 얼마나 의미적으로 가까운지"를 수치로 나타낸다. 이것이 zero-shot classification의 원리이다. > **추천 자료** > - [Radford et al., "Learning Transferable Visual Models From Natural Language Supervision" (2021)](https://arxiv.org/abs/2103.00020) — CLIP 원논문 > - [OpenAI CLIP GitHub](https://github.com/openai/CLIP) — 공식 코드 및 사전학습 모델 > - [Yannic Kilcher — CLIP 설명](https://www.youtube.com/watch?v=T9XSU0pKX2E) — CLIP의 아이디어를 잘 풀어서 설명 > - [HuggingFace — CLIP](https://huggingface.co/docs/transformers/model_doc/clip) — HuggingFace에서 다양한 CLIP 변형 사용 ### 11.2.4 Depth Anything **Monocular Depth Foundation Model**: 단일 이미지에서 상대적 깊이를 추정한다. 10.7에서 depth estimation을 다뤘지만, Depth Anything은 그것을 foundation model 수준으로 끌어올린 모델이다. 1.5M 라벨 데이터에 62M 비라벨 데이터까지 활용해서, 실내(NYU), 실외(KITTI), zero-shot 도메인에서 안정적으로 깊이를 추정한다. 단, 학습 데이터와 크게 다른 도메인(내시경, 수중 등)에서는 정확도가 떨어질 수 있다. 로봇이 새로운 환경에 배치될 때 추가 학습 없이 바로 깊이 정보를 얻을 수 있다. **특징**: - 1.5M 라벨 데이터 + 62M 비라벨 데이터 학습 - 다양한 도메인에서 강건 - V2: 더 정확한 절대 깊이 Depth Anything V2는 V1에서 한 걸음 더 나아가, metric depth (절대 깊이)를 추정할 수 있는 버전도 제공한다. 로보틱스에서는 상대 깊이보다 "정확히 몇 미터 떨어져 있는가"가 중요한 경우가 많으므로, V2의 metric 버전에 주목하자. > **추천 자료** > - [Yang et al., "Depth Anything: Unleashing the Power of Large-Scale Unlabeled Data" (2024)](https://arxiv.org/abs/2401.10891) — Depth Anything 원논문 > - [Yang et al., "Depth Anything V2" (2024)](https://arxiv.org/abs/2406.09414) — V2 원논문. Metric depth 지원 > - [Depth Anything GitHub](https://github.com/LiheYoung/Depth-Anything) — 공식 코드 > - [HuggingFace — Depth Anything](https://huggingface.co/docs/transformers/model_doc/depth_anything) — HuggingFace에서 바로 사용 > **실습**: [Depth Anything V2](https://alexjunholee.github.io/robotics-practice/app.html#hf_depth) > Depth Anything V2 모델로 이미지에서 깊이를 추정하는 과정을 직접 체험할 수 있다 (HuggingFace Space). ### 11.2.5 GroundingDINO **Open-Vocabulary Object Detection**: 텍스트 프롬프트로 임의의 객체를 탐지한다. 기존 detection 모델(YOLO, Faster R-CNN 등)은 학습에 사용된 클래스만 탐지할 수 있었다. "person, car, dog"으로 학습하면 "coffee mug"은 못 찾는다. GroundingDINO는 CLIP처럼 텍스트로 어떤 물체든 지정해서 탐지할 수 있다. 로봇에게 "저기 있는 빨간 컵 찾아"라고 자연어로 지시하면, 학습 없이 바로 찾을 수 있다는 뜻이다. ``` 입력: 이미지 + "person. car. traffic light." 출력: 해당 객체들의 bounding box ``` **Grounded-SAM**: GroundingDINO + SAM 결합 → 텍스트 프롬프트로 객체 탐지 + 세그멘테이션 Grounded-SAM은 로보틱스에서 실용적인 조합이다. "red cup"이라고 텍스트를 넣으면 GroundingDINO가 bounding box를 찾고, SAM이 그 안에서 정확한 마스크를 생성한다. 별도 학습 없이도 임의의 물체를 탐지하고 분할할 수 있어서, manipulation 파이프라인에서 활발히 사용되고 있다. > **추천 자료** > - [Liu et al., "Grounding DINO: Marrying DINO with Grounded Pre-Training for Open-Set Object Detection" (2023)](https://arxiv.org/abs/2303.05499) — GroundingDINO 원논문 > - [Grounded-SAM GitHub](https://github.com/IDEA-Research/Grounded-Segment-Anything) — 텍스트 기반 탐지+분할 파이프라인 > - [HuggingFace — Grounding DINO](https://huggingface.co/docs/transformers/model_doc/grounding-dino) — HuggingFace에서 사용 > **실습**: [Grounding DINO Demo](https://alexjunholee.github.io/robotics-practice/app.html#hf_grounding_dino) > 텍스트 프롬프트로 이미지에서 임의의 객체를 탐지하는 Open-Vocabulary Detection을 직접 체험할 수 있다 (HuggingFace Space). --- ### 11.3 VFM의 Spatial AI 응용 이 섹션은 앞서 배운 VFM들이 실제 로보틱스 시스템에서 어떻게 결합되어 사용되는지를 다룬다. 개별 모델의 능력도 중요하지만, 이것들을 조합해서 "공간을 이해하는 AI"를 만드는 것이 로보틱스의 궁극적 목표이다. **Open-vocabulary Scene Understanding**: - 사전 정의된 클래스 없이 장면 이해 - "navigate to the red chair" 같은 자연어 명령 처리 로봇이 실제 환경에서 동작하려면, 미리 정해둔 물체 목록에 의존하면 안 된다. 사람의 자연어 명령을 이해하고, 그에 해당하는 물체를 찾아서 행동해야 한다. CLIP + SAM + GroundingDINO 조합으로 이 파이프라인을 구현할 수 있다. **Zero-shot Semantic Segmentation**: - 새로운 환경에서 라벨링 없이 segmentation - CLIP + SAM 조합으로 구현 **Dense Feature for SLAM**: - DINOv2 features를 특징점 대신 사용 - 텍스처 없는 영역에서도 매칭 가능 - 최근 연구: DROID-SLAM + DINOv2 현장에서 겪는 문제와 직결된다. 고전 SLAM은 ORB, SIFT 같은 특징점에 의존하는데, 텍스처가 없는 벽면이나 바닥에서는 특징점이 잘 안 잡힌다. DINOv2의 dense feature는 시맨틱 정보를 포함하고 있어서, "하얀 벽이라도 이 부분과 저 부분은 다르다"를 구분할 수 있다. 이것이 SLAM의 robustness를 크게 높인다. **3D Scene Understanding**: - 2D VFM features를 3D로 리프팅 - Semantic NeRF, Feature 3DGS 2D에서 추출한 VFM feature를 3D 표현(NeRF, 3D Gaussian Splatting)에 심어넣으면, 3D 공간 자체에 시맨틱 정보를 담을 수 있다. 예를 들어, "이 3D 맵에서 의자는 어디에 있지?"라는 질문에 텍스트 쿼리로 답할 수 있게 된다. 이 방향의 연구가 Spatial AI에서 늘고 있다 (LERF, LangSplat, ConceptGraphs 등). > **추천 자료** > - [Kerr et al., "LERF: Language Embedded Radiance Fields" (2023)](https://arxiv.org/abs/2303.09553) — CLIP feature를 NeRF에 심는 연구. Spatial AI의 대표적 예 > - [Tschernezki et al., "Neural Feature Fusion Fields: 3D Distillation of Self-Supervised 2D Image Representations" (2022)](https://arxiv.org/abs/2209.03494) — 2D feature를 3D로 올리는 초기 연구 > - [Papers With Code — 3D Scene Understanding](https://paperswithcode.com/task/3d-scene-understanding) — 최신 연구 동향 --- ### 11.4 경량화 및 Edge 배포 로봇의 Local Module에서 사용하려면 경량화가 필요하다. VFM은 성능은 좋지만, 수억 개의 파라미터가 있어서 GPU 서버가 아니면 실시간 돌리기 어렵다. 하지만 로봇은 Jetson이나 임베디드 보드에서 30FPS로 돌려야 한다. 이 간극을 메우는 것이 경량화와 Edge 배포 기술이다. 아무리 좋은 모델도 로봇에서 실시간으로 돌릴 수 없으면 연구 논문에서만 빛나는 것이다. **경량화 기법**: | 기법 | 설명 | |------|------| | **Distillation** | 큰 모델의 지식을 작은 모델로 전이 | | **Quantization** | FP32 → INT8/INT4로 precision 감소 | | **Pruning** | 중요하지 않은 weight 제거 | 각 기법의 trade-off를 이해하는 것이 중요하다. Quantization은 모델 구조를 바꾸지 않으므로 적용이 가장 쉽고, Pruning은 실제 연산량을 줄이지만 정확도 손실이 있을 수 있고, Distillation은 아예 작은 모델을 새로 학습시키므로 가장 효과적이지만 비용이 높다. **경량 VFM**: - **FastSAM**: SAM의 경량 버전 (YOLO 기반) - **MobileSAM**: 모바일용 SAM - **EfficientViT-SAM**: 효율적인 ViT 백본 **Edge 배포 도구**: - **TensorRT**: NVIDIA GPU 최적화 - **ONNX Runtime**: 크로스 플랫폼 - **TFLite**: 모바일/임베디드 ```python # TensorRT 변환 예시 (PyTorch → ONNX → TensorRT) import torch # 1. ONNX 내보내기 torch.onnx.export(model, dummy_input, "model.onnx") # 2. TensorRT 변환 (trtexec 사용) # trtexec --onnx=model.onnx --saveEngine=model.trt --fp16 ``` 실무 팁: NVIDIA Jetson을 쓴다면 TensorRT가 거의 필수이다. FP16 변환만으로도 속도가 2-3배 빨라지면서 정확도 손실은 거의 없다. INT8까지 가면 더 빨라지지만 calibration 데이터가 필요하다. > **추천 자료** > - [NVIDIA TensorRT 문서](https://docs.nvidia.com/deeplearning/tensorrt/) — TensorRT 사용법과 최적화 가이드 > - [ONNX Runtime](https://onnxruntime.ai/) — 크로스 플랫폼 추론 최적화 > - [MobileSAM GitHub](https://github.com/ChaoningZhang/MobileSAM) — SAM의 모바일 경량화 버전 > - [FastSAM GitHub](https://github.com/CASIA-IVA-Lab/FastSAM) — YOLO 기반 SAM 경량화 > - [NVIDIA Jetson AI Courses](https://developer.nvidia.com/embedded/learn/jetson-ai-certification-programs) — 엣지 배포 실습 --- ### 11.5 심화: VFM Fine-tuning과 Adaptation *연구자가 되고 싶다면 여기서부터 읽어라.* VFM을 그대로 쓰면 zero-shot 성능이 나오지만, 특정 도메인(의료, 위성, 수중 등)에서는 성능이 떨어진다. 이때 fine-tuning이 필요한데, 수억 개 파라미터를 전부 학습시키는 것은 비용이 크다. Parameter-efficient fine-tuning (PEFT)은 모델의 극소수 파라미터만 학습하면서도 full fine-tuning에 근접한 성능을 얻는 방법이다. **Fine-tuning 전략 비교**: | 전략 | 학습 파라미터 비율 | 성능 | GPU 메모리 | 적용 난이도 | |------|-------------------|------|-----------|------------| | **Full fine-tuning** | 100% | 최고 (데이터 충분 시) | 매우 높음 | 낮음 | | **Linear probing** | <1% (head만) | 낮음 | 낮음 | 매우 낮음 | | **LoRA** | 0.1~1% | 높음 | 낮음 | 보통 | | **Adapter** | 1~5% | 높음 | 보통 | 보통 | | **Prompt tuning** | <0.1% | 보통 | 낮음 | 높음 | **LoRA (Low-Rank Adaptation)**: 핵심 아이디어: 사전학습된 weight matrix W에 low-rank update를 추가한다. $$W' = W + \Delta W = W + BA$$ 여기서 W는 d x d 행렬이고, B는 d x r, A는 r x d 행렬이다 (r << d). 원래 W의 파라미터 수 d^2 대신 2dr개만 학습한다. 예를 들어 d = 768, r = 8이면, 원래 589,824개의 파라미터 대신 12,288개만 학습한다 (약 2%). ```python from peft import LoraConfig, get_peft_model from transformers import AutoModelForImageClassification # 기본 모델 로드 model = AutoModelForImageClassification.from_pretrained( "facebook/dinov2-base", num_labels=10 ) # LoRA 설정 lora_config = LoraConfig( r=16, # rank lora_alpha=32, # scaling factor target_modules=["query", "value"], # attention의 Q, V에만 적용 lora_dropout=0.1, bias="none", ) # PEFT 모델 생성 model = get_peft_model(model, lora_config) model.print_trainable_parameters() # 출력 예: trainable params: 294,912 || all params: 86,567,178 || trainable%: 0.34% ``` **Adapter**: Transformer block 사이에 작은 bottleneck layer를 삽입한다. 원래 weight는 고정하고, adapter layer만 학습한다. ``` Input → [Frozen Attention] → [Adapter: down_proj → ReLU → up_proj] → [Frozen FFN] → Output ``` LoRA와의 차이: LoRA는 기존 weight에 합쳐지므로 추론 시 추가 비용이 없고, Adapter는 추가 layer이므로 약간의 추론 지연이 있다. **Prompt Tuning**: 입력에 학습 가능한 "가상 토큰"을 추가한다. 모델 자체는 전혀 건드리지 않고, 입력만 조작한다. - Visual Prompt Tuning (VPT): ViT의 각 layer 입력에 학습 가능한 토큰을 추가한다. - 파라미터 효율이 가장 높지만, 성능은 LoRA보다 약간 낮은 경향이 있다. **SAM을 특정 도메인에 맞추기**: SAM의 prompt encoder에 도메인 특화 자동 prompt 생성기를 붙이는 전략이 일반적이다. 1. **Grid prompt**: 이미지를 NxN 그리드로 나누고 각 교차점을 point prompt로 사용한다. 2. **학습된 prompt generator**: 이미지를 입력받아 자동으로 point/box prompt를 생성하는 경량 네트워크를 학습한다. 3. **LoRA + SAM**: image encoder에 LoRA를 적용하여 도메인 특화 feature를 학습한다. ```python # SAM + LoRA 적용 예시 (개념적) from segment_anything import sam_model_registry from peft import LoraConfig, get_peft_model sam = sam_model_registry["vit_b"](checkpoint="sam_vit_b.pth") # Image encoder에만 LoRA 적용 lora_config = LoraConfig( r=4, lora_alpha=8, target_modules=["qkv"], # SAM attention의 qkv projection ) sam.image_encoder = get_peft_model(sam.image_encoder, lora_config) # Mask decoder는 full fine-tuning (파라미터가 적으므로) for param in sam.mask_decoder.parameters(): param.requires_grad = True ``` **평가 방법론**: VFM adaptation 연구에서는 다음 프로토콜로 비교하는 것이 표준이다: | 프로토콜 | 설명 | 비교 목적 | |---------|------|----------| | **Zero-shot** | 학습 없이 바로 평가 | VFM의 기본 범용성 확인 | | **Few-shot (1/5/10-shot)** | 클래스당 소수 샘플로 학습 | 데이터 효율성 비교 | | **Full fine-tune** | 전체 학습 데이터 사용 | 상한선 확인 | | **PEFT (LoRA 등)** | 소수 파라미터로 학습 | 효율성-성능 trade-off | 비교 시 반드시 동일 backbone, 동일 데이터 split, 동일 augmentation을 사용해야 공정한 비교이다. Few-shot에서는 seed에 따라 결과 분산이 크므로 3~5회 반복 후 평균과 표준편차를 보고해야 한다. > **추천 자료** > - [Hu et al., "LoRA: Low-Rank Adaptation of Large Language Models" (2022)](https://arxiv.org/abs/2106.09685) -- LoRA 원논문 (LLM용이지만 ViT에도 그대로 적용 가능) > - [HuggingFace PEFT 라이브러리](https://github.com/huggingface/peft) -- LoRA, Adapter 등 PEFT 구현체 > - [Chen et al., "SAM Fails to Segment Anything? -- SAM-Adapter" (2023)](https://arxiv.org/abs/2304.09148) -- SAM의 도메인 adaptation 사례 --- > **기술 흐름: Vision Foundation Models** > - **2021**: CLIP (OpenAI) 발표. 이미지-텍스트 공유 임베딩으로 zero-shot 인식의 가능성을 열다. 4억 쌍의 이미지-텍스트 데이터로 학습. "open-vocabulary"의 시대가 시작됨 > - **2022**: Masked Autoencoders (MAE) 등 self-supervised 사전학습 방법이 주목받기 시작. DINO가 self-supervised ViT의 가능성을 보여줌 > - **2023**: SAM (Segment Anything Model) 발표. 11M 이미지, 1.1B 마스크로 학습. "어떤 물체든 분할"이라는 foundation model 수준의 범용성 달성. 같은 해 DINOv2 발표 — self-supervised 비전 feature의 새 기준 > - **2024**: SAM2 (비디오 segmentation 확장), Depth Anything V2 (metric depth 지원), Florence-2 (통합 비전 모델) 등 VFM이 빠르게 진화. 모델들의 경량화와 edge 배포가 활발해짐 > - **2025~**: VFM들의 3D 확장과 멀티모달 통합이 가속. 하나의 foundation model이 detection, segmentation, depth, tracking을 통합 처리하는 방향. 로보틱스에서는 VFM이 perception의 표준 백본으로 자리잡는 추세 > - **지금 주목할 것**: Foundation model의 핵심 가치는 **zero-shot 능력**이다. 새 환경, 새 물체에서도 추가 학습 없이 작동하므로 로봇의 범용성을 높인다. CLIP+SAM+DINOv2 조합은 NLMap, ConceptGraphs 등에서 open-vocabulary 로봇 인식의 대표적 파이프라인으로 쓰이고 있다. 경량화(FastSAM, MobileSAM)를 통해 실제 로봇에 올리는 것까지가 완전한 파이프라인이다 --- ## 12. Vision-Language-Action (VLA) & Embodied AI 로봇이 "빨간 컵을 집어서 테이블 위에 놓아줘"라는 자연어 명령을 받아 실행하는 것이 VLA의 목표다. 비전, 언어 모델, 제어를 하나로 합치는 분야이며, "왜 ChatGPT가 로봇을 움직이지 못하는지", "왜 시뮬레이션에서 잘 되던 정책이 실제 로봇에서 망하는지"를 이해하려면 이 챕터의 개념이 필요하다. CoRL, ICRA 2024-2025에서 VLA 관련 논문 비중이 크게 늘었다. ### 12.1 VLA 개념 기존 로봇 시스템은 "보는 것(vision)", "이해하는 것(language)", "행동하는 것(action)"이 각각 따로 놀았다. VLA는 이 세 가지를 하나의 모델로 통합해서, 사람처럼 "보고 → 이해하고 → 행동"하는 파이프라인을 만드는 것이다. **VLA (Vision-Language-Action)**는 시각, 언어, 행동을 통합하는 모델이다. ``` 입력: 이미지 + 자연어 명령 ("pick up the red cup") 출력: 로봇 행동 (관절 각도, gripper 명령 등) ``` **Embodied AI**: 물리적 환경에서 상호작용하며 학습하는 AI - 단순 인식을 넘어 **행동**까지 포함 - 시뮬레이션과 실제 환경의 간극 (Sim-to-Real) Embodied AI가 기존 AI와 다른 점은, 모델이 단순히 "이것은 컵이다"라고 분류하는 데 그치지 않고, 실제로 컵을 집어 올리는 **물리적 행동**을 수행해야 한다는 것이다. 이 과정에서 중력, 마찰, 충돌 같은 물리 법칙을 모두 고려해야 하기 때문에, 단순 이미지 분류보다 훨씬 어렵다. > **추천 자료** > - [Stanford CS25 — Transformers in Robotics](https://www.youtube.com/watch?v=X-B3nxHd5gA) — VLA 개념과 최신 연구 흐름을 잡기 좋은 세미나 > - [Google DeepMind Robotics Blog](https://deepmind.google/discover/blog/) — RT-1, RT-2, PaLM-E 등의 공식 블로그 포스트 > - [Brohan et al., "RT-2: Vision-Language-Action Models" (2023)](https://arxiv.org/abs/2307.15818) — VLA의 핵심 논문 ### 12.2 주요 모델 및 연구 ### 12.2.1 RT-1, RT-2 (Google DeepMind) RT-1과 RT-2는 "대규모 데이터로 학습한 범용 로봇 정책"이라는 개념을 처음으로 실증한 모델이다. RT-1 이전에는 로봇 하나당 하나의 태스크를 학습시키는 것이 일반적이었는데, RT-1/RT-2가 "하나의 모델로 수백 가지 태스크"를 수행할 수 있음을 보여줬다. **RT-1 (Robotics Transformer 1)**: - 대규모 로봇 데모 데이터로 학습 - 130K 에피소드, 700+ 태스크 - Tokenized action output **RT-2 (Robotics Transformer 2)**: - VLM (PaLI-X, PaLM-E)을 로봇 행동으로 파인튜닝 - Web-scale 데이터의 지식을 로봇에 전이 - "chain of thought" 추론 가능 RT-2의 핵심 통찰은 이것이다: 인터넷에서 학습한 거대 언어/비전 모델이 이미 "세상에 대한 지식"을 갖고 있으니, 이걸 로봇 행동으로 파인튜닝하면 제로샷(zero-shot)으로 새로운 물체나 상황에도 대응할 수 있다는 것이다. 예를 들어, RT-2는 학습 데이터에 없던 물체도 언어 지식을 활용해 집어 올릴 수 있다. > **추천 자료** > - [Google DeepMind — RT-2 Demo Video](https://deepmind.google/discover/blog/rt-2-new-model-translates-vision-and-language-into-action/) — RT-2의 실제 동작 영상과 설명 > - [Brohan et al., "RT-1: Robotics Transformer" (2022)](https://arxiv.org/abs/2212.06817) — RT-1 원 논문 > - [Brohan et al., "RT-2" (2023)](https://arxiv.org/abs/2307.15818) — RT-2 원 논문 ### 12.2.2 PaLM-E **Embodied Multimodal Language Model**: - PaLM (Language) + ViT (Vision) + 로봇 상태 - 562B 파라미터 - "다목적" 로봇 태스크 수행 PaLM-E가 흥미로운 이유는 "positive transfer"를 보여줬기 때문이다. 로봇 데이터, 웹 이미지, 텍스트를 함께 학습하면 오히려 각각을 따로 학습할 때보다 로봇 태스크 성능이 올라간다. 즉, 범용 지식이 로봇 행동에도 도움이 된다는 것을 실증한 것이다. > **추천 자료** > - [Driess et al., "PaLM-E: An Embodied Multimodal Language Model" (2023)](https://arxiv.org/abs/2303.03378) — PaLM-E 원 논문 ### 12.2.3 OpenVLA RT-2나 PaLM-E는 Google 내부 인프라 없이는 쓸 수 없다. OpenVLA는 오픈소스로 공개된 VLA 모델로, 실제로 연구실에서 다운로드 받아 파인튜닝하고 로봇에 올릴 수 있는 모델이다. **Open-source VLA**: - 7B 파라미터 (Llama 2 기반) - 970K 에피소드 학습 - 다양한 로봇 embodiment에 적용 가능 ```python # OpenVLA 사용 예시 (개념) from openvla import OpenVLAModel model = OpenVLAModel.from_pretrained("openvla/openvla-7b") action = model.predict( image=current_image, instruction="pick up the blue block and place it on the red target" ) ``` **RT-X 프로젝트**: OpenVLA와 함께 알아둘 것이 RT-X이다. 이것은 여러 연구 기관이 수집한 로봇 데이터를 모아 하나의 거대한 데이터셋(Open X-Embodiment)을 만들고, 이를 기반으로 범용 로봇 정책을 학습하는 프로젝트이다. 22개 이상의 로봇 유형에서 수집된 데이터가 포함되어 있다. **Octo**: RT-X 데이터로 학습된 또 다른 오픈소스 모델로, OpenVLA보다 작은 사이즈(93M 파라미터)로 더 가볍게 활용할 수 있다. 다양한 로봇 플랫폼에 빠르게 파인튜닝할 수 있도록 설계되었다. > **추천 자료** > - [OpenVLA GitHub](https://github.com/openvla/openvla) — 코드와 모델 가중치 공개 > - [Kim et al., "OpenVLA" (2024)](https://arxiv.org/abs/2406.09246) — OpenVLA 논문 > - [Open X-Embodiment Collaboration, "Open X-Embodiment" (2023)](https://arxiv.org/abs/2310.08864) — RT-X 데이터셋 논문 > - [Octo GitHub](https://github.com/octo-models/octo) — 경량 오픈소스 로봇 정책 모델 ### 12.2.4 Navigation 관련 로봇이 물체를 조작(manipulation)하는 것뿐 아니라, 환경 내에서 이동(navigation)하는 것도 Embodied AI의 핵심 과제이다. 아래 연구들은 LLM의 언어 이해 능력을 네비게이션에 활용하는 접근이다. **LINGO**: Language-guided Indoor Navigation **SayCan**: LLM이 "할 수 있는 것"과 "해야 하는 것"을 분리 - Affordance function: 로봇이 현재 할 수 있는 행동 - LLM: 목표 달성을 위해 해야 하는 행동 SayCan의 핵심 아이디어를 조금 풀어보면: LLM에게 "커피 만들어줘"라고 하면, LLM은 "1. 컵을 잡아 2. 커피 머신으로 가 3. 버튼을 눌러..."처럼 계획을 세울 수 있다. 하지만 로봇이 현재 컵 근처에 없다면 "컵을 잡아"는 실행 불가능하다. SayCan은 LLM의 계획(해야 하는 것)과 로봇의 현재 가능 행동(할 수 있는 것)을 곱해서, 실행 가능하면서도 목표에 가까운 행동을 선택한다. > **추천 자료** > - [Ahn et al., "Do As I Can, Not As I Say: Grounding Language in Robotic Affordances" (2022)](https://arxiv.org/abs/2204.01691) — SayCan 논문 > - [SayCan project page](https://say-can.github.io/) — 데모 영상 포함 ### 12.3 World Models 실제 로봇으로 수만 번의 시행착오를 하는 것은 시간과 비용 면에서 불가능에 가깝다. World Model은 로봇이 "머릿속에서 시뮬레이션"을 돌려보고, 그 결과를 바탕으로 행동을 결정할 수 있게 해준다. 자율주행에서 특히 각광받고 있는데, "앞 차가 갑자기 멈추면 어떻게 될까?"를 실제로 경험하지 않고도 예측할 수 있기 때문이다. **World Model**: 환경의 동작을 예측하는 모델 **왜 필요한가?** - 상상 속에서 계획 가능 (model-based RL) - 데이터 효율적 학습 - 안전한 탐색 **자율주행 분야**: - **GAIA-1 (Wayve)**: Video prediction + Action conditioning. 실제 주행 영상을 학습해서 "이렇게 핸들을 돌리면 이런 장면이 될 것"을 예측하는 생성 모델이다. - **DriveDreamer**: 주행 시나리오 생성. 텍스트 조건으로 다양한 시나리오를 생성해 학습 데이터를 증강하는 데 활용된다. - **MILE**: World model 기반 End-to-End driving. 미래 상태를 예측하는 implicit world model을 학습하고, 이를 기반으로 주행 정책을 도출한다. **구조** (일반적): ``` z_{t+1} = f(z_t, a_t) # Dynamics model (현재 상태 + 행동 → 다음 상태) o_t = g(z_t) # Observation model (잠재 상태 → 관측) r_t = h(z_t, a_t) # Reward model (보상 예측) ``` 선형대수 시간에 배운 상태 공간 모델(state-space model)과 비슷한 구조라는 걸 눈치챘을 것이다. x_{t+1} = Ax_t + Bu_t 가 비선형 신경망 버전으로 확장된 것이라고 보면 된다. > **추천 자료** > - [Hu et al., "GAIA-1: A Generative World Model for Autonomous Driving" (2023)](https://arxiv.org/abs/2309.17080) — Wayve의 World Model 논문 > - [Wang et al., "DriveDreamer" (2023)](https://arxiv.org/abs/2309.09777) — 주행 시나리오 생성 논문 > - [Yannic Kilcher — World Models Explained](https://www.youtube.com/watch?v=dPsXxLhZlHI) — World Model 개념 설명 영상 ### 12.4 End-to-End vs Modular 로봇 시스템을 설계할 때 가장 먼저 내려야 하는 아키텍처 결정이다. 모르면 논문을 읽을 때 "이 시스템이 왜 이렇게 설계되었는지"를 파악할 수 없다. 로봇 시스템 설계의 두 가지 철학이다. **End-to-End**: ``` 센서 입력 → [단일 신경망] → 행동 출력 ``` - 장점: 간단한 파이프라인, 중간 표현의 bottleneck 없음 - 단점: Interpretability 부족, 디버깅 어려움, 데이터 많이 필요 - 예시: NVIDIA PilotNet, Tesla FSD (추정) **자율주행에서의 End-to-End 최신 연구**: - **UniAD (Unified Autonomous Driving, 2023)**: End-to-End이면서도 내부에 detection, tracking, mapping, prediction, planning 모듈을 두어, 해석 가능성을 유지한 통합 모델이다. CVPR 2023 Best Paper. - **VAD (Vectorized Scene Representation for Efficient Autonomous Driving, 2023)**: 장면을 벡터화된 표현으로 변환하여 효율적인 End-to-End 주행 정책을 학습한다. - **GenAD (Generalized Autonomous Driving, 2024)**: 생성 모델 기반으로 다양한 주행 시나리오에 일반화 가능한 End-to-End 시스템이다. **Modular**: ``` 센서 → [인식] → [예측] → [계획] → [제어] → 행동 ``` - 장점: 각 모듈 독립적 개발/디버깅, 해석 가능 - 단점: 모듈 간 정보 손실, 최적화 어려움 - 예시: Apollo, Autoware **최근 트렌드**: 하이브리드 접근 - 인식은 학습 기반 - 계획/제어는 모델 기반 또는 제약 조건 포함 - UniAD처럼 End-to-End 프레임워크 안에 명시적 모듈을 배치하는 것이 대세 > **추천 자료** > - [Hu et al., "Planning-oriented Autonomous Driving (UniAD)" (2023)](https://arxiv.org/abs/2212.10156) — CVPR 2023 Best Paper > - [Jiang et al., "VAD" (2023)](https://arxiv.org/abs/2303.12077) — 벡터화 기반 자율주행 > - [Andrej Karpathy — Tesla AI Day 2022 Presentation](https://www.youtube.com/watch?v=ODSJsviD_SU) — End-to-End 자율주행 실무 관점 #### End-to-End vs Modular: 2026년 현실 학회에서는 end-to-end가 화제지만, 실제 배포된 로봇 시스템의 대부분은 모듈형이다. 왜 그런가? - **디버깅**: end-to-end 모델이 실패했을 때 원인을 찾기 어렵다. "왜 로봇이 컵을 놓쳤는가?"에 대해, 모듈형이면 "depth estimation이 틀렸다" 또는 "grasp planning이 잘못됐다"로 좁힐 수 있지만, end-to-end에서는 어디서 틀렸는지 모른다. - **안전 보장**: 모듈형은 각 모듈에 safety check를 넣을 수 있다 (속도 제한, 충돌 감지 등). End-to-end에서 이런 보장을 넣기 어렵다. - **부분 업데이트**: perception 모듈만 개선하고 싶을 때, 모듈형이면 해당 모듈만 교체하면 된다. End-to-end는 전체를 재학습해야 한다. - **데이터 효율**: end-to-end 학습은 대규모 데이터가 필요하다. RT-2는 130k 에피소드, OpenVLA는 970k 에피소드를 사용했다. 대부분의 연구실은 이 규모의 데이터를 수집할 여력이 없다. 현실적 방향: **하이브리드**. 인식은 VFM(foundation model)으로 범용화하고, 계획/제어는 모듈형으로 안전성을 확보하는 구조. 연구실의 Local/Global Module 설계(18장)가 이 방향이다. end-to-end가 모듈형을 완전히 대체하려면, (1) 디버깅 가능한 end-to-end, (2) safety guarantee가 가능한 학습 기반 제어, (3) 소규모 데이터로도 학습 가능한 few-shot 정책이 필요하다. 세 가지 모두 아직 해결되지 않았다. ### 12.5 Spatial AI + VLA 통합 VLA 모델이 아무리 좋아도 실시간으로 장애물을 피하지 못하면 로봇은 벽에 부딪힌다. 반대로, 장애물 회피만 잘 하는 로봇은 "커피 가져와"라는 복잡한 명령을 수행하지 못한다. 두 레벨을 통합해야 실제 로봇 시스템이 돌아간다. 연구실의 2-Module Architecture와 연결: **Local (Fast) Perception**: - Geometric understanding: 깊이, 장애물, 자세 - 실시간 반응 (10-100Hz) - Classical 또는 경량 학습 모델 **Global (Heavy) Understanding**: - Semantic understanding: 객체, 관계, 맥락 - VFM/VLA 기반 - 서버 또는 클라우드 처리 (1-10Hz) **통합 시나리오**: ``` 1. Local: 실시간 obstacle avoidance, odometry 2. Global: "kitchen에서 cup을 찾아서 table로 가져와" - VLM으로 cup 인식 - Semantic map에서 경로 계획 3. Local이 Global의 waypoint를 받아 실제 이동 수행 ``` ### 12.6 Sim-to-Real & Simulation Platforms 실제 로봇으로 데이터를 모으는 건 느리고 비싸고 위험하다. 그래서 시뮬레이션에서 먼저 학습하고 실제 로봇에 전이(transfer)하는 Sim-to-Real이 사실상 기본이 되었다. 하지만 시뮬레이션과 현실 사이에는 "Reality Gap"이 존재한다. 여기서 중요한 기법들을 알아보자. **Domain Randomization**: 시뮬레이션에서 텍스처, 조명, 물리 파라미터 등을 무작위로 변경하여 학습한다. 모델이 다양한 조건에 노출되면, 현실 환경도 그 중 하나의 변형(variation)으로 처리할 수 있게 된다. **주요 시뮬레이션 플랫폼**: - **NVIDIA Isaac Sim/Lab**: GPU 가속 물리 시뮬레이션. 수천 개의 환경을 병렬로 돌릴 수 있어 대규모 강화학습에 적합하다. Isaac Lab은 로봇 학습 연구를 위한 통합 프레임워크이다. - **AI2-THOR (Allen Institute)**: 실내 환경 시뮬레이터. 주방, 거실 등 가정 환경에서 물체 조작(manipulation)을 연습할 수 있다. Embodied AI 연구에서 가장 많이 쓰이는 플랫폼 중 하나이다. - **Habitat (Meta)**: 대규모 3D 스캔 환경(Matterport3D, Gibson 등)에서 내비게이션 학습이 가능하다. Habitat Challenge를 통해 매년 벤치마크를 제공한다. - **MuJoCo**: 접촉(contact) 물리에 강점이 있는 시뮬레이터. 로봇 팔 조작이나 보행 학습에 널리 사용된다. DeepMind가 인수 후 오픈소스로 전환했다. > **추천 자료** > - [NVIDIA Isaac Lab Documentation](https://isaac-sim.github.io/IsaacLab/) — 로봇 학습을 위한 시뮬레이션 프레임워크 > - [AI2-THOR Documentation](https://ai2thor.allenai.org/) — 실내 환경 시뮬레이터 > - [Habitat Documentation](https://aihabitat.org/) — Meta의 Embodied AI 플랫폼 > - [Tobin et al., "Domain Randomization for Transferring Deep Neural Networks from Simulation to the Real World" (2017)](https://arxiv.org/abs/1703.06907) — Domain Randomization 원 논문 ### 12.7 심화: Imitation Learning *연구자가 되고 싶다면 여기서부터 읽어라.* VLA와 Embodied AI에서 정책(policy)을 학습하는 방법은 크게 강화학습(RL)과 모방학습(IL)으로 나뉜다. 로보틱스에서는 IL이 RL보다 훨씬 자주 쓰인다. 그 이유를 이해하려면 각 접근법의 구조를 알아야 한다. **Behavioral Cloning (BC)** 가장 단순한 IL 방법이다. 전문가(사람 또는 스크립트)의 시연 데이터 `{(s_t, a_t)}`를 수집하고, 상태 `s_t`에서 행동 `a_t`를 예측하는 supervised learning을 수행한다. ``` Loss = E[ || π_θ(s_t) - a_t ||^2 ] ``` 간단하고 구현이 쉽지만 치명적인 문제가 있다: **distribution shift**. 학습 시에는 전문가의 상태 분포를 따르지만, 추론 시에는 자기 자신의 (불완전한) 행동이 다음 상태를 결정한다. 작은 오차가 누적되면서 전문가가 방문한 적 없는 상태로 빠지고, 거기서 어떻게 해야 할지 모른다. **DAgger (Dataset Aggregation)** Distribution shift를 완화하는 대표적 방법이다. 핵심 아이디어는 학습된 정책으로 데이터를 수집하되, 전문가의 라벨을 받아서 데이터셋에 추가하는 것이다. ``` 1. 초기 데이터 D = {전문가 시연}으로 정책 π_1 학습 2. for i = 1, 2, ... π_i로 rollout 수행 → 방문한 상태 {s_t} 수집 전문가에게 {s_t}에서의 행동 {a_t^*}를 질의 D = D ∪ {(s_t, a_t^*)} D로 π_{i+1} 학습 ``` 실제로는 전문가에게 매번 질의하는 것이 비싸기 때문에, human-in-the-loop 변형이나 DAgger의 근사 버전(HG-DAgger, ThriftyDAgger 등)이 쓰인다. **왜 RL보다 IL이 로보틱스에서 자주 쓰이는가?** | 기준 | RL | IL | |------|----|----| | Sample efficiency | 수백만 에피소드 필요 | 수백~수천 시연으로 충분 | | 보상 함수 | 직접 설계해야 함 (reward engineering) | 불필요 | | 안전성 | 탐색(exploration) 중 위험한 행동 가능 | 전문가 행동 모방이므로 상대적으로 안전 | | Sim-to-Real | 보상 함수의 sim-real gap도 문제 | 실제 시연 데이터를 쓰면 gap 감소 | 로보틱스에서 보상 함수를 제대로 설계하는 것은 매우 어렵다. "컵을 잡아라"의 보상을 어떻게 정의할 것인가? 컵과 그리퍼 사이의 거리? 그러면 로봇이 컵 옆에만 가서 멈출 수 있다. 잡았는지 여부? 그러면 sparse reward 문제가 생긴다. IL은 이 문제를 우회한다. > **추천 자료** > - [Ross et al., "A Reduction of Imitation Learning and Structured Prediction to No-Regret Online Learning" (2011)](https://arxiv.org/abs/1011.0686) — DAgger 원 논문 > - [Florence et al., "Implicit Behavioral Cloning" (CoRL 2021)](https://arxiv.org/abs/2109.00137) — BC의 한계를 극복하기 위한 implicit 접근 > - [Zare et al., "A Survey of Imitation Learning" (2024)](https://arxiv.org/abs/2309.15894) — IL 전반 서베이 ### 12.8 심화: Diffusion Policy *연구자가 되고 싶다면 여기서부터 읽어라.* 최근 로봇 조작(manipulation) 분야에서 가장 주목받는 정책 표현 방식이다. 핵심 아이디어는 행동 시퀀스(action trajectory)를 denoising diffusion 과정으로 생성하는 것이다. **왜 Diffusion인가?** 기존 BC는 `π_θ(s) → a`로 단일 action을 결정론적으로 예측한다. 하지만 현실에서는 같은 상태에서도 여러 가능한 행동이 있다(multi-modality). 예를 들어 테이블 위의 컵을 잡을 때 왼쪽에서 잡아도 되고 오른쪽에서 잡아도 된다. 결정론적 BC는 이 두 행동의 평균을 출력해서 둘 다 실패한다. Gaussian Mixture Model 같은 방법도 있지만, 모드 수를 미리 정해야 한다. Diffusion Policy는 이 multi-modal 분포를 자연스럽게 표현한다. **동작 원리** ``` 1. 랜덤 노이즈 a_T ~ N(0, I)에서 시작 (T = diffusion steps) 2. 현재 관측 s를 조건으로 반복적으로 denoising: a_{t-1} = denoise_θ(a_t, s, t) for t = T, T-1, ..., 1 3. 최종 a_0가 실행할 action trajectory ``` Action trajectory는 단일 action이 아니라 미래 수 스텝의 action 시퀀스 `[a_0, a_1, ..., a_H]`이다. 이 중 처음 몇 스텝만 실행하고(receding horizon), 다시 새 관측으로 다음 trajectory를 생성한다. **장점**: - Multi-modal action distribution을 명시적 가정 없이 표현 - Action sequence를 한 번에 생성하므로 temporally coherent한 행동 - 학습이 안정적 (denoising score matching은 잘 수렴함) **단점**: - Inference 시 여러 번의 denoising step이 필요하므로 느림 (10~100 steps) - 실시간 제어(>100Hz)에는 부적합할 수 있음. DDIM 같은 가속 기법이나 consistency distillation으로 완화 가능 **실무 참고**: 연속 action space에서 BC보다 일관되게 높은 성능을 보이며, 특히 접촉이 많은 manipulation 태스크(삽입, 조립 등)에서 두드러진다. > **추천 자료** > - [Chi et al., "Diffusion Policy: Visuomotor Policy Learning via Action Diffusion" (RSS 2023)](https://arxiv.org/abs/2303.04137) — Diffusion Policy 원 논문 > - [Diffusion Policy 프로젝트 페이지](https://diffusion-policy.cs.columbia.edu/) — 코드, 데모, 영상 > - [Ho et al., "Denoising Diffusion Probabilistic Models" (NeurIPS 2020)](https://arxiv.org/abs/2006.11239) — Diffusion model 기초 논문 ### 12.9 심화: Sim-to-Real Transfer *연구자가 되고 싶다면 여기서부터 읽어라.* 12.6에서 시뮬레이션 플랫폼과 Domain Randomization을 간략히 다뤘다. 여기서는 Sim-to-Real transfer의 구체적 기법들을 체계적으로 정리한다. **1. Domain Randomization (DR)** 시뮬레이션 환경의 파라미터를 학습 시마다 무작위로 변경한다. 모델이 충분히 다양한 조건에서 학습하면, 현실 환경이 그 변형 중 하나에 포함될 것이라는 가정이다. 랜덤화 대상: - **시각적(Visual)**: 텍스처, 조명 방향/세기, 카메라 위치/시야각, 배경 - **물리적(Physical)**: 마찰 계수, 관성 모멘트, 링크 질량, 관절 감쇠(damping) - **동역학(Dynamics)**: actuator 지연, 센서 노이즈, 제어 주기 DR의 한계: 랜덤화 범위를 너무 넓히면 학습 자체가 어려워지고, 너무 좁히면 현실을 커버하지 못한다. 적절한 범위를 찾는 것이 실무적으로 중요하다. **2. System Identification (SysID)** 실제 시스템의 물리 파라미터를 측정하거나 추정하여 시뮬레이터를 보정하는 방식이다. ``` 1. 실제 로봇에서 특정 trajectory를 실행하여 데이터 수집 2. 시뮬레이터의 파라미터 φ를 최적화: φ* = argmin_φ || f_sim(φ) - f_real ||^2 3. 보정된 시뮬레이터에서 정책 학습 ``` 전통적이고 효과적이지만, 모든 파라미터를 정확히 추정하기는 어렵고, 시뮬레이터가 모델링하지 않는 현상(케이블의 유연함, 접촉면의 미세 변형 등)에 대해서는 무력하다. **3. Real-to-Sim-to-Real (R2S2R)** DR과 SysID의 장점을 결합한 최근 접근이다. ``` 1. 실제 데이터를 소량 수집 2. 실제 데이터로 시뮬레이터를 교정 (SysID) 또는 차이를 모델링 3. 교정된 시뮬레이터에서 정책 학습 4. 학습된 정책을 실제 로봇에 적용 5. (반복) 실제 결과로 시뮬레이터를 다시 교정 ``` **4. Transfer 성공 여부 판단** 정량적으로 판단하는 가장 직접적인 방법: sim과 real에서 동일 태스크의 성공률을 비교한다. - **Sim 성공률 ≈ Real 성공률**: transfer 성공. 시뮬레이터가 현실을 잘 반영. - **Sim >> Real**: reality gap이 큼. DR 범위 확장 또는 SysID 보정 필요. - **Sim < Real**: 드물지만 발생. 시뮬레이터가 오히려 더 어려운 조건(보수적)으로 설정된 경우. 추가 지표로 행동 궤적(trajectory)의 유사도, 접촉력(contact force) 비교 등을 사용하기도 한다. > **추천 자료** > - [Tobin et al., "Domain Randomization for Transferring Deep Neural Networks from Simulation to the Real World" (2017)](https://arxiv.org/abs/1703.06907) — DR 원 논문 > - [Muratore et al., "Robot Learning from Randomized Simulations" (2022)](https://arxiv.org/abs/2111.00137) — DR 체계적 정리 > - [Hanna & Stone, "Grounded Action Transformation for Sim-to-Real" (AAAI 2017)](https://arxiv.org/abs/1511.07461) — transfer 방법론 > - [NVIDIA Isaac Lab Tutorials](https://isaac-sim.github.io/IsaacLab/) — 실습용 DR/SysID 파이프라인 > **추가 논문 (3D/Spatial 이해 + 벤치마크)** > - [Hong et al., "3D-LLM: Injecting the 3D World into Large Language Models" (NeurIPS 2023, arXiv:2307.12981)](https://arxiv.org/abs/2307.12981) — LLM에 3D 공간 이해 능력을 부여. 3D captioning, QA, navigation > - [Chen et al., "SpatialVLM: Endowing Vision-Language Models with Spatial Reasoning" (CVPR 2024, arXiv:2401.12168)](https://arxiv.org/abs/2401.12168) — VLM에 거리/크기 등 공간 추론 능력 추가 > - [Nasiriany et al., "RoboCasa: Large-Scale Simulation of Everyday Tasks for Generalist Robots" (RSS 2024, arXiv:2406.02523)](https://arxiv.org/abs/2406.02523) — 100개 주방 태스크, 150+ 물체 카테고리. 가정용 로봇 벤치마크 > - [Szot et al., "Habitat 3.0: A Co-Habitat for Humans, Avatars, and Robots" (ICLR 2024, arXiv:2310.13724)](https://arxiv.org/abs/2310.13724) — 사람-로봇 공존 시뮬레이션. Social navigation, 협업 태스크 > **기술 흐름: VLA & Embodied AI** > - **~2015**: 개별 태스크별 모방학습(imitation learning), 단일 물체 grasping 연구 중심 > - **2017~**: Domain Randomization을 통한 Sim-to-Real 전이 본격화, MuJoCo/PyBullet 기반 연구 > - **2020~**: 대규모 언어 모델(LLM)과 비전의 결합 시도. CLIPort, SayCan 등 언어 기반 로봇 제어 등장 > - **2022~**: RT-1, RT-2, PaLM-E 등 Foundation Model 기반 로봇 정책 등장. Open X-Embodiment 데이터셋 구축 > - **2024~**: OpenVLA, Octo 등 오픈소스 VLA 모델 공개. World Model 기반 계획(planning)이 자율주행과 조작 모두에서 주목. End-to-End 자율주행(UniAD, VAD, GenAD)이 modular 방식을 대체하기 시작 > - **지금 주목할 것**: Foundation Model을 로봇에 적용하는 연구가 2023년 이후 빠르게 늘고 있다 (RT-2, OpenVLA, Octo, pi0 등). OpenVLA/Octo처럼 오픈소스 모델을 자기 로봇에 파인튜닝할 수 있으니, 직접 실험해보는 것을 추천한다. --- ## 13. 3D 비전 (3D Vision) 로봇이 2D 이미지만으로는 "저 물체가 얼마나 멀리 있는지", "저 벽 뒤에 뭐가 있는지"를 알 수 없다. 3D 비전은 로봇에게 공간 감각을 부여하는 분야이다. 포인트 클라우드를 다루고, 3D 물체를 감지하고, 장면을 복원하는 방법을 배운다. 이걸 모르면 SLAM도, 로봇 조작(manipulation)도 제대로 이해할 수 없다. 다음 챕터(SLAM)의 기반이 되는 내용이니 꼼꼼히 봐두자. ### 13.1 Point Cloud 기초 LiDAR나 깊이 카메라에서 나오는 데이터가 바로 포인트 클라우드이다. 이미지는 픽셀 격자에 정렬된 2D 데이터인 반면, 포인트 클라우드는 3D 공간에 불규칙하게 흩어진 점들이다. 이 비정형 데이터를 어떻게 다루는지가 3D 비전의 출발점이다. **Point Cloud (포인트 클라우드)**는 3D 공간의 점들의 집합이다. 각 점은 최소한 (x, y, z) 좌표를 가지며, 추가로 색상(RGB), 반사도(intensity), 법선(normal) 등의 속성을 가질 수 있다. ### 13.1.1 데이터 구조 및 포맷 **일반적인 구조**: ``` Point: [x, y, z, r, g, b, intensity, ...] Point Cloud: N × D 행렬 (N개 점, D차원 속성) ``` 선형대수로 생각하면, 포인트 클라우드는 그냥 N×D 행렬이다. N은 수만~수백만 개의 점, D는 각 점의 속성 차원이다. 변환(회전, 이동)은 각 점에 4×4 변환 행렬을 곱하는 것과 같다. **주요 파일 포맷**: | 포맷 | 특징 | |---|---| | **PCD** | PCL 표준, ASCII/Binary | | **PLY** | 다목적, mesh도 지원 | | **LAS/LAZ** | 지리정보 표준, LAZ는 압축 | | **XYZ** | 단순 텍스트, 좌표만 | | **BIN** | KITTI 등에서 사용, 바이너리 | > **추천 자료** > - [Open3D Documentation](http://www.open3d.org/docs/release/) — 포인트 클라우드 처리의 현대적 라이브러리 > - [PCL (Point Cloud Library) Tutorials](https://pcl.readthedocs.io/projects/tutorials/en/latest/) — 포인트 클라우드 처리의 고전 라이브러리 ### 13.1.2 라이브러리 **PCL (Point Cloud Library)**: - C++ 기반, 가장 포괄적 - ROS와 통합 - 필터링, 세그멘테이션, 정합 등 ```cpp #include
#include
pcl::PointCloud
::Ptr cloud(new pcl::PointCloud
); pcl::io::loadPCDFile
("cloud.pcd", *cloud); ``` **Open3D**: - Python/C++, 현대적 API - 시각화 강점 - 딥러닝 친화적 ```python import open3d as o3d # 포인트 클라우드 읽기 pcd = o3d.io.read_point_cloud("cloud.pcd") # 시각화 o3d.visualization.draw_geometries([pcd]) # NumPy 변환 points = np.asarray(pcd.points) # (N, 3) ``` 실제로 작업할 때는 Open3D가 Python에서 바로 쓸 수 있어서 프로토타이핑에 좋고, PCL은 C++ ROS 노드를 만들 때 주로 쓴다. 처음 시작한다면 Open3D부터 시작하는 것을 추천한다. > **추천 자료** > - [Open3D Getting Started](http://www.open3d.org/docs/release/getting_started.html) — Python으로 포인트 클라우드 다루기 입문 > - [PCL Tutorials — Basic Usage](https://pcl.readthedocs.io/projects/tutorials/en/latest/#basic-usage) — C++ 기반 포인트 클라우드 처리 > - [Open3D YouTube Channel](https://www.youtube.com/@Open3D) — 시각화 및 처리 튜토리얼 ### 13.2 Point Cloud 처리 ### 13.2.1 필터링 (Filtering) 원시 포인트 클라우드는 노이즈가 많고 점의 밀도가 불균일하다. 이걸 그대로 쓰면 이후 알고리즘(정합, 세그멘테이션 등)이 느려지거나 결과가 나빠진다. 필터링은 모든 포인트 클라우드 파이프라인의 첫 번째 단계이다. **Voxel Grid Downsampling**: 공간을 격자로 나누고 각 격자 내 점들을 하나로 축소한다. ```python # Open3D voxel_pcd = pcd.voxel_down_sample(voxel_size=0.05) # 5cm 격자 ``` **Statistical Outlier Removal**: 이웃과의 거리 통계를 기반으로 이상치를 제거한다. ```python # 이상치 제거 cl, ind = pcd.remove_statistical_outlier(nb_neighbors=20, std_ratio=2.0) filtered_pcd = pcd.select_by_index(ind) ``` **Radius Outlier Removal**: 주어진 반경 내 이웃 수가 부족한 점을 제거한다. > **추천 자료** > - [Open3D — Point Cloud Filtering Tutorial](http://www.open3d.org/docs/release/tutorial/geometry/pointcloud.html) — Voxel downsampling, outlier removal 예제 > - [PCL — Filtering Tutorial](https://pcl.readthedocs.io/projects/tutorials/en/latest/passthrough.html) — PCL 기반 필터링 ### 13.2.2 Normal Estimation 각 점에서의 표면 법선 벡터를 추정한다. 많은 알고리즘의 전처리 단계이다. ICP (정합), 표면 재구성(reconstruction), 조명 계산 등 거의 모든 3D 처리에서 법선 벡터를 요구한다. 법선이 없으면 "이 점이 평면의 일부인지 모서리의 일부인지"를 알 수 없다. ```python # 법선 추정 pcd.estimate_normals( search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30) ) ``` 내부적으로는 각 점의 이웃 k개를 모아 공분산 행렬을 구하고, 그 최소 고유값에 대응하는 고유벡터가 법선이 된다. 선형대수 시간에 배운 PCA(주성분 분석)와 정확히 같은 원리이다. ### 13.2.3 Registration (정합) 두 포인트 클라우드를 정렬하는 과정이다. SLAM에서 연속 프레임을 이어 붙이거나, 여러 뷰에서 스캔한 데이터를 합칠 때 필요하다. **ICP (Iterative Closest Point)**: 1. 가장 가까운 점 쌍 찾기 2. 변환 계산 (최소자승법) 3. 변환 적용 4. 수렴할 때까지 반복 ```python # Point-to-Point ICP reg = o3d.pipelines.registration.registration_icp( source, target, max_correspondence_distance=0.05, estimation_method=o3d.pipelines.registration.TransformationEstimationPointToPoint() ) transformation = reg.transformation ``` ICP의 직관: "두 포인트 클라우드에서 가장 가까운 점 쌍을 찾고, 그 쌍들이 최대한 겹치도록 회전+이동 변환을 구한다. 한 번에 완벽하지 않으니 이걸 반복한다." 선형대수적으로는 SVD(특이값 분해)를 이용해 최적의 회전 행렬 R과 이동 벡터 t를 구하는 것이다. **Point-to-Plane ICP**: 점과 평면 거리 최소화 (더 정확) **GICP (Generalized ICP)**: 점 분포 고려 **NDT (Normal Distributions Transform)**: 공간을 셀로 나누고 각 셀의 정규분포 매칭 **Feature-based Registration**: - FPFH, SHOT 등 특징 추출 - RANSAC으로 초기 정합 - ICP로 정밀화 > **추천 자료** > - [Open3D — ICP Registration Tutorial](http://www.open3d.org/docs/release/tutorial/pipelines/icp_registration.html) — ICP 실습 코드 > - [Open3D — Global Registration (RANSAC + Feature)](http://www.open3d.org/docs/release/tutorial/pipelines/global_registration.html) — Feature 기반 정합 > - [Cyrill Stachniss — ICP Explained](https://www.youtube.com/watch?v=QWDM4cFdKFk) — ICP 알고리즘의 직관적 설명 > **실습**: [ICP 2D 단계별 시각화](https://alexjunholee.github.io/robotics-practice/app.html#icp_steps) | [ICP 3D](https://alexjunholee.github.io/robotics-practice/app.html#icp_3d) > ICP 알고리즘이 두 포인트 클라우드를 정합하는 과정을 iteration별로 확인하고, 2D와 3D 환경에서 수렴 과정을 비교할 수 있다. ### 13.3 3D Object Detection 포인트 클라우드에서 3D bounding box를 예측한다. 자율주행에서 "저 차가 어디 있고 얼마나 큰지"를 아는 핵심 기술이다. ### 13.3.1 Point-based Methods PointNet이 열어준 문은 크다. 포인트 클라우드를 복셀이나 이미지로 변환하지 않고 **생(raw) 포인트에 직접** 딥러닝을 적용했기 때문이다. 이전에는 "불규칙한 점들에 어떻게 CNN을 쓰지?"라는 질문에 답이 없었는데, PointNet이 그 문을 열었다. **PointNet (2017)**: - Raw 포인트에 직접 적용 - Permutation invariant (점 순서 무관) - Max pooling으로 global feature **PointNet++ (2017)**: - Hierarchical feature learning - Set Abstraction: 영역별 특징 추출 - 로컬 패턴 학습 가능 ```python # PointNet++ 개념 구조 # 1. Sampling: FPS로 중심점 선택 # 2. Grouping: Ball query로 이웃 수집 # 3. PointNet: 각 그룹에서 특징 추출 ``` PointNet의 핵심 아이디어: 포인트 클라우드의 점 순서가 바뀌어도 결과가 같아야 한다(permutation invariance). 이를 위해 각 점을 독립적으로 MLP에 통과시킨 뒤 max pooling으로 집계한다. 수학적으로 f({x1, ..., xn}) = g(MAX(h(x1), ..., h(xn))) 형태이다. ### 13.3.2 Voxel-based Methods **VoxelNet (2018)**: - 포인트 클라우드를 3D 복셀로 변환 - Voxel Feature Encoding - 3D CNN으로 처리 **SECOND (Sparsely Embedded Convolutional Detection)**: - Sparse Convolution 사용 - VoxelNet 대비 훨씬 빠름 - 널리 사용되는 베이스라인 **PointPillars (2019)**: - Pillar (수직 기둥) 단위 처리 - 2D CNN으로 변환하여 빠른 속도 - 실시간 가능 PointPillars의 아이디어가 영리한 이유: 3D 공간을 수직 기둥(pillar)으로 나누면, 각 pillar 안의 점들을 하나의 특징 벡터로 압축한 뒤, 이를 2D 이미지처럼 배열할 수 있다. 그러면 잘 검증된 2D CNN을 그대로 쓸 수 있어서, 3D CNN보다 훨씬 빠르다. ### 13.3.3 Multi-modal Methods 여러 센서를 결합하면 각 센서의 단점을 보완할 수 있다. 카메라는 색상과 텍스처 정보가 풍부하지만 깊이가 없고, LiDAR는 정확한 3D 정보가 있지만 텍스처가 없다. 이 둘을 어떻게 합치느냐가 관건이다. **BEVFusion**: - Camera + LiDAR 융합 - Bird's Eye View (BEV) 공간에서 통합 **TransFusion**: - Transformer 기반 융합 - Query 기반 detection > **추천 자료** > - [Qi et al., "PointNet: Deep Learning on Point Sets" (2017)](https://arxiv.org/abs/1612.00593) — 3D 딥러닝의 시작점 > - [Lang et al., "PointPillars" (2019)](https://arxiv.org/abs/1812.05784) — 실시간 3D 감지 > - [Liu et al., "BEVFusion" (2023)](https://arxiv.org/abs/2205.13542) — 멀티모달 융합의 대표작 > - [MMDetection3D GitHub](https://github.com/open-mmlab/mmdetection3d) — 3D Object Detection 통합 프레임워크 > **실습**: [BEV Projection 시각화](https://alexjunholee.github.io/robotics-practice/app.html#bev_projection) > 카메라 이미지를 Bird's Eye View로 변환하는 과정을 인터랙티브하게 확인하며, BEV 기반 3D 감지의 원리를 이해할 수 있다. ### 13.4 3D Reconstruction 여러 뷰 또는 깊이 정보로부터 3D 모델을 생성한다. 로봇이 환경을 3D로 "기억"하려면 이 기술이 필요하다. ### 13.4.1 Structure from Motion (SfM) 여러 장의 2D 사진만으로 3D 구조를 복원할 수 있다. 스마트폰 사진 몇 장으로 건물의 3D 모델을 만들 수 있다는 얘기이다. NeRF나 3D Gaussian Splatting의 입력 데이터(카메라 포즈)를 만드는 전처리 단계이기도 하다. 여러 이미지에서 카메라 포즈와 3D 구조를 동시에 복원한다. **파이프라인**: 1. 특징점 추출 및 매칭 2. 초기 두 뷰로 삼각측량 3. 점진적 카메라 추가 4. Bundle Adjustment (BA) Bundle Adjustment는 "모든 카메라 포즈와 3D 점 위치를 동시에 최적화"하는 것이다. 비선형 최소자승법(Levenberg-Marquardt 등)을 사용하며, 변수가 수만~수십만 개가 될 수 있다. 선형대수에서 배운 최소자승법의 대규모 비선형 확장이라고 보면 된다. **도구**: - **COLMAP**: 가장 많이 사용, GUI/CLI - **OpenMVG**: 라이브러리 형태 ```bash # COLMAP 사용 colmap feature_extractor --database_path db.db --image_path ./images colmap exhaustive_matcher --database_path db.db colmap mapper --database_path db.db --image_path ./images --output_path ./sparse ``` > **추천 자료** > - [COLMAP Documentation](https://colmap.github.io/) — SfM/MVS의 사실상 표준 도구 > - [Daniel Cremers — Multiple View Geometry (TUM)](https://www.youtube.com/playlist?list=PLTBdjV_4f-EJn6udZ34tht9EVIW7lbeo4) — 다중 뷰 기하학 핵심 강의 > - [Schönberger & Frahm, "Structure-from-Motion Revisited" (2016)](https://openaccess.thecvf.com/content_cvpr_2016/papers/Schonberger_Structure-From-Motion_Revisited_CVPR_2016_paper.pdf) — COLMAP 논문 ### 13.4.2 Multi-View Stereo (MVS) SfM 결과를 기반으로 dense 포인트 클라우드 생성 SfM이 "카메라가 어디 있었는지"와 "sparse한 3D 점들"을 복원한다면, MVS는 그 카메라 포즈를 이용해 **조밀한(dense)** 3D 포인트 클라우드를 만든다. SfM → MVS → Mesh 생성이 전형적인 3D 복원 파이프라인이다. **도구**: COLMAP (dense reconstruction), OpenMVS ### 13.4.3 Volumetric Reconstruction **TSDF (Truncated Signed Distance Function)**: - 공간을 복셀로 나누고 각 복셀에 표면까지의 거리 저장 - 여러 뷰 통합 - Marching Cubes로 mesh 추출 TSDF의 핵심 아이디어: 각 복셀에 "가장 가까운 표면까지의 부호 있는 거리(signed distance)"를 저장한다. 양수면 표면 바깥, 음수면 표면 안쪽이다. 여러 뷰에서 관측한 깊이를 가중 평균하면, 노이즈가 줄어들고 깨끗한 표면을 얻는다. 부호가 바뀌는 지점(0을 지나는 곳)이 바로 표면이다. ```python # Open3D TSDF Integration volume = o3d.pipelines.integration.ScalableTSDFVolume( voxel_length=0.01, sdf_trunc=0.04, color_type=o3d.pipelines.integration.TSDFVolumeColorType.RGB8 ) for i, (color, depth, pose) in enumerate(frames): rgbd = o3d.geometry.RGBDImage.create_from_color_and_depth(color, depth) volume.integrate(rgbd, intrinsic, np.linalg.inv(pose)) mesh = volume.extract_triangle_mesh() ``` > **추천 자료** > - [Open3D — TSDF Integration Tutorial](http://www.open3d.org/docs/release/tutorial/pipelines/rgbd_integration.html) — TSDF 실습 코드 > - [Curless & Levoy, "A Volumetric Method for Building Complex Models from Range Images" (1996)](https://graphics.stanford.edu/papers/volrange/volrange.pdf) — TSDF 원 논문 (고전이지만 읽어볼 가치가 있다) ### 13.5 Neural Rendering 딥러닝을 이용한 새로운 3D 표현 및 렌더링 방식이다. 기존 방법(mesh, point cloud)은 복잡한 장면(반사, 투명체, 가는 구조)을 표현하는 데 한계가 있었다. Neural Rendering은 장면을 "학습 가능한 함수"로 표현해서, 이런 복잡한 효과를 자연스럽게 처리한다. 최근에는 SLAM과 결합되어 실시간 매핑에도 쓰이고 있다. ### 13.5.1 NeRF (Neural Radiance Fields) **개념**: 3D 장면을 continuous 함수로 표현 ``` F: (x, y, z, θ, φ) → (r, g, b, σ) - 위치 (x, y, z)와 시점 방향 (θ, φ) - 색상 (r, g, b)과 밀도 (σ) 출력 ``` 직관적으로 설명하면: NeRF는 "3D 공간의 모든 점에 대해, 어떤 방향에서 보면 어떤 색과 밀도를 가지는지"를 신경망으로 외우는 것이다. 학습이 끝나면 어떤 카메라 위치에서든 사진을 "찍을" 수 있다(novel view synthesis). **렌더링**: 광선을 따라 색상과 밀도를 적분 (volume rendering) **장점**: - 사실적인 novel view synthesis - 반사, 투명 등 복잡한 효과 처리 **단점**: - 학습 시간 오래 걸림 - 동적 장면 어려움 **발전**: - Instant-NGP: 해시 인코딩으로 빠른 학습 (수 분) - Mip-NeRF: 안티앨리어싱 - Block-NeRF: 대규모 장면 > **추천 자료** > - [Mildenhall et al., "NeRF: Representing Scenes as Neural Radiance Fields" (2020)](https://arxiv.org/abs/2003.08934) — NeRF 원 논문 > - [NeRFStudio Documentation](https://docs.nerf.studio/) — NeRF 실험을 쉽게 할 수 있는 통합 프레임워크. NeRF를 직접 돌려보고 싶다면 여기서 시작하자. > - [Yannic Kilcher — NeRF Explained](https://www.youtube.com/watch?v=CRlN-cYFxTk) — NeRF의 핵심 아이디어를 직관적으로 설명 > - [Jon Barron — Understanding NeRF (ECCV 2022 Tutorial)](https://www.youtube.com/watch?v=HfJpQCBTqZs) — NeRF 저자 직강 ### 13.5.2 3D Gaussian Splatting (3DGS) 3DGS가 빠르게 확산된 이유는 NeRF의 치명적 단점인 느린 렌더링 속도를 해결했기 때문이다. NeRF는 한 프레임을 렌더링하는 데 수 초가 걸리지만, 3DGS는 100 FPS 이상으로 실시간 렌더링이 가능하다. 이 속도 덕분에 SLAM, 실시간 매핑 등 로봇 응용에 직접 활용할 수 있게 되었다. **개념**: 장면을 수백만 개의 3D Gaussian으로 표현 각 Gaussian: - 위치 (mean) - 공분산 (모양/크기/방향) - 색상 (Spherical Harmonics) - 불투명도 선형대수에서 배운 공분산 행렬을 떠올려보자. 3×3 공분산 행렬의 고유벡터가 타원체의 축 방향을, 고유값이 축의 길이를 결정한다. 3DGS는 이 개념을 그대로 활용해서, 각 Gaussian의 모양과 크기를 표현한다. **렌더링**: Gaussian을 이미지에 투영 (splatting) **장점**: - NeRF 대비 **실시간 렌더링** (100+ FPS) - 빠른 학습 (수 분) - 명시적 표현 (편집 용이) **응용**: - SLAM: SplaTAM, Gaussian Splatting SLAM - Mapping: 대규모 환경 표현 - Dynamic scenes: 동적 장면 확장 ```python # 3DGS 기본 개념 (pseudo-code) # 각 Gaussian: position, covariance, color, opacity # 렌더링: 카메라 뷰로 투영하여 이미지 생성 ``` **3DGS + SLAM (최신 트렌드)**: 3D Gaussian Splatting이 SLAM과 결합되면서 "Neural SLAM"의 새로운 장이 열리고 있다. 기존 SLAM이 sparse한 포인트 맵이나 복셀 맵을 만들었다면, 3DGS-SLAM은 포토리얼리스틱한 3D 맵을 실시간으로 구축한다. - **SplaTAM (2024)**: RGB-D 카메라 입력으로 3DGS 기반 dense SLAM을 수행한다. Tracking(카메라 포즈 추정)과 Mapping(Gaussian 추가/업데이트)을 번갈아 수행하며, 기존 Neural SLAM 대비 렌더링 품질과 속도 모두 크게 향상시켰다. - **MonoGS (2024)**: 단안(monocular) 카메라만으로 3DGS 기반 SLAM을 수행한다. 깊이 센서 없이도 dense한 3D 맵을 구축할 수 있다는 점에서 주목받고 있다. - **Gaussian-SLAM (2024)**: 서브맵(sub-map) 기반으로 대규모 환경에서도 3DGS SLAM을 돌릴 수 있다. 이 기술의 실용적 의미: 로봇이 돌아다니면서 포토리얼리스틱한 3D 맵을 실시간으로 만들 수 있다면, AR/VR 콘텐츠 생성, 디지털 트윈, 건물 검사 등 다양한 응용이 가능해진다. > **추천 자료** > - [Kerbl et al., "3D Gaussian Splatting for Real-Time Radiance Field Rendering" (2023)](https://arxiv.org/abs/2308.14737) — 3DGS 원 논문 > - [Huang et al., "2D Gaussian Splatting for Geometrically Accurate Radiance Fields" (SIGGRAPH 2024, arXiv:2403.17888)](https://arxiv.org/abs/2403.17888) — 2D Gaussian으로 표면 복원 품질 향상 > - [Keetha et al., "SplaTAM: Splat, Track & Map 3D Gaussians for Dense RGB-D SLAM" (2024)](https://arxiv.org/abs/2312.02126) — 3DGS + SLAM의 대표작 > - [Matsuki et al., "Gaussian Splatting SLAM" (2024)](https://arxiv.org/abs/2312.06741) — MonoGS 논문 > - [Wang et al., "DUSt3R: Geometric 3D Vision Made Easy" (CVPR 2024, arXiv:2312.14132)](https://arxiv.org/abs/2312.14132) — 카메라 내부/외부 파라미터 없이 이미지 쌍에서 dense 3D 복원. 3D reconstruction 패러다임 전환 > - [Leroy et al., "MASt3R: Matching And Stereo 3D Reconstruction" (ECCV 2024, arXiv:2406.09756)](https://arxiv.org/abs/2406.09756) — DUSt3R에 local feature matching 추가. 복원 + 정밀 대응점 동시 제공 > - [NeRFStudio Documentation](https://docs.nerf.studio/) — NeRF/3DGS 실험 통합 프레임워크 > - [3DGS Original Implementation (GitHub)](https://github.com/graphdeco-inria/gaussian-splatting) — 공식 코드 > **실습**: [3D Gaussian Splatting 시각화](https://alexjunholee.github.io/robotics-practice/app.html#gaussian_splatting) > 3D Gaussian의 위치, 공분산, 색상을 조작하며 splatting 렌더링 과정을 인터랙티브하게 이해할 수 있다. ### 13.6 심화: Neural Implicit Representations *연구자가 되고 싶다면 여기서부터 읽어라.* 13.5에서 NeRF와 3DGS를 다뤘다. NeRF는 density field를 사용하여 volume rendering을 수행하지만, density에서 명확한 surface를 추출하기 어렵다는 근본적 한계가 있다. 로보틱스에서 물체를 잡거나 충돌을 판단하려면 정확한 surface가 필요하다. 여기서 Signed Distance Function (SDF) 기반 접근이 등장한다. **Signed Distance Function (SDF)** 공간의 각 점 `x`에서 가장 가까운 표면까지의 부호가 있는 거리를 반환하는 함수이다. ``` f(x) > 0 : 표면 바깥 f(x) < 0 : 표면 안쪽 f(x) = 0 : 표면 위 (zero level set) ``` SDF의 핵심 성질: gradient의 크기가 어디서나 1이다 (Eikonal equation). ``` ||∇f(x)|| = 1 ``` 이 조건을 만족하는 함수만이 올바른 거리 함수이다. Neural network로 SDF를 학습할 때 이 조건을 정규화 항(regularization)으로 추가하는데, 이를 **Eikonal loss**라 한다. ``` L_eikonal = E_x[ (||∇f_θ(x)|| - 1)^2 ] ``` **DeepSDF** SDF를 neural network로 학습하는 초기 대표 연구이다. Decoder-only architecture를 사용하며, 각 물체의 형상을 latent code `z`로 표현한다. ``` f_θ(z, x) → SDF value ``` 새로운 물체에 대해서는 test-time optimization으로 `z`를 추정한다. **NeuS** NeRF의 volume rendering 품질과 SDF의 깨끗한 surface를 결합한 연구이다. 핵심 아이디어는 SDF 값을 density로 변환하는 함수를 도입하여, volume rendering framework 안에서 SDF를 학습하는 것이다. ``` density σ(x) = max(-dΦ_s(f(x))/dt, 0) / Φ_s(f(x)) ``` 여기서 `Φ_s`는 learnable parameter `s`에 의해 제어되는 sigmoid-like 함수이다. 학습이 진행될수록 `s`가 작아지면서 density가 surface 근처에 집중된다. **VolSDF** 유사한 접근이지만 density를 SDF의 Laplace 분포 CDF로 정의한다. ``` σ(x) = (1/β) · Ψ_β(-f(x)) ``` `β`가 줄어들수록 density가 surface에 집중된다. **Surface 추출** 학습된 SDF에서 `f(x) = 0`인 iso-surface를 mesh로 변환하는 표준 방법이 **Marching Cubes** 알고리즘이다. 공간을 격자로 나누고, 각 격자 꼭짓점에서 SDF 부호를 확인하여 surface가 지나가는 위치를 보간으로 결정한다. **비교표** | 표현 | 장점 | 단점 | 예시 | |------|------|------|------| | NeRF (density) | 렌더링 품질 높음 | surface 추출 어려움 | Instant-NGP | | SDF (neural) | 깨끗한 surface | 학습 어려움 | NeuS, VolSDF | | 3DGS (explicit) | 실시간 렌더링 | 메모리 사용량 | Gaussian Splatting | | Occupancy | 이진 분류로 단순 | 표면 디테일 한계 | ConvONet | > **추천 자료** > - [Wang et al., "NeuS: Learning Neural Implicit Surfaces by Volume Rendering" (NeurIPS 2021)](https://arxiv.org/abs/2106.10689) — NeuS 원 논문 > - [Yariv et al., "Volume Rendering of Neural Implicit Surfaces" (NeurIPS 2021)](https://arxiv.org/abs/2106.12052) — VolSDF 논문 > - [Park et al., "DeepSDF: Learning Continuous Signed Distance Functions for Shape Representation" (CVPR 2019)](https://arxiv.org/abs/1901.05103) — DeepSDF 원 논문 > - [Mescheder et al., "Occupancy Networks" (CVPR 2019)](https://arxiv.org/abs/1812.03828) — Occupancy 기반 접근의 대표작 ### 13.7 심화: Differentiable Rendering *연구자가 되고 싶다면 여기서부터 읽어라.* NeRF, 3DGS, NeuS 등 최근 3D 비전의 핵심 기술들은 모두 하나의 공통 원리를 공유한다: **렌더링 과정을 미분 가능하게 만들어서, 렌더링 결과와 실제 이미지의 차이로 3D 표현을 최적화**하는 것이다. 이 패러다임을 analysis-by-synthesis라 한다. **Volume Rendering Equation** NeRF 계열에서 사용하는 기본 렌더링 공식이다. 카메라에서 발사한 ray `r(t) = o + td` 위의 색상을 적분한다. ``` C(r) = ∫ T(t) · σ(t) · c(t) dt where T(t) = exp( -∫_{t_n}^{t} σ(s) ds ) ``` - `σ(t)`: 위치 `t`에서의 density (불투명도) - `c(t)`: 위치 `t`에서의 색상 (RGB) - `T(t)`: 누적 투과도 (ray가 `t`까지 도달할 확률) 실제로는 이 연속 적분을 discretize하여 ray 위의 N개 샘플 점에서 근사한다 (ray marching). ``` C(r) ≈ Σ_i T_i · α_i · c_i where α_i = 1 - exp(-σ_i · δ_i), T_i = Π_{j
**추천 자료** > - [Tewari et al., "Advances in Neural Rendering" (EGSR 2022)](https://arxiv.org/abs/2111.05849) — Differentiable rendering 서베이 > - [Ravi et al., "Accelerating 3D Deep Learning with PyTorch3D" (2020)](https://arxiv.org/abs/2007.08501) — PyTorch3D 논문 > - [Laine et al., "Modular Primitives for High-Performance Differentiable Rendering" (2020)](https://arxiv.org/abs/2011.03277) — nvdiffrast 논문 ### 13.8 심화: 3D Scene Graph *연구자가 되고 싶다면 여기서부터 읽어라.* 로봇에게 "주방에 있는 빨간 컵을 가져와"라고 명령하면, 포인트 클라우드나 mesh만으로는 이 명령을 수행할 수 없다. "주방"이 어디인지, "빨간 컵"이 어떤 물체인지, 그것이 주방 "안에 있다"는 관계를 이해해야 한다. 3D Scene Graph는 환경을 기하학적 표현을 넘어 의미론적 관계 그래프로 표현하는 방법이다. **구조** - **노드(Node)**: 물체, 방, 건물 등 — 계층적(hierarchical) 구조 - 건물 → 층 → 방 → 물체 - 각 노드는 3D 위치, 바운딩 박스, 의미론적 라벨을 가짐 - **엣지(Edge)**: 노드 간 관계 - "위에 있다" (on), "안에 있다" (in), "가까이 있다" (near), "지지한다" (support) 등 ``` [Building] └── [Floor 1] ├── [Kitchen] │ ├── [Table] ──(on)── [Red Cup] │ ├── [Sink] │ └── [Chair] └── [Living Room] ├── [Sofa] └── [TV] ``` **Hydra** MIT에서 개발한 실시간 3D scene graph 구축 시스템이다. RGB-D 또는 LiDAR 입력으로 로봇이 이동하면서 계층적 scene graph를 점진적으로 구축한다. 파이프라인: 1. Metric-semantic mesh 구축 (TSDF + semantic segmentation) 2. 방(room) 단위 분할 (free-space clustering) 3. 물체 노드 추출 및 관계 설정 4. 계층 구조 연결 Hydra의 핵심 기여는 이 모든 과정을 실시간(online)으로 수행한다는 것이다. 로봇이 탐색하면서 동시에 scene graph가 갱신된다. **ConceptGraphs** Foundation model (CLIP, LLM)을 활용하여 open-vocabulary scene graph를 구축하는 최근 연구이다. 기존 scene graph는 미리 정의된 카테고리(의자, 테이블 등)에 의존했다. ConceptGraphs는 CLIP으로 임의의 자연어 쿼리에 대응하는 물체를 찾고, LLM으로 물체 간 관계를 추론한다. ``` 1. RGB-D 프레임에서 open-vocabulary detector로 물체 감지 2. CLIP feature로 물체 임베딩 추출 3. 3D 공간에서 동일 물체 병합 (multi-view association) 4. LLM으로 물체 간 관계 추론 5. Scene graph 구축 ``` "빨간 컵" 같은 훈련 시 본 적 없는 쿼리도 처리할 수 있다는 것이 핵심이다. **왜 필요한가?** | 표현 | "주방의 빨간 컵을 가져와" 수행 가능? | 이유 | |------|------|------| | Point Cloud | 불가 | 의미 정보 없음 | | Semantic Map | 부분적 | "컵"은 찾지만 "주방에 있는"이라는 관계 처리 어려움 | | 3D Scene Graph | 가능 | 물체 + 관계 + 계층 모두 표현 | 로봇의 task planning, 자연어 기반 내비게이션, human-robot interaction 등에서 scene graph는 3D 표현과 고수준 추론을 연결하는 다리 역할을 한다. > **추천 자료** > - [Hughes et al., "Hydra: A Real-time Spatial Perception System for 3D Scene Graph Construction and Optimization" (RSS 2022)](https://arxiv.org/abs/2201.13360) — Hydra 원 논문 > - [Gu et al., "ConceptGraphs: Open-Vocabulary 3D Scene Graphs for Perception and Planning" (2023)](https://arxiv.org/abs/2309.16650) — ConceptGraphs 논문 > - [Rosinol et al., "3D Dynamic Scene Graphs: Actionable Spatial Perception with Places, Objects, and Humans" (RSS 2020)](https://arxiv.org/abs/2002.06289) — Dynamic Scene Graph 개념 제시 > - [Armeni et al., "3D Scene Graph: A Structure for Unified Semantics, 3D Space, and Camera" (ICCV 2019)](https://arxiv.org/abs/1910.02527) — 3D Scene Graph 초기 연구 > **기술 흐름: 3D Vision** > - **~2010**: 포인트 클라우드 처리 고전기. PCL 라이브러리, ICP 정합, TSDF 기반 볼류메트릭 복원이 주류 > - **2012~**: Kinect의 등장으로 RGB-D 기반 3D 복원 대중화. KinectFusion(TSDF + ICP)이 실시간 3D 복원의 문을 열었다 > - **2017~**: PointNet/PointNet++로 포인트 클라우드 딥러닝 시대 개막. VoxelNet, PointPillars 등 3D Object Detection 연구 급증 > - **2020~**: NeRF 등장으로 Neural Rendering 혁명. 사진 몇 장으로 포토리얼리스틱한 3D 장면을 만들 수 있게 됨. Instant-NGP, Mip-NeRF 등 빠른 후속 연구 > - **2023~**: 3D Gaussian Splatting이 NeRF의 속도 한계를 극복. 실시간 렌더링 + 명시적 표현의 장점. BEVFusion 등 멀티모달 3D 감지가 자율주행에서 표준으로 자리잡음 > - **2024~**: 3DGS + SLAM 결합(SplaTAM, MonoGS, Gaussian-SLAM)으로 Neural SLAM의 새 장을 열고 있다. 로봇이 이동하면서 실시간으로 포토리얼리스틱 3D 맵을 구축하는 시대 > - **지금 주목할 것**: 3DGS가 NeRF를 빠르게 대체하고 있으며, 특히 SLAM/로보틱스 응용에서 3DGS 기반 방법이 급증하고 있다. NeRFStudio에서 두 방법 모두 실험해볼 수 있으니 직접 비교해보는 것을 추천한다. --- ## 14. SLAM & Odometry 로봇이 낯선 환경에서 "나는 어디에 있고, 주변은 어떻게 생겼는가?"를 동시에 알아내는 문제가 SLAM이다. GPS가 안 되는 실내, 지하, 건물 내부에서 로봇이 자율적으로 움직이려면 SLAM은 선택이 아니라 필수이다. 이 챕터는 로봇 소프트웨어 엔지니어에게 가장 자주 요구되는 기술 중 하나이니, 이론과 실습 모두 탄탄히 잡아야 한다. ### 14.1 개념 소개 지도 없이 로봇을 돌려보면 바로 느낄 수 있다. 로봇은 자기가 어디 있는지 모르면 아무것도 못 한다. 네비게이션, 장애물 회피, 경로 계획 — 모든 것의 전제 조건이 "현재 위치"와 "주변 환경 정보"이다. **SLAM (Simultaneous Localization and Mapping)**: 자신의 위치를 추정하면서 동시에 주변 환경의 지도를 작성하는 문제이다. 닭과 달걀 문제: - 지도가 있어야 위치를 알 수 있음 - 위치를 알아야 지도를 만들 수 있음 → 동시에 해결 이것이 왜 "어려운 문제"인지 조금 더 설명하면: 센서는 항상 노이즈가 있다. 바퀴가 미끄러지기도 하고, 카메라 이미지가 흔들리기도 한다. 이런 불확실성이 시간이 지날수록 누적되어 위치 추정이 점점 틀어진다(drift). SLAM은 이 drift를 보정하면서 일관된 지도를 만드는 것이 핵심 도전이다. **Odometry vs SLAM**: | 특징 | Odometry | SLAM | |---|---|---| | 출력 | 상대적 이동 | 위치 + 지도 | | Loop Closure | 없음 | 있음 | | Drift | 누적 | 보정 가능 | | 계산량 | 적음 | 많음 | > **추천 자료** > - [Cyrill Stachniss — SLAM Course (University of Bonn)](https://www.youtube.com/playlist?list=PLgnQpQtFTOGQrZ4O5QzbIHgl3b1JHimN_) — SLAM 강의의 정석. SLAM을 처음 배운다면 이 시리즈를 보는 걸 권한다 > - [Thrun, Burgard, Fox, "Probabilistic Robotics" (Textbook)](https://mitpress.mit.edu/9780262201629/probabilistic-robotics/) — SLAM의 수학적 기반을 다루는 교과서. 칼만 필터, 파티클 필터, EKF-SLAM 등 > - [Barfoot, "State Estimation for Robotics" (Free PDF)](http://asrl.utias.utoronto.ca/~tdb/bib/barfoot_ser17.pdf) — 상태 추정의 수학을 깊이 있게 다루는 교재. 무료 PDF 제공 > - [Awesome-SLAM GitHub](https://github.com/SilenceOverflow/Awesome-SLAM) — SLAM 관련 논문, 라이브러리, 데이터셋을 모아놓은 목록 > - [정진용 블로그 — SLAM 강의 시리즈 (Freiburg Robot Mapping 기반)](https://jinyongjeong.github.io/2017/02/13/lec01_SLAM_bayes_filter/) — Bayes filter부터 EKF/UKF/Particle filter, Graph SLAM, Robust SLAM까지 15편 시리즈. 한글로 된 SLAM 입문 자료 중 가장 체계적 > - [김기섭 블로그 — SLAM Back-end 공부자료 5개 추천](https://gisbi-kim.github.io/blog/2021/10/03/slam-textbooks.html) — Error-state KF, Factor Graphs, Bundle Adjustment 등 핵심 자료 큐레이션 > **실습**: [SE(2) Odometry](https://alexjunholee.github.io/robotics-practice/app.html#se2_odometry) > 2D 평면에서의 odometry 누적 과정을 직접 조작하며, drift가 어떻게 발생하는지 확인할 수 있다. ### 14.2 Visual Odometry (VO) 카메라만으로 상대적 이동을 추정한다. SLAM의 "front-end"에 해당하며, 여기서 추정한 이동이 부정확하면 SLAM 전체가 무너진다. ### 14.2.1 Feature-based vs Direct Method 이 두 가지가 Visual Odometry의 양대 산맥이고, 각각의 장단점이 뚜렷하기 때문이다. 어떤 환경에서 로봇을 운용하느냐에 따라 선택이 달라진다. **Feature-based**: ``` 이미지 → 특징점 추출 → 매칭 → 움직임 추정 ``` - 장점: 조명 변화에 강건, 검증된 방법 - 단점: 특징점 없는 영역 어려움 (흰 벽, 텍스처 없는 바닥) - 예시: ORB-SLAM Feature-based 방법의 핵심은 "이미지에서 변하지 않는 특징적인 점(코너, 블롭 등)을 찾고, 그 점들의 움직임으로부터 카메라의 움직임을 역추정"하는 것이다. 선형대수적으로는 Essential Matrix나 Fundamental Matrix를 구하는 문제로 귀결된다. **Direct Method**: ``` 이미지 → 픽셀 밝기 직접 비교 → 움직임 추정 ``` - 장점: 모든 픽셀 정보 활용, 텍스처 없는 곳도 가능 - 단점: 조명 변화에 민감 - 예시: DSO, LSD-SLAM Direct Method는 "연속 프레임에서 같은 3D 점을 관측하면 밝기가 같아야 한다"는 가정(brightness constancy)을 이용한다. 특징점을 뽑을 필요가 없어서, 텍스처가 적은 환경에서도 작동할 수 있다. ### 14.2.2 Mono vs Stereo vs RGB-D 각 카메라 구성의 트레이드오프를 이해해야 실제 로봇에 맞는 센서를 선택할 수 있다. **Monocular**: - 단일 카메라 - Scale ambiguity (절대 크기 알 수 없음) - 초기화 필요 (충분한 움직임) Scale ambiguity가 뭐냐면: 단안 카메라로는 "물체가 가까이에 있는 작은 물체"인지 "멀리에 있는 큰 물체"인지 구분할 수 없다. 그래서 모노 SLAM의 지도는 실제 미터 단위가 아닌 임의의 스케일로 나온다. IMU나 다른 센서로 스케일을 복원해야 한다. **Stereo**: - 두 카메라로 깊이 계산 - Scale 복원 가능 - 베이스라인에 따라 정확도 제한 **RGB-D**: - 깊이 센서 포함 - Scale 문제 없음 - 실내 환경에 적합 > **추천 자료** > - [Daniel Cremers — Multiple View Geometry (TUM)](https://www.youtube.com/playlist?list=PLTBdjV_4f-EJn6udZ34tht9EVIW7lbeo4) — Visual Odometry의 수학적 기반을 배우기에 최적 > - [EuRoC MAV Dataset](https://projects.asl.ethz.ch/datasets/doku.php?id=kmavvisualinertialdatasets) — Visual(-Inertial) Odometry 벤치마크 데이터셋 > - [TUM RGB-D Benchmark](https://cvg.cit.tum.de/data/datasets/rgbd-dataset) — RGB-D SLAM/VO 벤치마크의 표준 ### 14.3 Visual SLAM ### 14.3.1 ORB-SLAM2/3 ORB-SLAM은 Visual SLAM의 "표준 교과서"와 같은 시스템이다. 대부분의 Visual SLAM 논문이 ORB-SLAM을 베이스라인으로 비교하며, 코드가 공개되어 있어 직접 돌려볼 수 있다. SLAM을 공부한다면 한 번은 직접 빌드해서 돌려보는 것을 권한다. 가장 널리 사용되는 Visual SLAM 시스템이다. **구성**: 1. **Tracking**: 현재 프레임에서 포즈 추정 2. **Local Mapping**: 키프레임 기반 지역 지도 관리 3. **Loop Closing**: 루프 감지 및 전역 최적화 이 세 쓰레드 구조가 ORB-SLAM의 핵심 설계이다. Tracking은 매 프레임 실시간으로, Local Mapping은 키프레임이 들어올 때, Loop Closing은 루프가 감지될 때 동작한다. 각각 다른 주기로 병렬 실행되므로, 실시간 성능을 유지하면서도 글로벌 일관성을 확보할 수 있다. **ORB-SLAM3 특징**: - Visual-Inertial 모드 지원 - 멀티맵 지원 - Fish-eye 카메라 지원 ORB-SLAM의 역사적 맥락: - **MonoSLAM (2007)**: 최초의 실시간 단안(monocular) SLAM. EKF 기반으로 작동했으나, 맵 크기가 커지면 계산량이 급증하는 한계가 있었다. - **PTAM (Parallel Tracking and Mapping, 2007)**: Tracking과 Mapping을 별도 쓰레드로 분리한 최초의 시스템. 이 아키텍처가 이후 ORB-SLAM에 큰 영향을 미쳤다. - **ORB-SLAM (2015)**: PTAM의 설계를 계승하면서 ORB 특징점, Loop Closure, 재위치 추정(relocalization)을 추가한 완전한 SLAM 시스템. - **ORB-SLAM2 (2017)**: Stereo, RGB-D 지원 추가. - **ORB-SLAM3 (2021)**: Visual-Inertial, 멀티맵 등 추가. ```bash # ORB-SLAM3 실행 예시 ./Examples/Monocular/mono_euroc \ Vocabulary/ORBvoc.txt \ Examples/Monocular/EuRoC.yaml \ ~/Datasets/EuRoC/MH01 ``` > **추천 자료** > - [Campos et al., "ORB-SLAM3: An Accurate Open-Source Library for Visual, Visual-Inertial and Multi-Map SLAM" (2021)](https://arxiv.org/abs/2007.11898) — ORB-SLAM3 논문 > - [ORB-SLAM3 GitHub](https://github.com/UZ-SLAMLab/ORB_SLAM3) — 공식 코드 > - [EuRoC MAV Dataset](https://projects.asl.ethz.ch/datasets/doku.php?id=kmavvisualinertialdatasets) — ORB-SLAM3 테스트용 표준 데이터셋 > - [정진용 블로그 — Visual SLAM 비교 실험 (KAIST Urban Dataset)](https://jinyongjeong.github.io/2019/10/22/visual_slam_compare/) — ORB-SLAM2 vs VINS-Fusion 실전 비교. 실제 데이터셋에서의 성능 차이 분석 ### 14.3.2 DSO (Direct Sparse Odometry) **Direct Method** + **Sparse Points** 독특한 조합이다. Direct Method는 보통 dense(모든 픽셀)하게 쓰이고, Sparse는 Feature-based에서 쓰이는 건데, DSO는 "Direct이면서 Sparse"라는 독특한 조합을 사용한다. 선별된 고품질 점들만 사용하면서 광도(photometric) 오차를 최소화한다. - 특징점 추출 없이 픽셀 밝기 직접 사용 - 선별된 점들만 사용 (Sparse) - Photometric bundle adjustment > **추천 자료** > - [Engel et al., "Direct Sparse Odometry" (2018)](https://arxiv.org/abs/1607.02565) — DSO 논문 ### 14.3.3 VINS-Mono/Fusion 직접 돌려보면 알겠지만, 카메라만으로는 빠른 움직임이나 텍스처 없는 환경에서 트래킹이 쉽게 실패한다. IMU를 결합하면 이런 상황에서도 안정적으로 동작한다. 실제 드론이나 모바일 로봇에서 가장 많이 쓰이는 Visual-Inertial SLAM 시스템이다. **Visual-Inertial Navigation System** - Camera + IMU tight coupling - Sliding window optimization - Loop closure 지원 - 모바일/드론에서 널리 사용 ``` 센서 입력 → IMU Preintegration → Visual Feature Tracking → Sliding Window Optimization → Loop Closure (optional) ``` VINS-Mono의 핵심 기여: IMU Preintegration이라는 기법을 활용해, 두 키프레임 사이의 수백 개 IMU 측정을 하나의 상대 변환으로 압축한다. 이렇게 하면 최적화할 때 IMU 데이터를 일일이 다룰 필요 없이, 압축된 제약 조건 하나만 추가하면 된다. 계산 효율이 크게 올라간다. > **추천 자료** > - [Qin et al., "VINS-Mono: A Robust and Versatile Monocular Visual-Inertial State Estimator" (2018)](https://arxiv.org/abs/1708.03852) — VINS-Mono 논문 > - [VINS-Mono GitHub](https://github.com/HKUST-Aerial-Robotics/VINS-Mono) — 공식 코드, ROS 지원 ### 14.4 LiDAR Odometry & SLAM 카메라 기반 방법이 조명 변화나 텍스처에 민감한 반면, LiDAR는 직접 3D 거리를 측정하므로 이런 문제에서 자유롭다. 자율주행, 실외 로봇에서는 LiDAR SLAM이 사실상 표준이다. ### 14.4.1 LOAM (Lidar Odometry and Mapping) LOAM은 LiDAR SLAM의 시조새와 같은 연구이다. 이후 나온 LeGO-LOAM, LIO-SAM, FAST-LIO 등 거의 모든 LiDAR SLAM이 LOAM의 아이디어를 계승하거나 확장한 것이다. **핵심 아이디어**: - Edge points와 Planar points 분류 - Point-to-edge, point-to-plane 거리 최소화 - Odometry와 Mapping 분리 (주파수 다르게) LOAM의 핵심 통찰: 포인트 클라우드에서 기하학적으로 의미 있는 점들(모서리, 평면)만 추려 사용한다. 모든 점을 다 매칭하면 느리고 노이즈에 취약하지만, edge/planar 점만 골라 쓰면 빠르고 정확하다. ### 14.4.2 LeGO-LOAM **Lightweight and Ground-Optimized LOAM**: - 지면 분리로 계산량 감소 - 지면을 기반으로 초기 추정 - 모바일 로봇에 적합 ### 14.4.3 LIO-SAM Factor Graph 기반 최적화를 LiDAR-Inertial SLAM에 적용한 대표작이다. Factor Graph를 이해하면 "센서를 하나 더 추가하고 싶을 때, 그냥 factor 하나만 추가하면 된다"는 확장성을 얻을 수 있다. **LiDAR-Inertial Odometry via Smoothing and Mapping**: - Factor graph 기반 - Tight IMU-LiDAR coupling - GPS, Loop closure 통합 ``` ┌──────────────┐ IMU ──────────────→ │ │ │ Factor Graph │ ──→ Pose LiDAR ────────────→ │ │ │ iSAM2 │ GPS (optional) ───→ │ │ └──────────────┘ ``` Factor Graph가 뭐냐면: 변수(로봇 포즈, 랜드마크 위치)와 제약(센서 측정)의 관계를 그래프로 표현하는 것이다. IMU 측정이 factor 하나, LiDAR 매칭이 factor 하나, GPS가 factor 하나, Loop Closure가 factor 하나... 이런 식으로 센서를 추가하려면 해당 factor만 추가하면 된다. GTSAM 라이브러리가 이 최적화를 효율적으로 수행한다. > **추천 자료** > - [Shan et al., "LIO-SAM: Tightly-coupled Lidar Inertial Odometry via Smoothing and Mapping" (2020)](https://arxiv.org/abs/2007.00258) — LIO-SAM 논문 > - [Vizzo et al., "KISS-ICP: In Defense of Point-to-Point ICP" (RA-L 2023, arXiv:2209.15397)](https://arxiv.org/abs/2209.15397) — 잘 만든 vanilla ICP가 복잡한 LiDAR odometry와 동등한 성능. 단순함의 힘 > - [LIO-SAM GitHub](https://github.com/TixiaoShan/LIO-SAM) — 공식 코드, ROS 지원 > - [GTSAM Documentation](https://gtsam.org/) — Factor Graph 최적화 라이브러리. LIO-SAM, ORB-SLAM3 등 다양한 SLAM 시스템의 백엔드로 사용된다 > - [Frank Dellaert — Factor Graphs for Robot Perception (ICRA Tutorial)](https://www.youtube.com/watch?v=yDRIIeNA-dc) — GTSAM 개발자가 직접 설명하는 Factor Graph > - [김기섭 블로그 — Scan Context-based LiDAR Pose-graph SLAM 구현](https://gisbi-kim.github.io/blog/2021/05/17/sclidarslam.html) — Scan Context를 LiDAR SLAM에 통합한 구현 해설 ### 14.4.4 FAST-LIO / FAST-LIO2 **Fast LiDAR-Inertial Odometry**: - Kalman Filter 기반 (optimization 대신) - ikd-Tree: 동적 KD-트리로 빠른 매핑 - 실시간 성능 FAST-LIO가 왜 빠른지: LIO-SAM이 Factor Graph 최적화(비선형 최소자승법)를 사용하는 반면, FAST-LIO는 Iterated Extended Kalman Filter(IEKF)를 사용한다. 최적화 문제를 풀지 않고 필터링으로 처리하니, 계산이 훨씬 가볍다. 또한 ikd-Tree라는 증분적 KD-트리를 사용해서 맵에 새 점을 추가할 때도 빠르다. > **추천 자료** > - [Xu & Zhang, "FAST-LIO: A Fast, Robust LiDAR-Inertial Odometry Package by Tightly-Coupled Iterated Kalman Filter" (2021)](https://arxiv.org/abs/2010.14709) — FAST-LIO 논문 > - [Xu et al., "FAST-LIO2: Fast Direct LiDAR-Inertial Odometry" (2022)](https://arxiv.org/abs/2107.06829) — FAST-LIO2 논문 > - [FAST-LIO2 GitHub](https://github.com/hku-mars/FAST_LIO) — 공식 코드 ### 14.5 Multi-sensor Fusion 현실 세계에서 단일 센서로 모든 상황을 커버하는 것은 불가능하다. 카메라는 어두우면 안 되고, LiDAR는 비가 오면 힘들고, IMU만으로는 drift가 커진다. 센서를 결합(fusion)하면 각 센서의 약점을 다른 센서가 보완해준다. ### 14.5.1 Camera + IMU (VIO) **Loosely-coupled**: - Visual과 Inertial 결과를 별도 처리 후 융합 - 구현 간단 **Tightly-coupled**: - 센서 데이터를 함께 최적화 - 더 정확하지만 복잡 - 예시: VINS-Mono, MSCKF Loosely vs Tightly의 차이를 직관적으로: Loosely-coupled는 "카메라가 말하는 위치"와 "IMU가 말하는 위치"를 나중에 평균내는 것이다. Tightly-coupled는 "카메라 특징점의 재투영 오차와 IMU의 가속도/각속도 측정을 하나의 비용 함수에 넣고 동시에 최적화"하는 것이다. 후자가 더 정확하지만 구현이 어렵다. **IMU Preintegration**: 두 키프레임 사이의 IMU 측정을 사전 적분하여 상대 변환 계산. 재선형화 없이 최적화 가능. ### 14.5.2 LiDAR + IMU (LIO) - IMU로 초기 추정 - LiDAR로 정밀 보정 - 고속 움직임에서 유리 IMU가 왜 LiDAR에 도움이 되냐면: LiDAR는 보통 10-20Hz로 스캔하는데, 로봇이 빠르게 움직이면 한 스캔 내에서도 로봇이 이동한다(motion distortion). IMU는 200-400Hz로 측정하므로, 스캔 중 로봇의 움직임을 보정(de-skewing)하는 데 사용된다. ### 14.5.3 Camera + LiDAR + IMU **최신 트렌드**: 모든 센서 통합 - 예시: R3LIVE, LVI-SAM - 각 센서의 장점 활용 R3LIVE는 LiDAR(기하 정보) + Camera(텍스처/색상 정보) + IMU(고속 움직임 보상)를 모두 결합한다. 결과적으로 정확한 위치 추정뿐 아니라 색상이 입혀진(colored) 고밀도 3D 맵까지 실시간으로 생성할 수 있다. > **추천 자료** > - [KITTI Odometry Benchmark](https://www.cvlibs.net/datasets/kitti/eval_odometry.php) — LiDAR/Visual Odometry 벤치마크의 표준 > - [EuRoC MAV Dataset](https://projects.asl.ethz.ch/datasets/doku.php?id=kmavvisualinertialdatasets) — VIO 벤치마크 데이터셋 > - [Lin & Zhang, "R3LIVE: A Robust, Real-time, RGB-colored, LiDAR-Inertial-Visual tightly-coupled state Estimation and mapping package" (2022)](https://arxiv.org/abs/2109.07982) — 3센서 융합의 대표작 > - [김기섭 블로그 — Filter-based VIO: MSCKF 계열 history 정리](https://gisbi-kim.github.io/blog/2021/04/27/msckf-history.html) — MSCKF 원본부터 stereo 확장까지 계보 정리 ### 14.6 Loop Closure & Global Optimization SLAM을 돌려보면 시간이 지날수록 지도가 뒤틀리는 걸 볼 수 있다. 로봇이 큰 원을 그리며 출발점으로 돌아왔는데, 지도에서는 출발점과 도착점이 안 맞는 것이다. Loop Closure가 바로 그 뒤틀림을 교정하는 핵심 메커니즘이다. 이것 없이는 대규모 환경에서 SLAM이 사실상 불가능하다. ### 14.6.1 Place Recognition 이전에 방문한 장소를 인식하여 drift를 보정한다. 같은 장소라도 시간, 조명, 계절이 바뀌면 아예 다르게 보이기 때문이다. 또한 비슷하게 생긴 다른 장소를 같은 장소로 오인하면(false positive) 지도가 오히려 더 망가진다. Place Recognition의 정밀도(precision)가 매우 높아야 하는 이유이다. **Bag of Words (BoW)**: - Visual vocabulary 기반 - 이미지 간 유사도 계산 - DBoW2 라이브러리 **NetVLAD**: - 딥러닝 기반 place recognition - End-to-end 학습 - 조명, 날씨 변화에 강건 **LiDAR Place Recognition**: - Scan Context: 포인트 클라우드를 2D 디스크립터로 - PointNetVLAD: 포인트 클라우드 학습 ### 14.6.2 Pose Graph Optimization 루프가 감지되면 전체 경로를 보정한다. ``` 노드: 로봇 포즈 에지: 상대 변환 (odometry, loop closure) 목표: 모든 에지 제약을 만족하는 노드 위치 찾기 ``` 직관적으로 설명하면: Odometry가 만든 경로는 "각 구간은 대충 맞지만, 전체적으로는 뒤틀린" 상태이다. Loop Closure가 "이 위치와 저 위치가 같은 곳이다"라는 제약을 추가하면, Pose Graph Optimization이 "모든 제약을 최대한 만족하도록" 전체 경로를 부드럽게 조정한다. 이것은 비선형 최소자승법 문제이다. **도구**: - **g2o (General Graph Optimization)**: 경량, ORB-SLAM에서 사용 - **GTSAM (Georgia Tech Smoothing and Mapping)**: Factor Graph 기반, LIO-SAM에서 사용 - **Ceres Solver**: Google의 범용 비선형 최적화 라이브러리 > **추천 자료** > - [GTSAM Documentation & Tutorials](https://gtsam.org/) — Factor Graph 기반 최적화 라이브러리. Pose Graph Optimization 예제 포함 > - [Cyrill Stachniss — Graph-based SLAM](https://www.youtube.com/watch?v=uHbRKvD8TWg) — Pose Graph Optimization의 직관적 설명 > - [g2o GitHub](https://github.com/RainerKuemmerle/g2o) — Graph Optimization 프레임워크 > - [정진용 블로그 — Robust Graph SLAM](https://jinyongjeong.github.io/2017/03/04/lec15_Robust_Graph_SLAM/) — M-estimator, Max-mixture, DCS 등 robust SLAM 기법 한글 해설 > **실습**: [Pose Graph Optimization](https://alexjunholee.github.io/robotics-practice/app.html#pose_graph) > Pose Graph의 노드(포즈)와 에지(제약)를 조작하고, loop closure 추가 시 전체 경로가 어떻게 보정되는지 확인할 수 있다. ### 14.7 Localization 사전 지도 기반으로 현재 위치를 추정한다. SLAM이 "지도를 만들면서 위치를 추정"하는 것이라면, Localization은 "이미 만들어진 지도에서 위치만 추정"하는 것이다. 실제 서비스 로봇은 SLAM으로 미리 지도를 만들고, 운용 중에는 Localization만 수행하는 경우가 많다. **Map-based Localization**: - 미리 만들어진 지도 사용 - SLAM보다 가벼움 - 지도 업데이트 필요 **Monte Carlo Localization (MCL)**: - 파티클 필터 기반 - 2D LiDAR + 점유 격자 지도 - ROS AMCL 패키지 MCL의 직관: 수천 개의 "가상 로봇(파티클)"을 지도 위에 뿌린다. 각 파티클은 "나는 여기에 이런 방향으로 있다"라는 가설이다. 실제 센서 측정과 비교해서, 측정과 잘 맞는 파티클은 살아남고 안 맞는 파티클은 사라진다. 시간이 지나면 파티클들이 실제 위치 주변에 모이게 된다. **LiDAR Localization**: - 포인트 클라우드 맵에 ICP/NDT - 정밀한 위치 추정 > **추천 자료** > - [Cyrill Stachniss — Monte Carlo Localization](https://www.youtube.com/watch?v=MsYlueVDLI0) — MCL/파티클 필터의 직관적 설명 > - [ROS Navigation Stack — AMCL](http://wiki.ros.org/amcl) — ROS에서 MCL 사용하기 > **실습**: [Particle Filter](https://alexjunholee.github.io/robotics-practice/app.html#particle_filter) > 파티클 필터 기반 로봇 위치 추정 과정을 시각화하며, 파티클의 수렴 과정을 인터랙티브하게 확인할 수 있다. > **실습**: [Occupancy Grid](https://alexjunholee.github.io/robotics-practice/app.html#occupancy_grid) > 2D 점유 격자 지도를 구축하는 과정을 시각화하며, 센서 측정이 어떻게 확률적 지도로 변환되는지 확인할 수 있다. ### 14.8 Learning-based & Neural SLAM (최신 트렌드) 전통적인 SLAM은 수작업으로 설계된 특징점, 매칭 알고리즘, 최적화 파이프라인에 의존한다. 최근에는 이 과정의 일부 또는 전체를 딥러닝으로 대체하려는 시도가 활발하다. **DROID-SLAM (2021)**: - Dense Recurrent Optical-flow 기반 SLAM - 특징점 추출/매칭 없이, Dense optical flow를 반복적으로 정제하여 카메라 포즈와 깊이를 동시에 추정 - 기존 Visual SLAM 대비 robustness가 크게 향상 - Differentiable한 Dense Bundle Adjustment(DBA) 레이어를 사용하여 end-to-end 학습 DROID-SLAM이 왜 주목받는지: 기존 feature-based SLAM(ORB-SLAM)은 특징점이 없는 환경에서 실패하고, direct method(DSO)는 조명 변화에 약하다. DROID-SLAM은 학습된 representation을 사용하기 때문에 이런 한계를 상당 부분 극복한다. 다만 GPU가 필수이고 실시간 성능은 아직 기존 방법에 미치지 못하는 경우가 있다. **3DGS-SLAM 융합**: 13.5.2에서 다룬 3D Gaussian Splatting이 SLAM의 맵 표현(map representation)으로도 활용되고 있다. SplaTAM, MonoGS 등이 대표적이며, 기존 SLAM의 sparse/dense 포인트 맵 대신 3D Gaussian으로 환경을 표현한다. 장면의 시각적 충실도가 높아지고, 렌더링 기반의 새로운 응용(가상 뷰 생성, AR 오버레이 등)이 가능해진다. > **추천 자료** > - [Teed & Deng, "DROID-SLAM: Deep Visual SLAM for Monocular, Stereo, and RGB-D Cameras" (2021)](https://arxiv.org/abs/2108.10869) — DROID-SLAM 논문 > - [Keetha et al., "SplaTAM" (2024)](https://arxiv.org/abs/2312.02126) — 3DGS 기반 Dense SLAM > - [Awesome-SLAM GitHub](https://github.com/SilenceOverflow/Awesome-SLAM) — 최신 SLAM 논문/프로젝트 모음 ### 14.9 심화: SLAM 백엔드 최적화 *연구자가 되고 싶다면 여기서부터 읽어라.* SLAM 프론트엔드가 센서 데이터를 처리해서 제약 조건(constraint)을 만들어내면, 백엔드는 이 제약 조건들을 동시에 만족하는 최적의 상태(포즈, 랜드마크)를 찾는다. 이 과정은 비선형 최소제곱(nonlinear least squares) 문제로 귀결된다. 여기서 다루는 내용은 g2o, GTSAM, Ceres 같은 라이브러리를 "왜 그렇게 설정하는지" 이해하기 위한 수학적 배경이다. **SLAM 백엔드가 푸는 문제의 직관** 복잡한 수식을 보기 전에 한 가지만 기억하자: SLAM 백엔드는 결국 **Ax = b를 푸는 문제**이다. 로봇이 주행하면서 얻는 데이터는 두 종류다: 1. **오도메트리**: "나는 1m 앞으로 갔다" (상대적 이동) 2. **관측**: "저 랜드마크가 3m 거리에 보인다" 이 측정값들을 모두 만족시키는 포즈와 랜드마크 위치를 찾고 싶지만, 센서 노이즈 때문에 완벽하게 만족시키는 해는 존재하지 않는다. 대신 "모든 측정값과의 오차 제곱합을 최소화"하는 해를 찾는다. 이것이 nonlinear least squares 문제이고, 이걸 효율적으로 푸는 것이 SLAM 백엔드의 역할이다. 비선형이라 한 번에 못 풀고, 현재 추정값 근처에서 선형화(linearize)해서 반복적으로 갱신한다. 이 "선형화 → Ax=b 풀기 → 업데이트 → 반복"이 Gauss-Newton이다. (참고: [김기섭 블로그 — SLAM back-end 시리즈](https://gisbi-kim.github.io/blog/2021/03/04/slambackend-1.html)) #### 14.9.1 Manifold 위의 Gauss-Newton SLAM의 상태 변수(포즈)는 SE(3) 위에 있다. SE(3)는 유클리드 공간이 아니라 Lie group이므로, 일반적인 Gauss-Newton update `x ← x + δx`를 그대로 쓸 수 없다. 회전 행렬에 벡터를 더하면 더 이상 회전 행렬이 아니게 된다. 해법은 Lie algebra se(3) 위에서 perturbation을 정의하는 것이다. **Update step (left perturbation)**: ``` T ← exp(δξ^) · T ``` 여기서 `δξ ∈ R^6`는 se(3) 위의 작은 perturbation이고, `exp(·)`는 exponential map, `^`(hat operator)는 6-벡터를 4x4 행렬로 변환한다. **Jacobian 계산**: 오차 함수 `e(T)`의 Jacobian을 `δξ`에 대해 계산한다. ``` J = ∂e / ∂δξ ``` 이것은 chain rule로 `∂e/∂T · ∂T/∂δξ`가 되는데, `∂T/∂δξ`가 바로 SE(3)의 left Jacobian이다. **Normal equation**: ``` (J^T Σ^{-1} J) δξ* = -J^T Σ^{-1} e ``` - `Σ`는 측정 노이즈 공분산 - `H = J^T Σ^{-1} J`가 Hessian의 Gauss-Newton 근사이고, 이것이 **information matrix** - 여러 제약 조건이 있으면 각각의 `J^T Σ^{-1} J`를 합산한다 (additive property) 이 과정을 수렴할 때까지 반복한다. 매 iteration마다 현재 추정치에서 Jacobian을 재계산하고, update를 적용한다. #### 14.9.2 Schur Complement (Marginalization) Bundle Adjustment(BA)에서 상태 변수는 카메라 포즈(p)와 랜드마크(l) 두 종류이다. Normal equation의 Hessian `H`는 다음 block 구조를 갖는다: ``` [H_pp H_pl] [δp] [b_p] [H_lp H_ll] [δl] = [b_l] ``` 포즈 수를 `m`, 랜드마크 수를 `n`이라 하면, 보통 `n >> m`이다. 이 큰 시스템을 직접 풀면 비싸다. **Schur complement**로 랜드마크를 marginalize한다: ``` (H_pp - H_pl · H_ll^{-1} · H_lp) δp = b_p - H_pl · H_ll^{-1} · b_l ``` 이것이 가능한 이유: **`H_ll`은 block diagonal**이다. 각 랜드마크는 다른 랜드마크와 직접 연결되지 않으므로(랜드마크끼리는 공통 factor가 없다), `H_ll`의 역행렬은 각 block을 독립적으로 역산하면 된다. 계산 비용이 `O(n)`으로 싸다. 결과적으로 풀어야 하는 시스템 크기가 `n`에 관계없이 포즈 수 `m`에만 비례하게 된다. 이것이 BA가 수만 개의 랜드마크를 다루면서도 실시간에 가까운 성능을 낼 수 있는 핵심이다. `δp`를 구한 뒤, `δl`은 back-substitution으로 복원한다: ``` δl = H_ll^{-1} (b_l - H_lp · δp) ``` #### 14.9.3 희소성과 Variable Ordering Pose graph optimization에서 `H` 행렬은 **sparse**하다. 각 포즈는 시간적으로 인접한 포즈, 그리고 loop closure로 연결된 포즈와만 제약 관계를 갖는다. 전체 포즈가 1000개라 해도, 각 포즈가 연결된 포즈는 기껏해야 수 개~수십 개이다. sparse linear system을 풀 때 Cholesky factorization(`H = L L^T`)을 사용하는데, 여기서 **fill-in** 문제가 발생한다. 원래 0이었던 위치가 factorization 과정에서 non-zero가 되는 현상이다. Fill-in이 많으면 메모리와 계산 비용이 급증한다. Fill-in을 최소화하려면 변수의 순서(variable ordering)를 잘 정해야 한다: - **COLAMD** (Column Approximate Minimum Degree): 가장 널리 쓰이는 heuristic. 연결이 적은 변수부터 제거하는 방식 - **AMD** (Approximate Minimum Degree): COLAMD와 유사하지만 symmetric 행렬에 특화 - **Nested dissection**: 그래프를 재귀적으로 분할하여 ordering을 결정. 대규모 문제에서 효과적 왜 이걸 알아야 하는가: g2o, GTSAM, Ceres 같은 라이브러리에서 solver를 설정할 때, linear solver type(DENSE_SCHUR, SPARSE_NORMAL_CHOLESKY 등)과 ordering strategy를 선택해야 한다. 이 배경 지식이 없으면 "기본 설정으로 돌렸더니 느려서 다른 라이브러리로 갈아탔다"는 식의 비효율이 생긴다. 실제로는 ordering만 바꿔도 10배 이상 속도 차이가 날 수 있다. ```python # Ceres Solver에서 ordering 설정 예시 (Python binding) options = ceres.SolverOptions() options.linear_solver_type = ceres.LinearSolverType.SPARSE_NORMAL_CHOLESKY options.sparse_linear_algebra_library_type = ceres.SparseLinearAlgebraLibraryType.SUITE_SPARSE # ordering은 보통 자동으로 COLAMD를 사용하지만, 수동 설정도 가능 ``` #### 14.9.4 최적화 라이브러리 비교 | 라이브러리 | 특징 | 주요 사용처 | |---|---|---| | **g2o** | Pose graph / BA 전용, 가벼움, C++ only | ORB-SLAM2/3, LSD-SLAM | | **GTSAM** | Factor graph 기반, Bayes tree(iSAM2) 지원, incremental 최적화에 강함 | LIO-SAM, VINS-Fusion, 연구용 | | **Ceres Solver** | 범용 nonlinear least squares, auto-diff 지원, Google 개발 | Cartographer, 다양한 프로젝트 | 선택 기준: - SLAM 전용이고 가볍게 쓰고 싶다 → g2o - Factor graph 모델링이 필요하고, incremental update(키프레임이 추가될 때마다 점진적으로 최적화)가 중요하다 → GTSAM (iSAM2) - SLAM 이외의 범용 최적화도 해야 하고, Jacobian을 직접 유도하기 싫다 → Ceres (auto-diff) > **추천 자료** > - Barfoot, "State Estimation for Robotics" Ch.4 (Nonlinear Estimation) — Manifold 위의 최적화를 체계적으로 설명 > - [CMU 16-833 Robot Localization and Mapping Lecture Notes](https://www.cs.cmu.edu/~kaess/pub/Dellaert17fnt.pdf) — Factor graph와 SLAM 백엔드 이론 > - [g2o Tutorial](https://github.com/RainerKuemmerle/g2o) / [GTSAM Tutorial](https://gtsam.org/tutorials/intro.html) — 라이브러리별 실습 > - [김기섭 블로그 — Gauss-Newton Opt == IEKF update?](https://gisbi-kim.github.io/blog/2022/03/05/gn-iekf-same.html) — GN 최적화와 반복 칼만 필터의 수학적 동치성 설명. 필터 vs 최적화 논쟁에 대한 정리 > **실습**: [Bundle Adjustment 시각화](https://alexjunholee.github.io/robotics-practice/app.html#bundle_adjustment) > 카메라 포즈와 3D 포인트를 동시에 최적화하는 Bundle Adjustment 과정을 인터랙티브하게 확인할 수 있다. ### 14.10 심화: IMU Preintegration *연구자가 되고 싶다면 여기서부터 읽어라.* 14.3.3에서 VINS-Mono를 소개할 때 IMU preintegration을 간단히 언급했다. 여기서는 그 수학적 배경을 다룬다. **문제 정의**: IMU는 보통 200~1000 Hz로 가속도와 각속도를 출력한다. 반면 SLAM 최적화는 키프레임 단위(수 Hz~수십 Hz)로 수행한다. 키프레임 사이에 수백 개의 IMU 측정이 존재하는데, 최적화할 때 이것을 전부 상태 변수로 넣으면 문제 크기가 폭발한다. **Preintegration의 아이디어**: 두 키프레임 `i`와 `j` 사이의 IMU 측정값들을 하나의 "상대 운동 측정(relative motion measurement)"으로 압축한다. 이 압축된 측정이 최적화의 factor로 들어간다. **Preintegrated measurements**: 키프레임 `i`에서 `j`까지의 상대 변화량 세 가지를 계산한다. ``` ΔR_ij = Π_{k=i}^{j-1} Exp((ω_k - b_g) · Δt) # 상대 회전 Δv_ij = Σ_{k=i}^{j-1} ΔR_ik · (a_k - b_a) · Δt # 상대 속도 Δp_ij = Σ_{k=i}^{j-1} (Δv_ik · Δt + 0.5 · ΔR_ik · (a_k - b_a) · Δt^2) # 상대 위치 ``` 여기서 `ω_k`, `a_k`는 IMU 측정값, `b_g`, `b_a`는 gyroscope/accelerometer bias, `Δt`는 IMU 샘플링 간격이다. 핵심 포인트: 이 preintegrated measurement들은 **키프레임 `i`의 좌표계를 기준으로** 계산된다. 따라서 키프레임 `i`의 절대 포즈가 최적화 과정에서 바뀌더라도, preintegrated measurement를 재계산할 필요가 없다. **공분산 전파**: IMU 측정 노이즈가 preintegrated measurement에 어떻게 전파되는지 계산한다. Discrete-time propagation으로 각 IMU 측정마다 공분산을 업데이트한다. ``` Σ_{k+1} = A_k · Σ_k · A_k^T + B_k · Q · B_k^T ``` - `A_k`: 상태 전이 행렬 (현재 상태에서의 Jacobian) - `B_k`: 노이즈 입력 행렬 - `Q`: IMU 노이즈 공분산 (데이터시트에서 확인) 이 공분산이 최적화에서 해당 factor의 information matrix(`Σ^{-1}`)로 사용된다. **Bias 변화 시 보정**: 최적화 과정에서 IMU bias 추정치가 바뀔 수 있다. Bias가 바뀌면 원칙적으로 preintegration을 처음부터 다시 해야 한다. 하지만 이것은 비싸다. 대신 **first-order approximation**으로 보정한다: ``` ΔR_ij ≈ ΔR_ij^0 · Exp(∂ΔR/∂b_g · δb_g) Δv_ij ≈ Δv_ij^0 + ∂Δv/∂b_g · δb_g + ∂Δv/∂b_a · δb_a Δp_ij ≈ Δp_ij^0 + ∂Δp/∂b_g · δb_g + ∂Δp/∂b_a · δb_a ``` `^0`은 이전 bias 추정치로 계산한 값, `δb`는 bias 변화량, 편미분들은 preintegration 과정에서 함께 축적해둔다. Bias 변화가 크지 않은 한(보통 그렇다) 이 근사는 충분히 정확하다. **왜 manifold에서 preintegration하는가**: 이전 방식은 IMU 측정을 가장 가까운 키프레임 timestamp에 interpolation해서 사용했다. 그런데 회전은 SO(3)에 살고 있으므로 단순 선형 보간이 정확하지 않다. Lie group 위에서 미리 integration해두면, 1) 회전 누적이 수학적으로 정확하고, 2) 결과가 relative motion measurement로서 factor graph에 바로 들어갈 수 있다. 이것이 Forster et al. (2015 RSS, 2017 TRO)의 핵심 기여다. **Tightly-coupled vs Loosely-coupled**: LIO-SAM을 예로 설명하면: - **Loosely-coupled**: IMU를 다음 포즈의 초기값(initial guess)으로만 사용한다. LiDAR odometry와 IMU가 각자 독립적으로 상태를 추정하고, 나중에 covariance 기반으로 합친다. LeGO-LOAM이 이 방식이다. - **Tightly-coupled**: IMU preintegration factor를 LiDAR odometry factor와 같은 factor graph 안에서 동시에 최적화한다. IMU가 단순 초기값이 아니라, 키프레임 사이의 상대 포즈에 대한 독립적 관측으로 작용한다. LIO-SAM이 이 방식이다. Tightly-coupled의 장점은 모션이 심한 상황(빠른 회전, 급격한 가감속)에서 드러난다. LiDAR scan matching만으로는 잡기 어려운 빠른 변화를 IMU factor가 잡아주기 때문이다. Factor graph 형식이므로 GPS factor, loop closure factor 등을 모듈처럼 추가할 수 있다는 것도 실용적 장점이다. **LIO-SAM의 구조**: GTSAM 기반으로, IMU preintegration factor + LiDAR odometry factor + GPS factor + loop closure factor를 하나의 그래프에서 최적화한다. LiDAR odometry는 edge feature와 planar feature를 따로 추출하고, 각각 다른 resolution의 voxel map으로 관리한다. Scan matching 시 planar feature는 point-to-plane distance, edge feature는 point-to-line distance를 최소화하는 상대 변환을 구한다. 이 기법이 VINS-Mono, ORB-SLAM3 (Visual-Inertial mode), LIO-SAM 등 현대 VIO/LIO 시스템의 IMU factor 구현에 그대로 사용된다. > **추천 자료** > - [Forster et al., "On-Manifold Preintegration for Real-Time Visual-Inertial Odometry" (TRO 2017, arXiv:1512.02363)](https://arxiv.org/abs/1512.02363) — Preintegration의 원본 논문. 수식이 많지만 이 분야의 필수 논문 > - [Forster et al., "IMU Preintegration on Manifold for Efficient VIO" (2015 RSS)](https://rpg.ifi.uzh.ch/docs/RSS15_Forster.pdf) — 위 논문의 초기 버전. 핵심 아이디어가 더 간결하게 정리되어 있다 > - [Shan et al., "LIO-SAM" (IROS 2020)](https://github.com/TixiaoShan/LIO-SAM) — Tightly-coupled LIO의 레퍼런스 구현. 코드와 논문 모두 읽을 것 > - [Sola et al., "A micro Lie theory for state estimation in robotics" (arXiv:1812.01537)](https://arxiv.org/abs/1812.01537) — Lie group/algebra의 실용적 정리. Preintegration 읽기 전에 이것부터 보면 좋다 > - GTSAM의 `PreintegratedImuMeasurements` 클래스 소스코드 — 이론이 코드로 어떻게 구현되는지 확인 > - [IMU Preintegration MATLAB 구현](https://github.com/GentleDell/imu_preintegration_matlab) — KITTI에서 테스트한 MATLAB 코드. 수식과 코드를 대조하며 공부하기 좋다 ### 14.11 심화: 관측가능성 분석 (Observability) *연구자가 되고 싶다면 여기서부터 읽어라.* SLAM/VIO 시스템을 돌려보면 "왜 이 상황에서 drift가 심한가?", "왜 가만히 서있으면 위치가 흔들리는가?" 같은 현상을 겪게 된다. 이런 현상의 상당수는 시스템의 **관측가능성(observability)** 한계에서 비롯된다. **Visual-Inertial 시스템의 관측 불가능한 상태**: VIO에서 추정할 수 없는(unobservable) 자유도가 4개 있다: 1. **Global position (3 DoF)** — 절대 위치를 알 수 없다. GPS 같은 절대 기준이 없으면 시작점을 원점으로 잡을 수밖에 없다 2. **Global yaw (1 DoF)** — 중력 방향 축 기준의 회전(heading). 나침반 없이는 "어느 쪽이 북쪽인지" 알 수 없다 반면 다음은 관측 가능하다: - **Roll/pitch**: IMU accelerometer가 중력 방향을 감지하므로, 중력 기준 roll/pitch는 추정 가능 - **Scale** (stereo/IMU가 있는 경우): stereo 카메라의 baseline이나 IMU의 가속도 측정으로 스케일 복원 가능. 단, **monocular 카메라만으로는 scale이 관측 불가능**하다 **Degenerate motion** — 특정 움직임 패턴에서 추가적인 상태가 관측 불가능해진다: - **순수 회전(pure rotation)**: Monocular VO에서 translation을 추정할 수 없다. Epipolar geometry에서 epipole이 무한원점으로 가기 때문이다. 실무에서 "카메라를 제자리에서 돌리면 트래킹이 깨진다"는 현상의 원인 - **등속 직진(constant velocity)**: IMU의 accelerometer가 중력과 가속도를 구분하는데, 가속이 없으면(등속이면) accelerometer bias와 중력 방향의 미세한 오차를 구분할 수 없다. IMU bias가 관측 불가능해진다 - **정지(stationary)**: 등속의 특수 케이스. 가만히 서있으면 visual feature의 parallax도 없고 IMU 가속도도 없어서, bias와 스케일 모두 관측 불가능. "왜 가만히 서있으면 VINS가 drift 하는가?"의 답 **EKF 기반 시스템에서의 문제**: 표준 EKF를 VIO에 적용하면, linearization 오차로 인해 이론적으로 관측 불가능한 방향에서도 공분산이 줄어드는(uncertainty가 인위적으로 감소하는) 현상이 발생한다. 이것은 inconsistency의 주요 원인이다. **OC-EKF (Observability-Constrained EKF)**: 이 문제를 해결하기 위해, EKF의 Jacobian을 수정하여 관측 불가능한 방향의 null space를 보존한다. 추정기가 "모르는 것은 모른다고 유지"하도록 강제하는 것이다. 실무적 함의: - VIO 시스템을 사용할 때는 **다양한 방향으로 움직이면서** 초기화해야 한다. 한 방향으로만 걸으면 IMU bias 추정이 제대로 되지 않는다 - Loop closure가 없는 VIO는 장시간 운용 시 반드시 drift 한다. 관측 불가능한 yaw 방향의 오차가 축적되기 때문 - Monocular VIO의 스케일은 가속/감속이 있어야 관측 가능. 일정 속도로만 움직이면 스케일 drift가 생긴다 > **추천 자료** > - [Hesch et al., "Observability-constrained Vision-aided Inertial Navigation" (TRO 2014)](https://ieeexplore.ieee.org/document/6672119) — OC-EKF/OC-VINS의 원본 논문 > - Barfoot, "State Estimation for Robotics" Ch.9 — 관측가능성 분석의 이론적 토대 > - [Huang & Dissanayake, "A critique of current developments in Simultaneous Localization and Mapping" (IJRR 2016)](https://journals.sagepub.com/doi/10.1177/0278364916643566) — SLAM의 관측가능성/일관성 문제를 비판적으로 정리 > **실습**: [Odometry Uncertainty 시각화](https://alexjunholee.github.io/robotics-practice/app.html#odom_uncertainty) > Odometry의 불확실성이 시간에 따라 어떻게 누적되는지, 공분산 타원이 어떻게 커지는지 인터랙티브하게 확인할 수 있다. ### 14.11.1 필터 기반 vs 최적화 기반: 뭐가 더 나은가? SLAM/VIO 분야에서 오래된 논쟁이다. 결론부터 말하면, 수학적으로 Gauss-Newton 최적화와 Iterated EKF (IEKF)는 동치이다. 같은 문제를 다른 관점에서 푸는 것이다. - **필터 (EKF, MSCKF 등)**: 새 측정이 들어올 때마다 상태를 점진적으로 업데이트한다. 과거 상태는 marginalize하여 현재 상태만 유지한다. 메모리 효율적이고, proprioceptive 센서(IMU)와의 결합에 자연스럽다. - **최적화 (BA, factor graph)**: 과거 상태를 전부 유지하고 한꺼번에 최적화한다. 과거 데이터를 relinearize할 수 있으므로 정확도가 높다. 하지만 상태 수가 늘어나면 계산량이 커진다 (sliding window나 iSAM2로 완화). 그러면 "VINS-Mono(최적화)가 MSCKF(필터)보다 낫다"는 건 solver 때문인가? 아니다. 차이는 solver가 아니라 **시스템 구조**(어떤 상태를 유지하는가, 어떤 측정을 사용하는가)에서 온다. 최적화 기반이 relinearize를 통해 과거 linearization error를 줄일 수 있다는 점이 실질적 이점이다. 실무적 선택: - IMU 중심 + 경량 → 필터 (MSCKF, FAST-LIO2의 IEKF) - 카메라 중심 + 정확도 → 최적화 (VINS-Mono, ORB-SLAM3) - 둘 다 필요 → 하이브리드 (LIO-SAM: IMU preintegration을 factor로 넣은 최적화) (참고: [김기섭 블로그 — Gauss-Newton Opt == IEKF update?](https://gisbi-kim.github.io/blog/2022/03/05/gn-iekf-same.html)) ### 14.12 심화: Semantic SLAM *연구자가 되고 싶다면 여기서부터 읽어라.* 기존 SLAM은 순수하게 기하학적(geometric)인 지도를 만든다. 포인트 클라우드, 메쉬, occupancy grid 등 "공간의 형태"만 기록한다. 벽이 있다는 건 알지만 그것이 벽인지, 문인지, 책장인지는 모른다. Semantic SLAM은 지도에 의미(semantic) 정보를 결합한다. **접근 방식**: 1. **Object-level SLAM**: 개별 물체를 랜드마크로 사용한다. 점(point) 대신 물체의 3D bounding box나 ellipsoid를 랜드마크로 추정한다. - CubeSLAM: 물체를 3D cuboid로 표현, 단안 카메라로 물체 수준 SLAM - QuadricSLAM: 물체를 dual quadric으로 표현, 더 compact한 parametrization - 장점: 점 기반보다 data association이 견고하고, 물체 수준의 추론 가능 - 단점: 물체 검출기에 의존, 비정형 물체 처리 어려움 2. **Panoptic SLAM**: 모든 픽셀에 semantic label이 부여된 3D 지도를 구축한다. Panoptic segmentation(stuff + things 모두 분류)의 결과를 3D로 융합한다. - 실시간 panoptic segmentation + depth estimation + SLAM의 조합 - 로봇이 "이 방에 의자가 3개, 테이블이 1개"를 지도에서 바로 쿼리 가능 3. **Open-vocabulary SLAM**: 사전 정의된 카테고리에 국한되지 않고, CLIP 등 vision-language model의 feature를 지도에 저장한다. 자연어 쿼리로 "빨간색 머그컵이 어디 있었지?"를 지도에서 검색할 수 있다. - ConceptGraphs: 3D scene graph에 open-vocabulary feature를 부여 - 13장에서 다룬 3D Scene Graph (Hydra 등)와 직접 연결되는 주제 **동적 물체 처리**: Semantic label은 동적 환경에서 SLAM의 robustness를 높이는 데도 쓰인다. "사람", "차" 등 동적일 가능성이 높은 클래스의 feature를 tracking/mapping에서 제외하면, 정적 환경만으로 깨끗한 SLAM이 가능하다. - DynaSLAM: ORB-SLAM2 + Mask R-CNN으로 동적 물체 마스킹 - DS-SLAM: semantic segmentation으로 동적 영역 필터링 ```python # 동적 물체 필터링의 의사코드 dynamic_labels = {'person', 'car', 'bicycle', 'dog'} for feature in detected_features: pixel = feature.pixel_coords label = semantic_map[pixel.y, pixel.x] if label in dynamic_labels: feature.ignore = True # SLAM에서 제외 ``` > **추천 자료** > - [Nicholson et al., "QuadricSLAM: Dual Quadrics from Object Detections as Landmarks in Object-Oriented SLAM" (RA-L 2019)](https://arxiv.org/abs/1804.04011) — Object-level SLAM의 대표 논문 > - [ConceptGraphs (arXiv:2309.16650)](https://arxiv.org/abs/2309.16650) — Open-vocabulary 3D scene graph. 13장과 연계해서 읽을 것 > - [Bescos et al., "DynaSLAM: Tracking, Mapping and Inpainting in Dynamic Scenes" (RA-L 2018)](https://arxiv.org/abs/1806.05620) — 동적 환경 SLAM ### 14.13 심화: Multi-Robot SLAM *연구자가 되고 싶다면 여기서부터 읽어라.* 한 대의 로봇이 넓은 환경을 탐색하려면 시간이 오래 걸린다. 여러 로봇이 동시에 나눠서 탐색하면 시간을 줄일 수 있지만, 각 로봇이 만든 부분 지도(submap)를 하나의 일관된 글로벌 지도로 합치는 것은 단순하지 않다. **Centralized 접근**: 모든 로봇의 센서 데이터 또는 로컬 지도를 중앙 서버로 전송하고, 서버에서 전체 SLAM을 수행한다. - 장점: 구현이 비교적 간단, 최적해에 가까운 결과 - 단점: 통신 대역폭이 병목. 원본 센서 데이터를 전부 보내면 네트워크가 버티지 못한다. 서버 단일 장애점(single point of failure) **Distributed 접근**: 각 로봇이 독립적으로 로컬 SLAM을 수행하고, 로봇 간에 만나거나(rendezvous) 같은 장소를 방문했을 때(inter-robot loop closure) 상대 포즈를 추정하여 지도를 정렬한다. - 분산 최적화: 각 로봇이 자신의 포즈만 최적화하되, 이웃 로봇과의 제약 조건을 통해 전체가 수렴하도록 한다 - 통신 효율: 원본 데이터 대신 compressed descriptor(예: NetVLAD vector), keyframe descriptor, 또는 요약 지도(summary map)를 교환 **핵심 문제들**: 1. **Inter-robot loop closure**: 로봇 A가 방문한 장소를 로봇 B가 나중에 방문했을 때, 이를 인식하는 것. 14.14에서 다루는 place recognition이 여기서 핵심이다. 2. **좌표계 정렬(coordinate frame alignment)**: 각 로봇이 독립적으로 시작하면, 서로의 초기 좌표계가 다르다. Inter-robot loop closure를 통해 상대 변환을 추정해야 한다. 최소 3개의 inter-robot correspondence가 있으면 상대 SE(3) 변환을 풀 수 있다. 3. **Outlier rejection**: Inter-robot loop closure는 false positive가 많을 수 있다. Pairwise Consistency Maximization(PCM)이나 Graduated Non-Convexity(GNC) 같은 robust 기법으로 outlier를 걸러내야 한다. 4. **분산 최적화 알고리즘**: Distributed Gauss-Seidel, ADMM(Alternating Direction Method of Multipliers) 등을 사용하여 각 로봇이 로컬 최적화를 수행하면서 전체 일관성을 유지한다. **대표 시스템**: | 시스템 | 특징 | |---|---| | **Kimera-Multi** | Distributed, 3D mesh + semantic, Kimera 기반 | | **DOOR-SLAM** | Distributed, outlier-robust, DGS 최적화 | | **Swarm-SLAM** | ROS2 기반, 다양한 센서 지원, 경량 | > **추천 자료** > - [Lajoie et al., "DOOR-SLAM: Distributed, Online, and Outlier Resilient SLAM for Robotic Teams" (RA-L 2020)](https://arxiv.org/abs/1909.12198) — Distributed SLAM + robust optimization > - [Tian et al., "Kimera-Multi: Robust, Distributed, Dense Metric-Semantic SLAM" (ICRA 2022)](https://arxiv.org/abs/2106.14386) — Multi-robot semantic SLAM > - [Cieslewski et al., "Data-Efficient Decentralized Visual SLAM" (ICRA 2018)](https://arxiv.org/abs/1710.05772) — 통신 효율적인 분산 SLAM의 초기 연구 ### 14.14 심화: Place Recognition *연구자가 되고 싶다면 여기서부터 읽어라.* Loop closure의 핵심 문제: "지금 보는 장면을 이전에 본 적 있는가?" 이것은 이미지 검색(image retrieval) 문제로 귀결된다. 현재 프레임의 descriptor를 과거 모든 키프레임의 descriptor와 비교해서 가장 유사한 것을 찾는다. SLAM의 정확도는 loop closure의 정확도에 크게 의존하고, loop closure의 정확도는 place recognition에 달려 있다. **전통적 방법: Bag of Visual Words (BoVW)** DBoW2 라이브러리가 대표적이며 ORB-SLAM2/3에서 사용된다. 1. 대규모 이미지에서 local feature(ORB 등)를 추출 2. k-means clustering으로 visual vocabulary(단어 사전) 구축 3. 각 이미지를 "어떤 visual word가 몇 번 나타났는지"의 histogram(BoW vector)으로 표현 4. BoW vector 간의 유사도(L1-score 등)로 이미지 비교 장점: 빠르다 (inverted index 사용), 검증된 방법. 단점: 시점/조명 변화에 취약, vocabulary 학습이 필요. **학습 기반 방법: Global Descriptor** 이미지 전체를 하나의 compact vector(global descriptor)로 압축하는 방식. 근본적으로 BoVW보다 robust하다. - **NetVLAD** (2016): CNN feature를 VLAD (Vector of Locally Aggregated Descriptors) aggregation으로 결합하여 compact descriptor 생성. Place recognition의 패러다임을 바꾼 논문. 도시 규모의 장소 인식에서 기존 방법을 압도 - **CosPlace** (2022): Contrastive learning 기반. 학습 파이프라인이 단순하면서도 성능이 높다. 대규모 학습 데이터에서 효과적 - **MixVPR** (2023): Feature mixing 기법으로 다양한 condition(주간/야간, 계절 변화 등)에서 robust. 최소한의 학습으로 좋은 성능 - **AnyLoc** (2023): Foundation model(DINOv2) feature를 활용한 범용 place recognition. 별도 학습 없이(zero-shot) 다양한 환경에서 동작. Indoor/outdoor, aerial/ground 등을 가리지 않는다 **LiDAR 기반 Place Recognition**: 시각 정보 없이 3D 구조만으로 장소를 인식한다. 조명 변화에 완전히 면역이지만, 구조적으로 유사한 환경(예: 긴 복도)에서 혼동될 수 있다. - **Scan Context** (IROS 2018): 3D 포인트 클라우드를 bird-eye view로 투영한 뒤, 거리/높이 기반의 2D descriptor 생성. Rotation-invariant한 매칭 가능 - **OverlapTransformer** (2022): Transformer 기반으로 LiDAR range image에서 global descriptor를 학습 **Cross-modal Place Recognition**: 카메라 이미지로 query하고 LiDAR 지도에서 검색하거나, 그 반대. 센서가 다른 로봇 간의 multi-robot SLAM에서 중요하다. **Sequence Matching**: 단일 이미지 매칭의 한계를 극복하기 위해, 연속된 프레임의 시퀀스를 함께 매칭한다. - **SeqSLAM** (2012): 이미지 개별 유사도는 낮아도, 시퀀스 패턴이 일치하면 같은 장소로 판단. 극적인 외관 변화(주간 vs 야간)에서도 동작 - 최근 방법: sequence descriptor를 학습하여 더 효율적으로 시퀀스 매칭 실무 팁: 대부분의 SLAM 시스템은 DBoW2를 기본으로 쓴다. 만약 조명/계절 변화가 큰 환경에서 운용해야 한다면, 학습 기반 방법(NetVLAD 이후의 방법들)으로 교체하는 것을 고려하라. AnyLoc은 fine-tuning 없이 쓸 수 있어서 진입 장벽이 낮다. > **추천 자료** > - [Arandjelovic et al., "NetVLAD: CNN architecture for weakly supervised place recognition" (arXiv:1511.07247)](https://arxiv.org/abs/1511.07247) — 학습 기반 place recognition의 시작점 > - [Keetha et al., "AnyLoc: Towards Universal Visual Place Recognition" (arXiv:2308.00688)](https://arxiv.org/abs/2308.00688) — Foundation model 기반 zero-shot place recognition > - [Kim & Kim, "Scan Context: Egocentric Spatial Descriptor for Place Recognition within 3D Point Cloud Map" (IROS 2018)](https://ieeexplore.ieee.org/document/8593953) — LiDAR place recognition의 대표 방법 > - [김기섭 블로그 — Scan Context-based LiDAR Pose-graph SLAM 구현](https://gisbi-kim.github.io/blog/2021/05/17/sclidarslam.html) — Scan Context를 LiDAR SLAM에 통합한 구현 해설 > - [다크 프로그래머 — Bag of Words 기법](https://darkpgmr.tistory.com/125) — BoW의 원리를 이미지 검색과 연결하여 설명 > **기술 흐름: SLAM & Odometry** > - **~2007**: 고전기. EKF-SLAM, FastSLAM(파티클 필터 기반)이 주류. MonoSLAM(2007)이 실시간 단안 SLAM의 시작을 알림. PTAM(2007)이 Tracking/Mapping 분리 아키텍처를 제안 > - **2010~2015**: LSD-SLAM, SVO 등 Direct Method 등장. LOAM(2014)이 LiDAR SLAM의 기틀 마련. ORB-SLAM(2015)이 Feature-based Visual SLAM의 완성형으로 등극 > - **2015~2020**: Visual-Inertial 시대. VINS-Mono(2018), MSCKF 등 VIO 시스템이 드론/모바일에서 표준으로 자리잡음. DSO(2018)가 Direct Sparse 방식 제안. LiDAR-Inertial 결합 본격화: LIO-SAM(2020) > - **2020~2023**: FAST-LIO/FAST-LIO2(2021/2022)가 경량 LiDAR-Inertial 시스템의 새 표준. ORB-SLAM3(2021)가 Visual-Inertial + 멀티맵 지원. DROID-SLAM(2021)이 learning-based SLAM의 가능성을 보여줌. R3LIVE 등 멀티센서 통합 시스템 등장 > - **2024~**: 3DGS 기반 SLAM(SplaTAM, MonoGS, Gaussian-SLAM)이 Neural SLAM의 새 장을 열고 있다. Foundation Model과 SLAM의 결합(예: 자연어로 장소를 설명하여 위치를 찾는 등) 연구도 시작 > - **지금 주목할 것**: 기존 geometric SLAM(ORB-SLAM3, FAST-LIO2)은 이미 성숙한 기술이므로 꼭 익혀두고, 3DGS-SLAM과 learning-based 방법은 트렌드로 지켜보자. 실무에서는 LIO-SAM/FAST-LIO2(실외)와 ORB-SLAM3(실내)가 여전히 가장 많이 쓰인다. 벤치마크 데이터셋(KITTI, EuRoC, TUM RGB-D)으로 직접 돌려보는 것이 가장 빠른 학습 방법이다. ### 14.15 심화: Long-term Mapping *연구자가 되고 싶다면 여기서부터 읽어라.* 실제 환경에서 로봇을 운용하면 "지도를 한 번 만들고 끝"이 아니다. 같은 장소를 여러 번 방문하면서 지도를 업데이트하고, 동적 물체(사람, 차량)를 제거하고, 여러 세션의 데이터를 통합해야 한다. 이것이 long-term mapping이고, 실용적인 로봇 시스템에서 피할 수 없는 문제다. #### 14.15.1 Incremental Smoothing: iSAM에서 iSAM2까지 Filter-based SLAM(EKF 등)은 state 수가 늘어나면 Jacobian 행렬이 커져서 실시간 처리가 어렵다. iSAM(Kaess et al., TRO 2008)은 QR factorization의 R matrix를 Givens rotation으로 incremental하게 업데이트할 수 있음을 보여줬다. 새로운 measurement가 추가될 때 전체를 재계산하지 않고, 변경된 부분만 갱신한다. 다만 non-zero element가 누적되면 주기적으로 re-ordering이 필요하다. iSAM2(Kaess et al., IJRR 2012)는 Bayes tree 구조를 도입하여 이 한계를 극복했다. 영향받는 subtree만 re-elimination하므로, 대규모 문제에서도 일관된 성능을 보인다. GTSAM의 핵심 엔진이 바로 iSAM2다. #### 14.15.2 Dynamic Object Removal 지도에서 동적 물체를 제거하는 것은 long-term mapping의 필수 과제다. **Removert** (Kim et al., 2020): multi-resolution range image를 이용한 static/dynamic 분류. Point cloud를 range image로 투영한 뒤, 다른 시점에서 관측한 range와 비교하여 동적 여부를 판단한다. 보수적으로 static point를 확보한 후, false removed point를 복원하는 two-stage 방식을 사용한다. 핵심은 multiple confidence level로 trade-off를 조절할 수 있다는 점이다. 기존 방법들과의 비교: - Voxel ray-casting: 정확하지만 연산이 비싸다 - Visibility-based: 앞의 물체를 지우지만 뒤의 static point를 보존한다는 가정이 필요 - Segmentation-based: 효과적이나 unknown label에 약하고 scan-to-map 관계를 무시 **SuMa++** (Chen et al., IROS 2019): surfel-based mapping에 semantic label을 추가. LiDAR point에 normal과 semantic 정보를 더해서, semantic과 motion 모두에서 dynamic으로 판정된 경우에만 surfel을 제거한다. 단순히 다 지우지 않는 이유는, motion degenerate한 환경에서 dynamic이지만 geometric으로 유용한 point가 있을 수 있기 때문이다. #### 14.15.3 Multi-Session SLAM 같은 환경을 여러 날에 걸쳐 매핑하면, 각 세션의 trajectory를 하나로 합쳐야 한다. 문제는 gauge freedom — 각 세션의 좌표계가 다르므로 단순히 합치면 안 맞는다. **LT-mapper** (Kim et al., 2021): Scan Context 기반 anchor node로 multi-session을 정렬하고, positive/negative change detection으로 지도를 업데이트한다. 변화 정도에 따라 high-dynamic, low-dynamic, weak non-dynamic, strong positive-dynamic을 구분하여 delta map을 관리한다. **Continuous-Time Estimation** (Furgale et al., ICRA 2012): discrete-time 대신 B-spline basis function으로 trajectory를 표현하면, 다른 Hz의 센서들을 더 적은 변수로 통합할 수 있다. 빠른 센서(IMU)와 느린 센서(LiDAR, 카메라) 사이의 self-calibration에도 활용 가능하다. > **추천 자료** > - [Kaess et al., "iSAM2: Incremental Smoothing and Mapping Using the Bayes Tree" (IJRR 2012)](https://www.cs.cmu.edu/~kaess/pub/Kaess12ijrr.pdf) — Bayes tree 기반 incremental SLAM의 원본 논문 > - [Kim et al., "Remove, then Revert: Static Point Cloud Map Construction using Multiresolution Range Images" (IROS 2020)](https://github.com/irapkaist/removert) — Dynamic point 제거의 실용적 방법. 코드 공개 > - [Kim et al., "LT-mapper: A Modular Framework for LiDAR-based Lifelong Mapping" (ICRA 2022)](https://github.com/gisbi-kim/lt-mapper) — Multi-session SLAM 프레임워크 > - [Chen et al., "SuMa++: Efficient LiDAR-based Semantic SLAM" (IROS 2019)](https://github.com/PRBonn/semantic_suma) — Semantic 정보를 활용한 LiDAR SLAM --- ## 15. 로봇 프레임워크 (Robot Frameworks) 로봇 소프트웨어를 처음 짜는 사람이 가장 당황하는 순간은, 센서 드라이버·경로 계획·모터 제어를 하나의 프로그램 안에서 전부 돌려야 한다는 걸 깨달을 때이다. 프레임워크는 이 문제를 구조적으로 해결해 준다. 프레임워크 없이 로봇을 만들면 "센서 데이터 받기 → 판단하기 → 모터 명령 보내기"만 해도 스레드 관리·메시지 직렬화·좌표 변환을 전부 직접 구현해야 하는데, 이걸 모르면 첫 프로젝트에서 벽에 부딪힌다. 이 챕터에서는 사실상 업계 표준인 ROS를 중심으로, 시뮬레이터와 주변 도구까지 폭넓게 다룬다. ### 15.1 ROS (Robot Operating System) ROS는 로봇 소프트웨어 개발을 위한 오픈소스 프레임워크이다. 운영체제가 아닌 **미들웨어**로, 프로세스 간 통신, 패키지 관리, 도구 등을 제공한다. 로봇은 카메라·LiDAR·모터·제어기 같은 수십 가지 모듈이 동시에 돌아가야 하는데, 이 모듈들이 서로 데이터를 주고받는 방법을 통일해 주는 것이 ROS의 핵심 역할이다. ROS 없이 이 작업을 하면 소켓 프로그래밍부터 시작해야 하는데, 연구 시간의 대부분이 인프라 구축에 날아간다. ### 15.1.1 ROS1 vs ROS2 | 특징 | ROS1 | ROS2 | | --- | --- | --- | | 출시 | 2007 | 2017 | | 통신 | Custom (TCPROS) | DDS 기반 | | 실시간 | 미지원 | 지원 | | 보안 | 없음 | SROS2 | | Multi-robot | 어려움 | 쉬움 | | Master | 필요 (roscore) | 불필요 | | Python | 2/3 | 3 only | **현재 권장**: ROS2 (Humble) 하지만 많은 패키지가 아직 ROS1만 지원하므로 상황에 따라 선택. **ROS1 → ROS2 마이그레이션 현황**: 2024년을 기점으로 ROS1 Noetic의 공식 지원이 종료(EOL)되었고, 학계와 산업계 모두 ROS2로의 전환이 가속화되고 있다. 새로운 프로젝트라면 특별한 이유가 없는 한 ROS2로 시작하는 것이 맞다. 다만 기존 ROS1 패키지를 사용해야 하는 경우 `ros1_bridge` 패키지를 통해 ROS1과 ROS2 노드를 동시에 운용할 수 있으니 참고하자. Nav2, MoveIt2 등 핵심 패키지의 ROS2 포팅이 완료된 상태이므로, 대부분의 일반적인 로봇 개발에서는 ROS2만으로 충분하다. > **추천 자료** > - [ROS2 공식 튜토리얼](https://docs.ros.org/en/humble/Tutorials.html) — ROS2 Humble 기준 공식 단계별 가이드. 처음이라면 "Beginner: CLI tools"부터 시작 > - [The Construct - ROS2 Basics](https://www.youtube.com/@TheConstruct) — ROS 전문 교육 채널. 시뮬레이터 내에서 실습 가능 > - [ROS1 to ROS2 Migration Guide](https://docs.ros.org/en/humble/How-To-Guides/Migrating-from-ROS1.html) — 기존 ROS1 코드 이전 공식 가이드 ### 15.1.2 핵심 개념 이 개념들은 ROS의 뼈대이다. Topic, Service, Action의 차이를 정확히 모르면 "센서 데이터를 어떤 방식으로 보내야 하지?" 하는 질문에서 매번 막히게 된다. 각각이 언제 적합한지 감을 잡는 것이 중요하다. **Node (노드)**: - 실행 가능한 프로세스 - 단일 목적 (sensor driver, controller 등) **Topic (토픽)**: - 비동기 메시지 스트림 - Publisher/Subscriber 패턴 - 예: 센서 데이터, 명령 ```python # ROS2 Python Publisher import rclpy from rclpy.node import Node from std_msgs.msg import String class MinimalPublisher(Node): def __init__(self): super().__init__('minimal_publisher') self.publisher_ = self.create_publisher(String, 'topic', 10) self.timer = self.create_timer(0.5, self.timer_callback) def timer_callback(self): msg = String() msg.data = 'Hello, World!' self.publisher_.publish(msg) ``` **Service (서비스)**: - 동기 요청/응답 - 일회성 작업에 적합 - 예: 설정 변경, 상태 조회 **Action (액션)**: - 비동기 목표 지향 작업 - Feedback 제공 - 취소 가능 - 예: 네비게이션, 조작 정리하면, Topic은 카메라 영상처럼 계속 흘러가는 데이터에, Service는 "지금 배터리 잔량 알려줘"처럼 한 번 물어보고 답 받는 상황에, Action은 "저기까지 가"처럼 시간이 걸리는 작업에 쓰인다. 이 세 가지를 구분하지 못하면 시스템 설계에서 계속 꼬인다. **Parameter (파라미터)**: - 노드 설정값 - 런타임 변경 가능 > **⚠ AI 에이전트 주의**: AI에게 ROS2 코드를 짜달라고 할 때는 QoS 설정을 명시하라. AI는 기본적으로 QoS를 빠뜨리고, 센서 토픽이 조용히 안 들어와서 한참 헤매게 된다. > **추천 자료** > - [ROS2 Concepts — Understanding nodes, topics, services, actions](https://docs.ros.org/en/humble/Concepts.html) — 공식 개념 문서 > - [The Construct - ROS2 Topics vs Services vs Actions](https://www.youtube.com/@TheConstruct) — 세 가지 통신 방식 비교 영상 ### 15.1.3 도구 로봇을 개발할 때 "일단 코드 짜고 로봇에 올려 보자"는 접근은 위험하다. 센서 데이터가 제대로 들어오는지, 좌표계가 맞는지 눈으로 확인할 수 있어야 디버깅 시간이 줄어든다. 아래 도구들은 ROS 개발자라면 매일 쓰게 되는 필수 유틸리티이다. **rviz / rviz2**: - 3D 시각화 도구 - 센서 데이터, TF, 경로 등 표시 **rqt**: - Qt 기반 GUI 도구 모음 - rqt_graph: 노드/토픽 관계 시각화 - rqt_plot: 데이터 그래프 **rosbag / ros2 bag**: - 데이터 녹화/재생 - 디버깅, 알고리즘 개발에 필수 알아두면 실험 시간이 크게 줄어든다. 실제 로봇을 매번 돌려가며 알고리즘을 테스트하면 시간도 비용도 많이 든다. rosbag으로 한 번 녹화해 두면 같은 데이터로 몇 번이고 반복 실험이 가능하다. 재현성 측면에서 필수 도구다. ```bash # ROS2 bag 녹화 ros2 bag record -a -o my_bag # 재생 ros2 bag play my_bag ``` **tf2 (Transform Library)**: - 좌표계 변환 관리 - 시간에 따른 변환 추적 로봇에는 카메라 좌표계, LiDAR 좌표계, 베이스 좌표계, 월드 좌표계 등이 동시에 존재한다. "카메라에서 본 점이 로봇 기준으로 어디인가?"를 계산하려면 좌표 변환이 필수인데, tf2가 이를 자동으로 관리해 준다. 선형대수를 배웠다면 SE(3) 변환 행렬로 이해하면 된다. ```python # TF2 listener 예시 from tf2_ros import Buffer, TransformListener tf_buffer = Buffer() tf_listener = TransformListener(tf_buffer, self) # base_link → camera_link 변환 조회 transform = tf_buffer.lookup_transform('base_link', 'camera_link', rclpy.time.Time()) ``` > **추천 자료** > - [ROS2 tf2 Tutorials](https://docs.ros.org/en/humble/Tutorials/Intermediate/Tf2/Tf2-Main.html) — 좌표 변환 공식 튜토리얼 > - [The Construct - rviz2 Complete Guide](https://www.youtube.com/@TheConstruct) — rviz2 활용 영상 > - [ros2 bag CLI 문서](https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Recording-And-Playing-Back-Data/Recording-And-Playing-Back-Data.html) — 데이터 녹화/재생 공식 가이드 ### 15.1.4 주요 패키지 ROS의 진짜 힘은 커뮤니티가 만들어 놓은 패키지 생태계에 있다. 아래 패키지들은 거의 모든 로봇 프로젝트에서 한 번은 쓰게 되니, 이름이라도 기억해 두자. | 패키지 | 용도 | | --- | --- | | sensor_msgs | 센서 메시지 타입 | | geometry_msgs | 기하학 메시지 (Pose, Twist 등) | | cv_bridge | OpenCV ↔︎ ROS 이미지 변환 | | image_transport | 이미지 압축 전송 | | pcl_ros | PCL ↔︎ ROS 포인트 클라우드 | | nav2 | Navigation 스택 (ROS2) | > **추천 자료** > - [Nav2 Documentation](https://docs.nav2.org/) — ROS2 Navigation 스택 공식 문서 > - [ROS2 Package Index](https://index.ros.org/packages/) — ROS2 패키지 검색 ### 15.2 시뮬레이션 실물 로봇으로 바로 실험하면 하드웨어가 망가지거나, 사람이 다칠 수 있다. 시뮬레이터에서 먼저 충분히 테스트하고 실물로 넘어가는 것이 안전하고 효율적이다. 특히 강화학습처럼 수만 번의 에피소드가 필요한 학습 방법론에서는 시뮬레이터 없이는 사실상 연구가 불가능하다. 최근에는 **Embodied AI** 연구가 급격히 성장하면서, 로봇이 가상 환경에서 자율적으로 학습하고 탐험하는 시뮬레이터의 중요성이 더욱 커지고 있다. NVIDIA Isaac Sim, AI2-THOR, Habitat 같은 플랫폼이 이 분야를 이끌고 있으며, "시뮬레이터에서 학습한 정책을 실제 로봇에 그대로 전이(Sim-to-Real Transfer)"하는 것이 핵심 연구 주제가 되었다. ### 15.2.1 Gazebo ROS와 연동되는 물리 시뮬레이션 환경이다. Gazebo는 ROS와 가장 긴밀하게 연동되는 시뮬레이터이다. ROS 프로젝트 대부분의 시뮬레이션 데모가 Gazebo 기반으로 제공되므로, ROS를 쓰겠다면 Gazebo 사용법은 알아야 한다. **구성 요소**: - **SDF (Simulation Description Format)**: 환경 정의 - **URDF (Unified Robot Description Format)**: 로봇 모델 **Gazebo Classic vs Gazebo Sim (Ignition)**: - Gazebo Sim: 새로운 버전, ROS2 권장 - 모듈화된 구조, 더 나은 확장성 ```xml
``` > **추천 자료** > - [Gazebo Sim 공식 튜토리얼](https://gazebosim.org/docs) — Gazebo Sim(구 Ignition) 공식 가이드 > - [URDF Tutorial (ROS2)](https://docs.ros.org/en/humble/Tutorials/Intermediate/URDF/URDF-Main.html) — 로봇 모델링 기초 > - [The Construct - Gazebo Sim with ROS2](https://www.youtube.com/@TheConstruct) — Gazebo + ROS2 실습 영상 ### 15.2.2 NVIDIA Isaac Sim Embodied AI 시대에서 대규모 합성 데이터 생성과 Sim-to-Real 학습의 핵심 도구가 되고 있다. 사실적인 렌더링과 정확한 물리 엔진을 결합하여, 시뮬레이터에서 학습한 정책이 실제 로봇에서도 잘 작동하도록 해 준다. **특징**: - 사실적인 그래픽 (RTX 렌더링) - 정확한 물리 시뮬레이션 (PhysX 5) - 합성 데이터 생성 (Domain Randomization) - ROS2 통합 **주 용도**: - 조작(Manipulation) 연구 - 대규모 합성 데이터 생성 - Sim-to-Real 학습 **Embodied AI 시뮬레이터 비교**: Isaac Sim 외에도 Embodied AI 연구에 많이 쓰이는 시뮬레이터들이 있다. | 시뮬레이터 | 주 용도 | 특징 | | --- | --- | --- | | NVIDIA Isaac Sim | 범용 (Manipulation, Navigation) | RTX 렌더링, PhysX 5, 대규모 합성 데이터 | | AI2-THOR | 실내 내비게이션, 물체 상호작용 | 120+ 실내 장면, 사실적 상호작용 | | Habitat (Meta) | 시각 내비게이션, Embodied QA | 초고속 렌더링 (수천 FPS), 대규모 학습 | | iGibson | 실내 로봇 작업 | 물리 기반 렌더링, 가정환경 | | MuJoCo | 로봇 제어, 강화학습 | 정확한 접촉 역학, 빠른 시뮬레이션 | > **추천 자료** > - [NVIDIA Isaac Sim 공식 문서](https://docs.omniverse.nvidia.com/isaacsim/latest/index.html) — 설치부터 고급 활용까지 > - [AI2-THOR Documentation](https://ai2thor.allenai.org/ithor/documentation) — Embodied AI 연구용 실내 시뮬레이터 > - [Habitat Documentation](https://aihabitat.org/docs/habitat2/) — Meta의 Embodied AI 플랫폼 ### 15.2.3 CARLA 자율주행 연구를 위한 시뮬레이터이다. 자율주행 논문에서 실험 환경으로 CARLA를 사용하는 경우가 매우 많다. 자율주행 쪽 연구를 하고 싶다면 CARLA 환경을 다루는 법을 익혀 두면 좋다. **특징**: - 도시 환경 시뮬레이션 - 다양한 날씨, 시간대 - 센서 시뮬레이션 (카메라, LiDAR, Radar) - ROS 브릿지 제공 > **추천 자료** > - [CARLA Documentation](https://carla.readthedocs.io/) — 공식 문서 및 Python API 레퍼런스 > - [CARLA Simulator YouTube](https://www.youtube.com/@intaborlado5265) — 시뮬레이터 활용 데모 영상 ### 15.3 기타 프레임워크 ROS와 시뮬레이터 외에도, 특정 용도에 특화된 프레임워크와 라이브러리들이 있다. 이것들을 직접 만들 필요 없이 가져다 쓸 수 있다는 것이 로보틱스 생태계의 장점이다. **Isaac ROS**: - NVIDIA GPU 가속 ROS 패키지 - DNN 추론, Visual SLAM, 3D 인식 - Jetson 최적화 **Autoware**: - 완전한 자율주행 스택 - 인식, 계획, 제어 포함 - ROS2 기반 (Autoware.Universe) **ROS 외 도구**: - **OpenCV**: 컴퓨터 비전 - **Open3D**: 3D 처리/시각화 - **Eigen**: 선형대수 (C++) - **Sophus**: SE(3), SO(3) 연산 예를 들어 Eigen과 Sophus는 로보틱스에서 좌표 변환을 다룰 때 핵심적으로 쓰이는 라이브러리이다. 선형대수를 수업에서 배웠다면, Eigen이 그 행렬 연산을 C++ 코드로 구현한 것이라고 보면 된다. Sophus는 거기에 회전(SO(3))과 강체 변환(SE(3))을 편하게 다루는 기능을 추가한 것이다. > **추천 자료** > - [OpenCV Tutorials](https://docs.opencv.org/4.x/d9/df8/tutorial_root.html) — 컴퓨터 비전 기초부터 고급까지 > - [Open3D Documentation](http://www.open3d.org/docs/) — 3D 데이터 처리 라이브러리 공식 문서 > - [Eigen Getting Started](https://eigen.tuxfamily.org/dox/GettingStarted.html) — C++ 선형대수 라이브러리 입문 ### 15.4 심화: 시스템 설계 *연구자가 되고 싶다면 여기서부터 읽어라.* **15.4.1 Latency Budgeting** - 전체 파이프라인의 latency를 구간별로 할당 - 예시: 자율주행 — 센서 입력(10ms) → 인식(50ms) → 계획(30ms) → 제어(10ms) = 100ms total - 각 구간이 budget을 초과하면 전체가 실패. 가장 느린 구간이 bottleneck - profiling 방법: ROS2 callback duration, `ros2 topic delay`, tracing (ros2_tracing) **15.4.2 Behavior Tree** - 유한 상태 기계(FSM)보다 확장성이 좋은 로봇 행동 설계 방법 - 노드 유형: Sequence (순차), Fallback (대안), Action (실행), Condition (조건) - 장점: 모듈적 — 하위 트리를 독립적으로 테스트/재사용 가능 - ROS2에서: BehaviorTree.CPP, Nav2에서 사용 - FSM은 상태가 늘어나면 전이가 기하급수적으로 복잡해진다. BT는 트리 구조로 복잡도를 관리 **15.4.3 Safety와 Failsafe** - Watchdog timer: 특정 시간 내에 heartbeat 없으면 safe stop - E-stop (Emergency Stop): 하드웨어 레벨의 전원 차단 - Software safety: 속도 제한, workspace 제한, collision check - ISO 13482: 서비스 로봇 안전 표준 (개요만) - 실무: 새 알고리즘을 올릴 때는 safety wrapper를 먼저 만들고, 그 안에서 실험 **15.4.4 배포와 필드 테스트** - CI/CD: colcon build + test 자동화, Docker 이미지 빌드 - Hardware-in-the-Loop (HIL): 실제 센서 데이터를 재생하면서 새 코드 테스트 - 필드 테스트 프로토콜: 통제된 환경 → 반통제 → 실제 환경, 단계적으로 - 로그 수집: rosbag + 시스템 로그 (journalctl) + 센서 상태 모니터링 > 추천 자료: > - [BehaviorTree.CPP Documentation](https://www.behaviortree.dev/) — BT 설계 패턴과 튜토리얼 > - [Nav2 Documentation](https://docs.nav2.org/) — ROS2 Navigation2 스택. BT 기반 설계의 실전 예시 ### 기술 흐름: 로봇 프레임워크의 과거 → 현재 → 미래 ``` 2007 ─── ROS1 출시 (Willow Garage) │ 로봇 미들웨어로 널리 사용됨 │ 2012 ─── Gazebo Classic 독립 프로젝트화 │ 시뮬레이션이 로봇 개발의 필수 단계로 자리잡음 │ 2017 ─── ROS2 첫 릴리즈 │ DDS 기반 통신, 실시간 지원, 보안 추가 │ 2019 ─── NVIDIA Isaac Sim 공개 │ RTX 기반 고품질 렌더링 + 합성 데이터 생성 │ 2020 ─── Habitat, AI2-THOR 등 Embodied AI 시뮬레이터 부상 │ 대규모 학습 기반 로봇 정책 연구 본격화 │ 2022 ─── ROS2 Humble LTS 출시 │ 산업계 채택 가속화, Nav2/MoveIt2 안정화 │ 2024 ─── ROS1 Noetic EOL (지원 종료) │ ROS2 전환의 사실상 마감 시점 │ 2025+ ── Embodied AI + Foundation Models 시대 시뮬레이터에서 대규모 사전학습 → Sim-to-Real 전이 언어 명령 기반 로봇 조작 (VLA) NVIDIA Isaac Lab 등에서 시뮬레이터→학습→실제 로봇 배포를 일관된 파이프라인으로 제공 시작 ``` --- ## 16. 개발 환경 & 도구 로봇 연구를 하다 보면, 알고리즘 자체보다 "환경 세팅"에 시간을 더 많이 쓰는 경우가 흔하다. CUDA 버전이 안 맞아서 하루를 날리거나, 다른 사람 코드를 클론했는데 파이썬 버전 차이로 안 돌아가는 경험을 한 번쯤 하게 된다. 이 챕터에서 다루는 도구들은 그런 삽질을 최소화해 주는 것들이다. 화려하지 않지만, 연구 생산성에 직접적으로 영향을 미치는 실전 지식이다. ### 16.1 프로그래밍 언어 AI 코딩 에이전트가 코드 작성을 상당 부분 대신해 주는 시대이다. 이제 중요한 건 코드를 처음부터 짜는 능력보다, **기존 코드를 읽고 이해하는 능력**이다. 다른 사람의 연구 코드를 클론해서 구조를 파악하고, AI가 생성한 코드를 검증하고, 문제가 생겼을 때 어디를 고쳐야 하는지 판단할 수 있어야 한다. ### 16.1.1 C++ **용도**: 실시간 시스템, ROS 노드, SLAM, 성능 중요 모듈 연구실에서 다루는 핵심 코드 대부분이 C++이다. SLAM, 실시간 제어, ROS 패키지의 핵심 로직이 전부 C++로 작성되어 있고, 이 코드를 읽고 수정할 일이 많다. ORB-SLAM3, LOAM, VINS-Mono 같은 코드를 이해하려면 C++에 익숙해야 한다. **장점**: - 빠른 실행 속도 - 메모리 직접 제어 - ROS/SLAM 코드 대부분 C++ **단점**: - 배우기 어려움 - 개발 속도 느림 - 메모리 관리 실수 **modern C++ (C++17/20)**: ```cpp // 스마트 포인터 auto ptr = std::make_shared
(); // Range-based for for (const auto& item : container) { ... } // Lambda auto func = [&](int x) { return x * 2; }; ``` > **추천 자료** > - [C++ Core Guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines) — 모던 C++ 코딩 가이드 (Bjarne Stroustrup, Herb Sutter) > - [The Cherno - C++ Playlist](https://www.youtube.com/playlist?list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb) — C++ 기초부터 고급까지 영상 시리즈 > **⚠ AI 에이전트 주의**: AI가 생성한 C++ 코드가 x86에서는 빌드되지만 Jetson(ARM)에서 실패하는 경우가 많다. 크로스 컴파일 환경이나 타겟 아키텍처를 알려줘라. ### 16.1.2 Python **용도**: 프로토타이핑, 딥러닝 학습/추론, 데이터 분석, 시각화 PyTorch 학습 스크립트나 데이터 전처리 같은 작업에 사용된다. 연구에서 자주 마주치긴 하지만, AI 에이전트가 가장 잘 다루는 언어이기도 해서 직접 작성하는 비중은 줄어들고 있다. 읽고 이해할 수 있으면 충분하다. **자주 쓰는 라이브러리**: ```bash pip install numpy scipy matplotlib pip install opencv-python open3d pip install torch torchvision pip install transformers # HuggingFace ``` > **추천 자료** > - [Real Python](https://realpython.com/) — Python 기초부터 고급까지 체계적 튜토리얼 > - [Fireship - Python in 100 Seconds](https://www.youtube.com/watch?v=x7X9w_GIm1s) — Python 전체를 빠르게 훑어보는 영상 ### 16.2 개발 환경 설정 ### 16.2.1 Ubuntu 로보틱스 개발은 사실상 Ubuntu에서 이루어진다. ROS가 Ubuntu를 1차 지원 플랫폼으로 삼고 있고, GPU 드라이버·CUDA·cuDNN 등의 호환성도 Ubuntu에서 가장 잘 검증되어 있기 때문이다. macOS나 Windows에서도 일부 개발이 가능하지만, 결국 실제 로봇에 올릴 때는 Ubuntu로 돌아오게 된다. **권장 버전**: - Ubuntu 22.04 LTS (ROS2 Humble) - Ubuntu 24.04 LTS (ROS2 Jazzy) **초기 설정**: ```bash # 기본 도구 sudo apt update && sudo apt upgrade -y sudo apt install -y build-essential cmake git curl wget # Python 관련 sudo apt install -y python3-pip python3-venv # 개발 도구 sudo apt install -y vim tmux htop ``` > **추천 자료** > - [The Missing Semester of Your CS Education (MIT)](https://missing.csail.mit.edu/) — 셸, vim, tmux, Git 등 "수업에서는 안 가르치지만 매일 쓰는" 개발 도구를 체계적으로 정리. 추천한다 > - [Fireship - Linux in 100 Seconds](https://www.youtube.com/watch?v=rrB13utjYV4) — Linux가 뭔지 빠르게 감 잡기 ### 16.2.2 CUDA / cuDNN 딥러닝에 필수적인 GPU 가속을 위한 NVIDIA 도구이다. 딥러닝 모델 학습은 CPU로는 현실적으로 불가능하다. GPU 가속을 위해 CUDA가 필요한데, PyTorch와 CUDA 버전이 안 맞으면 `import torch` 한 줄에서부터 에러가 난다. 로보틱스 연구자가 가장 많이 겪는 환경 문제 중 하나이다. **설치 확인**: ```bash nvidia-smi # GPU 상태 nvcc --version # CUDA 버전 ``` **권장 버전**: CUDA 12.x, cuDNN 8.x **주의**: PyTorch/TensorFlow 버전과 CUDA 버전 호환성 확인 필수 > **추천 자료** > - [PyTorch - Previous Versions](https://pytorch.org/get-started/previous-versions/) — PyTorch와 CUDA 버전 매칭 확인. 설치 전 확인할 것 > - [NVIDIA CUDA Toolkit Documentation](https://docs.nvidia.com/cuda/) — CUDA 공식 문서 **NVIDIA 드라이버 설치 트러블슈팅** Ubuntu에서 NVIDIA 드라이버 설치 시 가장 흔한 문제는 `nouveau` (오픈소스 드라이버)와의 충돌이다. ```bash # nouveau 비활성화 sudo bash -c "echo blacklist nouveau > /etc/modprobe.d/blacklist-nvidia-nouveau.conf" sudo bash -c "echo options nouveau modeset=0 >> /etc/modprobe.d/blacklist-nvidia-nouveau.conf" sudo update-initramfs -u sudo reboot # 드라이버 설치 (권장: apt 사용) sudo apt install nvidia-driver-535 # 버전은 GPU에 맞게 조정 sudo reboot # 확인 nvidia-smi ``` 설치 후 `nvidia-smi`에서 GPU가 안 보이면: (1) Secure Boot가 켜져 있는지 확인 (BIOS에서 끌 것), (2) `sudo dkms status`로 드라이버 모듈 상태 확인. (참고: [정진용 블로그](https://jinyongjeong.github.io/2016/11/22/ubuntu_graphic_driver_install/)) **CUDA/cuDNN 버전 호환성** PyTorch와 CUDA 버전이 안 맞으면 `import torch` 한 줄에서 에러가 난다. 확인 순서: ```bash # 1. GPU 확인 nvidia-smi # 오른쪽 상단의 CUDA Version은 "드라이버가 지원하는 최대 버전" # 2. 설치된 CUDA toolkit 확인 nvcc --version # 3. PyTorch가 사용하는 CUDA 확인 python -c "import torch; print(torch.version.cuda)" # 세 버전이 호환되어야 한다. PyTorch 공식 사이트에서 조합 확인: # https://pytorch.org/get-started/locally/ ``` (참고: [정진용 블로그](https://jinyongjeong.github.io/2016/09/19/cuda_setting/)) **OpenCV + CUDA 직접 빌드** apt로 설치하는 `python3-opencv`나 `pip install opencv-python`은 CUDA 가속이 안 된다. GPU 가속이 필요하면 (DNN 모듈, optical flow 등) 소스에서 직접 빌드해야 한다. ```bash # 의존성 설치 sudo apt install -y cmake git libgtk2.0-dev pkg-config \ libavcodec-dev libavformat-dev libswscale-dev \ libtbb-dev libjpeg-dev libpng-dev # OpenCV + contrib 소스 git clone https://github.com/opencv/opencv.git git clone https://github.com/opencv/opencv_contrib.git # 빌드 (CUDA 활성화) cd opencv && mkdir build && cd build cmake -D CMAKE_BUILD_TYPE=Release \ -D CMAKE_INSTALL_PREFIX=/usr/local \ -D OPENCV_EXTRA_MODULES_PATH=../../opencv_contrib/modules \ -D WITH_CUDA=ON \ -D CUDA_ARCH_BIN="8.6" \ # GPU에 맞게 조정 (RTX 3090=8.6, RTX 4090=8.9) -D WITH_CUDNN=ON \ -D OPENCV_DNN_CUDA=ON \ -D BUILD_opencv_python3=ON \ .. make -j$(nproc) sudo make install ``` `CUDA_ARCH_BIN`은 자기 GPU에 맞춰야 한다. 틀리면 빌드는 되지만 런타임에 느리거나 에러가 난다. [NVIDIA GPU Compute Capability](https://developer.nvidia.com/cuda-gpus)에서 확인. 주의: ROS 환경에서 pip opencv와 apt cv_bridge가 충돌하는 문제가 있다 (19장 AI 에이전트 트러블슈팅 참고). CUDA OpenCV를 직접 빌드하면 이 문제가 더 복잡해질 수 있으니, Docker로 격리하는 것을 권장한다. (참고: [다크 프로그래머 — OpenCV + CUDA 직접 빌드하기](https://darkpgmr.tistory.com/184)) ### 16.2.3 환경 관리 프로젝트마다 필요한 Python 버전과 라이브러리 버전이 다르다. 환경 관리 도구 없이 `pip install`을 전역으로 하면 프로젝트 A에 필요한 라이브러리가 프로젝트 B와 충돌하는 "의존성 지옥(dependency hell)"에 빠진다. Conda나 venv로 프로젝트별 독립 환경을 만드는 것이 기본이다. **Conda** (권장): ```bash # Miniconda 설치 wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh bash Miniconda3-latest-Linux-x86_64.sh # 환경 생성 conda create -n myenv python=3.10 conda activate myenv # 패키지 설치 conda install pytorch torchvision pytorch-cuda=12.1 -c pytorch -c nvidia ``` **venv** (가벼움): ```bash python3 -m venv myenv source myenv/bin/activate pip install -r requirements.txt ``` 예를 들어 SLAM 연구에는 Python 3.8이 필요하고, 최신 트랜스포머 모델에는 Python 3.10이 필요할 수 있다. Conda를 쓰면 `conda activate slam_env`, `conda activate transformer_env`로 전환하면 끝이다. > **추천 자료** > - [Conda Documentation - Managing Environments](https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html) — Conda 환경 관리 공식 가이드 > - [Python venv Documentation](https://docs.python.org/3/library/venv.html) — 파이썬 공식 가상환경 문서 ### 16.3 Docker ### 16.3.1 왜 Docker인가? 연구실에서 코드를 공유할 때 가장 많이 듣는 말이 "내 컴퓨터에선 돌아가는데..."이다. Docker는 그 문제를 해결한다. OS, 라이브러리, 환경 설정을 통째로 패키징해서 어디서든 동일한 실행 환경을 보장하기 때문이다. 논문 코드를 재현할 때도 Docker 이미지가 제공되면 훨씬 수월하다. - **재현성**: 동일한 환경 보장 - **격리**: 시스템 오염 방지 - **배포**: 쉬운 공유 및 배포 - **의존성**: 복잡한 의존성 관리 ### 16.3.2 기본 사용법 ```bash # 이미지 다운로드 docker pull nvidia/cuda:12.1.0-devel-ubuntu22.04 # 컨테이너 실행 docker run -it --rm \ --gpus all \ -v $(pwd):/workspace \ nvidia/cuda:12.1.0-devel-ubuntu22.04 bash # Dockerfile 빌드 docker build -t my_image . ``` **Dockerfile 예시**: ```docker FROM nvidia/cuda:12.1.0-devel-ubuntu22.04 RUN apt-get update && apt-get install -y \ python3-pip git COPY requirements.txt /tmp/ RUN pip3 install -r /tmp/requirements.txt WORKDIR /workspace ``` > **추천 자료** > - [Docker 공식 Getting Started Guide](https://docs.docker.com/get-started/) — Docker 처음이라면 여기부터. 컨테이너, 이미지, 볼륨 개념을 잘 설명 > - [NetworkChuck - Docker Tutorial](https://www.youtube.com/watch?v=eGz9DS-aIeY) — Docker를 재미있고 쉽게 설명하는 영상. 입문용으로 적합 > - [Fireship - Docker in 100 Seconds](https://www.youtube.com/watch?v=Gjnup-PuquQ) — Docker 핵심 개념을 빠르게 훑어보기 ### 16.3.3 NVIDIA Container Toolkit 일반 Docker 컨테이너 안에서는 GPU가 보이지 않는다. 딥러닝 학습이나 CUDA 기반 연산을 하려면 nvidia-container-toolkit을 설치하고 `--gpus all` 플래그를 사용해야 한다. 로보틱스 연구에서 Docker를 쓴다면 거의 100% 필요하다. 참고: 과거에 쓰던 `nvidia-docker2`는 deprecated되었다. 현재는 `nvidia-container-toolkit`이 표준이고, `--runtime=nvidia` 대신 `--gpus all`을 쓴다. ```bash # nvidia-container-toolkit 설치 (Ubuntu 22.04/24.04) curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | \ sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \ sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \ sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit # Docker 데몬에 NVIDIA 런타임 등록 sudo nvidia-ctk runtime configure --runtime=docker sudo systemctl restart docker # 테스트 docker run --rm --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi ``` ### 16.3.4 실전 레시피: ROS2 + GPU + GUI + 센서 로보틱스에서 Docker를 쓸 때는 GPU, GUI(RViz/Gazebo), USB 센서를 동시에 써야 하는 경우가 대부분이다. 이걸 하나씩 붙이면 충돌하기 쉽고, 한번에 설정하는 게 낫다. [turlucode/ros-docker-gui](https://github.com/turlucode/ros-docker-gui)가 이 조합을 잘 정리해둔 프로젝트이니 참고할 것. 아래는 그 구조를 기반으로 현재 환경(nvidia-container-toolkit + ROS2 Humble)에 맞춘 레시피다. **1단계: X11 포워딩 준비 (호스트)** ```bash # 호스트에서 한 번만 실행 sudo apt-get install -y xauth xhost +local:docker ``` `xhost +local:docker`는 Docker 컨테이너에서만 X 서버 접근을 허용한다. `xhost +`(전체 허용)보다 안전하지만, 보안이 중요한 환경이라면 `xauth` 기반 인증을 쓰는 게 맞다. **2단계: 실행 스크립트** ```bash #!/bin/bash # run_ros2_docker.sh — GPU + GUI + USB 센서 풀 세팅 docker run --rm -it \ --gpus all \ --privileged \ --net=host \ --ipc=host \ -e DISPLAY=$DISPLAY \ -e QT_X11_NO_MITSHM=1 \ -e ROS_DOMAIN_ID=42 \ -v /tmp/.X11-unix:/tmp/.X11-unix:rw \ -v $HOME/.Xauthority:/root/.Xauthority:ro \ -v /dev:/dev \ -v $HOME/catkin_ws:/root/catkin_ws \ --name ros2_dev \ osrf/ros:humble-desktop \ bash ``` 각 플래그가 뭘 하는지: | 플래그 | 역할 | |--------|------| | `--gpus all` | GPU 패스스루 (nvidia-container-toolkit) | | `--privileged` | USB/시리얼 디바이스 전체 접근. 프로덕션에서는 `--device`로 개별 매핑할 것 | | `--net=host` | DDS multicast 통신을 위해 호스트 네트워크 공유. ROS2 노드 간 통신에 필수 | | `--ipc=host` | 공유 메모리. Rviz 등 GUI 도구에서 필요 | | `-e QT_X11_NO_MITSHM=1` | 이거 빠지면 RViz가 segfault로 죽는다. MIT-SHM이 Docker에서 안 됨 | | `-e ROS_DOMAIN_ID=42` | 같은 네트워크의 다른 ROS2 시스템과 격리. 연구실에서 여러 명이 쓸 때 필수 | | `-v /dev:/dev` | 센서 USB가 언제 꽂힐지 모르므로 /dev 전체 마운트. `--privileged`와 세트 | | `-v .Xauthority` | X11 인증. 이걸 안 걸면 `cannot open display` | **3단계: 작업 후 컨테이너 저장** ```bash # 컨테이너 안에서 패키지 설치 등 작업을 했으면 커밋 docker commit ros2_dev my_ros2_workspace:v1 # 다음번에는 저장된 이미지로 실행 # run_ros2_docker.sh에서 이미지 이름만 바꾸면 된다 ``` **Dockerfile로 관리하는 방식** (더 권장): ```dockerfile FROM osrf/ros:humble-desktop # 기본 도구 RUN apt-get update && apt-get install -y \ python3-pip git wget curl vim \ ros-humble-rviz2 \ ros-humble-rqt* \ && rm -rf /var/lib/apt/lists/* # Python 패키지 RUN pip3 install torch torchvision numpy opencv-python-headless # ROS2 워크스페이스 RUN mkdir -p /root/ros2_ws/src WORKDIR /root/ros2_ws # 소스 빌드가 필요한 패키지가 있으면 여기서 # RUN cd src && git clone https://github.com/... # RUN . /opt/ros/humble/setup.sh && colcon build ENTRYPOINT ["/ros_entrypoint.sh"] CMD ["bash"] ``` `docker commit`보다 Dockerfile이 나은 이유: 나중에 "이 이미지에 뭐가 깔려있지?"를 추적할 수 있다. commit으로 만든 이미지는 히스토리가 없어서 재현이 안 된다. > **추천 자료** > - [turlucode/ros-docker-gui](https://github.com/turlucode/ros-docker-gui) — ROS + NVIDIA + GUI Docker 설정 레퍼런스. Melodic부터 Humble까지 지원 > - [NVIDIA Container Toolkit Documentation](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/index.html) — 공식 설치 가이드 > - [OSRF Docker Images](https://hub.docker.com/r/osrf/ros) — ROS 공식 Docker 이미지. `humble-desktop`이 GUI 포함 버전 > **AI 에이전트 주의**: AI에게 Docker 설정을 물어볼 때는 "ROS2 + GPU + USB 센서 + GUI 시각화"를 동시에 쓸 건지 한번에 알려줘라. 각각을 따로 물어보면 서로 충돌하는 설정을 준다. 특히 `QT_X11_NO_MITSHM=1`과 `ROS_DOMAIN_ID`는 AI가 빠뜨리는 대표적인 항목이다. ### 16.4 원격 관리: Git, SSH, 파일 전송 연구실 서버에 SSH로 접속해서 실험을 돌리고, 코드는 Git으로 관리하고, 데이터는 서버 간에 주고받는 것이 일상이다. 여기 나오는 도구들을 익혀 두면 이 과정이 매끄러워진다. ### 16.4.1 Git/GitHub ### 16.4.1.1 기본 워크플로우 코드를 관리하지 않으면, "어제 돌아가던 코드가 오늘은 왜 안 되지?"라는 상황이 반복된다. Git은 모든 변경 이력을 추적해 주므로, 언제든 과거 상태로 돌아갈 수 있다. 연구 코드라도 Git으로 관리하는 습관을 들이자. ```bash # 저장소 클론 git clone https://github.com/user/repo.git # 변경 사항 확인 git status git diff # 커밋 git add . git commit -m "feat: add new feature" # 푸시 git push origin main ``` > **추천 자료** > - [GitHub's Git Handbook](https://docs.github.com/en/get-started/using-git/about-git) — Git 핵심 개념을 깔끔하게 정리한 공식 가이드 > - [The Missing Semester - Version Control (Git)](https://missing.csail.mit.edu/2020/version-control/) — MIT 강의. Git의 내부 모델(DAG)까지 설명해 줘서 깊이 있게 이해 가능 ### 16.4.1.2 브랜치 전략 **Git Flow**: - `main`: 안정 버전 - `develop`: 개발 버전 - `feature/*`: 기능 개발 - `hotfix/*`: 긴급 수정 **GitHub Flow** (간단): - `main`: 항상 배포 가능 - `feature-branch`: 기능별 브랜치 → PR → Merge 연구실에서 여러 사람이 같은 코드를 수정할 때 브랜치 전략 없이 `main`에 직접 push하면 충돌이 끊이질 않는다. 연구 코드라면 GitHub Flow 정도면 충분하다. 기능 하나당 브랜치 하나를 만들고, PR을 통해 머지하는 습관을 들이자. ### 16.4.1.3 협업 **Pull Request (PR)**: 1. Fork 또는 브랜치 생성 2. 변경 사항 커밋 3. PR 생성 및 리뷰 요청 4. 코드 리뷰 후 머지 **커밋 메시지 규칙** (Conventional Commits): ``` feat: 새로운 기능 fix: 버그 수정 docs: 문서 변경 refactor: 리팩토링 test: 테스트 추가/수정 chore: 빌드/설정 변경 ``` > **추천 자료** > - [GitHub's Git Handbook](https://docs.github.com/en/get-started/using-git/about-git) — GitHub에서 직접 만든 Git 입문 가이드 > - [Conventional Commits Specification](https://www.conventionalcommits.org/) — 커밋 메시지 규칙 공식 스펙 ### 16.4.2 SSH 연구실 GPU 서버에 접속하는 기본 도구이다. 비밀번호 대신 키 인증을 쓰면 편하고 안전하다. ```bash # 키 생성 (처음 한 번) ssh-keygen -t ed25519 # 서버에 공개키 등록 ssh-copy-id user@server_ip # 접속 ssh user@server_ip # 포트 포워딩 (서버의 Jupyter/TensorBoard를 로컬에서 보기) ssh -L 8888:localhost:8888 user@server_ip ``` **~/.ssh/config**를 설정해 두면 매번 IP와 사용자명을 입력하지 않아도 된다: ``` Host lab-server HostName 192.168.1.100 User junholee IdentityFile ~/.ssh/id_ed25519 ``` 이후 `ssh lab-server`로 바로 접속 가능. VS Code Remote-SSH도 이 설정을 읽는다. ### 16.4.3 SCP & rsync 서버와 로컬 간에 파일을 주고받는 도구이다. **SCP** — 단순한 파일 복사: ```bash # 로컬 → 서버 scp model.pth user@server:/home/user/weights/ # 서버 → 로컬 scp user@server:/home/user/results/log.txt ./ # 디렉토리 복사 scp -r dataset/ user@server:/data/ ``` **rsync** — 변경된 부분만 전송. 대용량 데이터셋이나 반복 전송에 유리하다: ```bash # 로컬 → 서버 (변경분만 전송, 진행률 표시) rsync -avz --progress dataset/ user@server:/data/dataset/ # 서버 → 로컬 rsync -avz user@server:/home/user/results/ ./results/ # 삭제된 파일도 동기화 (미러링) rsync -avz --delete source/ user@server:/data/source/ ``` `scp`는 매번 전체를 복사하지만, `rsync`는 diff만 보내므로 수십 GB 데이터셋을 동기화할 때 차이가 크다. ### 16.4.4 Tailscale 연구실 서버가 NAT/방화벽 뒤에 있으면 외부에서 SSH 접속이 안 된다. Tailscale은 WireGuard 기반 VPN으로, 설치만 하면 어디서든 연구실 서버에 직접 접속할 수 있게 해 준다. ```bash # 설치 (서버와 로컬 양쪽에) curl -fsSL https://tailscale.com/install.sh | sh sudo tailscale up # 상태 확인 — 연결된 기기 목록과 IP 확인 tailscale status # 이후 Tailscale IP로 SSH ssh user@100.x.y.z ``` **장점**: - 포트 포워딩이나 공유기 설정이 필요 없다 - 카페, 집, 학교 어디서든 같은 IP로 서버에 접속 - Tailscale SSH를 쓰면 SSH 키 관리도 자동화 가능 **~/.ssh/config**에 Tailscale IP를 등록해 두면 편하다: ``` Host lab-gpu HostName 100.x.y.z User junholee ``` > **추천 자료** > - [Tailscale 공식 문서](https://tailscale.com/kb/) — 설치부터 ACL 설정까지 > - [The Missing Semester - Remote Machines](https://missing.csail.mit.edu/2020/command-line/#remote-machines) — SSH, 포트 포워딩, tmux 등 원격 작업 기초 ### 16.5 실험 관리 ### 16.5.1 Weights & Biases (wandb) 딥러닝 실험을 하다 보면 "어제 돌린 모델의 하이퍼파라미터가 뭐였지?"라는 상황이 매일 발생한다. 실험 관리 도구 없이 엑셀이나 노트로 기록하면 금방 한계에 부딪힌다. wandb는 학습 과정을 자동으로 로깅하고 시각화해 주며, 팀원과 결과를 공유하기도 쉽다. **특징**: - 실험 로깅 및 시각화 - 하이퍼파라미터 추적 - 모델 버전 관리 - 팀 협업 ```python import wandb # 초기화 wandb.init(project="my-project", config={ "learning_rate": 0.001, "epochs": 100 }) # 로깅 for epoch in range(epochs): loss = train_one_epoch() wandb.log({"loss": loss, "epoch": epoch}) # 완료 wandb.finish() ``` > **추천 자료** > - [Weights & Biases 공식 문서 및 Quickstart](https://docs.wandb.ai/quickstart) — wandb 시작 가이드. 5분이면 첫 실험 로깅 가능 > - [Weights & Biases YouTube](https://www.youtube.com/@WeightsBiases) — 사용법 튜토리얼 및 MLOps 관련 강연 ### 16.5.2 MLflow 오픈소스 실험 관리 플랫폼이다. wandb가 클라우드 기반 서비스라면, MLflow는 자체 서버에서 운영할 수 있는 오픈소스 대안이다. 데이터 보안이 중요한 환경에서 유용하다. ```python import mlflow mlflow.set_experiment("my-experiment") with mlflow.start_run(): mlflow.log_param("lr", 0.001) mlflow.log_metric("accuracy", 0.95) mlflow.pytorch.log_model(model, "model") ``` ### 16.5.3 TensorBoard ```python from torch.utils.tensorboard import SummaryWriter writer = SummaryWriter("runs/experiment1") writer.add_scalar("Loss/train", loss, epoch) writer.add_image("Sample", image, epoch) writer.close() ``` ```bash tensorboard --logdir runs ``` TensorBoard는 PyTorch에서도 바로 쓸 수 있고, 별도의 계정 생성 없이 로컬에서 바로 시각화가 가능하다는 장점이 있다. 간단한 실험이라면 wandb 대신 TensorBoard만으로도 충분하다. > **추천 자료** > - [PyTorch TensorBoard Tutorial](https://pytorch.org/tutorials/recipes/recipes/tensorboard_with_pytorch.html) — PyTorch에서 TensorBoard 사용하는 공식 가이드 > - [MLflow Documentation](https://mlflow.org/docs/latest/index.html) — MLflow 공식 문서 ### 16.6 코드 포매팅 ### 16.6.1 Linting & Formatting 코드 스타일이 사람마다 다르면 코드 리뷰에서 로직보다 스타일 논쟁에 시간을 더 쓰게 된다. 자동 포매터를 쓰면 이 문제가 사라진다. 혼자 연구할 때도 일관된 코드 스타일은 나중에 자기 코드를 다시 읽을 때 큰 도움이 된다. **Python**: ```bash # Ruff (빠른 린터, Black 호환 포맷터) pip install ruff ruff check . ruff format . # Black (포매터) pip install black black . # 타입 체크 pip install mypy mypy . ``` **C++**: ```bash # clang-format clang-format -i src/*.cpp ``` ### 16.6.2 Testing 코드에 테스트를 작성하는 것은 연구 코드에서도 중요하다. "모델 forward pass가 제대로 되는지", "데이터 전처리 결과가 예상과 같은지" 같은 기본적인 테스트만 있어도 리팩토링할 때 훨씬 안심이 된다. **Python (pytest)**: ```python # test_module.py def test_addition(): assert 1 + 1 == 2 def test_function(): result = my_function(input) assert result == expected ``` ```bash pytest tests/ -v ``` **C++ (gtest)**: ```cpp #include
TEST(MyTest, BasicTest) { EXPECT_EQ(1 + 1, 2); } ``` > **추천 자료** > - [Real Python - Python Testing with pytest](https://realpython.com/pytest-python-testing/) — pytest 사용법 상세 튜토리얼 > - [The Missing Semester (MIT)](https://missing.csail.mit.edu/) — 셸, 에디터, 디버깅, 프로파일링 등 개발 도구 전반. 연구실 입학 전에 한 번 쭉 보면 좋다 > - [Fireship YouTube](https://www.youtube.com/@Fireship) — 각종 개발 도구를 "100 Seconds" 시리즈로 빠르게 훑어볼 수 있다 > - [정진용 블로그 — 로봇 소프트웨어 개발 문화](https://jinyongjeong.github.io/2025/02/14/developmen_culture/) — 코드 리뷰, CI/CD, 스타일 가이드 등 로봇 개발팀의 개발 문화 정착 방법 > - [정진용 블로그 — 로봇 개발과 테스트 코드](https://jinyongjeong.github.io/2025/02/19/test_code/) — 로봇 소프트웨어에서 테스트 코드가 필수인 6가지 이유 ### 기술 흐름: 개발 환경 & 도구의 과거 → 현재 → 미래 ``` 2005 ─── Git 탄생 (Linus Torvalds) │ 분산 버전 관리의 시작 │ 2008 ─── GitHub 출시 │ 오픈소스 협업의 중심지가 됨 │ 2010 ─── Conda (Anaconda) 등장 │ Python 환경 관리 표준으로 자리잡음 │ 2013 ─── Docker 공개 │ 컨테이너 기반 가상화로 재현성 문제 해결 │ 2015 ─── TensorBoard (TensorFlow와 함께 공개) │ 딥러닝 학습 시각화의 시작 │ 2017 ─── nvidia-docker 공개 │ Docker 컨테이너에서 GPU 사용 가능 │ 2018 ─── Weights & Biases 출시 │ 실험 추적·시각화·팀 협업을 클라우드로 │ 2020 ─── Ruff 등장 (2022), Black 대중화 │ Python 코드 품질 도구의 고속화 │ 2023+ ── AI-assisted 개발 도구 확산 Copilot, Cursor 등 AI 코딩 보조 도구 Dev Container 표준화 (VS Code Remote) 재현 가능한 연구를 위한 Docker + wandb 조합 보편화 ``` --- ## 17. 데이터셋 & 벤치마크 로보틱스와 컴퓨터 비전 연구에서 데이터셋은 알고리즘만큼 중요하다. 좋은 데이터 없이는 좋은 모델을 만들 수 없고, 공정한 벤치마크 없이는 논문에서 자기 방법이 진짜 좋은지 증명할 수 없다. 이 챕터에서는 주요 데이터셋의 구성과 특징, 그리고 자체 데이터를 수집·관리하는 방법까지 다룬다. 최근 트렌드로, **합성 데이터(Synthetic Data)**의 중요성이 급격히 커지고 있다. 실제 데이터를 수집하고 라벨링하는 것은 비용이 많이 들고 시간이 오래 걸리는데, 시뮬레이터에서 자동으로 생성한 합성 데이터로 사전학습하고 소량의 실제 데이터로 미세조정(fine-tuning)하는 방식이 점점 보편화되고 있다. NVIDIA Isaac Sim의 Domain Randomization이나 Habitat의 대규모 장면 생성이 대표적인 예이다. **Sim-to-Real 데이터셋** — 시뮬레이터에서 생성한 데이터와 대응하는 실제 데이터를 쌍으로 제공하는 데이터셋 — 도 활발히 구축되고 있다. ### 17.1 자율주행/로봇 데이터셋 ### 17.1.1 KITTI / KITTI360 자율주행 연구의 시작점이 된 오래된 데이터셋이다. KITTI는 2012년에 공개된 이후 자율주행·3D 비전 연구의 사실상 표준 벤치마크 역할을 해 왔다. 비록 지금은 더 크고 다양한 데이터셋이 있지만, 많은 논문이 여전히 KITTI 결과를 보고하고 있어서 기준점으로 알아 두어야 한다. 특히 Visual Odometry, Stereo Depth Estimation 분야에서는 아직도 KITTI가 1차 벤치마크이다. **구성**: - 스테레오 카메라 - 3D LiDAR (Velodyne HDL-64E) - GPS/IMU - 2D/3D 라벨 **태스크**: - Stereo depth estimation - Optical flow - Visual odometry / SLAM - 3D object detection - Semantic segmentation **다운로드**: https://www.cvlibs.net/datasets/kitti/ > **추천 자료** > - [KITTI Benchmark 공식 사이트](https://www.cvlibs.net/datasets/kitti/) — 데이터셋 다운로드 및 각 태스크별 리더보드 확인 > - [KITTI-360 사이트](https://www.cvlibs.net/datasets/kitti-360/) — 더 넓은 범위의 360도 데이터셋 > - [다크 프로그래머 — KITTI 데이터 사용하기 (LiDAR-카메라 변환)](https://darkpgmr.tistory.com/190) — KITTI 데이터의 좌표계 변환과 LiDAR-카메라 매핑 실습 ### 17.1.2 nuScenes 대규모 자율주행 데이터셋이다. KITTI보다 센서 구성이 더 풍부하고(360도 카메라, Radar 포함), 데이터 규모도 훨씬 크다. 최근 자율주행 논문에서 KITTI와 함께 가장 많이 인용되는 데이터셋이다. 특히 3D Object Detection과 BEV(Bird's Eye View) 기반 인식 연구에서 핵심적으로 쓰인다. **구성**: - 6개 카메라 (360° 커버) - 5개 Radar - 1개 LiDAR - 1000 장면, 40K 키프레임 **특징**: - 23개 객체 클래스 - 풍부한 Annotation (속성, 가시성) - 밤, 비 등 다양한 조건 **평가 메트릭**: mAP, NDS > **추천 자료** > - [nuScenes devkit Documentation](https://www.nuscenes.org/nuscenes) — 데이터셋 사용법, devkit API, 튜토리얼 노트북 > - [nuScenes devkit GitHub](https://github.com/nutonomy/nuscenes-devkit) — Python devkit 코드 및 예제 ### 17.1.3 Waymo Open Dataset Google의 대규모 자율주행 데이터셋이다. nuScenes와 함께 최신 자율주행 연구의 양대 벤치마크이다. 데이터 품질과 규모 면에서 가장 앞서 있으며, 매년 챌린지를 통해 최신 기술 동향을 파악할 수 있다. **규모**: - 1,150 장면 (20초) - 12M LiDAR 라벨 - 12M 카메라 라벨 **특징**: - 높은 품질의 센서 - 다양한 환경 (도시, 교외, 밤) - 연간 챌린지 개최 > **추천 자료** > - [Waymo Open Dataset 공식 사이트](https://waymo.com/open/) — 데이터셋 다운로드 및 챌린지 참가 > - [Waymo Open Dataset GitHub](https://github.com/waymo-research/waymo-open-dataset) — 공식 도구 및 예제 코드 ### 17.1.4 VIO / VINS용 데이터셋 Visual-Inertial Odometry(VIO)나 SLAM 연구를 한다면 아래 데이터셋은 알아야 한다. 이 분야의 논문이라면 거의 예외 없이 이 데이터셋들에서 평가를 수행한다. **TUM RGB-D**: - RGB-D 카메라 시퀀스 - 정밀 ground truth (모션 캡처) - 실내 환경 - Visual SLAM 평가 표준 **EuRoC MAV**: - 드론 비행 데이터 - 스테레오 + IMU - VIO 평가 표준 - 다양한 난이도 > **추천 자료** > - [TUM RGB-D Benchmark](https://cvg.cit.tum.de/data/datasets/rgbd-dataset) — Visual SLAM 평가 표준 데이터셋 및 평가 도구 > - [EuRoC MAV Dataset](https://projects.asl.ethz.ch/datasets/doku.php?id=kmavvisualinertialdatasets) — VIO 평가 표준 데이터셋 ### 17.2 컴퓨터 비전 데이터셋 ### 17.2.1 ImageNet 이미지 분류의 표준 벤치마크이다. 딥러닝 전환기의 출발점이 된 데이터셋이다. 2012년 AlexNet이 ImageNet에서 압도적 성능을 보여준 이후, 거의 모든 비전 모델이 ImageNet 사전학습(pretrained) 가중치를 사용하게 되었다. 로보틱스에서도 카메라 기반 인식 모듈의 백본(backbone)은 대부분 ImageNet에서 사전학습된 모델을 가져다 쓴다. - 1000 클래스 - 120만 학습 이미지 - 사전학습(pretraining) 표준 ### 17.2.2 COCO 객체 탐지, 세그멘테이션의 표준이다. Object detection 연구를 한다면 COCO 데이터셋의 평가 메트릭(COCO mAP)은 업계 표준이므로 이해해야 한다. IoU threshold별 AP를 계산하는 방식이 PASCAL VOC와 다르니 주의할 것. **특징**: - 80 객체 카테고리 - 33만 이미지, 150만 객체 인스턴스 - Dense annotation (bounding box, segmentation mask) **태스크**: - Object detection - Instance segmentation - Keypoint detection - Captioning ### 17.2.3 ScanNet / NYU Depth V2 **ScanNet**: - 1513개 실내 장면 - RGB-D 시퀀스 - 3D semantic segmentation - 카메라 포즈, 메시 제공 **NYU Depth V2**: - 실내 RGB-D - Depth estimation 벤치마크 - 464 장면, 407K 프레임 실내 로봇(가정용, 서비스 로봇 등)을 한다면 ScanNet과 NYU Depth V2는 핵심 벤치마크이다. 특히 ScanNet은 3D 장면 이해(Scene Understanding) 연구에서 빠지지 않는다. > **추천 자료** > - [COCO Dataset](https://cocodataset.org/) — 공식 사이트, 데이터셋 다운로드 및 evaluation 도구 > - [ScanNet Benchmark](http://www.scan-net.org/) — 3D Scene Understanding 벤치마크 > - [Papers With Code - Datasets](https://paperswithcode.com/datasets) — 태스크별 데이터셋 검색 및 리더보드 통합 사이트 ### 17.3 데이터셋 활용법 ### 17.3.1 다운로드 및 포맷 이해 각 데이터셋마다 고유한 디렉토리 구조와 포맷이 있다. 데이터셋을 다운로드했는데 디렉토리 구조와 라벨 포맷을 제대로 이해하지 못하면, 데이터 로더를 짜는 데만 며칠이 걸릴 수 있다. 특히 3D 라벨은 좌표계(coordinate system)가 데이터셋마다 다르므로(카메라 좌표계 vs LiDAR 좌표계, y-up vs z-up 등) 문서를 꼼꼼히 읽어야 한다. **예시: KITTI Object Detection**: ``` kitti/ ├── training/ │ ├── image_2/ # Left RGB images │ ├── velodyne/ # LiDAR point clouds (.bin) │ ├── calib/ # Calibration files │ └── label_2/ # 2D/3D annotations └── testing/ └── ... ``` **라벨 파일 읽기 예시**: ```python # KITTI label format: type truncated occluded alpha bbox(4) dimensions(3) location(3) rotation_y with open('label.txt', 'r') as f: for line in f: parts = line.strip().split() obj_type = parts[0] bbox = [float(x) for x in parts[4:8]] # left, top, right, bottom dimensions = [float(x) for x in parts[8:11]] # height, width, length location = [float(x) for x in parts[11:14]] # x, y, z ``` > **추천 자료** > - [KITTI Benchmark 공식 사이트 - Object Detection DevKit](https://www.cvlibs.net/datasets/kitti/eval_object.php?obj_benchmark=3d) — 라벨 포맷 설명 및 평가 코드 > - [nuScenes devkit Tutorial Notebooks](https://github.com/nutonomy/nuscenes-devkit/tree/master/python-sdk/tutorials) — Jupyter 노트북으로 데이터 구조 이해 ### 17.3.2 DataLoader 구현 PyTorch에서 데이터 로딩을 위한 표준 패턴이다. PyTorch의 `Dataset`과 `DataLoader` 패턴을 모르면 학습 코드를 짤 수 없다. 특히 `__getitem__`에서 데이터를 어떻게 전처리하느냐, `num_workers`를 몇으로 설정하느냐에 따라 학습 속도가 크게 달라질 수 있다. ```python from torch.utils.data import Dataset, DataLoader class MyDataset(Dataset): def __init__(self, root_dir, transform=None): self.root_dir = root_dir self.transform = transform self.samples = self._load_samples() def _load_samples(self): # 파일 목록 로드 return list_of_samples def __len__(self): return len(self.samples) def __getitem__(self, idx): sample = self.samples[idx] image = load_image(sample['image_path']) label = sample['label'] if self.transform: image = self.transform(image) return image, label # 사용 dataset = MyDataset(root_dir='./data') dataloader = DataLoader(dataset, batch_size=32, shuffle=True, num_workers=4) ``` > **추천 자료** > - [PyTorch Data Loading Tutorial](https://pytorch.org/tutorials/beginner/data_loading_tutorial.html) — 커스텀 Dataset 작성 공식 가이드 > - [Real Python - PyTorch DataLoader](https://realpython.com/python-data-loading/) — DataLoader 활용법 상세 설명 ### 17.4 자체 데이터 수집 공개 데이터셋만으로는 자기 연구에 딱 맞는 데이터를 구할 수 없는 경우가 많다. 자체 로봇에 맞는 센서 구성, 특수한 환경 조건 등을 위해 직접 데이터를 수집해야 할 때가 있다. 이때 센서 동기화, 캘리브레이션, 라벨링 과정을 체계적으로 해 두지 않으면 나중에 데이터를 못 쓰게 되는 상황이 생긴다. ### 17.4.1 센서 동기화 여러 센서의 데이터를 시간 동기화하지 않으면 퓨전 자체가 의미가 없다. 카메라와 LiDAR의 타임스탬프가 10ms만 어긋나도 고속 주행 시 수십 cm의 위치 오차가 생긴다. 센서 퓨전의 기본 전제가 "같은 시점의 데이터"인데, 동기화가 안 되면 그 전제가 무너진다. **하드웨어 동기화**: - 트리거 신호로 동시 촬영 - PPS (Pulse Per Second) 신호 **소프트웨어 동기화**: - 타임스탬프 기반 근사 동기화 - 보간(interpolation) 사용 **ROS에서는 받는 시점만 필터링 가능:** ```python import message_filters # Approximate Time Synchronizer image_sub = message_filters.Subscriber(self, Image, '/camera/image') lidar_sub = message_filters.Subscriber(self, PointCloud2, '/lidar/points') sync = message_filters.ApproximateTimeSynchronizer( [image_sub, lidar_sub], queue_size=10, slop=0.1 ) sync.registerCallback(self.callback) ``` ### 17.4.2 캘리브레이션 **Camera Intrinsic**: 체커보드 사용 (OpenCV calibrateCamera) **Camera-LiDAR Extrinsic**: - 체커보드 기반 (평면 맞춤) - Target-based (특수 타겟 사용) - Target-less (자동 특징점 매칭) **Camera-IMU**: Kalibr 사용 권장 캘리브레이션이 부정확하면 카메라에서 본 객체 위치와 LiDAR에서 본 객체 위치가 일치하지 않게 된다. 센서 퓨전의 정확도는 캘리브레이션 품질에 직결된다. 선형대수를 배웠다면, intrinsic은 3x3 카메라 행렬 K, extrinsic은 4x4 변환 행렬 [R|t]에 해당한다. > **추천 자료** > - [OpenCV Camera Calibration Tutorial](https://docs.opencv.org/4.x/dc/dbb/tutorial_py_calibration.html) — 체커보드 기반 카메라 캘리브레이션 > - [Kalibr GitHub](https://github.com/ethz-asl/kalibr) — Camera-IMU 캘리브레이션 표준 도구 ### 17.4.3 라벨링 도구 데이터를 수집했으면 라벨링(annotation)을 해야 한다. 라벨링은 연구에서 가장 시간이 많이 드는 작업 중 하나이며, 라벨 품질이 모델 성능을 좌우한다. 최근에는 SAM(Segment Anything Model) 같은 기초 모델을 활용한 반자동 라벨링이 보편화되고 있다. **CVAT (Computer Vision Annotation Tool)**: - 웹 기반, 무료 - 이미지, 비디오 annotation - 다양한 태스크 지원 (bbox, polygon, points) **Labelbox**: - 클라우드 기반 - 팀 협업 기능 - 3D annotation 지원 **3D Labeling**: - SUSTechPOINTS: LiDAR 포인트 클라우드 - KITTI-360 labeling tool **합성 데이터를 통한 자동 라벨링**: 시뮬레이터(NVIDIA Isaac Sim, AI2-THOR 등)에서 데이터를 생성하면 라벨이 자동으로 함께 생성되므로 수동 라벨링이 필요 없다. Domain Randomization을 통해 텍스처, 조명, 배경을 무작위로 변형하면 모델의 일반화 성능도 높일 수 있다. 실제 데이터 대비 수집 비용이 거의 0에 가깝다는 것이 큰 장점이다. > **추천 자료** > - [CVAT Documentation](https://docs.cvat.ai/) — 오픈소스 라벨링 도구 공식 문서 > - [Roboflow](https://roboflow.com/) — 라벨링, 데이터 증강, 모델 학습을 통합 제공하는 플랫폼 > - [NVIDIA Isaac Sim - Synthetic Data Generation](https://docs.omniverse.nvidia.com/isaacsim/latest/replicator_tutorials/index.html) — 합성 데이터 생성 가이드 ### 기술 흐름: 데이터셋 & 벤치마크의 과거 → 현재 → 미래 ``` 2009 ─── ImageNet 공개 │ 대규모 이미지 분류 벤치마크의 시작 │ 2012 ─── KITTI 공개 / AlexNet의 ImageNet 제패 │ 자율주행 벤치마크의 탄생, 딥러닝 혁명 시작 │ 2014 ─── COCO 공개 │ Object Detection, Segmentation 표준 벤치마크 │ 2017 ─── ScanNet 공개 │ 실내 3D Scene Understanding 연구 활성화 │ 2019 ─── nuScenes, Waymo Open Dataset 공개 │ 대규모·고품질 자율주행 데이터셋 시대 │ 2020 ─── 합성 데이터 연구 본격화 │ Domain Randomization, Sim-to-Real Transfer │ NVIDIA Isaac Sim 기반 대규모 합성 데이터 생성 │ 2023 ─── Foundation Model 시대의 데이터셋 │ SA-1B (SAM 학습용, 10억 마스크) │ Open X-Embodiment (로봇 조작 데이터 통합) │ 2024+ ── 데이터셋의 미래 트렌드 합성 데이터 + 실제 데이터 혼합 학습 보편화 Sim-to-Real 데이터셋 (시뮬·실제 쌍 데이터) 자동 라벨링 (Foundation Model 기반) 로봇 조작 데이터의 대규모 수집·공유 (Open X-Embodiment) 멀티모달 데이터셋 (비전 + 언어 + 촉각 + 힘/토크) ``` --- ## 18. 연구실 연구 방향 이 챕터에서는 우리 연구실이 어떤 방향으로 연구를 하고 있는지, 그리고 왜 그런 구조를 택했는지를 이야기한다. 앞 챕터들에서 배운 개념들이 실제 연구에서 어떻게 엮이는지를 보는 챕터이기도 하다. ### 18.1 개요 Spatial AI 시스템을 **두 개의 모듈**로 구분하여 설계한다. ``` ┌──────────────────────────────────────────────────────────────┐ │ Spatial AI System │ ├──────────────────────────────────────────────────────────────┤ │ ┌─────────────────────┐ ┌─────────────────────────────┐ │ │ │ Local Module │ │ Global Module │ │ │ │ (경량, 온보드) │ ←→ │ (중량, 서버/클라우드) │ │ │ │ │ │ │ │ │ │ • 실시간 Geometry │ │ • VFM 기반 이해 │ │ │ │ • Odometry │ │ • Semantic Scene Graph │ │ │ │ • Local Obstacle │ │ • Long-term Memory │ │ │ │ • 10-100 Hz │ │ • 1-10 Hz │ │ │ └─────────────────────┘ └─────────────────────────────┘ │ └──────────────────────────────────────────────────────────────┘ ``` **왜 두 모듈인가? — 직관적으로 이해하기** "그냥 좋은 컴퓨터 하나 올리면 안 되나요?"라고 생각할 수 있다. 솔직히 그게 가능하면 그렇게 하고 싶다. 하지만 현실은 그렇지 않다. 먼저 **물리적 제약**을 생각해 보자. 로봇은 움직여야 한다. NVIDIA A100 GPU 서버를 드론에 올릴 수는 없다 — 무게만 해도 수십 kg이고, 전력도 수백 와트를 먹는다. 배터리로 돌아가는 로봇에게는 비현실적이다. 그래서 로봇에 실제로 탑재할 수 있는 컴퓨터는 Jetson Orin 같은 임베디드 보드인데, 이 보드로는 DINOv2나 SAM 같은 대형 모델을 실시간으로 돌릴 수 없다. 다음으로 **시간 제약**이 있다. 로봇이 복도를 걸어가고 있는데, 벽에 부딪히기 0.1초 전에 "잠깐, 서버 응답 기다리는 중..."이면 안 된다. 장애물 회피처럼 "지금 당장" 반응해야 하는 것과, "저 물체가 뭔지 이해하기" 처럼 좀 느려도 괜찮은 것은 본질적으로 다른 종류의 문제다. 그래서 우리는 이렇게 나눈다: 1. **계산 자원의 현실**: 로봇 온보드 컴퓨터(Jetson 등)는 대형 모델을 돌릴 여력이 없다 2. **실시간 요구사항**: 장애물 회피는 즉각 반응 필요 — 0.1초가 생사를 가른다 3. **깊은 이해**: VFM/VLA는 높은 계산량 필요 — "저건 깨진 유리컵이니 조심해" 같은 판단 4. **상호 보완**: 기하학적 정밀함(Local) + 의미론적 이해(Global) = 진짜 똑똑한 로봇 > 비유하자면, Local Module은 로봇의 **반사 신경**이고, Global Module은 로봇의 **대뇌 피질**이다. 뜨거운 냄비를 만지면 손을 먼저 떼고(반사), 그 다음에 "아, 불이 켜져 있었구나"라고 이해한다(인지). 로봇도 마찬가지다. ### 18.2 Local Module: Lightweight Geometry 로봇에 직접 탑재되어 실시간으로 동작하는 모듈이다. 로봇이 "지금 이 순간" 안전하게 움직이기 위해 필요한 최소한의 정보를 처리한다. #### 18.2.1 목표 - **Odometry**: 자신의 움직임 추정 — "나는 지금 어디에 있는가?" - **Obstacle Detection**: 즉각적인 장애물 감지 — "앞에 뭔가 있다, 피해!" - **Local Mapping**: 주변 환경의 기하학적 지도 — "내 주변 3m 이내는 이렇게 생겼다" **실제 시나리오**: 배달 로봇이 아파트 복도를 지나가고 있다고 하자. 갑자기 아이가 뛰어나온다. 이때 Local Module은 depth 센서로 즉시 장애물을 감지하고, odometry로 자기 위치를 파악해서, 0.05초 안에 회피 경로를 계산한다. "아이인지 강아지인지"는 몰라도 된다 — 그건 Global Module의 일이다. Local Module은 "앞에 뭔가 있으니 피하자"만 알면 된다. #### 18.2.2 특징 - **저지연**: 10-100 Hz 동작 (10ms~100ms마다 한 번씩 처리) - **제한된 자원**: Jetson, 임베디드 GPU — 전력 15~30W 수준 - **확정적 동작**: 예측 가능한 응답 시간 — "최악의 경우에도 50ms 안에 답을 준다" #### 18.2.3 기술 스택 **Classical 방법**: - ORB-SLAM3: Feature-based Visual SLAM — 카메라 하나로 위치 추정 (→ 4장, 9장 참고) - VINS-Mono: Visual-Inertial Odometry — 카메라+IMU 융합 (→ 9장 참고) - FAST-LIO2: LiDAR-Inertial Odometry — LiDAR+IMU 융합 (→ 2장, 9장 참고) **경량 학습 모델**: - 경량 depth estimation — MobileNet 기반으로 압축 (→ 5장 참고) - 압축된 segmentation 모델 — knowledge distillation 적용 (→ 5장, 6장 참고) - TensorRT 최적화 — NVIDIA GPU에서 2-5배 속도 향상 **Edge 배포**: ```bash # TensorRT 최적화 예시 trtexec --onnx=model.onnx --saveEngine=model.trt --fp16 --workspace=4096 ``` > TensorRT가 뭐냐면, PyTorch로 학습한 모델을 NVIDIA GPU에 최적화된 형태로 변환해주는 도구다. FP16(반정밀도)으로 바꾸면 모델 크기가 절반이 되면서도 정확도는 거의 유지된다. Jetson에서 YOLO를 돌릴 때 TensorRT 없이는 5 FPS, 있으면 30 FPS — 이 차이가 실제 로봇에서는 "쓸 수 있다 vs 없다"의 차이다. #### 18.2.4 예시 구현 ```python # Local Module 개념 코드 class LocalModule: def __init__(self): self.odometry = FastLIO2() self.obstacle_detector = LightweightObstacleNet() # TensorRT def process(self, sensor_data): # 1. Odometry 업데이트 (100 Hz) pose = self.odometry.update(sensor_data.imu, sensor_data.lidar) # 2. 장애물 감지 (30 Hz) obstacles = self.obstacle_detector(sensor_data.image) # 3. Global Module로 키프레임 전송 if self.is_keyframe(pose): self.send_to_global(sensor_data, pose) return pose, obstacles ``` **시나리오로 읽기**: 위 코드에서 `process()`는 센서 데이터가 들어올 때마다 호출된다. IMU 데이터는 100Hz(초당 100번)로 오고, 카메라 이미지는 30Hz로 온다. 매 프레임마다 "나 지금 어디?" (odometry)와 "앞에 뭐 있어?" (obstacle)를 계산하고, 중요한 순간(키프레임)에만 Global Module에 데이터를 보낸다. 모든 프레임을 보내면 네트워크가 터지니까. ### 18.3 Global Module: VFM-based Understanding 서버 또는 클라우드에서 동작하는 고수준 이해 모듈이다. Local Module이 "앞에 뭔가 있다"까지만 알면, Global Module은 "저건 깨진 유리잔이고, 주인이 거실에서 떨어뜨린 것 같다"까지 이해한다. #### 18.3.1 목표 - **전체 지도 이해**: 공간 구조와 의미 파악 — "여기는 주방이고, 저기는 거실이다" - **Semantic Scene Graph**: 객체 간 관계 표현 — "컵이 테이블 위에 있다" - **Long-term Memory**: 환경 변화 추적 — "어제는 여기에 의자가 없었는데 오늘은 있다" **실제 시나리오**: 가정용 서비스 로봇이 매일 집 안을 돌아다니면서 환경을 학습한다. Global Module은 "거실에 소파, TV, 테이블이 있고, 주방에는 냉장고, 싱크대가 있다"는 고수준 지도를 유지한다. 사용자가 "거실 테이블에 있는 리모컨 가져와"라고 하면, Scene Graph에서 리모컨의 위치를 찾아 Local Module에 waypoint를 전달한다. #### 18.3.2 특징 - **높은 정확도**: 대형 VFM 활용 — DINOv2, SAM2 같은 수십억 파라미터 모델 - **풍부한 컴퓨팅**: GPU 서버, 클라우드 — RTX 4090, A100 수준의 GPU - **비실시간 허용**: 1-10 Hz — "1초에 한 번 업데이트해도 괜찮다" #### 18.3.3 기술 스택 **Vision Foundation Models** (→ 6장 참고): - DINOv2: Dense feature extraction — 이미지의 모든 픽셀에 대해 의미 있는 feature 벡터 생성 - SAM2: Open-vocabulary segmentation — "아무 물체나" 지정하면 정확하게 분리 - GroundingDINO: Text-guided detection — "빨간 컵"이라고 말하면 찾아줌 **3D Understanding** (→ 6장, 8장 참고): - Gaussian Splatting with semantic features — 예쁘고 빠른 3D 재구성 + 의미 정보 - 3D Scene Graph 구축 — 객체들의 관계를 그래프로 표현 - VFM features의 3D lifting — 2D 이미지에서 뽑은 feature를 3D 공간에 올리기 **Language Integration** (→ 6장, 7장 참고): - CLIP features for open-vocabulary — "본 적 없는 물체"도 텍스트로 검색 가능 - LLM for scene reasoning — "이 방은 어떤 용도일까?" 추론 - VLA for action planning — "컵을 집으려면 어떻게 팔을 움직여야 할까?" #### 18.3.4 예시 구현 ```python # Global Module 개념 코드 class GlobalModule: def __init__(self): self.dinov2 = load_dinov2() self.sam = load_sam2() self.scene_graph = SemanticSceneGraph() self.gaussian_map = GaussianSplatMap() def process_keyframe(self, image, depth, pose): # 1. VFM feature 추출 features = self.dinov2.extract(image) # 2. Open-vocabulary segmentation masks = self.sam.segment(image, prompts=self.get_prompts()) # 3. 3D Scene Graph 업데이트 self.scene_graph.update(masks, depth, pose, features) # 4. Gaussian Map 업데이트 self.gaussian_map.add_keyframe(image, depth, pose, features) def query(self, text_prompt): # "Where is the red cup?" → 위치 반환 return self.scene_graph.find(text_prompt) ``` **시나리오로 읽기**: Local Module에서 키프레임이 올 때마다 `process_keyframe()`이 호출된다. DINOv2로 이미지에서 rich한 feature를 뽑고, SAM으로 물체들을 분리하고, 이걸 3D Scene Graph와 Gaussian Map에 누적한다. 나중에 사용자가 "빨간 컵 어디 있어?"라고 물으면 `query()`로 검색한다. 이 전체 과정이 1초 정도 걸려도 괜찮다 — 실시간 안전은 Local Module이 책임지니까. ### 18.4 두 모듈의 협업 두 모듈은 독립적으로 동작하지만, 서로 정보를 주고받으며 협력한다. 마치 드라이버(Local)와 네비게이션 앱(Global)의 관계와 비슷하다 — 드라이버는 눈앞의 도로를 보고 운전하고, 네비게이션은 전체 경로를 안내한다. #### 18.4.1 Local → Global **전송 내용**: - 키프레임 이미지/포인트 클라우드 - 로컬 포즈 - 센서 메타데이터 **키프레임 선택 기준**: - 이동 거리/회전량 threshold — "1m 이동하거나 30도 회전하면 보내기" - 장면 변화 감지 — "새로운 방에 들어갔다" - 정보량 (특징점 수, 커버리지) — "이 프레임에 새로운 정보가 많다" #### 18.4.2 Global → Local **전송 내용**: - 사전 지도 (필요 영역) — "주방 근처의 장애물 정보" - Semantic 정보 (객체 위치, 클래스) — "테이블은 여기, 의자는 저기" - 네비게이션 waypoints — "이 경로를 따라가라" **예시 시나리오**: ``` 1. 사용자: "Go to the kitchen and bring the cup" 2. Global: - VLM으로 명령 이해 - Scene Graph에서 kitchen, cup 위치 찾기 - 경로 계획 3. Global → Local: - Waypoints: [현재 → 복도 → 주방 → 컵 앞] - 주방 영역의 local map - 컵의 예상 위치 4. Local: - Waypoints 따라 이동 - 실시간 장애물 회피 - 컵 근처에서 정밀 접근 ``` **다른 시나리오 — 통신 불안정 상황**: 로봇이 지하 주차장에서 작업 중인데 WiFi가 끊어졌다. 이 경우 Local Module만으로 동작해야 한다. Odometry로 위치를 추정하고, 장애물을 피하면서 마지막으로 받은 waypoint까지 이동한다. WiFi가 복구되면 그동안의 데이터를 Global에 한꺼번에 보내고, 업데이트된 계획을 받는다. 이런 **graceful degradation**이 실제 로봇에서는 매우 중요하다. #### 18.4.3 통신 및 동기화 **통신 방식**: - ROS2 DDS: 로컬 네트워크 (같은 건물 안) - WebSocket: 클라우드 연결 (원격 서버) - 5G/WiFi: 모바일 로봇 (실외 환경) **동기화 전략**: - 키프레임 기반 (연속 스트리밍 X) — 대역폭 절약 - 비동기 처리 (Global 완료 안 기다림) — Local은 멈추지 않는다 - 캐싱 (자주 방문 영역) — 매번 같은 데이터를 보내지 않는다 ### 18.5 연구 과제 예시 아래 연구 과제들은 실제로 우리 연구실에서 진행하거나 진행할 수 있는 주제들이다. 각 과제에 대해 **선행 학습이 필요한 챕터**를 표시해두었으니, 관심 있는 주제가 있으면 해당 챕터부터 공부하자. #### Local Module 연구 1. **더 가벼운 SLAM** - 신경망 기반 경량 VO — 기존 VO를 neural network로 대체하되, Jetson에서 돌아가게 - 이벤트 카메라 활용 — 초고속, 저전력 카메라로 극한 환경에서 SLAM - 하드웨어 가속 (FPGA) — SLAM의 핵심 연산을 하드웨어로 구현 - **선행 학습**: 4장(카메라 모델), 9장(Visual Odometry, SLAM) 필수. 3장(최적화)도 권장 2. **효율적 장애물 인식** - Depth-only obstacle detection — RGB 없이 depth 정보만으로 장애물 감지 - Temporal consistency — 프레임 간 일관성 유지 (한 프레임에서 보였다 안 보였다 하면 안 됨) - Uncertainty-aware — "이게 장애물인지 확실하지 않다"는 정보도 활용 - **선행 학습**: 5장(Depth Estimation, Object Detection) 필수. 3장(좌표 변환)도 중요 3. **센서 융합 최적화** - Tight coupling 경량화 — IMU+Camera+LiDAR를 촘촘하게 결합하되 가볍게 - 센서 드롭아웃 대응 — 센서 하나가 고장나도 계속 동작 - **선행 학습**: 2장(센서), 9장(Visual Odometry), 3장(최적화) 필수 #### Global Module 연구 1. **VFM의 3D 확장** - DINOv2 features in 3D — 2D feature를 3D 공간에 올려서 활용 - Semantic Gaussian Splatting — 3D 재구성에 의미 정보를 같이 넣기 - 3D scene understanding — "이 공간은 어떤 구조인가" 이해 - **선행 학습**: 5장(Depth), 8장(3D 표현), 6장(VFM) 필수. 4장(카메라 모델)은 기본 2. **VLA 통합** - Open-vocabulary manipulation — "저 빨간 거 집어" 같은 명령으로 로봇팔 제어 - Language-guided navigation — 자연어 명령으로 이동 - 상황 인식 행동 — "아이가 있으니 천천히 움직여라" - **선행 학습**: 6장(VFM 활용), 7장(VLA) 필수. 5장(Detection)도 알면 좋다 3. **Scalability** - 대규모 환경 표현 — 아파트 단지 전체, 캠퍼스 전체를 하나의 지도로 - 지도 압축 및 업데이트 — 수 GB짜리 지도를 효율적으로 관리 - Multi-robot 협업 — 여러 로봇이 함께 지도를 만들고 공유 - **선행 학습**: 9장(SLAM), 3장(최적화), 6장(VFM) 필수 #### Integration 연구 1. **효율적 통신** - 무엇을 언제 전송할 것인가? — 모든 걸 보내면 대역폭 낭비, 안 보내면 Global이 무용지물 - 대역폭 제한 하 최적 전략 — 5G가 끊기면? WiFi가 느리면? - **선행 학습**: 9장(SLAM, 키프레임 선택), 위 Local/Global 모듈 이해 2. **Fallback 전략** - 통신 끊김 시 Local-only 동작 — 서버 연결 없이도 기본 임무 수행 - Graceful degradation — 기능이 점진적으로 줄어들되, 갑자기 멈추지는 않기 - **선행 학습**: 전체적인 시스템 이해 필요. 최소 3-9장은 읽고 오자 3. **일관성 유지** - Local/Global 지도 동기화 — 두 모듈의 지도가 다르면 로봇이 혼란 - Semantic 정보 일관성 — "저건 의자"라고 했는데 나중에 "테이블"로 바뀌면 안 됨 - **선행 학습**: 3장(최적화), 9장(SLAM, 맵 관리) 필수 --- ## 19. AI 코딩 에이전트와 협업하기 ### 19.1 왜 이 챕터가 필요한가 AI 코딩 에이전트(Claude, Copilot, ChatGPT 등)는 일반 소프트웨어 개발에서는 강력한 도구다. 함수 하나 짜달라고 하면 꽤 쓸만한 코드가 나오고, 에러 메시지를 붙여넣으면 원인을 잘 짚어준다. 하지만 로보틱스는 다르다. 하드웨어, OS, 네트워크, 실시간성이 복잡하게 얽혀있고, "코드는 맞는데 안 되는" 상황이 일상이다. AI는 이런 영역에서 자주 틀리거나, 자신 있게 엉뚱한 방향을 제시하거나, 아예 포기한다. 이 챕터는 AI에게 속지 않고, AI를 제대로 부려먹기 위한 가이드다. 여기 나오는 내용은 전부 실제로 겪은 문제들이다. AI가 틀리는 패턴을 미리 알아두면 삽질이 줄어든다. ### 19.2 ROS에서 AI가 자주 틀리는 것들 #### 19.2.1 QoS 설정 AI는 거의 항상 QoS(Quality of Service)를 무시하거나 default(RELIABLE)로 놓는다. 문제는 센서 토픽(카메라, LiDAR)이 보통 BEST_EFFORT로 퍼블리시된다는 점이다. subscriber가 RELIABLE이면 데이터가 아예 안 들어온다. 에러 메시지도 안 뜨고 그냥 조용히 안 되니까, AI는 "토픽이 없나?" 하고 엉뚱한 방향으로 디버깅을 시작한다. ```bash # 토픽의 QoS 프로파일 확인 ros2 topic info /camera/image_raw --verbose ``` 출력에서 `Reliability: BEST_EFFORT`, `Durability: VOLATILE` 같은 정보를 확인하고, subscriber의 QoS를 맞춰야 한다. ```python from rclpy.qos import QoSProfile, ReliabilityPolicy, DurabilityPolicy qos = QoSProfile( reliability=ReliabilityPolicy.BEST_EFFORT, durability=DurabilityPolicy.VOLATILE, depth=10 ) self.subscription = self.create_subscription(Image, '/camera/image_raw', self.callback, qos) ``` AI에게 코드를 요청할 때는 "이 토픽의 QoS는 BEST_EFFORT / SENSOR_DATA다"라고 명시적으로 알려줘야 한다. 안 그러면 기본값으로 만들어서 데이터가 안 들어오는데, AI는 원인을 못 찾는다. #### 19.2.2 use_sim_time과 tf2 타이밍 rosbag 재생 시 `use_sim_time:=true`를 안 하면 tf lookup이 전부 실패한다. AI는 "tf2 lookup failed" 에러를 보면 높은 확률로 `static_transform_publisher`를 추가하라고 한다. 엉뚱한 방향이다. 실제 원인은 시뮬레이션 클럭과 시스템 클럭의 불일치다. bag 파일의 타임스탬프는 과거 시점인데, 노드는 현재 시스템 시간을 기준으로 tf를 조회하니까 당연히 못 찾는다. ```bash # 올바른 rosbag 재생 ros2 bag play my_bag --clock # 노드 실행 시 sim_time 활성화 ros2 launch my_package my_launch.py use_sim_time:=true ``` tf2 lookup에는 timeout과 try/except가 필요한데, AI는 이걸 빼먹는 경우가 많다. ```python from rclpy.duration import Duration try: transform = tf_buffer.lookup_transform( 'base_link', 'camera_link', rclpy.time.Time(), timeout=Duration(seconds=1.0) ) except tf2_ros.LookupException as e: self.get_logger().warn(f'TF lookup failed: {e}') ``` #### 19.2.3 Workspace 소싱 순서 ROS2 workspace의 소싱 순서가 중요하다. `/opt/ros/humble/setup.bash`를 먼저 source하고, 그 다음에 `~/ros2_ws/install/setup.bash`를 source해야 한다. AI는 하나만 source하거나, 순서를 뒤집거나, overlay workspace 개념 자체를 모르는 경우가 많다. ```bash # 올바른 순서 source /opt/ros/humble/setup.bash source ~/ros2_ws/install/setup.bash ``` `.bashrc`에 넣었는데 새 터미널에서 패키지를 못 찾으면, AI는 패키지 재설치를 권한다. 실제로는 `source` 문제인 경우가 대부분이다. `echo $AMENT_PREFIX_PATH`로 현재 소싱된 workspace를 확인하자. #### 19.2.4 커스텀 메시지와 빌드 AI는 `.msg` 파일은 잘 만든다. 하지만 `CMakeLists.txt`와 `package.xml`의 의존성 추가를 빠뜨리는 경우가 잦다. `rosidl_generate_interfaces` 설정이 빠지면 빌드는 되는데 Python에서 import할 때 실패한다. 이 에러가 나면 AI는 "패키지가 설치 안 됐다"고 오진하기 쉽다. ```cmake # CMakeLists.txt에 반드시 추가 find_package(rosidl_default_generators REQUIRED) rosidl_generate_interfaces(${PROJECT_NAME} "msg/MyCustomMsg.msg" DEPENDENCIES std_msgs geometry_msgs ) ``` ```xml
rosidl_default_generators
rosidl_default_runtime
rosidl_interface_packages
``` 또 하나: `--symlink-install` 없이 빌드하면 Python 코드 수정이 반영되지 않는다. AI는 이걸 "캐시 문제"라고 오진한다. ```bash # Python 패키지 수정이 바로 반영되려면 colcon build --symlink-install ``` #### 19.2.5 네임스페이스와 리매핑 `ros2 topic echo /camera/image_raw` 했는데 데이터가 안 오면, AI는 드라이버 문제라고 진단하기 쉽다. 하지만 실제로는 네임스페이스 때문에 토픽 이름이 `/robot1/camera/image_raw`일 수 있다. ```bash # 토픽 목록부터 확인하라 ros2 topic list # 특정 패턴으로 필터링 ros2 topic list | grep camera ``` AI에게 디버깅을 시킬 때는 `ros2 topic list`와 `ros2 node list` 출력을 먼저 제공해야 한다. 이 정보 없이 "토픽이 안 들어와요"라고 하면 AI는 추측으로 답할 수밖에 없다. #### 19.2.6 Launch 파일 AI가 ROS2 Python launch 파일을 쓸 때 흔히 하는 실수들: - ROS1 XML 문법을 섞는다 (ROS2 launch는 Python이 기본이다) - `LaunchDescription`의 action 순서를 잘못 잡는다 (노드 의존성 고려 필요) - `ComposableNode` vs 일반 `Node` 구분을 못 한다 - `PushRosNamespace`를 빠뜨려서 multi-robot 설정이 꼬인다 ```python # multi-robot launch 파일에서 네임스페이스 적용 from launch.actions import GroupAction, PushRosNamespace robot1_group = GroupAction([ PushRosNamespace('robot1'), Node(package='my_pkg', executable='my_node', name='sensor_node'), ]) ``` AI에게 launch 파일을 요청할 때는 "ROS2 Python launch 파일", "multi-robot이면 네임스페이스 적용", "ComposableNode 사용 여부" 등을 명시해야 한다. ### 19.3 Docker에서 AI가 모르는 것들 #### 19.3.1 GUI/시각화 문제 Docker 안에서 RViz나 Gazebo 같은 GUI 도구를 띄우려면 X11 포워딩이 필요하다. AI는 흔히 `xhost +local:docker`를 권하지만, 이건 모든 로컬 연결에 X 서버 접근을 허용하는 것이라 보안상 위험하다. 제대로 하려면 다음과 같이 설정한다: ```bash docker run -it \ --env DISPLAY=$DISPLAY \ --env QT_X11_NO_MITSHM=1 \ -v /tmp/.X11-unix:/tmp/.X11-unix:rw \ --ipc=host \ my_image ``` 여기서 중요한 포인트: - `QT_X11_NO_MITSHM=1` — AI는 이 옵션을 거의 모른다. 이거 없으면 RViz가 segfault로 죽는다. MIT-SHM(공유 메모리) 확장이 Docker 환경에서 제대로 동작하지 않기 때문이다. - `--ipc=host` — 호스트와 IPC 네임스페이스를 공유한다. 이거 없으면 shared memory 문제로 시각화 도구가 죽는 경우가 많다. - Wayland 환경(Ubuntu 22.04+ 기본)에서는 X11 소켓 마운트만으로 안 되는 경우가 있다. `XDG_SESSION_TYPE=x11`로 X11 세션을 강제하거나, XWayland를 거쳐야 할 수 있다. #### 19.3.2 USB 디바이스 패스스루 카메라, LiDAR, IMU 등 USB 장치를 Docker 안에서 쓰려면 디바이스를 명시적으로 매핑해야 한다. AI는 보통 이걸 모르고 "드라이버 설치하라"고 한다. ```bash # 특정 디바이스만 매핑 (권장) docker run -it --device=/dev/ttyUSB0 --device=/dev/video0 my_image # 모든 디바이스 접근 허용 (보안상 비추, 디버깅용으로만) docker run -it --privileged my_image ``` `--privileged`는 편하지만 컨테이너에 호스트의 거의 모든 권한을 주는 것이므로, 프로덕션에서는 필요한 device만 매핑하는 게 맞다. 한 가지 더: USB 장치가 Docker 컨테이너 시작 후에 꽂히면 인식이 안 된다. AI는 이 상황을 전혀 고려하지 못한다. 컨테이너를 재시작하거나, `--privileged` + `-v /dev:/dev` 조합을 써야 한다. #### 19.3.3 ROS 네트워킹 Docker 컨테이너 간 ROS2 통신에서 `--network=host`가 가장 간단하지만, 호스트의 포트를 전부 공유하므로 포트 충돌 위험이 있다. AI는 bridge 네트워크에서 ROS2가 왜 안 되는지 모르는 경우가 많다. 원인은 DDS(Data Distribution Service)가 multicast를 사용하는데, Docker bridge 네트워크에서는 multicast가 기본적으로 안 되기 때문이다. ```bash # 가장 간단한 방법 (개발 환경에서) docker run -it --network=host my_ros2_image # ROS_DOMAIN_ID로 다른 사람과 충돌 방지 docker run -it --network=host -e ROS_DOMAIN_ID=42 my_ros2_image ``` `ROS_DOMAIN_ID`가 같은 네트워크의 다른 사람 ROS2와 충돌할 수 있다. 연구실에서 여러 명이 동시에 ROS2를 쓰면 서로의 토픽이 보이는 상황이 생긴다. DDS 설정을 세밀하게 해야 할 때는 Cyclone DDS config XML로 특정 네트워크 인터페이스만 사용하게 제한한다: ```xml
eth0
``` ```bash export CYCLONEDDS_URI=file:///path/to/cyclone_dds.xml ``` #### 19.3.4 파일 권한 문제 Docker 안에서 생성된 파일은 기본적으로 root 소유다. 호스트에서 편집하거나 삭제하려면 `sudo`가 필요하다. ```bash # 호스트 유저 권한으로 실행 docker run -it --user $(id -u):$(id -g) my_image ``` 하지만 일부 ROS 패키지가 root 권한을 필요로 해서 `--user` 옵션을 쓰면 또 안 되는 경우가 있다. AI는 이런 상황에서 `chmod 777`을 남발하는데, 실무에서는 이러면 안 된다. Dockerfile에서 non-root 유저를 만들고 필요한 디렉토리 권한만 설정하는 것이 올바른 방법이다. ```dockerfile # Dockerfile에서 non-root 유저 설정 RUN useradd -m -s /bin/bash rosuser && \ usermod -aG dialout rosuser USER rosuser ``` ### 19.4 하드웨어/드라이버에서 AI가 포기하는 것들 #### 19.4.1 시리얼 포트 권한 `/dev/ttyUSB0` 접근 시 `Permission denied`가 뜨면, AI는 `sudo chmod 666 /dev/ttyUSB0`을 권한다. 되긴 되지만, 재부팅하면 리셋된다. 매번 이걸 치고 있을 수는 없다. 올바른 방법은 udev rule을 작성하는 것이다: ```bash # 벤더/프로덕트 ID 확인 udevadm info -a -n /dev/ttyUSB0 | grep -E 'idVendor|idProduct' ``` ```bash # /etc/udev/rules.d/99-sensors.rules SUBSYSTEM=="tty", ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a9", MODE="0666", SYMLINK+="gps" ``` ```bash # udev rule 적용 sudo udevadm control --reload-rules && sudo udevadm trigger ``` 이렇게 하면 해당 USB 장치가 항상 `/dev/gps`라는 고정 이름으로 잡히고, 권한도 자동으로 설정된다. 여러 개의 동일 장치를 구분해야 할 때(예: IMU 2개)도 시리얼 넘버로 구분할 수 있다. AI는 udev를 거의 모른다. #### 19.4.2 USB 대역폭 USB3 카메라 3대를 같은 USB 허브에 꽂으면 대역폭 부족으로 프레임 드롭이 발생한다. AI는 "드라이버 업데이트하라" 또는 "해상도를 낮춰라"고 하지만, 실제 원인은 USB 컨트롤러의 대역폭 한계다. ```bash # 어떤 카메라가 어떤 USB 컨트롤러에 붙어있는지 확인 lsusb -t ``` 이 문제는 소프트웨어로 해결할 수 없다. 물리적으로 다른 USB 컨트롤러에 카메라를 분산 연결해야 한다. 데스크탑 PC의 앞면 USB와 뒷면 USB가 다른 컨트롤러에 연결되어 있는 경우가 많으니, `lsusb -t`의 Bus 번호를 확인하고 분산 배치하자. #### 19.4.3 LiDAR 연결 (IP 설정) Velodyne이나 Ouster LiDAR에서 "데이터가 안 온다"는 문제의 90%는 네트워크 설정이 원인이다. AI는 드라이버 재설치나 ROS 패키지 재빌드를 권하지만, 그 전에 확인해야 할 것이 있다. LiDAR는 고정 IP(예: `192.168.1.201`)를 사용한다. 호스트 PC의 이더넷 인터페이스를 같은 서브넷(예: `192.168.1.100`)으로 설정해야 통신이 된다. ```bash # 1단계: LiDAR에 ping이 되는지 확인 ping 192.168.1.201 # 2단계: 호스트 이더넷 인터페이스 IP 설정 sudo ip addr add 192.168.1.100/24 dev eth0 sudo ip link set eth0 up # 3단계: UDP 패킷이 오는지 Wireshark로 확인 sudo tcpdump -i eth0 udp port 2368 -c 10 ``` `ping`부터 해보면 대부분 여기서 걸린다. Wireshark(또는 tcpdump)로 UDP 패킷이 오는지 확인하는 게 가장 확실한 디버깅 방법이다. 패킷이 오는데 ROS에서 안 보이면 그때 드라이버를 의심해도 늦지 않다. #### 19.4.4 카메라 드라이버 (v4l2) AI는 `cv2.VideoCapture(0)`만 알지, `/dev/video*`가 여러 개일 때 어떤 게 실제 카메라인지 구분하지 못한다. USB 카메라 하나를 꽂아도 `/dev/video0`, `/dev/video1`이 생기는 경우가 흔한데(metadata용 디바이스), AI는 이걸 모른다. ```bash # 카메라 디바이스 매핑 확인 v4l2-ctl --list-devices # 지원하는 포맷과 해상도 확인 v4l2-ctl -d /dev/video0 --list-formats-ext ``` 자동 노출(auto exposure), 자동 화이트밸런스 — 이런 자동 설정이 SLAM 성능을 망치는 경우가 많다. 밝기가 계속 변하면 특징점 추출이 불안정해진다. ```bash # 수동 노출 설정 (SLAM용) v4l2-ctl -d /dev/video0 --set-ctrl=exposure_auto=1 v4l2-ctl -d /dev/video0 --set-ctrl=exposure_absolute=100 # 화이트밸런스 고정 v4l2-ctl -d /dev/video0 --set-ctrl=white_balance_automatic=0 ``` AI는 이런 low-level 카메라 제어를 거의 모른다. "SLAM이 불안정하다"고 하면 알고리즘 파라미터 튜닝을 권하지만, 카메라 자동 설정을 끄는 것만으로 극적으로 개선되는 경우가 있다. #### 19.4.5 Jetson (ARM) 환경 AI가 생성한 코드나 Docker 설정은 거의 100% x86 기준이다. NVIDIA Jetson(ARM64)에서는 안 돌아가는 경우가 많다. 주의해야 할 점: - `pip install`로 설치할 때 pre-built 바이너리(wheel)가 ARM용으로 없는 패키지가 많다. 특히 `scipy`, `opencv-python`은 소스에서 빌드해야 해서 수십 분이 걸린다. - JetPack 버전에 따라 CUDA, cuDNN, TensorRT 버전이 고정되어 있다. AI가 최신 버전을 설치하라고 권하면 전체 시스템이 꼬인다. - Docker를 쓸 때는 NVIDIA에서 제공하는 `l4t`(Linux for Tegra) 기반 이미지를 써야 한다. ```bash # Jetson에서 Docker 이미지 — x86 이미지 쓰면 안 된다 docker pull nvcr.io/nvidia/l4t-pytorch:r35.2.1-pth2.0-py3 # JetPack 버전 확인 cat /etc/nv_tegra_release ``` AI에게 코드를 요청할 때는 "Jetson Orin, JetPack 5.1.2, CUDA 11.4 환경이다"라고 명시해야 한다. #### 19.4.6 실시간 제어와 타이밍 AI는 `time.sleep(0.01)`로 100Hz 루프를 만들라고 하지만, 실제로는 정확하지 않다. `time.sleep()`의 정밀도는 OS 스케줄러에 의존하며, 일반 Linux 커널에서는 수 밀리초의 jitter가 발생한다. ```python # AI가 주로 권하는 방법 (정확하지 않음) import time while True: do_control() time.sleep(0.01) # 실제로는 10~15ms가 될 수 있다 ``` Python의 GIL(Global Interpreter Lock) 때문에 멀티스레드 타이밍은 더 보장이 안 된다. 진짜 실시간 제어가 필요하면 C++과 RT(Real-Time) 커널(PREEMPT_RT)을 써야 한다. ```bash # 실제 퍼블리시 주파수 확인 — 항상 이걸로 검증하라 ros2 topic hz /cmd_vel ``` AI가 "100Hz로 제어하면 된다"고 해도, `ros2 topic hz`로 실제 주파수를 확인해야 한다. 기대한 주파수와 실제 주파수가 다르면 제어가 제대로 안 된다. ### 19.5 AI 에이전트가 포기하는 패턴들 #### 19.5.1 "It works in simulation" Gazebo에서 잘 되는데 실제 로봇에서 안 되는 상황. AI는 "시뮬레이션에서 되니까 코드는 맞고 하드웨어 문제"라고 결론 내리기 쉽다. 하지만 실제 원인은 sim-to-real gap인 경우가 대부분이다: - **센서 노이즈**: 시뮬레이션의 가우시안 노이즈와 실제 센서 노이즈는 분포가 다르다 - **통신 지연**: Gazebo 안에서는 토픽 전달이 즉시지만, 실제로는 수~수십 ms의 지연이 있다 - **타이밍 불일치**: 시뮬레이션은 완벽한 동기화를 보장하지만, 실제로는 센서 간 타임스탬프가 어긋난다 - **좌표계 불일치**: URDF와 실제 로봇의 센서 위치/각도가 미세하게 다르면 tf가 틀어진다 AI에게는 "시뮬레이션에서는 되는데 실제 로봇에서 안 된다. 센서 노이즈 수준은 X이고, 통신 지연은 Y ms이고, 좌표계 캘리브레이션은 Z 방법으로 했다"처럼 sim-to-real의 차이를 구체적으로 알려줘야 한다. #### 19.5.2 하드웨어 문제를 소프트웨어로 고치려 함 케이블 불량, 접촉 불량, 전원 부족 — AI는 이런 물리적 문제를 진단할 수 없다. "센서 데이터가 간헐적으로 끊긴다"고 하면 AI는 버퍼 크기 조절, 타임아웃 설정, QoS 변경 등을 권한다. 하지만 실제로는 USB 케이블이 느슨하거나, USB 허브의 전원이 부족한 경우가 많다. ```bash # 커널 로그에서 하드웨어 문제 단서 찾기 dmesg | tail -20 # USB 연결 해제/재연결 이벤트 확인 dmesg | grep -i usb | tail -20 ``` `dmesg`에 `USB disconnect`, `device descriptor read/64, error -71` 같은 메시지가 보이면 소프트웨어 문제가 아니다. 케이블을 교체하거나, 유전원 USB 허브를 쓰거나, 다른 포트에 꽂아보는 게 먼저다. #### 19.5.3 환경 문제 진단 포기 라이브러리 버전 충돌이 복잡하게 얽히면 AI는 "전부 재설치하라"고 한다. 실제로는 `pip show package_name`으로 버전을 확인하고, 어떤 것이 충돌하는지 좁혀야 한다. 특히 OpenCV 관련 충돌은 로보틱스의 클래식이다: - `opencv-python` (기본) - `opencv-python-headless` (GUI 없는 서버용) - `opencv-contrib-python` (추가 모듈 포함) - `cv_bridge` (ROS 패키지, 자체 OpenCV를 참조) 이 네 가지가 동시에 설치되면 서로 충돌한다. ```bash # 현재 설치된 OpenCV 확인 pip show opencv-python opencv-python-headless opencv-contrib-python # 해결: ROS 환경에서는 pip opencv를 설치하지 않는다 pip uninstall opencv-python opencv-python-headless opencv-contrib-python sudo apt install ros-humble-cv-bridge ``` ROS 환경에서는 `apt install ros-humble-cv-bridge`만 쓰고, pip으로 opencv를 따로 설치하지 않는 것이 가장 깔끔하다. #### 19.5.4 "2-3번 시도 후 포기" AI는 같은 접근을 2-3번 반복하다 안 되면 "다른 방법을 시도해 보세요"라며 넘긴다. 엔지니어는 여기서 포기하지 않는다. 로그를 뒤지고, strace를 걸고, 패킷을 캡처한다. AI에게 더 좋은 답을 얻으려면, 에러 메시지뿐 아니라 low-level 정보를 함께 줘야 한다: ```bash # 시스템 로그 dmesg | tail -30 journalctl -u my_service --since "5 minutes ago" # 프로세스 추적 strace -f -e trace=open,read,write ros2 run my_pkg my_node 2>&1 | head -100 # 네트워크 패킷 캡처 sudo tcpdump -i eth0 -w capture.pcap ``` 이런 정보를 AI에게 제공하면, "재설치하세요" 대신 실제 원인에 가까운 답을 받을 수 있다. ### 19.6 AI 에이전트를 제대로 쓰는 법 #### 19.6.1 컨텍스트를 충분히 제공하라 AI에게 질문할 때 가장 중요한 것은 컨텍스트의 양과 질이다. 틀린 예: "카메라가 안 돼요" 맞는 예: "Ubuntu 22.04, ROS2 Humble, Intel RealSense D435, `rs-enumerate-devices`에서는 보이는데 `ros2 launch realsense2_camera rs_launch.py`하면 `Could not open device` 에러가 난다. Docker 안에서 실행 중이고, `--device=/dev/video0`은 매핑했다." **AI에게 제공해야 할 정보 체크리스트**: - OS 버전, ROS 버전 - 하드웨어 플랫폼 (x86 vs ARM/Jetson) - 센서 모델명 - 에러 메시지 전문 (일부만 복사하지 말고 전체를 줘라) - `ros2 topic list`, `ros2 node list` 출력 - Docker 사용 여부와 실행 옵션 (`docker run` 명령 전체) - 네트워크 구성 (유선/무선, IP 대역) #### 19.6.2 AI의 답을 검증하는 방법 AI의 답을 그대로 실행하기 전에 다음을 확인하라: - **"이 패키지를 설치하라"** → 해당 패키지가 자기 ROS 버전과 Ubuntu 버전을 지원하는지 먼저 확인. `apt search ros-humble-<패키지명>`으로 존재 여부를 체크한다. - **"이 설정을 바꿔라"** → 바꾸기 전에 현재 설정을 백업하고, 왜 바꿔야 하는지 근거를 AI에게 물어본다. 근거를 설명하지 못하면 의심하라. - **"재설치하라"** → 90%는 재설치 안 해도 된다. 먼저 정확한 에러 원인을 좁혀라. `pip show`, `dpkg -l | grep`, `apt policy` 등으로 현재 상태를 확인하는 게 먼저다. - **AI가 코드를 줄 때** → 하드코딩된 경로(`/home/user/...`), 하드코딩된 IP(`192.168.1.100`), x86 전용 패키지(`amd64` wheel) 등이 포함되어 있는지 확인한다. #### 19.6.3 AI가 잘하는 것 vs 못하는 것 | AI가 잘하는 것 | AI가 못하는 것 | |---|---| | 알고리즘 구현 (SLAM, detection 등) | 하드웨어 디버깅 | | ROS2 노드/서비스 코드 작성 | QoS/DDS 설정 최적화 | | Python/C++ 코드 리팩토링 | USB/시리얼 권한 문제 | | 논문 읽기/요약 | 네트워크 설정 (LiDAR IP 등) | | CMakeLists.txt 작성 | 실시간 타이밍 문제 | | 데이터 전처리 파이프라인 | Docker 안에서의 하드웨어 접근 | | 시각화 코드 (matplotlib, Open3D) | 센서 간 시간 동기화 실전 | | 에러 메시지 해석 (일반적인) | dmesg/커널 로그 기반 디버깅 | 결국 이런 구조다: AI는 "순수 소프트웨어" 영역에서는 강하지만, 하드웨어와 소프트웨어가 만나는 경계에서 약하다. 그리고 로보틱스 문제의 대부분이 그 경계에서 발생한다. AI가 강한 영역은 맡기고, AI가 약한 영역에서는 직접 디버깅한 결과를 AI에게 먹여서 분석하게 하는 게 맞다. ### 19.7 AI를 연구 도구로 쓰는 워크플로우 19.6절에서 AI의 강점과 약점을 정리했다. 이 절에서는 그걸 바탕으로, 로보틱스 연구자가 하루 일과에서 AI를 어떻게 쓰면 되는지 구체적 워크플로우를 정리한다. 원칙론이 아니라 실전 가이드다. #### 19.7.1 논문 읽기 - 1패스에서 abstract/conclusion 읽은 뒤 AI에게 PDF 던지고: "이 논문의 contribution을 3줄로", "Related work에서 가장 많이 비교하는 baseline이 뭐야", "Eq.5를 단계별로 유도해줘" - 단, AI 요약만 믿으면 안 되는 경우: 논문의 subtle한 가정, limitation section의 뉘앙스, 실험 세팅의 세부사항은 직접 읽어야 한다 - Semantic Scholar + AI: 관련 논문 추천받고, 각 논문의 차이점을 AI에게 비교 정리시키기 #### 19.7.2 코드 작성 - 프로토타이핑: "KITTI 데이터셋에서 ORB 특징점 뽑아서 매칭하는 코드 짜줘. OpenCV 쓰고, Lowe's ratio test 0.75로" — 이런 식으로 구체적으로 지시 - 디버깅: 에러 메시지 + 코드 + "이 에러의 원인이 뭐야" — AI가 잘하는 영역 - 리팩토링: "이 코드를 PyTorch Dataset 클래스로 바꿔줘" — 구조 변환에 강함 - AI가 못하는 것 (19장 앞부분 참조): ROS QoS, 하드웨어 권한, 네트워크 설정, 실시간 타이밍 #### 19.7.3 실험 설계 - "내 모델이 baseline 대비 뭘 개선했는지 보여주려면 어떤 ablation을 해야 해?" — AI가 실험 설계 초안을 잡아줄 수 있다 - "이 테이블의 결과를 보고 어떤 분석을 할 수 있어?" — AI에게 결과 해석을 시키면 놓친 관점을 찾을 수 있다 - 주의: AI의 실험 제안을 그대로 따르면 안 된다. 자기 연구의 contribution과 맞지 않는 ablation을 제안할 수 있다. 실험 설계의 최종 판단은 연구자의 몫이다. #### 19.7.4 논문 쓰기 - 초고 작성: 핵심 아이디어와 실험 결과를 AI에게 주고 "Introduction 초고를 써줘" — 구조 잡기에 유용 - 문법/표현 교정: 영어 논문의 어색한 표현 수정. Grammarly보다 AI가 context를 더 잘 이해한다 - 주의: AI가 쓴 문장을 그대로 제출하면 안 된다. 본인의 voice로 다시 써야 한다. 리뷰어는 AI 생성 문체를 알아본다 - BibTeX 생성: "이 논문의 BibTeX를 만들어줘" — Google Scholar에서 복사하는 것보다 빠를 때가 있다. 단, AI가 year이나 venue를 틀리는 경우가 있으니 반드시 검증 #### 19.7.5 일상 워크플로우 예시 하루 일과에서 AI를 어떻게 쓰는지 구체적 시나리오: ``` 09:00 — 새 논문 3편 arXiv에서 확인. AI에게 각각 1문장 요약 요청 09:30 — 흥미로운 1편 선택, 2패스 읽기. 모르는 수식은 AI에게 유도 요청 10:30 — 어제 학습 결과 분석. loss curve 캡처해서 AI에게 "이 패턴이 정상인가?" 확인 11:00 — 새 실험 코드 작성. AI에게 DataLoader 구조 생성 시킴. 수동으로 augmentation 로직 수정 14:00 — SLAM 코드 디버깅. ROS2 에러 → 19장 참고해서 직접 해결 (AI는 QoS를 모른다) 16:00 — 논문 Related Work 섹션 초고. AI에게 비교 논문 5편의 차이점 표 만들게 시킴 17:00 — 표 검증. AI가 2편의 method를 혼동한 것 발견, 수동 수정 ``` --- ## 20. 추천 자료 이 챕터에서는 로보틱스 연구를 시작할 때 도움이 될 교과서, 강의, 논문, 그리고 학습 경로를 정리한다. 자료가 너무 많아서 오히려 뭘 봐야 할지 모르겠다면, 맨 아래 **학습 경로** 섹션을 먼저 보자. ### 20.1 교과서 #### Computer Vision **Multiple View Geometry in Computer Vision** (Hartley & Zisserman) - 다시점 기하학의 핵심 교재 - 카메라 모델, Epipolar Geometry, 3D 복원 - 수학적으로 엄밀 — 솔직히 처음부터 끝까지 읽기는 고통스럽지만, 필요한 챕터만 발췌해서 읽으면 된다 - 링크: [Cambridge University Press](https://www.cambridge.org/core/books/multiple-view-geometry-in-computer-vision/0B6F289C78B2B23F596CAA76D3D43F7A) - 저자 홈페이지에서 일부 챕터 PDF 제공: https://www.robots.ox.ac.uk/~vgg/hzbook/ **Computer Vision: Algorithms and Applications** (Szeliski) - 포괄적인 CV 교과서 - 최신 버전 (2022)에 딥러닝 포함 - **무료 PDF 제공** — 학생에게는 축복 - 무료 PDF: https://szeliski.org/Book/ #### Robotics **Probabilistic Robotics** (Thrun, Burgard, Fox) - 확률적 로보틱스의 표준 교재 - Kalman Filter, Particle Filter, SLAM - 필수 교재 — SLAM을 연구하려면 읽어야 한다 - 링크: [MIT Press](https://mitpress.mit.edu/9780262201629/probabilistic-robotics/) - PDF는 공식적으로 무료가 아니지만, 저자의 강의 슬라이드가 대부분의 내용을 커버한다 **State Estimation for Robotics** (Tim Barfoot) - 상태 추정 심화 - Lie Groups, Factor Graph — 수학적으로 깊지만 설명이 친절하다 - **무료 PDF 제공** - 무료 PDF: http://asrl.utias.utoronto.ca/~tdb/bib/barfoot_ser17.pdf #### Deep Learning **Deep Learning** (Goodfellow, Bengio, Courville) - 딥러닝 이론 표준 교과서 - **무료 온라인** 제공 - 무료 PDF: https://www.deeplearningbook.org/ **Dive into Deep Learning** (d2l.ai) - 실습 중심 — 코드와 함께 배운다 - **무료, 인터랙티브** - 링크: https://d2l.ai/ - PyTorch, TensorFlow, JAX 버전 모두 지원 #### 수학 보충 **Introduction to Linear Algebra** (Gilbert Strang) - 선형대수를 직관적으로 설명하는 명저 - MIT OCW 강의와 함께 보면 효과 극대화 - 링크: https://math.mit.edu/~gs/linearalgebra/ila6/indexila6.html **Convex Optimization** (Boyd & Vandenberghe) - 최적화 이론의 표준 교재 - **무료 PDF 제공** - 무료 PDF: https://web.stanford.edu/~boyd/cvxbook/ ### 20.2 온라인 강의 #### Computer Vision **CS231n: Convolutional Neural Networks for Visual Recognition** (Stanford) - 딥러닝 비전의 기초 — 이 분야를 시작하는 거의 모든 사람이 보는 강의 - 무료 강의 자료, 영상 - 강의 영상: https://www.youtube.com/playlist?list=PL3FW7Lu3i5JvHM8ljYj-zLfQRF3EO8sYv - 강의 노트: https://cs231n.github.io/ **CS231A: Computer Vision, From 3D Reconstruction to Recognition** (Stanford) - 3D Vision 중심 - 기하학 기반 - 강의 자료: https://web.stanford.edu/class/cs231a/ #### SLAM **Cyrill Stachniss SLAM Course** (YouTube) - SLAM 이론 강의 — 독일어 억양이지만 설명이 정말 명확하다 - SLAM 입문자에게 가장 추천하는 강의 - YouTube: https://www.youtube.com/playlist?list=PLgnQpQtFTOGQrZ4O5QzbIHgl3b1JHimN_ **Multiple View Geometry** (TUM, Daniel Cremers 교수) - YouTube 공개 — 수학적으로 탄탄한 강의 - YouTube: https://www.youtube.com/playlist?list=PLTBdjV_4f-EJn6udZ34tht9EVIW7lbeo4 **SLAM 입문 (한국어)**: - SLAM KR 커뮤니티의 스터디 자료: https://github.com/slam-kr #### ROS **ROS2 공식 튜토리얼** - 가장 최신 정보 - 링크: https://docs.ros.org/en/humble/Tutorials.html (Humble 기준) - ROS2 Iron/Jazzy 등 다른 버전은 상단 드롭다운에서 변경 **The Construct** (온라인 플랫폼) - ROS 전문 강의 - 일부 무료 - 링크: https://www.theconstructsim.com/ #### 딥러닝 기초 **CS229: Machine Learning** (Stanford, Andrew Ng) - ML 기초 — 딥러닝 전에 이것부터 보는 것을 추천 - YouTube: https://www.youtube.com/playlist?list=PLoROMvodv4rMiGQp3WXShtMGgzqpfVfbU **Neural Networks: Zero to Hero** (Andrej Karpathy) - 신경망을 밑바닥부터 구현하면서 배우기 - 설명이 매우 직관적이고, 코드와 함께 진행 - YouTube: https://www.youtube.com/playlist?list=PLAqhIrjkxbuWI23v9cThsA9GvCAUhRvKZ ### 20.3 유튜브 채널 추천 교과서나 강의보다 가볍게 볼 수 있는 유튜브 채널들이다. 출퇴근길에, 밥 먹으면서, 쉬는 시간에 틀어놓으면 감각이 쌓인다. | 채널 | 주제 | 특징 | | --- | --- | --- | | **Cyrill Stachniss** | SLAM, Robotics | SLAM의 정석 강의. 학부 수업 수준의 체계적 설명 | | **First Principles of Computer Vision** (Shree Nayar) | Computer Vision | Columbia 교수가 CV 기초를 하나하나 설명. 정말 친절 | | **Andrej Karpathy** | Deep Learning, AI | Tesla AI Director 출신. Neural Net을 밑바닥부터 구현 | | **Yannic Kilcher** | 논문 리뷰 | 최신 ML/AI 논문을 매주 리뷰. 논문 읽는 법을 배울 수 있다 | | **Two Minute Papers** | AI 연구 트렌드 | 최신 연구를 2-3분 영상으로 소개. "What a time to be alive!" | | **3Blue1Brown** | 수학 시각화 | 선형대수, 미적분을 시각적으로 설명. 수학이 막힐 때 | | **Computerphile** | CS 전반 | 컴퓨터과학의 다양한 주제를 쉽게 설명 | | **sentdex** | Python, ML | Python으로 ML/로보틱스 실습. 코드 중심 | | **The Coding Train** | 알고리즘 시각화 | 알고리즘을 시각적으로 이해. 에너지 넘치는 진행 | **링크 모음**: - Cyrill Stachniss: https://www.youtube.com/@CyrillStachniss - First Principles of Computer Vision: https://www.youtube.com/@firstprinciplesofcomputerv3258 - Andrej Karpathy: https://www.youtube.com/@AndrejKarpathy - Yannic Kilcher: https://www.youtube.com/@YannicKilcher - Two Minute Papers: https://www.youtube.com/@TwoMinutePapers - 3Blue1Brown: https://www.youtube.com/@3blue1brown - Computerphile: https://www.youtube.com/@Computerphile - sentdex: https://www.youtube.com/@sentdex - The Coding Train: https://www.youtube.com/@TheCodingTrain ### 20.4 논문 읽기 #### 어떻게 읽을 것인가? 논문 읽기는 처음에 너무 어렵다. 2시간 걸려서 한 편 읽었는데 뭔 소린지 모르겠는 경험, 누구나 한다. 그래서 **3-패스 방법**을 추천한다: 1. **첫 번째 패스** (5-10분) - Title, Abstract, Conclusion - Figure, Table 훑어보기 - 핵심 contribution 파악 - 이 단계에서 "이 논문이 나에게 필요한가?"를 판단한다 2. **두 번째 패스** (1시간) - 전체 읽기 (수식 스킵 가능) - 방법론 이해 - 관련 연구 파악 - Figure를 꼼꼼히 보라 — 저자가 가장 공들인 곳이다 3. **세 번째 패스** (수 시간~며칠) - 수식 유도 따라가기 - 코드 분석 - 재구현 시도 - 여기까지 오면 그 논문의 전문가 > 팁: 처음에는 일주일에 1편을 3-패스로 완전히 이해하는 게, 매일 1편을 대충 읽는 것보다 낫다. 나중에 속도가 붙으면 자연스럽게 빨라진다. > AI 활용: 1패스 후에 Claude나 GPT에게 논문 PDF를 주고 "이 논문의 contribution 3줄 요약", "이 수식(Eq.5)을 단계별로 설명", "이 논문과 [비교 논문]의 차이점"을 물어보면 2패스 시간을 크게 줄일 수 있다. 단, AI 요약만 읽고 원문을 안 읽는 건 안 된다 — AI가 미묘한 가정이나 한계를 놓치는 경우가 많다. AI는 이해 보조 도구이지 대체재가 아니다. #### 필독 논문 리스트 **Classical CV/SLAM**: - ORB-SLAM: Mur-Artal et al., 2015 — [arXiv:1502.00956](https://arxiv.org/abs/1502.00956) - LOAM: Zhang & Singh, 2014 — [RSS 2014](https://www.ri.cmu.edu/pub_files/2014/7/Ji_LidarMapping_RSS2014_v8.pdf) - VINS-Mono: Qin et al., 2018 — [arXiv:1708.03852](https://arxiv.org/abs/1708.03852) **Deep Learning 기초**: - ResNet: He et al., 2015 — [arXiv:1512.03385](https://arxiv.org/abs/1512.03385) - Transformer (Attention Is All You Need): Vaswani et al., 2017 — [arXiv:1706.03762](https://arxiv.org/abs/1706.03762) - ViT: Dosovitskiy et al., 2020 — [arXiv:2010.11929](https://arxiv.org/abs/2010.11929) **Object Detection**: - Faster R-CNN: Ren et al., 2015 — [arXiv:1506.01497](https://arxiv.org/abs/1506.01497) - YOLO (original): Redmon et al., 2015 — [arXiv:1506.02640](https://arxiv.org/abs/1506.02640) - DETR: Carion et al., 2020 — [arXiv:2005.12872](https://arxiv.org/abs/2005.12872) **Foundation Models**: - CLIP: Radford et al., 2021 — [arXiv:2103.00020](https://arxiv.org/abs/2103.00020) - SAM (Segment Anything): Kirillov et al., 2023 — [arXiv:2304.02643](https://arxiv.org/abs/2304.02643) - DINOv2: Oquab et al., 2023 — [arXiv:2304.07193](https://arxiv.org/abs/2304.07193) **최신 트렌드**: - RT-2: Brohan et al., 2023 — [arXiv:2307.15818](https://arxiv.org/abs/2307.15818) - 3D Gaussian Splatting: Kerbl et al., 2023 — [arXiv:2308.14737](https://arxiv.org/abs/2308.14737) - Depth Anything: Yang et al., 2024 — [arXiv:2401.10891](https://arxiv.org/abs/2401.10891) > 논문 검색 팁: [Google Scholar](https://scholar.google.com/), [Semantic Scholar](https://www.semanticscholar.org/), [Papers With Code](https://paperswithcode.com/), [arXiv](https://arxiv.org/)를 활용하자. Papers With Code는 특히 벤치마크 순위와 코드 링크를 같이 보여줘서 편하다. ### 20.5 주요 학회 #### Computer Vision | 학회 | 수준 | 특징 | | --- | --- | --- | | **CVPR** | Top | 가장 큰 CV 학회. 매년 6월 | | **ICCV** | Top | 2년 주기 (홀수년). CVPR과 함께 CV 양대 산맥 | | **ECCV** | Top | 유럽 중심, 2년 주기 (짝수년) | | **NeurIPS** | Top | ML 전반, Vision 포함. 매년 12월 | | **ICML** | Top | ML 전반. 매년 7월 | | **ICLR** | Top | 딥러닝 중심. 매년 5월 | #### Robotics | 학회 | 수준 | 특징 | | --- | --- | --- | | **ICRA** | Top | IEEE 로보틱스. 가장 큰 로보틱스 학회 | | **IROS** | Top | IEEE/RSJ. ICRA와 함께 양대 산맥 | | **RSS** | Top | 소규모, 선별적. 질이 높다 | | **CoRL** | Top | 로봇 학습 특화. 최근 급성장 | #### 자율주행 | 학회/저널 | 특징 | | --- | --- | | **CVPR Workshop** (WAD, OmniCV) | 자율주행 워크샵 | | **T-IV** | 지능형 차량 저널 | | **T-ITS** | 교통 시스템 저널 | ### 20.6 유용한 GitHub 저장소 #### SLAM ``` # ORB-SLAM3 — Visual(-Inertial) SLAM의 레퍼런스 https://github.com/UZ-SLAMLab/ORB_SLAM3 # VINS-Fusion — 다중 카메라+IMU 융합 https://github.com/HKUST-Aerial-Robotics/VINS-Fusion # LIO-SAM — LiDAR-Inertial SLAM (factor graph 기반) https://github.com/TixiaoShan/LIO-SAM # FAST-LIO2 — 빠른 LiDAR-Inertial Odometry https://github.com/hku-mars/FAST_LIO # RTAB-Map — RGB-D SLAM, 대규모 환경 지원 https://github.com/introlab/rtabmap # SplaTAM — 3D Gaussian Splatting 기반 SLAM https://github.com/spla-tam/SplaTAM ``` #### Deep Learning ``` # Ultralytics YOLO — YOLOv8/v11, 가장 쓰기 쉬운 detection 프레임워크 https://github.com/ultralytics/ultralytics # HuggingFace Transformers — NLP/Vision 모델 허브 https://github.com/huggingface/transformers # OpenMMLab — Detection, Segmentation, 3D 등 종합 프레임워크 https://github.com/open-mmlab # PyTorch Lightning — 학습 코드 구조화 https://github.com/Lightning-AI/pytorch-lightning # timm (PyTorch Image Models) — 사전학습된 Vision 모델 모음 https://github.com/huggingface/pytorch-image-models ``` #### 3D Vision ``` # Open3D — 포인트 클라우드, 메쉬 처리 https://github.com/isl-org/Open3D # 3D Gaussian Splatting — 원본 구현 https://github.com/graphdeco-inria/gaussian-splatting # NeRF Studio — NeRF/3DGS 통합 프레임워크 https://github.com/nerfstudio-project/nerfstudio # Depth Anything V2 — 범용 depth estimation https://github.com/DepthAnything/Depth-Anything-V2 # COLMAP — Structure from Motion 파이프라인 https://github.com/colmap/colmap ``` #### VFM/VLA ``` # Segment Anything (SAM) — Meta의 범용 세그멘테이션 https://github.com/facebookresearch/segment-anything # SAM 2 — 비디오까지 확장 https://github.com/facebookresearch/sam2 # DINOv2 — Self-supervised vision features https://github.com/facebookresearch/dinov2 # Grounded-SAM — 텍스트로 물체 찾기 + 세그멘테이션 https://github.com/IDEA-Research/Grounded-Segment-Anything # OpenVLA — 오픈소스 Vision-Language-Action 모델 https://github.com/openvla/openvla ``` #### ROS / 로봇 개발 ``` # ROS2 공식 저장소 https://github.com/ros2 # Nav2 — ROS2 네비게이션 스택 https://github.com/ros-navigation/navigation2 # MoveIt2 — 로봇팔 모션 플래닝 https://github.com/moveit/moveit2 # micro-ROS — 마이크로컨트롤러용 ROS https://github.com/micro-ROS ``` #### 유용한 Awesome 리스트 ``` # Awesome SLAM — SLAM 자료 종합 https://github.com/SilenceOverflow/Awesome-SLAM # Awesome Robotics — 로보틱스 자료 종합 https://github.com/kiloreux/awesome-robotics # Awesome 3D Gaussian Splatting — 3DGS 논문/코드 모음 https://github.com/MrNeRF/awesome-3D-gaussian-splatting ``` ### 20.7 추천 학습 경로 아래는 기존 1.4절에 있던 학습 경로를 확장한 것이다. 각 단계별로 구체적인 자료와 링크를 달았으니, 자기 수준에 맞는 단계부터 시작하면 된다. #### 입문 단계 (1-3개월) **목표**: 기초 도구 습득 — "일단 뭔가 돌려볼 수 있는 상태"가 되는 것 | 주제 | 학습 내용 | 추천 자료 | | --- | --- | --- | | Python 숙달 | 문법, 클래스, 파일 I/O | [점프 투 파이썬](https://wikidocs.net/book/1) (무료, 한국어) | | NumPy, OpenCV 기초 | 배열 연산, 이미지 읽기/처리 | [OpenCV 공식 튜토리얼](https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html) | | 선형대수 복습 | 행렬, 고유값, SVD | [3Blue1Brown: Essence of Linear Algebra](https://www.youtube.com/playlist?list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab) | | 확률/통계 복습 | 베이즈 정리, 가우시안 | [StatQuest](https://www.youtube.com/@statquest) | | ROS2 기본 | 노드, 토픽, 서비스 | [ROS2 공식 튜토리얼](https://docs.ros.org/en/humble/Tutorials.html) | | Git 사용법 | commit, branch, PR | [Git 입문](https://backlog.com/git-tutorial/kr/) (한국어) | **실습 과제**: - OpenCV로 이미지 처리 (grayscale 변환, edge detection, feature 추출) - 간단한 ROS2 노드 작성 (publisher/subscriber) - 카메라 캘리브레이션 수행 — 본 문서 9장 참고 - 본 문서의 **2장, 3장**을 읽으면서 좌표 변환과 카메라 모델을 이해한다 **마일스톤**: Python으로 이미지를 읽어서 특징점을 추출하고, 두 이미지 간 매칭을 시각화할 수 있으면 입문 단계 졸업이다. #### 중급 단계 (3-6개월) **목표**: 핵심 기술 이해 — "논문을 읽고 코드를 돌려볼 수 있는 상태" | 주제 | 학습 내용 | 추천 자료 | | --- | --- | --- | | 딥러닝 기초 (PyTorch) | CNN, 학습, 역전파 | [CS231n](https://www.youtube.com/playlist?list=PL3FW7Lu3i5JvHM8ljYj-zLfQRF3EO8sYv) + [PyTorch 공식 튜토리얼](https://pytorch.org/tutorials/) | | Object Detection | YOLO, Faster R-CNN | [Ultralytics 문서](https://docs.ultralytics.com/) + 본 문서 10장 | | Visual SLAM 이해 | ORB-SLAM3 분석 | [Cyrill Stachniss SLAM 강의](https://www.youtube.com/playlist?list=PLgnQpQtFTOGQrZ4O5QzbIHgl3b1JHimN_) + 본 문서 14장 | | 포인트 클라우드 처리 | Open3D 사용법 | [Open3D 튜토리얼](http://www.open3d.org/docs/release/tutorial/) + 본 문서 13장 | | Depth Estimation | 단안 카메라 depth 추정 | 본 문서 10장 + [Depth Anything 코드](https://github.com/DepthAnything/Depth-Anything-V2) | **실습 과제**: - KITTI 데이터셋 다루기 — [KITTI 홈페이지](https://www.cvlibs.net/datasets/kitti/) - YOLOv8 파인튜닝 — 커스텀 데이터셋으로 fine-tuning - ORB-SLAM3 실행 및 분석 — TUM RGB-D 데이터셋으로 평가 - TUM RGB-D 벤치마크 — ATE, RPE 계산해보기 - 본 문서의 **9-14장**을 읽으면서 이론적 배경을 다진다 **마일스톤**: ORB-SLAM3를 직접 빌드하고 데이터셋으로 돌려서, trajectory를 ground truth와 비교할 수 있으면 중급 단계 졸업이다. #### 고급 단계 (6개월+) **목표**: 연구 능력 개발 — "새로운 아이디어를 내고 실험할 수 있는 상태" | 주제 | 학습 내용 | 추천 자료 | | --- | --- | --- | | VFM 이해 및 활용 | DINOv2, SAM, CLIP | 본 문서 10-11장 + 논문 직접 읽기 | | 3D 재구성 심화 | NeRF, 3D Gaussian Splatting | [NeRF Studio](https://github.com/nerfstudio-project/nerfstudio) + 본 문서 13장 | | 논문 읽기 및 구현 | 최신 논문 분석 | [Papers With Code](https://paperswithcode.com/) + [Yannic Kilcher 채널](https://www.youtube.com/@YannicKilcher) | | 새로운 아이디어 실험 | 가설 수립, 실험 설계 | 연구실 세미나 + 학회 워크샵 참여 | | 벤치마크 평가 | 정량적 비교 | 각 분야별 표준 벤치마크 (KITTI, ScanNet, Replica 등) | **실습 과제**: - 최신 논문 코드 분석 — GitHub에서 코드를 받아 직접 돌려보기 - 자체 개선 아이디어 실험 — "이 부분을 바꾸면 어떨까?" 시도 - 논문 작성 시도 — LaTeX로 4-6페이지짜리 draft 써보기 - 본 문서의 **10-13장**을 읽으면서 최신 연구 방향을 파악한다 **마일스톤**: 기존 논문의 방법을 수정/개선한 실험을 하고, 그 결과를 정량적으로 비교할 수 있으면 고급 단계에 진입한 것이다. 학회 workshop에 제출할 수 있는 수준이 되는 것을 목표로 하자. #### 학습 순서 요약 ``` 입문 (1-3개월) 중급 (3-6개월) 고급 (6개월+) ───────────── ───────────── ───────────── Python + NumPy PyTorch + CNN VFM (DINOv2, SAM) OpenCV 기초 YOLO 파인튜닝 3DGS / NeRF 선형대수/확률 복습 ORB-SLAM3 분석 논문 구현 ROS2 기초 KITTI/TUM 벤치마크 아이디어 실험 Git 사용법 포인트 클라우드 (Open3D) 논문 작성 Depth Estimation ↓ ↓ ↓ "코드를 돌릴 수 있다" "논문을 읽고 재현한다" "새 아이디어를 실험한다" ``` ### 20.8 연구 스킬 *대학원 수준.* #### 20.8.0 연구자로서의 마인드셋 연구 스킬을 다루기 전에, 연구를 시작하는 사람이 가져야 할 태도에 대해 먼저 이야기한다. 논문 쓰는 법, 실험 설계 같은 기술적 스킬은 하면 늘지만, 마인드셋이 잘못 잡히면 몇 년을 해도 제자리다. **엔지니어는 직업이 아니라 정체성이다** 로보틱스 엔지니어라는 말을 들으면 보통 "로봇 만드는 사람"을 떠올린다. 로봇 팔을 조립하고, SLAM 코드를 돌리고, 모터 드라이버를 디버깅하는 사람. 틀린 말은 아니지만, 그건 엔지니어가 하는 "일"이지 엔지니어가 "무엇인지"에 대한 답은 아니다. 엔지니어는 직업이 아니라 정체성이다. 세상을 보는 방식이다. 문제가 있으면 그걸 모델링하고, 모델링한 문제를 풀고, 가능한 최적의 해를 찾아내는 것. 이게 엔지니어가 세상을 대하는 태도이고, 이 태도는 연구실을 나서도 꺼지지 않는다. **모든 것은 최적화 문제다** > "이 세상 모든것이 optimization이다." > > — Dr. Dongjin Hyun, Head of Robotics LAB, Hyundai Motor Company 엔지니어링에는 100%가 없다. 0%도 없다. 사이언스는 자연 법칙을 발견하는 학문이다. "빛의 속도는 299,792,458 m/s이다" 같은 단정적인 진술을 만든다. 엔지니어링은 다르다. 우리가 다루는 건 현실 세계의 더럽고 애매한 문제들이다. 센서는 노이즈를 뱉고, 모터는 명령한 토크를 정확히 내지 않고, 환경은 예측 불가능하게 변한다. 이런 문제에는 "정답"이 없다. 있는 건 "이 조건 하에서 가장 나은 선택"뿐이다. 그래서 엔지니어가 하는 일의 본질은 최적화다. 불확실하고 확률적인(probabilistic) 세상을 기댓값으로 모델링하고, 적절한 수준에서 단순화(level of abstraction)를 결정하고, 그 위에서 최적화를 돌리는 것. SLAM에서 센서 노이즈를 가우시안으로 모델링하고 factor graph를 풀어서 최적의 포즈를 추정하는 것이 그렇고, MPC에서 유한 구간의 미래를 예측하고 비용 함수를 최소화하는 제어 입력을 구하는 것이 그렇다. 그런데 이건 로봇 문제에만 해당하는 이야기가 아니다. 점심을 어디서 먹을지 고르는 것도, 오늘 밤을 새서 논문을 쓸지 내일 집중해서 쓸지 결정하는 것도, 대학원에서 어떤 연구 주제를 선택할지도 — 본질적으로 같은 구조의 문제다. 불확실한 세상에서, 내가 관측할 수 있는 정보(observable state) 안에서, 기대 보상(expected reward)을 최대화하는 선택지를 찾는 것. 엔지니어의 눈으로 보면 세상의 거의 모든 의사결정이 이 프레임에 들어맞는다. **근시안적 판단의 정체** 이 관점에서 보면 "근시안적인 판단"이 뭔지도 명확해진다. 우리가 누군가를 보고 "왜 저런 선택을 하지?"라고 생각할 때, 그 사람이 멍청해서가 아니다. 그 사람의 loss function에 장기적 state가 들어가 있지 않아서 그런 것이다. 미래의 나에 대한 상태 — 3년 뒤의 커리어, 건강, 관계 — 가 의사결정 모델에 반영이 안 되어 있으면, 지금 눈앞에 보이는 state만 가지고 greedy한 최적화를 하게 된다. 그리고 그 사람의 관점에서는, 자기가 구성한 상태 공간 안에서 최적의 결정을 내린 것이다. 틀린 게 아니라 모델이 부족한 것이다. 연구도 마찬가지다. 이번 주 실험 결과를 빨리 내려고 밤을 새는 것과, 일주일을 투자해서 코드를 제대로 구조화한 뒤 실험하는 것 — 단기 loss function에서는 밤새는 쪽이 최적이지만, 6개월 뒤까지 state를 확장하면 후자가 압도적으로 낫다. 코드가 깨끗하면 이후 실험을 10배 빨리 반복할 수 있으니까. 연구에서 "장기전이 중요하다"는 조언은, 수학적으로 말하면 "optimization horizon을 늘려라"는 이야기다. 그래서 좋은 연구자가 되려면 자기 의사결정 모델의 state space를 의식적으로 확장해야 한다. "지금 이 논문"이 아니라 "3년 뒤의 나", "이 분야의 5년 뒤"까지 state에 넣어야 한다. 물론 먼 미래의 state는 불확실하다. 하지만 불확실하다고 빼버리면 근시안이 되고, 넣되 큰 variance를 부여하면 합리적인 장기 계획이 된다. 칼만 필터에서 process noise를 키우는 것과 같은 원리다. **그러면 방향은 어디서 오는가** 여기서 중요한 질문이 생긴다. 최적화를 하려면 비용 함수가 있어야 한다. "뭘 최소화할 것인가", "뭘 최대화할 것인가". 연구에서는 논문 수? citation? 졸업 시기? 연봉? 이런 것들은 측정 가능한 메트릭이지만, 그게 정말 최적화해야 할 목적 함수인지는 별개의 문제다. 내가 뭘 원하는지, 어디로 가야 하는지 — 이 질문에 대한 답은 엔지니어링 안에 없다. 선형대수를 아무리 잘 풀어도, SLAM 논문을 100편 읽어도, 이 질문에는 답이 안 나온다. 이건 인문학의 영역이다. 철학, 문학, 역사. 다른 사람들은 나의 거울이라는 말이 있다. 책을 읽는 것은 그 내용을 이해하는 것이기도 하지만, 그 글을 쓴 사람이 어떤 삶을 살았고, 무슨 고민을 했고, 왜 그런 생각에 도달했는지를 들여다보는 것이기도 하다. 그걸 통해 자기 자신을 되돌아볼 수 있다. 내가 이 글을 읽고 무엇을 느꼈는지를 인식하는 것 — 메타인지라는 게 별거 아니고 이게 전부다. 사람은 같은 실수를 반복하는 동물이다. 그건 어쩔 수 없다. 하지만 수천 년간 반복하면서도 여기까지 온 건, 기록이라는 게 있었기 때문이다. 글을 쓰고, 지식을 전달하고, 그 지식을 받아들이는 자기 자신을 인지하는 것. 문명의 진보가 여기서 온다. 고전 문학을 읽어도 공감할 수 있는 건, 인간의 DNA가 수천 년 단위로는 거의 변하지 않았기 때문이다. 옛날 사람들이 겪은 고민 — 방향에 대한 불안, 동기의 상실, 비교에서 오는 좌절 — 은 지금 대학원생이 겪는 것과 본질적으로 같다. 그러니까 연구자로서 방향을 잡으려면, 가끔은 논문이 아니라 책을 읽어라. 엔지니어링 밖의 책을. 거기서 자기가 뭘 중요하게 여기는지, 어떤 키워드가 자기 삶에서 반복되는지 — 성장인지, 사람인지, 자유인지, 인정인지 — 힌트를 찾을 수 있다. 정답은 없고, 정답이 없다는 걸 아는 것 자체가 시작이다. **세 가지: 방향, 엔진, 도구** 정리하면, 연구자로서 오래 가려면 세 가지가 필요하다. 첫째, **방향**. 내가 어디로 가야 하는지. 위에서 말했듯이 이건 엔지니어링 밖에서 찾아야 한다. 방향이 없으면 아무리 열심히 달려도 "열심히 달렸다"는 사실만 남는다. 둘째, **엔진**. 동기, 원동력. 내 마음속에 있는 엔진이 무엇을 연료로 넣어서 출력을 내는가? 순수한 호기심일 수도 있고, 성장에 대한 갈망일 수도 있고, 사람들과 나누는 교류에서 오는 에너지일 수도 있다. 어떤 사람은 "문제를 풀었을 때의 쾌감"이 연료이고, 어떤 사람은 "내가 만든 게 세상에서 돌아가는 걸 보는 것"이 연료이다. 이건 사람마다 다르고 정답이 없다. 다만 자기 엔진이 뭘로 돌아가는지는 알아야 한다. 모르면 연료가 떨어졌을 때 왜 멈췄는지도 모르고, 다시 시동을 걸 수도 없다. 셋째, **도구**. 학위, 프로그래밍 실력, 수학, 연구실 인프라, 동료, 가족의 지원. 이 문서에서 21개 챕터에 걸쳐 다루는 기술적 내용이 전부 여기에 해당한다. 도구는 중요하다. 칼이 날카로워야 나무를 벨 수 있다. 하지만 방향과 엔진 없이 도구만 갈고닦으면, 어디로 가야 할지 모르는 채로 칼만 날카로운 사람이 된다. 그리고 그런 사람은 생각보다 많다. 이 문서의 나머지 부분은 전부 "도구"에 대한 이야기다. 방향과 엔진은 각자가 찾아야 한다. --- 아래는 연구를 시작하는 단계에서의 구체적인 전략이다. (이 부분은 [Giseop Kim, "연구 초입자를 위한 지속가능한 성장 가이드"](https://gsk1m.github.io/productivity/2024/05/25/entering-research.html)의 내용을 참고하여 재구성했다.) **꾸준함이 폭발적 성장보다 낫다** 연구 초반에는 성장이 느리다. 논문을 읽어도 이해가 안 되고, 코드를 짜도 안 돌아가고, 실험 결과가 기대와 다르다. 이게 정상이다. 한 주기에 하나씩, 일관된 속도로, 지치지 않게 밀고 나가는 것이 중요하다. 한 달에 논문 10편을 읽고 번아웃 되는 것보다, 매주 1편을 6개월 동안 꾸준히 읽는 쪽이 낫다. 선형 성장이 복리로 돌아온다. **옆 사람 속도에 흔들리지 마라** 같이 입학한 동기가 벌써 논문을 냈다고, 옆 연구실이 더 좋은 장비를 가졌다고 조급해지면 안 된다. 연구 분야마다 성과가 나오는 속도가 다르고, 같은 분야 안에서도 주제에 따라 천차만별이다. 비교 대상은 어제의 나 자신이어야 한다. 타인의 연구 철학과 노력 방식은 배우되, 숫자(논문 편수, citation)에 집착하면 방향을 잃는다. **논문은 대작보다 견고한 기초가 먼저다** 첫 논문에서 Nature를 노리는 것보다, "리젝 이유가 없는 논문"을 목표로 하는 편이 현실적이다. 실험이 재현 가능하고, 비교가 공정하고, 주장이 데이터로 뒷받침되는 논문. 화려한 novelty보다 빈틈없는 완성도가 첫 논문에서는 더 중요하다. 하나의 논문에는 하나의 핵심 메시지만 담아라. 제목을 먼저 지어보고 — 그 제목이 논문의 contribution을 한 문장으로 요약하는지 확인하라. **이론 공부보다 연구를 해야 연구를 잘하게 된다** 교과서를 다 읽고 시작하려면 영원히 시작하지 못한다. "이 개념을 완벽히 이해한 다음에 실험하겠다"는 마인드가 가장 위험하다. 필요한 만큼만 공부하고 바로 실험에 뛰어들어라. 모르는 건 하다가 막힐 때 찾아보면 된다. 농사는 씨를 뿌려야 시작이지, 농업 이론서를 다 읽는 게 시작이 아니다. 기술 부채는 쌓이겠지만, 그건 나중에 하나씩 갚으면 된다. 안 갚으면 안 되지만, 시작을 미루는 것보다는 낫다. **지적 성실성** 좋은 연구자는 자기가 틀렸을 수 있다는 가능성을 항상 열어둔다. 실험 결과가 가설과 다르면, 결과를 의심하기 전에 가설을 의심하라. 자기 마음을 바꿀 수 있는 것이 지적으로 성실한 태도다. 이를 위해서는 다양한 관점의 논문을 많이 읽어서 메타인지를 키워야 한다. "내가 모르는 것이 무엇인지 아는 것"이 연구 역량의 핵심이다. **20.8.1 논문 쓰기** - 구조: Abstract → Introduction → Related Work → Method → Experiments → Conclusion - Introduction 쓰는 법: (1) 문제 정의, (2) 기존 방법의 한계, (3) 우리의 접근, (4) contribution 목록 - 실험 섹션: baseline 비교, ablation study, 정성적 결과 - 흔한 실수: contribution이 불명확, 실험이 불공정한 비교, related work에서 핵심 논문 빠뜨림 - 팁: 논문을 쓰기 전에 figure/table을 먼저 그려라. 스토리가 잡힌다 **20.8.2 실험 설계와 Ablation Study** - Ablation: 모델/시스템의 각 구성 요소를 하나씩 빼면서 기여도를 측정 - 변인 통제: 한 번에 하나만 바꿔라. 두 개 이상 동시에 바꾸면 어떤 것의 효과인지 모른다 - 통계적 유의성: 같은 실험을 여러 번(최소 3회) 반복하고 평균/표준편차 보고 - 공정한 비교: 같은 데이터, 같은 split, 같은 하드웨어에서 비교. 다른 논문의 숫자를 그대로 가져오면 조건이 다를 수 있다 **20.8.3 학회 발표** - 발표 구조: 문제(1분) → 기존 한계(1분) → 제안 방법(3분) → 실험 결과(3분) → 결론(1분) - 슬라이드: 글자 줄이고 그림/다이어그램 위주. 한 슬라이드에 하나의 메시지 - 포스터: 3m 거리에서 제목과 핵심 figure가 보여야 한다 - 데모 영상: 30초~1분. 처음에 결과 요약, 그 다음 상세 **20.8.4 논문 리뷰 (Peer Review)** - 리뷰어 관점에서 체크할 것: novelty, technical soundness, experiments, clarity, reproducibility - 건설적 피드백: "이 부분이 잘못됐다" 대신 "이 부분은 X를 추가하면 더 강해질 것 같다" - Rebuttal 쓰기: 리뷰어의 핵심 우려를 하나씩 주소를 달아 답변. 감정적 대응 금지 **20.8.5 도구** - LaTeX: Overleaf 또는 로컬 (texlive + vscode) - 참고 문헌 관리: 예전에는 Mendeley, Zotero 같은 전용 도구를 썼지만, 요즘은 **PDF 리더 + AI** 조합이 더 효율적이다. 예를 들어: - Adobe Acrobat으로 논문을 읽고 형광펜/메모를 남긴 뒤, Claude나 GPT에게 PDF를 던져서 핵심 요약, BibTeX 생성, related work 비교를 시킬 수 있다 - Google Scholar에서 논문을 찾고, "Cite" 버튼으로 BibTeX를 직접 복사하는 것만으로도 충분한 경우가 많다 - Zotero + Better BibTeX는 여전히 쓸만하지만 필수는 아니다. 논문 수가 100편을 넘어가면 관리 도구가 있는 게 편하다 - Semantic Scholar의 Research Feed로 관련 논문 추천을 받는 것도 추천한다 - 파이프라인 그림: TikZ (정밀 제어), draw.io (빠른 제작), Inkscape (SVG 편집) - 테이블: booktabs 패키지 (\toprule, \midrule, \bottomrule) - 알고리즘: algorithm2e 패키지 - 수식: 표기법을 논문 전체에서 통일하라 (notation table 만들기) > 추천 자료: > - [How to Write a Great Research Paper (Simon Peyton Jones, Microsoft Research)](https://www.microsoft.com/en-us/research/academic-program/write-great-research-paper/) — 논문 쓰기의 고전 강연 > - [How to Read a Paper (S. Keshav)](http://ccr.sigcomm.org/online/files/p83-keshavA.pdf) — 3-pass reading method > - [Tips for Writing Technical Papers (Jennifer Widom, Stanford)](https://cs.stanford.edu/people/widom/paper-writing.html) — 간결한 실전 조언 --- ## 21. 부록 ### A. 용어 사전 #### A.1 약어 | 약어 | 풀이 | 설명 | | --- | --- | --- | | SLAM | Simultaneous Localization and Mapping | 동시적 위치추정 및 지도작성 | | VO | Visual Odometry | 시각 주행거리계 | | VIO | Visual-Inertial Odometry | 시각-관성 주행거리계 | | LIO | LiDAR-Inertial Odometry | 라이다-관성 주행거리계 | | IMU | Inertial Measurement Unit | 관성 측정 장치 | | DoF | Degrees of Freedom | 자유도 | | SE(3) | Special Euclidean Group (3D) | 3D 강체 변환 그룹 | | SO(3) | Special Orthogonal Group (3D) | 3D 회전 그룹 | | FoV | Field of View | 시야각 | | ToF | Time of Flight | 비행 시간 (거리 측정 방식) | | CNN | Convolutional Neural Network | 합성곱 신경망 | | ViT | Vision Transformer | 비전 트랜스포머 | | VFM | Vision Foundation Model | 비전 기반 모델 | | VLA | Vision-Language-Action | 시각-언어-행동 모델 | | VLM | Vision-Language Model | 시각-언어 모델 | | LLM | Large Language Model | 대규모 언어 모델 | | mAP | mean Average Precision | 평균 정밀도 | | ICP | Iterative Closest Point | 반복적 최근접점 | | NDT | Normal Distributions Transform | 정규분포 변환 | | NeRF | Neural Radiance Fields | 신경 방사장 | | 3DGS | 3D Gaussian Splatting | 3D 가우시안 스플래팅 | | BEV | Bird's Eye View | 조감도 | | TSDF | Truncated Signed Distance Function | 절단 부호 거리 함수 | | BA | Bundle Adjustment | 번들 조정 | | PGO | Pose Graph Optimization | 포즈 그래프 최적화 | | DDS | Data Distribution Service | ROS2의 통신 미들웨어 | | ONNX | Open Neural Network Exchange | 모델 변환 포맷 | | TRT | TensorRT | NVIDIA 추론 최적화 엔진 | | ATE | Absolute Trajectory Error | 절대 궤적 오차 | | RPE | Relative Pose Error | 상대 포즈 오차 | #### A.2 용어 **Keyframe**: 중요 정보를 포함하는 선택된 프레임. 모든 프레임을 처리하면 너무 느리니까, 의미 있는 변화가 있는 프레임만 골라서 사용한다. **Loop Closure**: 이전 방문 장소 재인식을 통한 드리프트 보정. "아, 여기 아까 왔던 곳이네" → 누적 오차를 한꺼번에 교정. **Drift**: 오차의 누적. 100m를 걸어가면서 매 걸음마다 1cm씩 오차가 나면, 도착할 때는 100cm 오차가 된다. **Reprojection Error**: 3D 점을 이미지에 재투영했을 때의 오차. "이 3D 점이 카메라 이미지 어디에 보여야 하는가"의 예측값과 실제값의 차이. **Feature Descriptor**: 특징점 주변을 설명하는 벡터. 두 이미지에서 같은 점을 찾을 때, 이 벡터를 비교한다. **Homography**: 평면 간의 변환. 책상 위를 찍은 두 사진을 정합할 때 사용. **Essential Matrix**: 캘리브레이션된 카메라 쌍의 기하 관계. 5DoF(회전 3 + 이동 방향 2). **Fundamental Matrix**: 캘리브레이션되지 않은 카메라 쌍의 기하 관계. 7DoF. **Epipole**: 한 카메라의 중심이 다른 카메라 이미지에 투영된 점. **Zero-shot**: 학습 없이 새로운 태스크 수행. "고양이"를 학습 안 했는데 "고양이 찾아줘"가 되는 것. **Few-shot**: 적은 예제로 새로운 태스크 학습. 예시 3-5개만 주면 학습. **Fine-tuning**: 사전학습 모델을 특정 태스크에 맞게 재학습. 대형 모델을 내 데이터에 맞게 조정. **Domain Adaptation**: 소스 도메인에서 타겟 도메인으로 적응. 시뮬레이션에서 학습 → 실제 환경 적용. **Sim-to-Real**: 시뮬레이션에서 실제 환경으로 전이. Domain Adaptation의 대표적 사례. **Gaussian Splatting**: 3D 장면을 수백만 개의 3D 가우시안으로 표현하는 방법. NeRF보다 빠르고 편집 가능. **Factor Graph**: 변수 간의 제약 조건을 그래프로 표현. SLAM 최적화의 핵심 자료구조. **Knowledge Distillation**: 큰 모델(teacher)의 지식을 작은 모델(student)에 전달하는 기법. ### B. 자주 묻는 질문 (FAQ) **Q: Python과 C++ 중 어떤 것을 먼저 배워야 하나요?** A: 연구실 코드의 핵심은 C++이다. SLAM, ROS 패키지, 실시간 제어 모듈 전부 C++로 되어 있고, 이 코드를 읽고 수정할 일이 많다. Python은 딥러닝 스크립트나 데이터 전처리에 쓰이지만, AI 코딩 에이전트가 잘 도와주는 영역이라 직접 숙달할 필요성은 줄었다. 둘 다 "코드를 읽고 이해하는 능력"이 핵심이고, 작성은 AI와 협업하면 된다. **Q: GPU가 없으면 연구를 할 수 없나요?** A: 간단한 실험은 CPU로 가능하다. 하지만 딥러닝 학습에는 GPU가 필수이다. Google Colab(무료)이나 연구실 서버를 활용하자. Colab 무료 버전으로도 YOLO fine-tuning 정도는 충분히 가능하다. **Q: ROS1과 ROS2 중 어떤 것을 배워야 하나요?** A: 새로 배운다면 ROS2를 권장한다. ROS1은 2025년에 공식 지원이 종료(EOL)되었다. 하지만 사용하려는 패키지가 ROS1만 지원하면 어쩔 수 없이 ROS1을 먼저 배울 수도 있다. 좋은 소식은, ROS1을 알면 ROS2는 금방 배운다는 것이다. **Q: 논문을 어디서 찾나요?** A: [arXiv](https://arxiv.org/) (무료 프리프린트 서버), [Google Scholar](https://scholar.google.com/) (논문 검색), [Papers With Code](https://paperswithcode.com/) (코드 포함 논문 검색)를 활용하자. 학회별로 보고 싶으면 [CVPR Open Access](https://openaccess.thecvf.com/)나 [IEEE Xplore](https://ieeexplore.ieee.org/)도 유용하다. **Q: SLAM을 공부하려면 어디서 시작해야 하나요?** A: Cyrill Stachniss의 [YouTube SLAM 강의](https://www.youtube.com/playlist?list=PLgnQpQtFTOGQrZ4O5QzbIHgl3b1JHimN_)로 시작하고, ORB-SLAM3 코드를 분석해보자. 그 전에 본 문서의 9장(카메라 모델)과 14장(Visual Odometry)을 읽으면 강의가 훨씬 잘 들릴 것이다. **Q: 연구 아이디어는 어떻게 찾나요?** A: 최신 학회 논문의 Limitation 섹션을 읽어보자. 해결되지 않은 문제에서 아이디어를 얻을 수 있다. 또 다른 방법은 두 분야를 결합하는 것이다 — 예: "3D Gaussian Splatting + Semantic SLAM"처럼 아직 합쳐지지 않은 것을 합치는 시도. **Q: 어떤 GPU를 사야 하나요?** A: GPU 선택에서 가장 중요한 스펙은 **VRAM**이다. 모델 크기가 VRAM에 안 들어가면 아예 못 돌린다. 그 다음이 연산 속도 (TFLOPS)이고, 학습 시간에 직접 영향을 준다. **개인용 (데스크톱)** | VRAM | 카드 | FP32 TFLOPS | FP16 TFLOPS | 용도 | |------|------|-------------|-------------|------| | 8GB | RTX 4060 | 15.1 | 15.1 | YOLOv8, ResNet 학습, 소규모 fine-tuning | | 8GB | RTX 5060 Ti 8GB | ~30 | ~30 | 4070급 연산이지만 VRAM이 8GB라 VFM은 빠듯 | | 12GB | RTX 3060 12GB | 12.7 | 12.7 | 연산은 느리지만 12GB VRAM이 이 가격대에서 유일. 중고로 저렴. 학생 엔트리용 | | 12GB | RTX 4070 | 29.1 | 29.1 | Depth Anything, SegFormer. SAM은 batch 1로 겨우 가능 | | 16GB | RTX 5060 Ti 16GB | ~30 | ~30 | SAM, DINOv2 inference. 중간 규모 학습. **가성비 추천** | | 16GB | RTX 4070 Ti Super | 44.1 | 44.1 | 위와 VRAM 동일, 연산 속도가 1.5배 | | 24GB | RTX 3090 (중고) | 35.6 | 35.6 | VRAM 24GB를 가장 싸게 확보. 학습 가능, 속도만 느림 | | 24GB | RTX 4090 | 82.6 | 82.6 | 개인용 최고. VLA fine-tuning, 3DGS 대형 장면 | | 32GB | RTX 5090 | 104.8 | 209.6 | 개인용 최대 VRAM. FP16에서 4090의 2.5배 | **서버/연구실용 (데이터센터)** | VRAM | 카드 | FP32 TFLOPS | TF32 TFLOPS | BF16 TFLOPS | 특징 | |------|------|-------------|-------------|-------------|------| | 16/32GB | V100 SXM2 | 15.7 | — | — | Tensor Core 1세대. TF32 미지원. 아직 많은 연구실에서 현역. 중고로 싸게 구할 수 있다 | | 24GB | A10 | 31.2 | 62.5 | 125.0 | 추론 서버용. 학습에는 느림 | | 40/80GB | A100 SXM | 19.5 | 156 | 312 | FP32는 느리지만 TF32/BF16에서 압도적. NVLink로 multi-GPU 확장 | | 80GB | H100 SXM | 66.9 | 989 | 1979 | A100 대비 TF32 6배, BF16 6배. Transformer Engine 지원 | | 80GB | H200 SXM | 66.9 | 989 | 1979 | H100과 동일 연산, HBM3e로 메모리 대역폭 1.5배 | | 141GB | B200 | 90 | 2250 | 4500 | 최신. 단일 GPU로 70B+ 모델 학습 가능 | 숫자를 읽는 법: - **FP32**: 전통적 부동소수점. OpenCV, 고전 SLAM 등에서 사용. 개인 GPU는 이 수치가 실질 성능. - **TF32/BF16**: PyTorch에서 `torch.cuda.amp` (mixed precision) 사용 시 적용. 학습 속도가 2-6배 빨라진다. 데이터센터 GPU(A100, H100)는 이 모드에서 진가를 발휘하므로, FP32 TFLOPS만 보고 "A100이 4090보다 느리네?"라고 판단하면 안 된다. - **TFLOPS**: Tera Floating Point Operations Per Second. 높을수록 빠르다. **참고 사항**: - RTX 5060 Ti는 8GB와 16GB 두 버전이 있다. 반드시 16GB를 사라. 8GB는 VRAM이 부족해서 금방 한계에 부딪힌다. - AMD GPU (RX 7900 XTX 등)는 ROCm 지원이 개선되고 있으나, CUDA 생태계와의 호환성 문제가 아직 있다. 트러블슈팅에 시간 쓰기 싫으면 NVIDIA를 사라. - 연구실 서버에 A100/H100이 있다면 개인 GPU는 디버깅/프로토타이핑용이다. 서버 사양을 먼저 확인하자. - 중고 RTX 3090 (24GB)은 VRAM 대비 가격이 가장 낫다. 전력 소모(350W)가 크고 소음이 심하다는 점은 감안해야 한다. - Google Colab Pro(월 $10)로 A100을 시간 단위로 쓸 수도 있다. GPU 구매 전에 먼저 시도해 볼 만하다. **Q: 논문은 하루에 몇 편 읽어야 하나요?** A: "하루에 N편" 같은 기준은 의미 없다. 처음에는 **일주일에 1편을 완벽하게** 이해하는 게 훨씬 낫다. 위 20.4의 3-패스 방법을 따라서, 한 편을 깊이 파고들자. 6개월쯤 지나면 Abstract만 봐도 "아, 이런 류의 논문이구나" 감이 온다. 그때부터 속도가 붙는다. 참고로, 랩미팅 발표를 위해 논문을 읽는 것과 자기 연구를 위해 읽는 것은 깊이가 다르다. 후자는 코드까지 분석해야 한다. **Q: 코딩을 잘 못하는데 연구를 할 수 있나요?** A: 2026년 기준, 코드를 한 줄 한 줄 직접 쓰는 능력보다 **코딩 에이전트(Claude, Copilot 등)를 잘 활용하는 능력**이 더 중요해졌다. 에이전트에게 "KITTI 데이터셋 로더 만들어줘", "이 학습 루프에 wandb 로깅 추가해줘"라고 시키면 코드가 나온다. 직접 타이핑하는 시간은 크게 줄었다. 다만, 에이전트가 만든 코드가 맞는지 틀린지 판단하려면 **도메인 지식**이 있어야 한다. "이 DataLoader의 num_workers가 왜 0이면 느린지", "이 loss가 왜 NaN이 나오는지", "이 SLAM 코드에서 좌표계가 왜 뒤집혀 있는지" — 이런 건 에이전트가 알아서 못 잡는다 (14장 참고). 결국 좋은 코드가 왜 좋은지, 나쁜 코드가 왜 나쁜지를 구분하는 눈이 필요하고, 그 눈은 좋은 코드를 많이 읽어야 생긴다. 추천 접근법: 유명 오픈소스(ORB-SLAM3, Ultralytics, HuggingFace Transformers 등)의 코드를 읽으면서 "왜 이렇게 짰는지"를 이해하라. 코딩 실력은 타이핑 속도가 아니라 코드를 읽고 판단하는 능력이다. **Q: 학회 발표는 어떻게 준비하나요?** A: 학회 발표는 크게 **구두 발표(oral)**와 **포스터 발표(poster)**로 나뉜다. - **포스터**: 대부분의 첫 발표는 포스터다. A0 크기로 연구 내용을 요약한다. 핵심은 **Figure를 크게, 텍스트를 적게**. 지나가는 사람이 3초 안에 관심을 가지게 해야 한다. 발표 연습은 연구실 동료들 앞에서 최소 3번은 하자. - **구두 발표**: 보통 15-20분이다. 슬라이드는 20장 이내, 한 슬라이드에 한 메시지. 데모 영상이 있으면 좋다. 질문에 대비해서 supplementary 슬라이드도 준비하자. - 공통: 영어 발표가 대부분이니, 스크립트를 써서 연습하되 외우지는 말자. 자연스러운 영어보다 내용 전달이 중요하다. **Q: 영어 논문 읽기가 너무 힘든데요?** A: 이건 정말 시간이 해결해준다. 몇 가지 팁: - **구조를 먼저 파악하라**: 대부분의 논문은 Introduction → Related Work → Method → Experiments → Conclusion 구조다. Method만 진짜 새로운 내용이고, 나머지는 패턴이 비슷하다. - **분야별 어휘를 먼저 익혀라**: "ablation study", "state-of-the-art", "we empirically show" 같은 표현은 반복된다. 처음 20편쯤 읽으면 이런 표현에 익숙해진다. - **번역 도구를 부끄러워하지 마라**: DeepL, Google Translate로 모르는 문장을 번역하는 건 전혀 부끄러운 일이 아니다. 다만, 번역에만 의존하면 영어 실력이 안 는다. "원문 → 번역 확인 → 다시 원문" 순서로 읽자. - **PDF 리더에서 형광펜을 활용하라**: 핵심 문장을 색칠하면서 읽으면 집중도가 올라간다. Adobe Acrobat, Zotero 내장 뷰어 등 본인이 편한 도구를 쓰면 된다. ### C. 트러블슈팅 가이드 **자주 쓰는 apt 명령어** ```bash sudo apt update # 패키지 목록 갱신 sudo apt upgrade # 설치된 패키지 업그레이드 sudo apt install
# 패키지 설치 sudo apt remove
# 패키지 제거 (설정 파일 유지) sudo apt purge
# 패키지 + 설정 파일 완전 제거 sudo apt autoremove # 사용하지 않는 의존성 제거 apt list --installed # 설치된 패키지 목록 apt search
# 패키지 검색 sudo apt --fix-broken install # 의존성 깨졌을 때 복구 ``` (참고: [정진용 블로그](https://jinyongjeong.github.io/2016/06/07/Ubuntu_apt_get_commend/)) **SSH 키 설정 (비밀번호 없이 서버 접속)** ```bash # 키 생성 (Enter 연타로 기본값 사용) ssh-keygen -t ed25519 # 공개키를 서버에 복사 ssh-copy-id user@server_ip # 이후 비밀번호 없이 접속 가능 ssh user@server_ip ``` GitHub에도 같은 공개키(`~/.ssh/id_ed25519.pub`)를 등록하면 `git push`에 비밀번호가 필요 없다. (참고: [정진용 블로그](https://jinyongjeong.github.io/2016/06/02/SSH_keygen_setting/)) **CPU 성능 모드 설정 (실험 시)** SLAM이나 딥러닝 실험에서 CPU throttling 때문에 성능이 들쭉날쭉한 경우가 있다. ```bash # 현재 CPU governor 확인 cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor # performance 모드로 변경 (모든 코어) echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor # 영구 설정 (재부팅 후에도 유지) sudo apt install cpufrequtils echo 'GOVERNOR="performance"' | sudo tee /etc/default/cpufrequtils sudo systemctl restart cpufrequtils ``` 노트북에서는 배터리 소모가 커지니 전원 연결 상태에서만 사용할 것. (참고: [정진용 블로그](https://jinyongjeong.github.io/2020/02/04/Ubuntu_cpu_freq_change/)) #### C.1 CUDA / PyTorch 관련 **문제**: `CUDA out of memory` **해결**: ```python # 1. 배치 사이즈 줄이기 (가장 먼저 시도) batch_size = 16 # → 8 또는 4 # 2. 메모리 정리 torch.cuda.empty_cache() # 3. Gradient accumulation 사용 (배치 효과는 유지하면서 메모리 절약) accumulation_steps = 4 for i, (inputs, labels) in enumerate(dataloader): loss = model(inputs, labels) / accumulation_steps loss.backward() if (i + 1) % accumulation_steps == 0: optimizer.step() optimizer.zero_grad() # 4. Mixed Precision Training (메모리 절반으로) from torch.cuda.amp import autocast, GradScaler scaler = GradScaler() with autocast(): output = model(input) loss = criterion(output, target) ``` **문제**: `CUDA version mismatch` **해결**: ```bash # 설치된 CUDA 버전 확인 nvcc --version # PyTorch에서 인식하는 CUDA 버전 확인 python -c "import torch; print(torch.version.cuda)" # 둘이 다르면 PyTorch 재설치 (CUDA 버전에 맞춰서) pip install torch --index-url https://download.pytorch.org/whl/cu121 ``` **문제**: `RuntimeError: CUDA error: device-side assert triggered` **해결**: 이건 보통 라벨 인덱스가 범위를 벗어났을 때 발생한다. CPU에서 돌려보면 더 자세한 에러 메시지가 나온다. ```bash CUDA_LAUNCH_BLOCKING=1 python train.py ``` #### C.2 ROS 관련 **문제**: `Package not found` **해결**: ```bash # Workspace 소싱 확인 source ~/ros2_ws/install/setup.bash # 패키지 설치 확인 ros2 pkg list | grep package_name # .bashrc에 소싱 추가 (매번 수동으로 안 해도 됨) echo "source ~/ros2_ws/install/setup.bash" >> ~/.bashrc ``` **문제**: `TF tree not connected` **해결**: ```bash # TF 트리 확인 ros2 run tf2_tools view_frames # Static transform 추가 (예시) ros2 run tf2_ros static_transform_publisher 0 0 0 0 0 0 base_link camera_link ``` **문제**: `Topic not published` / 데이터가 안 들어옴 **해결**: ```bash # 현재 활성 토픽 확인 ros2 topic list # 특정 토픽 데이터 확인 ros2 topic echo /camera/image_raw --once # QoS 설정 불일치 확인 (ROS2에서 흔한 문제) ros2 topic info /camera/image_raw -v ``` #### C.3 Docker 관련 **문제**: `Permission denied` **해결**: ```bash # 도커 그룹에 사용자 추가 sudo usermod -aG docker $USER # 로그아웃 후 재로그인 ``` **문제**: GUI 프로그램 실행 안 됨 **해결**: ```bash # X11 forwarding xhost +local:docker docker run -it --env DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix ... ``` **문제**: Docker 안에서 GPU가 안 잡힘 **해결**: ```bash # nvidia-container-toolkit 설치 sudo apt-get install -y nvidia-container-toolkit sudo systemctl restart docker # GPU 옵션 추가해서 실행 docker run --gpus all -it nvidia/cuda:12.1.0-base-ubuntu22.04 nvidia-smi ``` #### C.4 OpenCV 관련 **문제**: `cv2.imshow() not working` **해결**: ```bash # OpenCV headless 버전 제거 후 재설치 pip uninstall opencv-python-headless pip install opencv-python ``` **문제**: OpenCV와 ROS의 cv_bridge 충돌 **해결**: ```bash # ROS의 cv_bridge가 시스템 OpenCV를 참조하는 경우 # conda/venv 환경의 OpenCV와 충돌할 수 있다 # 해결: ROS workspace 빌드 시 Python 경로 명시 colcon build --cmake-args -DPython3_EXECUTABLE=/usr/bin/python3 ``` #### C.5 빌드/컴파일 관련 **문제**: ORB-SLAM3 빌드 에러 (OpenCV 버전 충돌) **해결**: ```bash # OpenCV 4.x에서는 일부 API가 변경됨 # CMakeLists.txt에서 OpenCV 버전 확인 find_package(OpenCV 4 REQUIRED) # Pangolin 빌드 에러 시 sudo apt-get install libglew-dev libpython2.7-dev ``` **문제**: Eigen 버전 관련 에러 **해결**: ```bash # 시스템 Eigen 버전 확인 pkg-config --modversion eigen3 # 특정 버전 필요 시 직접 설치 sudo apt-get install libeigen3-dev ``` ### D. 체크리스트: 연구 시작 전 확인 사항 #### D.1 환경 설정 - [ ] Ubuntu 설치 완료 (22.04 LTS 권장) - [ ] NVIDIA 드라이버 설치 (`nvidia-smi`로 확인) - [ ] CUDA / cuDNN 설치 (`nvcc --version`으로 확인) - [ ] Conda 또는 venv 환경 설정 - [ ] PyTorch GPU 동작 확인 (`torch.cuda.is_available()`) - [ ] ROS2 설치 (필요시, Humble 또는 Jazzy) - [ ] Git 설정 (`git config --global user.name/email`) - [ ] Docker 설치 (선택, 재현성을 위해 권장) - [ ] VS Code + 필수 확장 설치 (Python, Remote-SSH, Jupyter) #### D.2 기초 지식 - [ ] Python 기본 문법 (클래스, 데코레이터, 리스트 컴프리헨션) - [ ] NumPy 배열 연산 (broadcasting, indexing, reshape) - [ ] OpenCV 이미지 처리 (읽기, 변환, 필터, 특징점) - [ ] 선형대수 기초 (행렬 곱셈, 고유값 분해, SVD) - [ ] 확률/통계 기초 (베이즈 정리, 가우시안 분포, MLE/MAP) #### D.3 연구 도구 - [ ] 논문 읽기/관리 환경 세팅 (PDF 리더 + AI 요약 워크플로우, 또는 Zotero) - [ ] 코드 에디터 (VS Code — 원격 서버 SSH 연결 가능) - [ ] 실험 로깅 (Weights & Biases 또는 MLflow) - [ ] GitHub 계정 (+ SSH 키 설정) - [ ] LaTeX 환경 (Overleaf 추천 — 온라인, 협업 가능) - [ ] 슬라이드 도구 (Google Slides, Keynote, 또는 LaTeX Beamer) #### D.4 데이터셋 준비 - [ ] 연구 관련 데이터셋 다운로드 - [ ] 데이터 포맷 이해 (이미지 크기, depth 단위, 좌표계) - [ ] DataLoader 구현 (PyTorch Dataset/DataLoader) - [ ] 데이터 시각화 코드 작성 (디버깅용) ### E. 첫 주 생존 가이드 연구실에 처음 들어왔을 때, 뭘 해야 할지 막막한 건 당연하다. 이 가이드는 "첫 주에 최소한 이것만은 하자"를 정리한 것이다. #### Day 1-2: 환경 구축 ``` [ ] 연구실 서버 계정 받기 (관리자에게 요청) [ ] SSH로 서버 접속 확인 [ ] VS Code Remote-SSH 설정 [ ] 서버에 conda 환경 만들기 [ ] PyTorch + CUDA 동작 확인 [ ] 연구실 GitHub organization에 가입 [ ] Slack/Discord 채널 가입 ``` > 팁: 서버 환경 설정에서 막히면 선배에게 물어보자. "이미 해봤는데 안 돼서요"라고 하면 거의 100% 도와준다. 아무것도 안 해보고 물어보면... 알아서 하라고 할 수도 있다. #### Day 3-4: 기존 코드 파악 ``` [ ] 연구실의 기존 코드/프로젝트 리포지토리 클론 [ ] README 읽기 (있다면) [ ] 기존 코드 빌드/실행 해보기 [ ] 데이터셋 다운로드 및 경로 설정 [ ] 간단한 데모 돌려보기 ``` > 팁: 코드가 돌아가지 않는 건 정상이다. 환경이 다르고, 경로가 다르고, 버전이 다르니까. 에러 메시지를 복사해서 Google에 검색하면 대부분 Stack Overflow에 답이 있다. #### Day 5: 논문 읽기 시작 ``` [ ] 지도교수/선배에게 "먼저 읽어야 할 논문 3편" 추천받기 [ ] 논문 관리 환경 세팅 (PDF 리더 + AI 워크플로우) [ ] 추천받은 논문 3편의 Abstract과 Conclusion 읽기 [ ] 모르는 용어 정리 (본 문서의 부록 A 활용) ``` > 팁: 처음 읽는 논문은 이해가 안 되는 게 정상이다. "이 논문이 무슨 문제를 풀려고 하는가?"만 파악해도 첫 주로서는 충분하다. #### Day 6-7: 연구 방향 파악 ``` [ ] 연구실 최근 논문/프로젝트 살펴보기 [ ] 선배들의 연구 주제 파악 (누가 어떤 주제를 하고 있는지) [ ] 본 문서의 18장 (연구실 연구 방향) 읽기 [ ] 관심 있는 연구 방향 2-3개 메모 [ ] 다음 주 랩미팅에서 발표할 자기소개 준비 ``` #### 첫 주에 하지 않아도 되는 것들 - 논문을 완벽하게 이해하기 — 시간이 해결해준다 - 최신 연구 트렌드를 전부 파악하기 — 점진적으로 - 코드를 처음부터 짜기 — 기존 코드를 수정하는 것부터 - GPU 서버를 완벽하게 세팅하기 — 선배 환경을 복사하자 - 연구 아이디어를 내기 — 최소 1-2개월은 배우는 시간이다 #### 생존을 위한 마인드셋 1. **모르는 건 당연하다**: 3학년 학부생이 SLAM을 모르는 건 당연하다. 부끄러워하지 말고 물어보자. 2. **선배를 활용하라**: 선배들은 같은 고통을 겪었기에 도와주고 싶어한다. 다만, "제가 이것저것 해봤는데 안 돼요"라고 해야지, "이거 해주세요"라고 하면 안 된다. 3. **기록하라**: 오늘 뭘 했는지, 뭘 모르겠는지, 어떤 에러가 났는지 기록해두자. 나중에 같은 문제를 만났을 때 과거의 내가 도와준다. 4. **작게 시작하라**: 거대한 시스템을 이해하려 하지 말고, 작은 코드 조각부터 돌려보자. "이 함수가 뭘 하는지" 하나만 이해해도 진전이다. 5. **비교하지 마라**: 선배가 논문을 술술 읽는 건 그만큼 시간을 투자했기 때문이다. 3개월 후의 나는 지금보다 훨씬 잘할 것이다. --- ## 마무리: 어디서부터 시작할까? 학습 순서는 **20.7절의 학습 경로**를 기준으로 진행하면 된다. 막히면 이 문서의 해당 섹션을 다시 참고하고, 그래도 해결이 안 되면 선배나 연구실 Slack에 질문하자. AI 코딩 에이전트를 활용하는 법은 19장을 참고하라. 이 문서는 지속적으로 업데이트할 예정이다. 참고로, 이 가이드를 여기까지 읽었다면 이미 쓸만한 능력이 하나 있다 — 긴 글을 끝까지 읽는 능력. 코드는 에이전트가 짜주는 시대지만, 8,000줄짜리 문서를 읽고 맥락을 파악하는 건 아직 사람이 해야 한다. 잘 읽는 사람이 AI도 잘 부린다. --- *본 문서는 지속적으로 업데이트됩니다. 질문이나 피드백은 연구실 Slack 채널로 보내주세요.* **마지막 업데이트**: 2026년 3월 (Enhanced Edition)