๐Ÿ”ง LIO-SAM์—์„œ LiDARโ€“IMU ํƒ€์ž„์Šคํƒฌํ”„ ๋ฌธ์ œ ํ•ด๊ฒฐ ๊ธฐ๋ก

๊น€๋ฏผ์ค€ยท2026๋…„ 2์›” 1์ผ

3๊ฐœ์˜ Python ์Šคํฌ๋ฆฝํŠธ์™€ ์‹œ๊ฐ„์ถ• ๋ณด์ • ๋กœ์ง ์ •๋ฆฌ


0๏ธโƒฃ ํ•œ ์ค„ ์š”์•ฝ

IMU ์„ผ์„œ ์‹œ๊ฐ„๊ณผ ROS ์‹œ๊ฐ„์ด ์„œ๋กœ ๋‹ค๋ฅธ ์‹œ๊ฐ„์ถ•์„ ์‚ฌ์šฉํ•ด ๋ฐœ์ƒํ•œ LIO-SAM ๋™๊ธฐํ™” ๋ฌธ์ œ๋ฅผ,
IMU ์‹œ๊ฐ„์˜ ์ฆ๊ฐ€ ์†๋„๋ฅผ ROS ์‹œ๊ฐ„์— ๋งž์ถฐ ์„ ํ˜• ๋ณด์ •ํ•˜๊ณ ,
LiDAR์™€ IMU๋ฅผ ๋™์ผํ•œ ROS ์‹œ๊ฐ„ ๊ธฐ์ค€์œผ๋กœ ์žฌ์Šคํƒฌํ”„ํ•˜์—ฌ
ํƒ€์ž„์Šคํƒฌํ”„ ์˜ค์ฐจ๋ฅผ 100ms ์ดํ•˜๋กœ ์•ˆ์ •ํ™”ํ–ˆ๋‹ค.


1๏ธโƒฃ ๋ฌธ์ œ ๋ฐฐ๊ฒฝ: ์™œ LIO-SAM์ด ๊ณ„์† ๋ถˆ์•ˆ์ •ํ–ˆ๋‚˜?

LIO-SAM์€ ๊ตฌ์กฐ์ ์œผ๋กœ ๋‹ค์Œ์„ ์ „์ œ๋กœ ํ•œ๋‹ค.

  • LiDAR ํฌ์ธํŠธ๊ฐ€ ์ธก์ •๋œ ์‹œ๊ฐ
  • IMU ์ธก์ •๊ฐ’์ด ์ ๋ถ„๋˜๋Š” ์‹œ๊ฐ

์ด ๋‘ ์‹œ๊ฐ„์ด ๋™์ผํ•œ ์‹œ๊ฐ„์ถ•์— ์žˆ์–ด์•ผ ํ•œ๋‹ค.

ํ•˜์ง€๋งŒ ์‹ค์ œ ํ™˜๊ฒฝ์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

  • IMU timestamp
    โ†’ ์„ผ์„œ ๋‚ด๋ถ€ ์‹œ๊ฐ„ or ๋“œ๋ผ์ด๋ฒ„๊ฐ€ ์ƒ์„ฑํ•œ ์‹œ๊ฐ„
  • LiDAR timestamp
    โ†’ ROS time (PC ์‹œ์Šคํ…œ ์‹œ๊ฐ„)

์ฆ‰, ์„œ๋กœ ๋‹ค๋ฅธ ์‹œ๊ณ„๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ƒํƒœ์˜€๋‹ค.

๊ทธ ๊ฒฐ๊ณผ:

  • โ€œIMU data too late / too earlyโ€
  • deskew ์‹คํŒจ
  • ์‹œ๊ฐ„์ด ์ง€๋‚ ์ˆ˜๋ก ๋ˆ„์ ๋˜๋Š” ๋“œ๋ฆฌํ”„ํŠธ

๐Ÿ‘‰ ๋ฌธ์ œ์˜ ๋ณธ์งˆ์€
์ ˆ๋Œ€ ์‹œ๊ฐ„ ์˜คํ”„์…‹ + ์‹œ๊ฐ„ ํ๋ฆ„ ์†๋„(Hz) ๋ถˆ์ผ์น˜์˜€๋‹ค.


2๏ธโƒฃ ์ „์ฒด ํ•ด๊ฒฐ ์ „๋žต (3๋‹จ๊ณ„)

