LIO-SAM 실습해보기 (1)

6 분 소요

들어가면서.

LiDAR SLAM에서 가장 기본되는 알고리즘인 LIO-SAM을 공부해보자.
LiDAR Inertial Odometry via Smoothing And Mapping을 줄여서 LIO-SAM이라 한다. LiDAR가 가장 널리 쓰이는 센서로 정착되고, IMU의 사용이 쉬워짐과 동시에, 이런 다른 타입의 센서들을 Factor Graph로 통합하여 Map을 그리는 방식이 가장 흔하기 때문이다. 방 하나정도 공간에 대한 SLAM은 이 이전의 EKF나 PF 기반의 알고리즘과 마이컴 수준으로도 가능했지만 공간이 점점 커짐에 따라 다루어야 하는 메모리도 커지게 되고, 이를 대응하기 위에 여러 센서들을 적용하려면 결국 그래프방식을 쓸수밖에 없는 논리로 흘러가게 된다. 이 후 Loop Closing을 위한 기법들이 더 추가되는 것외에 기본적으로 Lio-SAM은 가장 먼저 적용해볼 수 있는 SLAM 알고리즘이다.

이 글에선 이론적인것은 일단 배제하고 실습중심으로 학습해나가볼까 한다.

그래도 필요한 경우 중간중간 논문을 인용해가면서 진행해보겠다.

학습 순서.

다음과 같은 순서로 최대한 스피디하게 진행해볼까 한다.

  1. ROS2에 설치해보기
  2. IMU/PointCloud 다뤄보기
  3. 데이터셋을 이용하여 LIO-SAM 구현해보기
  4. 라즈베리파이에 실제 센서 연결해보기.

딱히 알려주는 사람이 없어서, ChatGPT를 활용하여 실습을 진행해본다.

Day 1. 개발환경의 구축과 내부 구조 이해

1일차 실습의 목표는 다음과 같이 정해본다.

  • LIO-SAM은 어떤 ROS Topic을 입력으로 사용하는가
  • imageProjection의 역할은?
  • deskew는 무엇이고 필요한 이유는?
  • mapOptimization 은 어떤 역할을 하는가?
  • params.yaml은 무엇이고 어떤 역할을 하는가?

이걸 염두해두고 진행해보자.

실습환경 확인

실습 환경은 다음과 같이 설정하고 진행해본다.

  • ubuntu 22.04
  • ROS 2 Humble
  • gcc 11+
  • RAM은 가급적 16GB이상.(이거 제한을 둬야 하나?)

이걸 이제 지금 시스템에서 확인해보자. ROS2설치와 같은 것들은 다른 곳에서 참조하자.

lsb_release -a
printenv ROS_DISTRO
ros2 doctor
2026-08-05-14-54-51
2026-08-05-14-54-51
2026-08-05-14-55-19
2026-08-05-14-55-19

만약 오류가 난다면 확인해보자. 가장 흔한 오류는 ros2: command not found로, source명령어 문제일수도 있다. 확인해보자.

source /opt/ros/humble/setup.bash

Workspace

LIO-SAM코드를 저장하고 돌려볼 workspace를 확인해보자.

mkdir -p ~/lio_ws/src
cd ~/lio_ws
tree -L 2
2026-08-05-15-00-47
2026-08-05-15-00-47

Download

원래 LIO-SAM은 ROS1 환경에서 발표되었으나, 이후 ROS2로 컨버전된 브랜치도 있다. 따라서 일단 공식 코드를 따라가보자.

cd ~/lio_ws/src
git clone https://github.com/TixiaoShan/LIO-SAM.git
cd LIO-SAM
git checkout ros2
2026-08-05-15-26-56
2026-08-05-15-26-56

Dependancy

LIO-SAM을 실행하기 위한 필수패키지들을 확인해보자.

cd ~/lio_ws

rosdep install \
  --from-paths src \
  --ignore-src \
  -r \
  -y
2026-08-05-15-29-20
2026-08-05-15-29-20

