Loosely-coupled Lidar-Inertial odometry ์ค์ต
- ์ ๋ฒ ๋ธ๋ก๊ทธ ๊ธ ์์ IMU๋ง์ผ๋ก odometry (์ฆ, relative motion ์ถ์ ) ๋ฅผ ํ๊ธฐ ์ํด์๋ global attidue๊ฐ ์ค์ํจ์ ์์๋ณด์๋ค.
- ๋ฌผ๋ก PVA (position, velocity, attitude) ๊ฐ ๋ชจ๋ ์ค์ํ๋ค.
- ํ์ง๋ง ๊ฐ๋น์ผ GPS+INS ์์ด๋, LiDAR ์ผ์๋ฅผ ์ด์ฉํด์ ์ด๋ฌํ ๋ณด์ ์ ํด์ค ์ ์๋ค.
- Python๋ง ์ด์ฉํด์ ๊ฐ๋จํ๊ฒ loosely-coupled lidar-inertial odometry (a.k.a mini-pyllio) ๋ฅผ ๊ตฌํํด๋ณด์.
๊ฐ์
- ์ ๋ฒ ๋ธ๋ก๊ทธ ๊ธ ์์ IMU๋ง์ผ๋ก odometry (์ฆ, relative motion ์ถ์ ) ๋ฅผ ํ๊ธฐ ์ํด์๋ global attidue๊ฐ ์ค์ํจ์ ์์๋ณด์๋ค.
- KITTI dataset ์์ ์ ๊ณตํ๋ ๊ฐ๋น์ผ GPS+INS ์ผ์ (== oxts) ๊ฐ ์ ๊ณตํ๋ Global rotation ์ ๊ณต๊ธํ ๊ฒฝ์ฐ, ์ด ๊ฐ์ผ๋ก ์ค๋ ฅ์ ์ ๋ณด์ํด์ค ์ ์์๊ณ , ๊ฒฐ๊ณผ์ ์ผ๋ก ์ ํจํ ๊ฐ์๋๋ฅผ ๊ณ์ฐํ ์ ์๊ฒ ๋๊ณ , ์ด๋ velocity ์ position ์์ธก์ ๋ ์ ํ ์ ์๊ฒ ํด์ฃผ์๋ค.
- ํ์ง๋ง ๊ฐ๋น์ผ GPS+INS ์์คํ ์ ์ฌ์ฉํ ์ ์๋ค๋ฉด ์ด๋ป๊ฒ ํด์ผ ํ ๊น? ํน์ ์ค๋ด์์ GPS ์ ํธ๊ฐ ๊ณต๊ธ๋์ง ์์ ๋์๋ ์ด๋ป๊ฒ ํด์ผํ ๊น?
- LiDAR ๋ฅผ ์ด์ฉํด์ ์ด ์ญํ ์ ๋์ ํด๋ณด์.
๊ตฌํ
๋ชฉ์
- Python ๋ง์ ์ด์ฉํด์ ์ต์ํ์ ์ฝ๋๋ก ๊ตฌํํ๋ ๊ฒ์ด ๋ชฉ์ ์ด์๋ค.
- IMU propagation ์ Pypose ๋ฅผ ์ฌ์ฉํ์๊ณ , LiDAR scan-to-scan registration ์ Open3D ๋ฅผ ์ฌ์ฉํ์๋ค.
๊ตฌํ์ฒด
- https://github.com/gisbi-kim/mini-pyllio
- ์ค์ตํ๋ ๋ฐฉ๋ฒ์ github readme ๋ฅผ ์ฐธ๊ณ .
ํบ์๋ณด๊ธฐ
- ๋ฉ์ธ๋ถ๋ถ๋ง ๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ์ด ๊ฐ๋จํ๋ค.
# state estimation for a KITTI dataset raw data sequence (e.g., 2011_09_30_0018) llio = LLIO("cfg.yml") llio.init_integrator() for idx, data in enumerate(llio.dataloader): state = llio.propogate(data) state = llio.correct(data, llio.cfg["use_lidar_correction"]) llio.append_log(data, state) llio.visualize_traj()
- ์ ์ผ ๋ง์ง๋ง์ ๊ฒฐ๊ณผ๋ฅผ ๊ทธ๋ฆฌ๋๋ก ํ์๋ค (
visualize_traj()
).
- ์ ์ผ ๋ง์ง๋ง์ ๊ฒฐ๊ณผ๋ฅผ ๊ทธ๋ฆฌ๋๋ก ํ์๋ค (
- ๋จ๊ณ๋ณ๋ก ์์ฝํด๋ณด์๋ฉด:
- ์ค๋น ๋จ๊ณ
LLIO
(Loosely-coupled LiDAR Inertial Odometry) ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ฉด dataset์ load ํ๋ค.def load_dataset(self): self.dataset = KittiDataloader( self.cfg["input"]["dataroot"], self.cfg["input"]["dataname"], self.cfg["input"]["datadrive"], duration=self.cfg["step_size"], step_size=self.cfg["step_size"], )
- ๊ต์ก์ฉ ๋ชฉ์ ์ด๊ธฐ ๋๋ฌธ์ lidar scan ์ ํ๋ฒ์ ๋ค ์ฝ์ด์ ๋ฉ๋ชจ๋ฆฌ์ ์ฌ๋ฆฌ๋๋ก ๊ฐ๋จํ๊ฒ ๊ตฌํํ์๋ค. ๊ทธ๋์ ์ ์ด๊ฐ ์์๋๊ณ (๋ฐ์ดํฐ์ ์ ํฌ๊ธฐ์ ๋ฐ๋ผ) ์G ์ ๋์ ์ฌ๋ถ์ ๋ฉ๋ชจ๋ฆฌ๊ฐ ํ์ํ ์ ์๋ค (ps. 2011_09_30_0020 ๊ฐ frame์ด 1000๊ฐ ์ ๋๋๋ ๊ฝค ์งง์ ์ํ์ค์ด๊ธฐ ๋๋ฌธ์ ์ด๊ฑธ๋ก ์ค์ตํ๋ฉด ํธ๋ฆฌํ ๊ฒ์ด๋ค).
- ์์ configuration์์
step_size
๋ lidar aiding ์ ๋ฐ์ ๊ฐ๊ฒฉ์ ์๋ฏธํ๋ค.- ์๋ฅผ ๋ค์ด 5๋ก ์ค์ ํ๋ฉด, LiDAR ๋ 5 frame ๋ง๋ค ํ๋ฒ์ฉ๋ง ๋ณด์ ์ ํ๋ค. ๋ณด์ ํ์ง ์๋ ๋๋จธ์ง 4 ํ๋ ์ ๊ฐ๊ฒฉ์ ๋ํด์๋ ๋ณด์ ์์ด, imu only๋ก propagation ์ ํ๋ค๋ผ๊ณ ์๊ฐํ๋ฉด ๋๋ค.
- ์ค์ ๋ก๋, IMU (๋ณดํต 100hz ์ด์) ๋ณด๋ค LiDAR ์ผ์ (10hz) ์ frame ์ด ๋ sparse ํ๊ฒ ๋ค์ด์ค๊ธฐ ๋๋ฌธ์, correction ์ sparse ํ๊ฒ ์ผ์ด๋๊ณ , propagation ์ ๋ ์์ฃผ ์ผ์ด๋๋ ์ํฉ์ด ์ผ๋ฐ์ ์ด๋ค. ๊ทธ๋์ ๋ณดํต์ ๋ชจ๋ lidar frame ์ ๋ํด์ correction์ ์ํํ ์ ์๋ค (๊ทธ๊ฒ๋ณด๋ค ์ด๋ฏธ imu hz๊ฐ ๋ ๋๊ธฐ ๋๋ฌธ์).
- ๋ค๋ง ์ด ์ค์ต์์ ์์๋ KITTI dataset ์ raw data ๋ฅผ ์ฌ์ฉํ๊ณ ์๋๋ฐ, ์ฌ๊ธฐ์์๋ imu data (in the
oxts
directory) ๊ฐ lidar frame ์ ๋๊ธฐํ๋์ด ์๋ค (๋ฏธ๋ฆฌ relative frame ์ฌ์ด์ angular velocity ์ linear acceleration์ preintegrationํด๋ ๊ฐ์ ์ ๊ณตํ๊ณ ์๋ค ๋ผ๊ณ ๋ณด๋ฉด ๋๊ฒ ๋ค). ๋ฐ๋ผ์ ๊ฐ์ ๊ฐ์์ ํ์ผ์ด ์กด์ฌํ๋ค. ์ด๋ฐ ์ํฉ์์step_size
๋ฅผ 5๋ก ์ค์ ํ๋ค๋ฉด, ์์ ๋งํ๋ฏ, LiDAR ๋ 5 frame ๋ง๋ค ํ๋ฒ์ฉ๋ง ๋ณด์ ์ ํ๋ค. ๋ณด์ ํ์ง ์๋ ๋๋จธ์ง 4 ํ๋ ์ ๊ฐ๊ฒฉ์ ๋ํด์๋ ๋ณด์ ์์ด, imu only๋ก propagation ์ ํ๋ค๋ผ๊ณ ์๊ฐํ๋ฉด ๋๋ค. 10hz lidar ์ธ ๊ฒฝ์ฐ, 0.5์ด ๋์ imu๋ก๋ง odometry๋ฅผ ์ํํ๋ ๊ฒ๊ณผ ๋์ผํ๋ค. ๊ทธ๋ ๊ฒ ์๋ฌ๊ฐ ๋์ ๋ ๋์ฏค, lidar scan-to-scan matching ์ ์ํํด์ IMU state ์ PVA (position, velocity, attitude) ๋ฅผ ๋ณด์ ํด์ค๋ค. ๊ทธ๋ฌ๋ฉด IMU๋ ๋ค์ ์ข์ ์ํ๋ฅผ ๊ฐ๊ฒ ๋๊ณ , ๋ค์ ํ๋์ (๋ฌผ๋ก 0.5์ด์ ๊ฐ์ด ์ฌ์ ํ ์งง์ ์๊ฐ (IMU์ ์ฅ์์๋ ๋ค์ ๊ธด์๊ฐ์ผ์ง๋) ๋ด์์ ๋ง์ด๋ค) lidar (๊ฐ ๋ค์ด์ค์ง ์๊ฑฐ๋) ๋ณด์ ์ ๋ฐ์ง ๋ชปํ๋๋ผ๋ ์ด๋์ ๋ ์ egomotion estimation ์ ํด์ค ์ ์๊ธฐ๋ฅผ ์ฐ๋ฆฌ๋ ๊ธฐ๋ํ ์ ์๋ค.
- Main
- Propagation
- Propagation ์ ์ํด์๋ Pypose์
IMUPreintegrator
๋ฅผ ์ฌ์ฉํ๋ค.def propogate(self, data): propagated_state = self.integrator( # == forward() dt=data["dt"], gyro=data["gyro"], acc=data["acc"], rot=self.get_rotation(data) )
- ๋ ๊ตฌ์ฒด์ ์ธ ๊ตฌํ ๋ํ ์ผ์ ์ด ๋ผ์ธ์ ์ฐธ๊ณ .
- Propagation ์ ์ํด์๋ Pypose์
- Correction
- Correction ๋จ๊ณ์์๋ ๋จผ์ scan-to-scan matching ์ ํตํด์ (imu propagation only๋ณด๋ค ์ ๋ฐํด์ง) relative motion ์ ๊ณ์ฐ ํ๋ค. ๊ทธ๋ฆฌ๊ณ ์ด๋ฅผ ์ง์ pose ์ compose ํด์ global pose ๋ฅผ correctํด์ค๋ค.
# registration and get a more precise relative motion pcd = utils.downsample_points(data["velodyne"][0], self.cfg) dx_imu = self.registration(source=pcd, target=self.pcd_previous) # correct the current global pose pose_corrected_prev = self.pose_corrected pose_corrected = pose_corrected_prev @ dx_imu # update the state of the estimator dt_batch = data["dt"][..., -1, :] * self.cfg["step_size"] # sec corrected_state = self.update_state(dt_batch, pose_corrected)
- ์ด ๊ณผ์ ์์ ์ด์งํผ ICPํด์ relative motion ์ ๊ตฌํ ๊ฑฐ๋ฉด IMU๊ฐ ์ ํ์ํ๊ฐ? ๋ผ๋ ์ง๋ฌธ์ ํ ์ ์๊ฒ ๋ค.
- ํ์ง๋ง ICP๋ initial guess ๊ฐ ์ข์ ๋์๋ง ์ ๋ต์ผ๋ก์ ์๋ ด์ ๋ณด์ฅํ ์ ์๋ค.
- ๋ง์ฝ ๋ก๋ด์ด ๊ณ ์์ผ๋ก ์ฃผํํ๊ณ ์๊ฑฐ๋, ํน์ ํ์ ํ๊ณ ์๊ฑฐ๋, ํน์ ๋์ ๋ฌผ์ฒด๊ฐ ๋๋ฌด ๋ง๊ฑฐ๋ ๋ฑ ๋ค์ํ ์ด์ ๋ก ์ธํด ์ฃผ๋ณ ํ๊ฒฝ์ structure ๊ฐ locally converge ํ ์ํ์ด ๋ง๋ค.
- ํนํ ์์ ์์์์ step_size ๋ฅผ 5๋ก ์ค์ ํ๊ณ ์๋๋ฐ, ์ฆ 0.5์ด ์ ํ์ ์ค์บ์ ์ ํฉํ๋ ค๊ณ ํ๋ ๊ฒ์ด๋ค. KITTI dataset์์์ ์๋์ฐจ์ ๊ฒฝ์ฐ 0.5์ด ๋ง์ ์ meter ๋ฅผ ์งํํ๊ฒ ๋๋๋ฐ, ์ meter ์ ๋์ translation ์ด ์์ ๋ initial guess ์์ด (== using identity) ICP ๊ฐ ์๋ ดํ๊ธฐ๋ฅผ ๊ธฐ๋ํ๊ธฐ๋ ์ผ๋ฐ์ ์ผ๋ก ์ด๋ ต๋ค (์๋ ์คํ๊ฒฐ๊ณผ์์ ์ ๋์ ์ผ๋ก ์์๋ณด์๋ค).
- ๊ทธ๋์ ์๋์ ๊ฐ์ด IMU ๊ฐ propagate ํ relative motion ์ ์ด์ฉํด์ initial guess ๋ฅผ ๊ณต๊ธํด์ฃผ๋ฉด ICP๊ฐ ๋ ์ ์ํ๋ ์ ์๊ธฐ ๋๋ฌธ์, IMU๊ฐ LiDAR์๊ฒ ํ์ํ ๊ฒ์ด๋ค.
dx_imu_initial = self.relative_pose_propagated dx_lidar_initial = i2v @ dx_imu_initial @ v2i # important! icplib = o3d.pipelines.registration dx_lidar = icplib.registration_generalized_icp( source, target, self.cfg["icp_inlier_threshold"], dx_lidar_initial, icplib.TransformationEstimationForGeneralizedICP(), ).transformation
- Tip: ์ด ๋
error = np.linalg.inv(dx_lidar) @ dx_lidar_initial
์ ๊ฐ์ด initial guess ์ ICP๋ก ๋ถํฐ ์ป์ transform ์ฌ์ด์ ์ฐจ์ด๋ฅผ ๊ณ์ฐํด๋ณด๋ฉด, IMU๊ฐ ์ผ๋ง๋ ์ข์ initial ์ ๊ณต๊ธํด์ฃผ๋ ์ง ์์๋ณผ ์ ์๋ค. step size ๋ฅผ 1์ด๋ 2๋ก ์ค์ ํ ๊ฒฝ์ฐ ๊ฑฐ์ ์ cm ์์ค๋ง์ ์ฐจ์ด๋ฅผ ๋ณด์ด๋ ๊ฒ์ ์ ์ ์๋ค. - PS: ๋ฌผ๋ก IMU์์ด LiDAR ๋ง์ผ๋ก๋ robust ํ local keypoint matching ์ ํตํด ์ข์ registration initial ์ ๋ง๋ค์ด ์ค ์๋ ์๋ค (์์: Open3Dโs global registration, or Quatro ICRA22). ํ์ง๋ง IMU ๊ฐ ๊ฐ๋ ์ฅ์ ์, ์ด๋ฐ ๋ฐฉ์๋ค์ ๋นํด (propagationํ๋ ๊ฒ์) ์ถ๊ฐ์ ์ธ ๋น์ฉ์ด ๊ฑฐ์ ๋ค์ง ์๋๋ค๋ ๊ฒ์ด๋ค! ๋ฌผ๋ก ์ด๋์ชฝ์ด ํญ์ ๋ ๋ซ๋ค ๋ผ๊ณ ๋งํ ์๋ ์๋ ๊ฒ์ด๊ณ , point cloud ์ผ์ ๋ฐ์ดํฐ๋ง ๋ฉ๊ทธ๋ฌ๋ ์ฃผ์ด์ง๋ ์ํฉ์ธ๊ฐ, ํน์ a robot ์ด real-time ์ผ๋ก recursively state ๋ฅผ ์์ธกํด๋๊ฐ๋ ์ํฉ์ธ๊ฐ, ์ ๋ฐ๋ผ ์์ง๋์ด๊ฐ ์์๊ณผ ์๊ตฌ์ฌํญ์ ๊ณ ๋ คํด์ ์ ์ ํ๊ฒ ์ ํํด์ผ ํ ๊ฒ์ด๋ค.
- Tip: ์ด ๋
- ์ด๋ ๊ฒ ๊ณ์ฐํ ๋ณด์ ๋ relative motion ์ ์ด์ฉํด์ global pose ๋ฅผ correction ํด์ค๋ค.
def update_state(self, dt_batch, pose_corrected): # prepare global_pos = torch.tensor(pose_corrected[:3, -1]).unsqueeze(0) pose_corrected_prev = self.pose_corrected global_delta_pos = torch.tensor( pose_corrected[:3, -1] - pose_corrected_prev[:3, -1]).unsqueeze(0) global_vel = global_delta_pos / dt_batch global_rot = pp.SO3(R.from_matrix(pose_corrected[:3, :3]).as_quat()) # update (loosely coupled, thus do explicit replacement) the engine state self.integrator.pos = global_pos self.integrator.vel = global_vel self.integrator.rot = global_rot
- ์ด ๊ณผ์ ์์ integrator ๋ด๋ถ์ state ๋ฅผ ๊ทธ๋ฅ ๋ฐ๊ฟ์น๊ธฐ ํ๋ ์ ์ด๋ฏ๋ก loosely coupled lidar-inertial odometry system ์ด๋ผ๊ณ ๋งํ ์ ์๋ค. correction ๊ณผ์ ์์ IMU๋ LiDAR์๊ฒ initial ์ ๋ ๋ฆฝ์ ์ผ๋ก ์ ๊ณตํ๊ณ , ์ค์ ๋ก ๋ณด์ ์น๋ LiDAR registration ๋ง์ผ๋ก ์ด๋ฃจ์ด์ก๊ธฐ ๋๋ฌธ์ด๋ค.
- Correction ๋จ๊ณ์์๋ ๋จผ์ scan-to-scan matching ์ ํตํด์ (imu propagation only๋ณด๋ค ์ ๋ฐํด์ง) relative motion ์ ๊ณ์ฐ ํ๋ค. ๊ทธ๋ฆฌ๊ณ ์ด๋ฅผ ์ง์ pose ์ compose ํด์ global pose ๋ฅผ correctํด์ค๋ค.
- Propagation
- ์ค๋น ๋จ๊ณ
์คํ ๊ฒฐ๊ณผ
- ์ธํ
- LiDAR registrationํ ๋ point cloud ๋ 0.5m ๋ก voxel downsampling ํ์๋ค.
2011_09_30_0018
์ํ์ค์ ๋ํด ์คํํ์๋ค.
- Ablations for each comparison methods
- Z ๋น๊ต
- ์์ฝ
- LiDAR only ๋ scan๊ณผ scan ์ฌ์ด ๊ฐ๊ฒฉ์ด ๋ฉ์ด์ง ์๋ก initial guess ์์ด๋ failํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
- GT rotation ์ ๊ณต๊ธํ ๊ฒ๋ณด๋ค, sparse ํ๋๋ผ๋ lidar ๋ก ๋ณด์ ํ ๊ฒ์ด ๋ ์์ ์ ์ธ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์ฌ์ฃผ๋ ๊ฒ์ ์ ์ ์๋ค.
๊ฒฐ๋ก
- IMU only odometry ๋ ์ธ๋ถ ๋์ ์์ด๋ ๊ธ๋ฐฉ ๋ฐ์ฐํ๋ค.
- Python ๋ง ์ด์ฉํด์ ๊ฐ๋จํ๊ฒ Loosely-coupled LiDAR + IMU ๊ธฐ๋ฐ Odometry system์ ๊ตฌํํด๋ณด์๋ค.
- LiDAR๊ฐ rough ํ๊ฒ๋ง ์ฐ์ฌ๋ (i.e., 0.5m resolution, 0.5sec ๋ง๋ค ํ ๋ฒ๋ง ๋ณด์ ), IMU state ๋ฅผ ์ ๋ณด์ ํ ์ ์์์ ์คํ์ ์ผ๋ก ์์๋ณด์๋ค.
- ์ด๋ ๊ฒ PVA๊ฐ ์ ๋ณด์ ๋ IMU๋ ๊ทธ ๋ค์ํด์ LiDAR registration ์ ์ํ ์ข์ initial guess ๋ฅผ ์์ฑํด์ฃผ๊ณ , ๊ฒฐ๊ณผ์ ์ผ๋ก ์ ์ํ์ด ๋ฐ๋ณต๋๋ค.