๋‹จ๊ณ„์ฝ”๋“œ์—ญํ• 
1imu_timestamp_fixer.pyIMU ์‹œ๊ฐ„ โ†’ ROS ์‹œ๊ฐ„์œผ๋กœ ์„ ํ˜• ๋ณด์ •
2simple_sync.pyLiDAR & IMU๋ฅผ ๊ฐ™์€ ROS now ๊ธฐ์ค€์œผ๋กœ ํ†ต์ผ
3timestamp_check_synced.py์‹ค์ œ ์‹œ๊ฐ„์ฐจ๊ฐ€ 100ms ์ดํ•˜์ธ์ง€ ๊ฒ€์ฆ

3๏ธโƒฃ 1๋‹จ๊ณ„: IMU ์‹œ๊ฐ„์ถ•์„ ROS ์‹œ๊ฐ„์ถ•์œผ๋กœ ๋ณด์ •

(imu_timestamp_fixer.py)

์ด ๋‹จ๊ณ„๊ฐ€ ์œ ์ผํ•˜๊ฒŒ โ€˜์‹œ๊ฐ„ ๊ณ„์‚ฐโ€™์„ ์ˆ˜ํ–‰ํ•˜๋Š” ํ•ต์‹ฌ ๋‹จ๊ณ„๋‹ค.

๐Ÿ”น ํ•ต์‹ฌ ์•„์ด๋””์–ด

IMU timestamp๋ฅผ ๊ทธ๋Œ€๋กœ ์“ฐ์ง€ ๋ง๊ณ ,
IMU ์‹œ๊ฐ„์˜ ์ฆ๊ฐ€ ์†๋„ ์ž์ฒด๋ฅผ ROS ์‹œ๊ฐ„์— ๋งž์ถ˜๋‹ค.


๐Ÿ”น ๊ธฐ์ค€ ์‹œ์ (anchor) ์„ค์ •

์ฒซ IMU ๋ฉ”์‹œ์ง€๊ฐ€ ๋“ค์–ด์˜จ ์ˆœ๊ฐ„์„ ๊ธฐ์ค€์ ์œผ๋กœ ์‚ผ๋Š”๋‹ค.

if self.first_sensor_time is None:
    self.first_sensor_time = msg.header.stamp.to_sec()
    self.first_ros_time = rospy.Time.now().to_sec()
    return
  • first_sensor_time : IMU๊ฐ€ ๋งํ•˜๋Š” โ€œ์ฒซ ์‹œ๊ฐ„โ€
  • first_ros_time : ๊ทธ ์ˆœ๊ฐ„์˜ ROS ์‹œ๊ฐ„

๐Ÿ‘‰ โ€œ๊ฐ™์€ ์ˆœ๊ฐ„์„ ์„œ๋กœ ๋‹ค๋ฅธ ์‹œ๊ณ„๋กœ ๊ธฐ๋กํ•œ ๊ธฐ์ค€์ โ€


๐Ÿ”น ์‹œ๊ฐ„ ๊ฒฝ๊ณผ๋Ÿ‰ ๋ˆ„์ 

์ดํ›„ ๋“ค์–ด์˜ค๋Š” IMU ๋ฉ”์‹œ์ง€๋งˆ๋‹ค:

sensor_time = msg.header.stamp.to_sec()
ros_time = rospy.Time.now().to_sec()

sensor_elapsed = sensor_time - self.first_sensor_time
ros_elapsed = ros_time - self.first_ros_time

์ฆ‰,

[
\Delta t{sensor} = t{sensor} - t0_{sensor}
][ \Delta t_{ros} = t_{ros} - t0_{ros} ]


๐Ÿ”น ์Šค์ผ€์ผ ํŒฉํ„ฐ ๊ณ„์‚ฐ

IMU ์‹œ๊ฐ„๊ณผ ROS ์‹œ๊ฐ„์˜ ์ฆ๊ฐ€ ์†๋„ ๋น„์œจ์„ ๊ณ„์‚ฐํ•œ๋‹ค.

if sensor_elapsed > 0:
    scale = ros_elapsed / sensor_elapsed
    self.scale_factors.append(scale)

if len(self.scale_factors) < 100:
    return

scaling_factor = sum(self.scale_factors) / len(self.scale_factors)

์ˆ˜์‹์œผ๋กœ ํ‘œํ˜„ํ•˜๋ฉด:

[
s = \text{mean}\left(\frac{\Delta t{ros}}{\Delta t{sensor}}\right)
]

  • s > 1 โ†’ IMU ์‹œ๊ฐ„์ด ๋А๋ฆฌ๊ฒŒ ํ๋ฆ„
  • s < 1 โ†’ IMU ์‹œ๊ฐ„์ด ๋น ๋ฅด๊ฒŒ ํ๋ฆ„

๐Ÿ”น ์ตœ์ข… ๋ณด์ •๋œ IMU timestamp

corrected_elapsed = sensor_elapsed * scaling_factor
corrected_stamp = rospy.Time.from_sec(
    self.first_ros_time + corrected_elapsed
)

msg.header.stamp = corrected_stamp
self.pub.publish(msg)

์ฆ‰,

[
t{imu}^{corrected}
= t0
{ros}

  • s \cdot (t{sensor} - t0{sensor})
    ]

๐Ÿ‘‰ ๊ฒฐ๊ณผ:

  • IMU timestamp๊ฐ€ ROS ์‹œ๊ฐ„์ถ•์œผ๋กœ ๋ณ€ํ™˜
  • ์žฅ์‹œ๊ฐ„ ์‹คํ–‰ ์‹œ ๋ฐœ์ƒํ•˜๋˜ ๋“œ๋ฆฌํ”„ํŠธ ์ œ๊ฑฐ

4๏ธโƒฃ 2๋‹จ๊ณ„: LiDAR์™€ IMU๋ฅผ ๋™์ผํ•œ ROS ์‹œ๊ฐ„์œผ๋กœ ๊ฐ•์ œ

(simple_sync.py)

์ด ๋‹จ๊ณ„๋Š” ๊ณ„์‚ฐ๋ณด๋‹ค๋Š” ์ •์ฑ…์  ์„ ํƒ์ด๋‹ค.

๐Ÿ”น IMU ์žฌ์Šคํƒฌํ”„

def imu_callback(msg):
    msg.header.stamp = rospy.Time.now()
    imu_pub.publish(msg)

๐Ÿ”น LiDAR ์žฌ์Šคํƒฌํ”„

def lidar_callback(msg):
    msg.header.stamp = rospy.Time.now()
    msg.is_dense = True
    points_pub.publish(msg)

๐Ÿ‘‰ ์˜๋ฏธ:

โ€œ์„ผ์„œ๊ฐ€ ์–ธ์ œ ์ธก์ •ํ–ˆ๋Š”์ง€โ€๋ณด๋‹ค
โ€œ์ง€๊ธˆ ์ฒ˜๋ฆฌ๋˜๊ณ  ์žˆ๋Š” ์‹œ๊ฐโ€์„ ๊ธฐ์ค€์œผ๋กœ ํ†ต์ผ


โš ๏ธ ์ด ๋ฐฉ์‹์˜ ํŠน์„ฑ

  • ์žฅ์ 

    • ํฐ ์‹œ๊ฐ„ ์˜คํ”„์…‹ ์ฆ‰์‹œ ์ œ๊ฑฐ
    • LIO-SAM ์•ˆ์ •์„ฑ ํ™•๋ณด
  • ๋‹จ์ 

    • ์‹ค์ œ ์„ผ์„œ ์ธก์ • ์‹œ๊ฐ์ด ์•„๋‹Œ

      • ์ฝœ๋ฐฑ ์ง€์—ฐ
      • ์Šค์ผ€์ค„๋ง ์ง€์—ฐ
        ํฌํ•จ

โžก ์ •ํ™•๋„๋ณด๋‹ค๋Š” ์•ˆ์ •์„ฑ์„ ์šฐ์„ ํ•œ ๊ตฌ์กฐ


5๏ธโƒฃ 3๋‹จ๊ณ„: ์‹ค์ œ ๋™๊ธฐํ™” ์˜ค์ฐจ ๊ฒ€์ฆ

(timestamp_check_synced.py)

๐Ÿ”น ์ตœ์‹  IMU / LiDAR ์‹œ๊ฐ„ ๋น„๊ต

diff = abs(self.latest_imu_time - self.latest_lidar_time)

if diff < 0.1:
    rospy.loginfo(f"Synced OK: diff={diff:.4f}s")
else:
    rospy.logwarn(f"Time diff too large: {diff:.4f}s")

์ˆ˜์‹์œผ๋กœ๋Š” ๋‹จ์ˆœํžˆ:

[
\text{diff} = |t{imu} - t{lidar}|
]


๐Ÿ”น ํŒ์ • ๊ธฐ์ค€

  • 100ms ์ดํ•˜ โ†’ ์ •์ƒ
  • ๊ทธ ์ด์ƒ โ†’ ๋™๊ธฐํ™” ์‹คํŒจ