이것 외에도 gt-sam도 설치가 확인된다. 아래도 한번 실행해보자. 혹시나 오류가 뜬다면 개별적으로 알아보자.

내 경우는 아래 명령어에서 gtsam에서 문제가 발생했는데, 혹시나 모르니까 이 부분은 별도로 한번 알아보자.

sudo apt install \
libgtsam-dev \
libeigen3-dev \
libpcl-dev

일단 빌드

colcon build \
--symlink-install \
--cmake-args -DCMAKE_BUILD_TYPE=Release

빌드후 확인해보자.

source install/setup.bash
ros2 pkg list | grep lio
2026-08-05-15-41-01
2026-08-05-15-41-01

Launch

ros2 launch lio_sam run.launch.py

지금은 센서 데이터가 없으므로, 아무것도 보이지 않는다.

2026-08-05-15-48-19
2026-08-05-15-48-19

rviz도 실행되는걸 볼 수 있다.

2026-08-05-15-48-41
2026-08-05-15-48-41

Topic의 분석

다른 터미널 창을 띄워서 ROS2의 토픽을 보자.

ros2 topic list
2026-08-05-15-52-28
2026-08-05-15-52-28

나온 토픽들을 훑어보자.

입력: /points /imu/data /odometry/gpsz # GPS 사용 시, 설정 확인 필요

핵심 출력: /lio_sam/mapping/odometry /lio_sam/mapping/path /lio_sam/mapping/cloud_registered /lio_sam/mapping/map_local /lio_sam/mapping/map_global /odometry/imu

우선 입력들부터 보자. 만약 센서데이터를 처리하는 보드들이 있다면 아래 형태의 토픽으로 쏴줘야 한다는 소리다.

/points

LiDAR나 다른 센서로부터 들어온 point cloud 원본데이터다. 메세지 타입 형태는 PointCloud2 다.

2026-08-05-16-41-36
2026-08-05-16-41-36

/imu/data

IMU로 부터 들어온 데이터다.

2026-08-05-16-42-04
2026-08-05-16-42-04

/odometry/gpsz

만약 GPS를 사용한다면 들어오는 값이다.

2026-08-05-16-45-51
2026-08-05-16-45-51

/lio_sam/deskew/cloud_deskewed

IMU를 사용해 scan motion distortion을 보정한 포인트클라우드다. 보정에 관련한 알고리즘은 LOAM을 따른다. 그 부분을 참고하자.

2026-08-05-16-51-02
2026-08-05-16-51-02

/lio_sam/feature/cloud_corner

Edge 또는 corner feature point cloud다.
feature 매칭에 사용하는 데이터들이다.
관련해서 상세한건 LOAM과 LIO-SAM논문을 참조하자.

/lio_sam/feature/cloud_surface

평면 또는 surface feature point cloud다. 위와 같다.

/lio_sam/feature/cloud_info

Feature extraction 결과와 odometry 초기 추정 등을 mapping 모듈로 전달하는 내부 메시지다.

/lio_sam/deskew/cloud_info
        ↓
featureExtraction
        ↓
/lio_sam/feature/cloud_info
        ↓
mapOptimization
2026-08-05-16-54-19
2026-08-05-16-54-19

/odometry/imu_incremental

이제 IMU값들을 보자. 이름은 incremental이지만, 실제론 증분을 적분해서 얻은 현재 Pose를 Odometry 형태로 Publish한다. 아직 Pose를 보정하지 않은 값이다.

/odometry/imu

LiDAR mapping odometry와 IMU incremental odometry를 결합한 고주기 odometry 출력

/lio_sam/mapping/odometry
            +
/odometry/imu_incremental
            ↓
TransformFusion
            ↓
/odometry/imu

/lio_sam/imu/path

앞서 얻은 /odometry/imu 를 시각화한 것이다.

/lio_sam/mapping/odometry

최종 핵심 출력값이다. factor graph 최적화가 반영된 mapping pose다.

/lio_sam/mapping/cloud_registered