๐Ÿ‘‰ ์ด ํŒŒ์ดํ”„๋ผ์ธ์˜ ๋ช…ํ™•ํ•œ ๋ชฉํ‘œ๋Š”
โ€œLIO-SAM์ด ์š”๊ตฌํ•˜๋Š” ์‹œ๊ฐ„ ์กฐ๊ฑด(โ‰ค100ms)โ€ ์ถฉ์กฑ


6๏ธโƒฃ ์‹ค์ œ๋กœ ์˜ค์ฐจ๋Š” ์–ด๋А ์ •๋„๊นŒ์ง€ ๋‚ด๋ ค๊ฐ”๋‚˜?

โœ” ์ฝ”๋“œ๊ฐ€ ๋ณด์žฅํ•˜๋Š” ๋ฒ”์œ„

  • 100ms ์ดํ•˜
  • ์Šคํฌ๋ฆฝํŠธ๋กœ ๋ช…์‹œ์  ๊ฒ€์ฆ ๊ฐ€๋Šฅ

โœ” ์‹ค์ œ ์ฒด๊ฐ ๋ฒ”์œ„ (ํ™˜๊ฒฝ ๊ธฐ์ค€)

  • IMU: 100~200Hz
  • LiDAR: 10Hz

โžก ์ผ๋ฐ˜์ ์œผ๋กœ:

  • ์ˆ˜ ms ~ ์ˆ˜์‹ญ ms
    โžก ์ตœ์•…์˜ ๊ฒฝ์šฐ:
  • LiDAR ์ฃผ๊ธฐ ๊ทผ์ฒ˜ (โ‰ˆ100ms)

์ฆ‰,

โ€œํ•ญ์ƒ 5ms ์ดํ•˜โ€๋Š” ์•„๋‹ˆ์ง€๋งŒ,
LIO-SAM์ด ์•ˆ์ •์ ์œผ๋กœ ๋™์ž‘ํ•˜๊ธฐ์—” ์ถฉ๋ถ„ํžˆ ์ž‘์€ ๋ฒ”์œ„๋กœ ์ˆ˜๋ ดํ•œ๋‹ค.


7๏ธโƒฃ ์ด ๊ณผ์ •์„ ํ•œ ๋ฌธ์žฅ์œผ๋กœ ์ •๋ฆฌํ•˜๋ฉด

IMU ์„ผ์„œ ์‹œ๊ฐ„์˜ ํ๋ฆ„์„ ROS ์‹œ๊ฐ„์— ์„ ํ˜• ๋งคํ•‘ํ•ด ๋“œ๋ฆฌํ”„ํŠธ๋ฅผ ์ œ๊ฑฐํ•˜๊ณ ,
LiDAR์™€ IMU ๋ฉ”์‹œ์ง€๋ฅผ ๋™์ผํ•œ ROS ์‹œ๊ฐ„ ๊ธฐ์ค€์œผ๋กœ ์žฌ์Šคํƒฌํ”„ํ•จ์œผ๋กœ์จ
LIO-SAM์—์„œ ์š”๊ตฌํ•˜๋Š” ํƒ€์ž„์Šคํƒฌํ”„ ๋™๊ธฐ ์กฐ๊ฑด(100ms ์ดํ•˜)์„ ๋งŒ์กฑ์‹œ์ผฐ๋‹ค.


8๏ธโƒฃ ์ •๋ฆฌํ•˜๋ฉฐ

  • ์ง„์งœ ํ•ต์‹ฌ ๊ณ„์‚ฐ์€ IMU ์‹œ๊ฐ„ ์Šค์ผ€์ผ ๋ณด์ •
  • simple_sync๋Š” ๋™๊ธฐํ™” ์•ˆ์ •์„ฑ์„ ์œ„ํ•œ ๊ฐ•์ œ ์ •์ฑ…
  • timestamp_check๋Š” ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋์Œ์„ ์ˆซ์ž๋กœ ์ฆ๋ช…ํ•˜๋Š” ๋‹จ๊ณ„
profile
์ง€๊ธˆ๊นŒ์ง€ ํ•ด์˜จ ์—ฌ๋Ÿฌ ํ™œ๋™๋“ค์„ ๊ฐ„๋žตํ•˜๊ฒŒ๋ผ๋„ ์ •๋ฆฌํ•ด๋ณด๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€