현재 scan을 최적화된 pose로 map frame에 변환한 등록 포인트클라우드

/lio_sam/mapping/cloud_registered_raw

Feature extraction 전후 또는 downsampling 차이를 확인하기 위한 raw registered cloud

/lio_sam/mapping/map_local

현재 pose 주변의 local submap

/lio_sam/mapping/map_global

누적된 global point cloud map

/lio_loop/loop_closure_detection

외부 loop detector가 loop 후보를 제공하는 입력

/lio_sam/mapping/icp_loop_closure_history_cloud

ICP loop closure에 사용된 과거 keyframe cloud를 보여주는 디버깅 토픽

/lio_sam/mapping/loop_closure_constraints

검출·적용된 loop closure constraint를 시각화하는 토픽

/points
   │
   │
   ├─────────────────────────────┐
   │                             │
   ▼                             │
imageProjection                  │
   ▲                             │
   │                             │
/imu/data                        │
   │                             │
   ├──────────────┐              │
   │              │              │
   ▼              ▼              │
deskew       IMU preintegration  │
   │              │              │
   │              └──► /odometry/imu_incremental
   │                             │
   ▼                             │
/lio_sam/deskew/cloud_deskewed   │
/lio_sam/deskew/cloud_info       │
   │                             │
   ▼                             │
featureExtraction                │
   │                             │
   ├──► /lio_sam/feature/cloud_corner
   ├──► /lio_sam/feature/cloud_surface
   └──► /lio_sam/feature/cloud_info
                   │
                   ▼
             mapOptimization
                   │
                   ├──► /lio_sam/mapping/odometry
                   ├──► /lio_sam/mapping/odometry_incremental
                   ├──► /lio_sam/mapping/path
                   ├──► /lio_sam/mapping/cloud_registered
                   ├──► /lio_sam/mapping/map_local
                   └──► /lio_sam/mapping/map_global

params.yaml 둘러보기

~/lio_ws/src/LIO-SAM/config 폴더안에 가보면, params.yaml파일이 있다.

내용을 훑어보자. 우선 눈에 띄는건 Topics다. 이건 앞서서 이야기했던대로 입력과 관련된 주요 토픽들이 yaml에 나열되어있다.

# Topics
pointCloudTopic: "/points"                   # Point cloud data
imuTopic: "/imu/data"                        # IMU data
odomTopic: "odometry/imu"                    # IMU pre-preintegration odometry, same frequency as IMU
gpsTopic: "odometry/gpsz"                    # GPS odometry topic from navsat, see module_navsat.launch file

또 눈여겨 볼만한건 LiDAR 관련사항들이다.

# Sensor Settings
  sensor: ouster                               # lidar sensor type, either 'velodyne', 'ouster' or 'livox'
  N_SCAN: 64                                   # number of lidar channels (i.e., Velodyne/Ouster: 16, 32, 64, 128, Livox Horizon: 6)
  Horizon_SCAN: 512                            # lidar horizontal resolution (Velodyne:1800, Ouster:512,1024,2048, Livox Horizon: 4000)
  downsampleRate: 1                            # default: 1. Downsample your data if too many
  # points. i.e., 16 = 64 / 4, 16 = 16 / 1
  lidarMinRange: 1.0                           # default: 1.0, minimum lidar range to be used
  lidarMaxRange: 1000.0                        # default: 1000.0, maximum lidar range to be used

특히 sensor, N_SCAN, Horizon_SCAN 값은 센서의 스펙에 맞춰 설정값을 바꿔줘야 한다.

IMU-LiDAR 외부 파라미터도 주의해야 한다.
현재는 IMU와 LiDAR 가 같은 위치에 있다고 가정하지만, 이 두 센서간의 위치가 다르다면 이에 맞춰 수정해줘야 한다. 이게 안맞면 로봇이 앞으로 움직였는데 옆으로 이동하는 불상사가 생긴다거나 trajectory가 발산할수도 있다.

extrinsicTrans:  [ 0.0,  0.0,  0.0 ]
extrinsicRot:    [-1.0,  0.0,  0.0,
                    0.0,  1.0,  0.0,
                    0.0,  0.0, -1.0 ]
extrinsicRPY: [ 0.0,  1.0,  0.0,
                -1.0,  0.0,  0.0,
                0.0,  0.0,  1.0 ]

imuRPYWeight 는 IMU를 얼마나 신뢰하는지에 대한 값이다. 참고하자.

feature 추출의 임계값도 설정할 수 있다.

# LOAM feature threshold
edgeThreshold: 1.0
surfThreshold: 0.1
edgeFeatureMinValidNum: 10
surfFeatureMinValidNum: 100

뭔가 포인트들간의 매칭이 잘 안되거나, 복도같은 곳들이 많다면 조정해봐야 한다.

비슷하게 Voxel과 관련된 부분도 성능에 영향을 받는다. 값이 커질수록 포인트 숫자도 감소하고, CPU부담도 줄어든다. 대신 작은 구조물을 놓칠 가능성이 있다.

# voxel filter paprams
odometrySurfLeafSize: 0.4                     # default: 0.4 - outdoor, 0.2 - indoor
mappingCornerLeafSize: 0.2                    # default: 0.2 - outdoor, 0.1 - indoor
mappingSurfLeafSize: 0.4                      # default: 0.4 - outdoor, 0.2 - indoor

Mapping의 주기와 CPU 사용량을 조절할 수도 있다. GPU사용은 안되나?

# CPU Params
numberOfCores: 4                              # number of cores for mapping optimization
mappingProcessInterval: 0.15                  # seconds, regulate mapping frequency

위의 초기값에서 0.15초이므로 대략 6.7Hz로 맵핑을 수행한다.
만약 시스템에 부담이 크다면 낮춰볼수도 있겠다.

키프레임을 얼마나 생성할건지도 눈여겨 봐야 한다.

surroundingkeyframeAddingDistThreshold: 1.0
surroundingkeyframeAddingAngleThreshold: 0.2

현재는:

  • 1m 이상 이동
  • 또는 약 0.2 rad, 약 11.5도 이상 회전

하면 새 keyframe을 추가한다.

당연하게도 값을 작게 하면 keyframe 증가,지도 세밀,메모리와 CPU 증가를 기대할 수 있고,
값을 크게 하면 계산량 감소, 빠른 회전이나 좁은 공간에서 정보 손실이 예상된다.

scan-to-map optimization에 사용할 파라미터들도 볼 수 있다.

surroundingKeyframeDensity: 2.0               # meters, downsample surrounding keyframe poses   
surroundingKeyframeSearchRadius: 50.0         # meters, within n meters scan-to-map optimization

loop closure관련한 항목도 있다.
시스템 성능에 따라 적절히 조절할 필요가 있다.

# Loop closure
loopClosureEnableFlag: true
loopClosureFrequency: 1.0                     # Hz, regulate loop closure constraint add frequency
surroundingKeyframeSize: 50                   # submap size (when loop closure enabled)
historyKeyframeSearchRadius: 15.0             # meters, key frame that is within n meters from
# current pose will be considerd for loop closure
historyKeyframeSearchTimeDiff: 30.0           # seconds, key frame that is n seconds older will be
# considered for loop closure
historyKeyframeSearchNum: 25                  # number of hostory key frames will be fused into a
# submap for loop closure
historyKeyframeFitnessScore: 0.3              # icp threshold, the smaller the better alignment

만약 로봇이 2차원 평면에서만 움직인다면, Z축을 굳이 연산할 필요가 없다.

# robot motion constraint (in case you are using a 2D robot)
z_tollerance: 1000.0                          # meters
rotation_tollerance: 1000.0                   # radians

코드 둘러보기

LIO-SAM 소스폴더 들어가보면 파일이 참 단촐하게 있다.

2026-08-05-19-41-28
2026-08-05-19-41-28

코드의 분석은 좀 더 나중에 해보자.

댓글남기기