Pose-graph optimization ์ด๋?
- ๊ณ์ฐ ํจ์จ์ฑ์ ๋์ด๊ธฐ ์ํด Landmark ๋ฅผ state vector ์์ ์ ์ธํ๋ ์๋๋ค์ด 2000๋ ๋ ํ๋ฐ SLAM ํ๊ณ์์ ์ด์ด์ก๋ค.
- ๊ทธ๋ฆฌ๊ณ 3D ์ ๋ณด๋ฅผ ์ง์ ์ ๊ตํ๊ฒ ์ธก์ ํ๋ LiDAR sensor ๊ฐ ๋์คํ๋๋ฉฐ, ambiguous landmark๋ฅผ ๊ตณ์ด ์ต์ ํํ ํ์ ์์ด, ๋ก๋ด์ pose๋ง์ ์ต์ ํ ํ๋ ๊ฒ์ด ๋์ฑ ๋์คํ๋ ๋ฏํ๋ค.
- SymForce ๋ฅผ ์ด์ฉํด์ Pose-graph Optimization์ ๊ตฌํํด๋ณด์.
Why SymForce for PGO?
- ์ฌ์ค Pose-graph optimization ์ ๋ํด์๋ GTSAM์์ ์ด๋ฏธ ๊ทธ ๊ธฐ๋ฅ์ (API๋ ์ฐ๊ธฐ ์ฝ๊ฒ) ๋๋ฌด ์ ์ ๊ณตํ๊ณ ์๊ธฐ ๋๋ฌธ์ ์๋ก์ด library๊ฐ ๊ตณ์ด ํ์ํด? ๋ผ๋ ์๊ฐ์ด ๋ค๊ธฐ๋ ํ๋ค.
- LIO-SAM์ด ๊ทธ์ค ๊ฐ์ฅ ์ ๋ช
ํ GTSAM์ PGO์ ์ฌ์ฉํ ์์์ด๋ค (๊ฑฐ์ star๋ ์ด์ 2์ฒ๊ฐ๊ฐ ๋ค ๋์ด ๊ฐ๋ค).
- https://github.com/TixiaoShan/LIO-SAM ์์
mapOptmization.cpp
์ ๋ณด๋ฉด๋๋ค.
- https://github.com/TixiaoShan/LIO-SAM ์์
- ํ์ง๋ง ๋ณดํต ์ด๋ ๊ฒ ๋๋ฉด ์์ง๋์ด๊ฐ ํ ์ผ์ ๋งค์ฐ ์ถ์ํ๋๊ฒ ๋๋ค.
isam = new ISAM2(parameters);
๋ฅผ ์ ์ธํ๊ณ ,gtSAMgraph.add(some factor)
ํ๊ณ ,isam->update(gtSAMgraph, initialEstimate);
ํ๋ ์์ด๋ค ..
- ๊ทธ๋์ ๋ด๋ถ์ ์ผ๋ก ์ด๋ป๊ฒ ์ค์ ๋ก ์ต์ ํ๊ฐ ์ด๋ฃจ์ด์ง๋์ง ์ด์ง๊ฐํ ์ด์ ์ด ์์ผ๋ฉด ๋ชจ๋ฅด๊ณ ์ฐ๊ฒ ๋๋ค.
- ๋ด ์๊ธฐ๋ค ..
- GTSAM์ C++์์ custom loss ๋ฅผ ์์ฑํ ์ ์์ง๋ง Ceres์ ๋นํด ๊ณต๊ฐ๋ ๊ตฌํ์ฒด๋ docs ๊ฐ ์ ์ ๋๋์ด๋ค. ๊ทธ๋์ ๊ธฐ์กด built-in cost ๋ค์ ๊ตฌํ์ ํบ์๋ณด๋ฉฐ ๋ฐ๋ผ ์์ฑํด์ผ ํ๊ณ .. ๊ทธ๋ฆฌ๊ณ ์ ์ง ๋ญ๊ฐ ๋ด๋ถ๊ตฌ์กฐ๋ฅผ ์ข ์๊ธฐ๊ฐ ์ด๋ ต๋ค๋ ์ธ์์ด ์์๋ค. ๋ด๋ถ์ ์ผ๋ก๋ traits ๊ณผ Template ์ผ๋ก ๋ณต์กํ๊ฒ ์ง์ฌ์ ธ์๋๋ฐ .. ํ์ ํ๊ธฐ๊ฐ ์ข ๋ณต์กํ๋ค.
- ๋ฐ๋ฉด SymForce๋ Jacobian ์ Symbolic ํ๊ฒ ๊ณ์ฐํด์ ๋ณด์ฌ์ฃผ๋ฏ๋ก ๋ช
ํํ ์ธ์์ด ์๋ค..
- ์์ ๋ธ๋ก๊ทธ ํํ ๋ฆฌ์ผ ์์ Symforce ๊ฐ ๊ตฌํด์ค Jacobain์ ๊ฐ์ ธ๋ค ์ฌ์ฉํด์ ์ง์ Gauss-Newton iteration ์ ๊ตฌ์ฑํด๋ณผ ์ ์์๋ค.
- ๋ฌด์๋ณด๋ค python ์์๋ custom loss ๋ฅผ ์ฝ๊ฒ ์์ฑํ ์ ์์ด์ ์ด๊ฒ์ ๊ฒ ํด๋ณด๊ธฐ๊ฐ ์ข์๋ฏ ํ๋ค.
- ps. ํํธ, GTSAM ์ ์ฌ์ฉํ๋ ๊ทธ๋ฃน์ด ๋ช ์์๊ณ , ๊ทธ ์ธ์๋ ๋์ฒด๋ก Ceres ๋ฅผ ๋ง์ด๋ค ์ฌ์ฉํ๋ ๋ฏํ๋ค. Ceres ๋ก PGO๋ฅผ ๊ตฌํํ ๊ฐ์ฅ ์ ๋ช
ํ ์ฌ๋ก๋ ์๋ง VINS-mono์ผ ๊ฒ์ด๋ค.
https://github.com/HKUST-Aerial-Robotics/VINS-Mono/tree/master/pose_graph
์ฌ๊ธฐ๋ฅผ ๋ณด๋ฉด๋๋ค.
- LIO-SAM์ด ๊ทธ์ค ๊ฐ์ฅ ์ ๋ช
ํ GTSAM์ PGO์ ์ฌ์ฉํ ์์์ด๋ค (๊ฑฐ์ star๋ ์ด์ 2์ฒ๊ฐ๊ฐ ๋ค ๋์ด ๊ฐ๋ค).
- ๊ทธ๋์ SymForce tutorial ์ฐจ์์์ ์ด๊ฒ์ ๊ฒ ์์ฑํด๋ณด๊ณ ์๋ค.
- Iterative optimizatino์ From scratch ๋ก ๊ตฌํํ๋ ๊ฒ์ ์ง๋ ํฌ์คํ ์์ ํด๋ณด์๊ณ , ์์ ๋ธ๋ก๊ทธ ํํ ๋ฆฌ์ผ์ todo ์์ ์๊ธฐํ๋ฏ SymForce ๊ฐ ์ ๊ณตํ๋ optimizer class ๋ฅผ ์ฌ์ฉํด๋ณด์.
- ๊ด๋ จ ์์๋ landmark ๋ฅผ bearing measurement ๋ก ์ธก์ ํด์ global localization ์ ์ํํ๋ ๊ฒ์ด ๊ณต์readme ์ ์๋ค.
- PGO๊ฐ ๋ฑ ์์ง๋ ์์๋ฐ ์ด์งํผ ๊ตฌ์กฐ๋ ๋น์ทํ๋๊น ์ง์ ์์ฑํด๋ณด์.
SymForce๊ธฐ๋ฐ์ PGO ๊ตฌํ
- ์ฌ๊ธฐ์ ์ ์ฒด ์ค์ต ์ฝ๋๊ฐ ์๋ค.
Cost functions
- PGO์์ ์ฌ์ฉํ๋ cost function ์ (์ต์ํ์ผ๋ก) prior ์ odometry, loop closure ๊ฐ ์๋ค.
- SymForce ๋ก๋ ๋ค์๊ณผ ๊ฐ์ด ๊ตฌํ๋๋ค.
eps = 0.000001 def prior_residual( pose : sf.Pose3, pose_prior: sf.Pose3, diagonal_sigmas: sf.V6 ) -> sf.V6: tangent_error = pose.local_coordinates(pose_prior, epsilon=eps) return sfT.cast(sf.V6, sf.M.diag(diagonal_sigmas.to_flat_list()).inv() * sf.V6(tangent_error)) def odometry_residual( pose_prev: sf.Pose3, pose_next: sf.Pose3, movement : sf.Pose3, diagonal_sigmas: sf.V6, ) -> sf.V6: # The original reference of the below lines # - see https://github.com/symforce-org/symforce/blob/main/symforce/examples/robot_3d_localization/robot_3d_localization.py#L63 movement_predicted = pose_prev.inverse() * pose_next tangent_error = movement_predicted.local_coordinates(movement, epsilon=eps) return sfT.cast(sf.V6, sf.M.diag(diagonal_sigmas.to_flat_list()).inv() * sf.V6(tangent_error)) def loop_residual( pose_prev: sf.Pose3, pose_next: sf.Pose3, movement : sf.Pose3, diagonal_sigmas: sf.V6, ) -> sf.V6: return odometry_residual(pose_prev, pose_next, movement, diagonal_sigmas)
- ์ค๋ช
- prior ๋ pose ์ SE(3)์ direct ๋ก ์ถ์ ํ๋ unary factor (์ธ์๊ฐ ํ๋๋ง ์ฐ์ธ๋ค๋ ๋ป) ์ด๋ค.
- odometry ๋ consecutive ํ node ์ฌ์ด์ motion ์ ์ ์ฝํ๋ค.
- ๋ฐ๋ผ์ pose $i$ ์ pose $i+1$ ์ฌ์ด์ ์์ธก๋ ๊ฐ (motion model) ์ด ์ค์ ๋ก ์ธก์ ๋ ๊ฐ (measurement)๊ณผ ์ผ์น๋๋๋ก pose $i$์ $i+1$ ์ ์์น๊ฐ ์กฐ์ ๋๋ค.
- ์ด๋ฐ ํน์ฑ ๋๋ฌธ์ GTSAM์์๋ ์ด๋ฅผ between factor ๋ผ๊ณ ์นญํ๊ธฐ๋ ํ๋ค.
- ์ฆ, ๋ ์ฌ์ด์ relative ํ ์ ์ฝ๋ง์ ๋ง์กฑํ๋ฉด ๋๊ธฐ ๋๋ฌธ์ ๋ ๋
ธ๋์ ์ ์ญ์ ์ธ ์์น ์์ฒด๋ ์ด๋์ ๋์ฌ์๋ ์๊ด์ด ์๊ฒ ๋๋ค.
- ์๋ฅผ ๋ค์ด, ๋ ๋ ธ๋๊ฐ 1๊ณผ 2์ ์์ด๋ ๊ทธ ์ฌ์ด ๊ฐ๊ฒฉ์ 1์ด๊ณ , 2์ 3์ ์์ด๋ ๊ทธ ์ฌ์ด ๊ฐ๊ฒฉ์ 1์ด๋ฏ๋ก ์ฌ์ ํ ์ ์ฝ์ ๋ง์กฑํ๋ค.
- ๋ฐ๋ผ์ ์ต์ํ์ prior ๊ฐ ํ์ํ๋ฉฐ ๋ณดํต SLAM์์๋ ์ฒซ node ์ ์์ฃผ ๊ฐํ (==๋งค์ฐ ์์ covariance๋ฅผ ๊ฐ์ง๋) prior ์ ์ฝ์ ๊ฑธ์ด์ฃผ๊ฒ ๋๋ค.
- ํน์ GPS ๊ฐ์ ๊ฐ์ ์ฌ์ฉํ์ฌ ์ ์ญ์ ์ธ ์์น๋ฅผ ๊ณ ์ ํ ์๋ ์๋ค.
- rotation ์ ๋ณด๊ฐ ์์ด translation (์ฆ, lat, lng, altitude) ๋ง ์ฌ์ฉ๊ฐ๋ฅํ ๊ฒฝ์ฐ, ํ๋์ node ๋ง prior ๋ฅผ ๊ฑธ์ด์ฃผ๋ ๊ฒ์ผ๋ก๋ ๋ถ์กฑํ๊ณ ๋ ์ด์์ ์๋ก ๋ค๋ฅธ ๋ ธ๋์ prior ๋ฅผ ๊ฑธ์ด์ฃผ์ด์ผ global heading ์ ์ ์ ์๊ฒ ๋๋ค.
- ps. ๋ง์ฝ prior ๋ฅผ ์๊ฑธ์ด์ฃผ๋ฉด ์ด ๋
ธ๋๋ค์ด ๋ชจ์์๋ ์ ์งํ๋ฉด์ ์ ์ญ์ ์ผ๋ก ๋ฏธ์ณ๋ ๋ฐ๊ฒ (โฆ) ๋๋๋ฐ, ์ด๋ฅผ ๊ฒ์ด์ง ํ๋ผ๋ธ๋ผ ์ด๋ผ๊ณ ๋ ํ๋ ๋ฏํ๋ค. Ceres ์ ๊ณต์ example ์์๋ ๋ฑ ์ด ์ด์ผ๊ธฐ๊ฐ ์๋ค.
// The pose graph optimization problem has six DOFs that are not fully // constrained. This is typically referred to as gauge freedom. You can apply // a rigid body transformation to all the nodes and the optimization problem // will still have the exact same cost. The Levenberg-Marquardt algorithm has // internal damping which mitigates this issue, but it is better to properly // constrain the gauge freedom. This can be done by setting one of the poses // as constant so the optimizer cannot change it.
- ๋ฐ๋ผ์ pose $i$ ์ pose $i+1$ ์ฌ์ด์ ์์ธก๋ ๊ฐ (motion model) ์ด ์ค์ ๋ก ์ธก์ ๋ ๊ฐ (measurement)๊ณผ ์ผ์น๋๋๋ก pose $i$์ $i+1$ ์ ์์น๊ฐ ์กฐ์ ๋๋ค.
- loop closure ๋ not consecutive ํ ๋ node ์ฌ์ด์ motion์ ์ ์ฝํ๋ค.
- ๋ฐ๋ผ์ ์ํ์ ์ผ๋ก๋ odometry ์ ์์ ํ ๋์ผํ๋ค.
- ๊ทธ๋์ ์์ ๊ตฌํ์์๋
loop_residual
์์๋ ๋ด๋ถ์ ์ผ๋กodometry_residual
์ callํ๋๋ก ๊ตฌํํ์๋ค.
- ๊ทธ๋์ ์์ ๊ตฌํ์์๋
- ๋ค๋ง ๋ถ๋ฆฌํด์ ์ด์ผ๊ธฐํ๋ ๊ฒ์ด ์ข์ ์ด์ ๋,
- odometry์ loop closure ์๋ noise scale ์ ๋ค๋ฅด๊ฒ ์ ์ฉํ๋ ๊ฒ์ด ํ์ค ๋ฌธ์ ๋ฅผ ํ ๋์ ๋์์ด ๋๊ธฐ ๋๋ฌธ์ด๋ค.
- ์ด๋ ๋ณธ์ธ์ด ์ฌ์ฉํ๋ ์ผ์์ ํ์ง์ ๋ฐ๋ผ ๋ฌ๋ฆฌ ์ ์ฉํด์ผ ํ๋ค. ์๋ฅผ ๋ค์ด odometry๋ก wheel odometer ๋ฅผ ์ฌ์ฉํ๋๋ฐ ๊ทธ๊ฒ์ ํ์ง์ด ์ข์ง ์๋ค๋ฉด odometry residual์ ๋ํด์๋ ๋์ noise ๋ฅผ ๋ถ์ฌํ ์ ์๊ฒ ๋ค. ๋ฐ๋ฉด, ๊ณ ์ ๋ฐ์ lidar ์ผ์ scan ์ ํตํด loop closing ์ ์ํํ๋ค๋ฉด loop closure residual์ ๋ํด์๋ ๋ฎ์ noise (==๋์ ์ ๋ขฐ๋) ๋ฅผ ์ฌ์ฉํ ์ ์๊ฒ ๋ค.
- ๋ํ loop closure factor ๋ฅผ ์ถ๊ฐํ๊ธฐ ์ด์ ์, loop closure detection (or aka place recognition) ์ด๋ผ๋ ๊ณผ์ ์ ์ํํด์ผ ํ๋๋ฐ, ์ด๊ฒ์ด 100% ํญ์ ์ฌ๋ฐ๋ฅด๋ค๋ ๋ณด์ฅ์ด ์๋ค. ๋ฐ๋ผ์ odometry factor ์ ๋ฌ๋ฆฌ loop closure factor ์ ๋ํด์๋ robust kernel ์ ์์์ฃผ๋ ์์
๋ฑ์ด ์๊ตฌ๋๊ธฐ๋ ํ๋ค.
- ์ฌ๊ธฐ์์ GTSAM์ Cauchy kernel ์ ์ฌ์ฉํ ์์ ๋ฅผ ์ฐธ๊ณ ๋ฐ๋.
- odometry์ loop closure ์๋ noise scale ์ ๋ค๋ฅด๊ฒ ์ ์ฉํ๋ ๊ฒ์ด ํ์ค ๋ฌธ์ ๋ฅผ ํ ๋์ ๋์์ด ๋๊ธฐ ๋๋ฌธ์ด๋ค.
- ๋ฐ๋ผ์ ํ๋ํฐ์ปฌํ๊ฒ๋ loop closure factor ์ odometry factor ๋ฅผ ๋ถ๋ฆฌํด์ ์๊ฐํ๋ ํธ์ด ์ข๋ค.
- ๋ฐ๋ผ์ ์ํ์ ์ผ๋ก๋ odometry ์ ์์ ํ ๋์ผํ๋ค.
- ์ค๋ช
2
- ์์ ์ฝ๋๋ฅผ ๋ณด๋ฉด $\text{SE}(3)$๋ฅผ ์ธํ์ผ๋ก ๋ฐ์ง๋ง ์ค์ ๋ก diff๋ local_coordinates๋ผ๋ ํจ์๋ฅผ ํตํด์ ๊ณ์ฐ๋๋ ๊ฒ์ ์ ์ ์๋ค.
- SymForce ์ ์ฝ๋๋ฅผ ๋ฏ์ด๋ณด๋ฉด Pose3์ ๋ํด local_coordinates ์ ์๋(์๋ณธ๋งํฌ)์ ๊ฐ์ด ๊ตฌํ๋์ด ์๋ค.
@staticmethod def local_coordinates(a, b, epsilon): # type: (sym.Pose3, sym.Pose3, float) -> T.List[float] # Total ops: 50 # Input arrays _a = a.data _b = b.data # Intermediate terms (4) _tmp0 = -_a[0] * _b[0] - _a[1] * _b[1] - _a[2] * _b[2] _tmp1 = _a[3] * _b[3] _tmp2 = min(1 - epsilon, abs(_tmp0 - _tmp1)) _tmp3 = ( 2 * (2 * min(0, (0.0 if -_tmp0 + _tmp1 == 0 else math.copysign(1, -_tmp0 + _tmp1))) + 1) * math.acos(_tmp2) / math.sqrt(1 - _tmp2 ** 2) ) # Output terms _res = [0.0] * 6 _res[0] = _tmp3 * (-_a[0] * _b[3] - _a[1] * _b[2] + _a[2] * _b[1] + _a[3] * _b[0]) _res[1] = _tmp3 * (_a[0] * _b[2] - _a[1] * _b[3] - _a[2] * _b[0] + _a[3] * _b[1]) _res[2] = _tmp3 * (-_a[0] * _b[1] + _a[1] * _b[0] - _a[2] * _b[3] + _a[3] * _b[2]) _res[3] = -_a[4] + _b[4] _res[4] = -_a[5] + _b[5] _res[5] = -_a[6] + _b[6] return _res
- ์ฌ๊ธฐ์ return ๊ฐ์ ๋ Pose3์ธ a, b์ difference ์ด๋ฉฐ 3-4-5 ์ธ๋ฑ์ค๊ฐ translation ์ฐจ์ด์ ํด๋นํ๊ณ , 0-1-2 ์ธ๋ฑ์ค๊ฐ rotation ์ฐจ์ด์ ํด๋นํ๋ค.
- ๋งค๋ฒ ๊ฒ์๊ธ์์ ์ด์ผ๊ธฐํ์ง๋ง rotation ์ minimal representation์ 3-dim ์ด๊ธฐ ๋๋ฌธ์ โฆ
- ps. Rotation ๊ณต๋ถ์๋ฃ ์ถ์ฒ์ ์ฌ๊ธฐ ๋ฅผ ์ฐธ๊ณ ๋ฐ๋.
- ๋งค๋ฒ ๊ฒ์๊ธ์์ ์ด์ผ๊ธฐํ์ง๋ง rotation ์ minimal representation์ 3-dim ์ด๊ธฐ ๋๋ฌธ์ โฆ
Total ops
๋ฅผ ๋ช ๊ธฐํ ๊ฒ๋ ํฅ๋ฏธ๋ก์ด ๋ถ๋ถ์ด๋ค. ์ฆ, ์ ์ฐ์ฐ์ด Pose3์ ๋ํด์ deterministic ํ๊ฒ ์ต์ ํ๋์ด ์๋ค๋ ๋ป.
- SymForce ์ ์ฝ๋๋ฅผ ๋ฏ์ด๋ณด๋ฉด Pose3์ ๋ํด local_coordinates ์ ์๋(์๋ณธ๋งํฌ)์ ๊ฐ์ด ๊ตฌํ๋์ด ์๋ค.
- ์๋ฌดํผ ์ฐ๋ฆฌ๊ฐ ์์์ผ ํ ๊ฒ ํ์ค์์ฝ์, rotation matrix์ vector space ๊ฐ ์๋๊ธฐ ๋๋ฌธ์, Pose3 (== $\text{SE}(3)$) type ์ฌ์ด์ error ๋ฅผ ๊ณ์ฐํ๊ณ ์ถ์ ๋๋
local_coordinates
๋ผ๋ ํจ์๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค๋ ๊ฒ์ด๋ค.- ps. ์ง๋ from scratch ๊ฒ์๊ธ์์๋ minimal ํ vector3 ์ธ rotvec์ ๋จผ์ ์ ์ํ๊ณ , ์ด๋ฅผ ์ด์ฉํด์ SO(3)์ธ rotmat์ ๋ง๋ค์ด์ ์ผ์๋ค.
rotvec = sf.V3.symbolic("Theta") # i.e., angle-axis parametrization rotmat = LieGroupOps.from_tangent(sf.Rot3, rotvec) # for debug, display(rotmat.to_rotation_matrix())
- ps. ์ง๋ from scratch ๊ฒ์๊ธ์์๋ minimal ํ vector3 ์ธ rotvec์ ๋จผ์ ์ ์ํ๊ณ , ์ด๋ฅผ ์ด์ฉํด์ SO(3)์ธ rotmat์ ๋ง๋ค์ด์ ์ผ์๋ค.
- ์์ ์ฝ๋๋ฅผ ๋ณด๋ฉด $\text{SE}(3)$๋ฅผ ์ธํ์ผ๋ก ๋ฐ์ง๋ง ์ค์ ๋ก diff๋ local_coordinates๋ผ๋ ํจ์๋ฅผ ํตํด์ ๊ณ์ฐ๋๋ ๊ฒ์ ์ ์ ์๋ค.
Experiments Setting
- ์ํผ ์ฌ์ค์ด ๊ธธ์๋๋ฐ ์คํ์ ๋ค์๊ณผ ๊ฐ๋ค.
Dataset generation
- ๋จผ์ ํ ์ด ๋ฐ์ดํฐ์ ์ ์ง์ ๋ง๋ค์ด๋ณด์. ์์ธํ ๊ฑด ์ฝ๋๋ฅผ ์ฐธ๊ณ (๋งํฌ)๋ฐ๋.
move()
ํจ์๋ฅผ ํตํด ์ ๋ค๊ฐํ ๋ชจ์์ trajectory๋ฅผ ๊ตฌ์ฑํ๋ n๊ฐ์ pose ๋ฅผ ์์ฑํ๋ค. ์์๋ ์๋์ ๊ฐ๋ค.
Hyper parameter setting
- PGO์์ hyper parameter ๋ผ๊ณ ํ๋ฉด ์์์ ์ด์ผ๊ธฐํ 3์ข ๋ฅ์ factors ์ ๋ํ noise ๋ฅผ ๊ฒฐ์ ํ๋ ์ผ์ด ๋๊ฒ ๋ค.
- ์ด ์์ ์์๋ ๋ค์๊ณผ ๊ฐ์ด ๊ตฌ์ฑํด๋ณด์๋ค.
## noises prior_diagonal_sigmas = np.array([0.001, 0.001, 0.001, 0.01, 0.01, 0.01]) * 0.001 odometry_diagonal_sigmas = np.array([0.05, 0.05, 0.05, 0.1, 0.1, 0.1]) loop_diagonal_sigmas = odometry_diagonal_sigmas * 0.1
- prior ์ ๊ฒฝ์ฐ ์ด์งํผ ์ ์ผ ์ฒซ ๋ ธ๋์๋ง ๋ถ์ฌํ ๊ฒ์ด๋ฉฐ, ์ ์ฒด trajectory ๊ฐ ํ๋ค๋ฆฌ์ง ์์์ผ ํ๋ฏ๋ก (==gauge problem์ ๋น ์ง์ง ์์์ผ ํ๋ฏ๋ก) ์์ฃผ ์์ ๊ฐ์ผ๋ก ์ค์ ํ์๋ค.
- odometry ๋ (rad์ meter ๋ผ๋ ๋จ์๋ฅผ ๊ณ ๋ คํ์ฌ) ์ ๋นํ ๊ฐ์ผ๋ก ์ค์ ํ์๋ค.
- ps. ์ด ๋ ์ด ๊ฐ์ std์ผ์๋ ์๊ณ covariance (std์ ์ ๊ณฑ) ์ผ์๋ ์๋๋ฐ, library๋ง๋ค ๋ค๋ฅผ ์ ์์ผ๋ฏ๋ก ๋ฌธ๋งฅ์ ํ์
ํ์ฌ์ผ ํ๋ค. ์๋ฅผ ๋ค์ด GTSAM์์๋ covariance ์ด๋ค.
- ํ์ง๋ง ๊ทธ๊ฒ๋ณด๋ค ๋ ์ค์ํ ๊ฑด ์ด์งํผ ์ ์ ์ค์ผ์ผ์ ์คํ์ ์ผ๋ก๋ ํ์ ํ๋ ๊ฒ์ด ์๊ตฌ๋๋ค.
- ์ด ์์ ์์๋ ์์
sfT.cast(sf.V6, sf.M.diag(diagonal_sigmas.to_flat_list()).inv() * sf.V6(tangent_error))
์ ๊ฐ์ด error ์ noise ์ inverse ๋ฅผ ๋ฐ๋ก ๊ณฑํด์ฃผ๋ ํํ๋ก ๊ตฌํ๋์๋ค.- ์ฆ, std๊ฐ ๋๋ค. ์๋ ์์์ ์ฐธ๊ณ ํ๋ฉด ์ดํด๊ฐ ๋๋ค. (์ถ์ฒ: Factor Graphs for Robot Perception, 2017). error $e$์ ๋ฐ๋ก (์ญ์๊ฐ)๊ณฑํด์ง๋ ๊ฒ์ covariance ์ sqrt์์ ์ ์ ์๋ค. ๊ทธ๋์ ์์ ๊ตฌํ์์๋ ๋ณ์ ์ด๋ฆ์ diagonal_
sigma
๋ผ๊ณ ์นญํด๋์๋ค.
- ์ฆ, std๊ฐ ๋๋ค. ์๋ ์์์ ์ฐธ๊ณ ํ๋ฉด ์ดํด๊ฐ ๋๋ค. (์ถ์ฒ: Factor Graphs for Robot Perception, 2017). error $e$์ ๋ฐ๋ก (์ญ์๊ฐ)๊ณฑํด์ง๋ ๊ฒ์ covariance ์ sqrt์์ ์ ์ ์๋ค. ๊ทธ๋์ ์์ ๊ตฌํ์์๋ ๋ณ์ ์ด๋ฆ์ diagonal_
- ps. ์ด ๋ ์ด ๊ฐ์ std์ผ์๋ ์๊ณ covariance (std์ ์ ๊ณฑ) ์ผ์๋ ์๋๋ฐ, library๋ง๋ค ๋ค๋ฅผ ์ ์์ผ๋ฏ๋ก ๋ฌธ๋งฅ์ ํ์
ํ์ฌ์ผ ํ๋ค. ์๋ฅผ ๋ค์ด GTSAM์์๋ covariance ์ด๋ค.
- loop closure ์ noise ๋ odometry๋ณด๋ค ๋ ์๊ฒ (๋ ๊ฐ๋ ฅํ๊ฒ ์์ฉํ๋๋ก) ์ค์ ํ์๋ค.
Key-based Value Management
- SymForce ๊ฐ ๋ ์ข์ ์ ์ด ๋ฐ๋ก Key ๊ธฐ๋ฐ์ผ๋ก value ๋ฅผ ๊ด๋ฆฌํ๋ ๊ฒ์ด๋ค.
- factors๋ค์ ์ค์ ๋ก ์ ์ธํ ๋ ๋ค์๊ณผ ๊ฐ์ด ํ๋ฉด ๋๋ค.
# 1/ Prior factors
if use_factors['prior']:
for i in range(num_poses):
if i not in [0]:
continue
# only add at the first node
factors.append(Factor(
residual=prior_residual,
keys=[f"poses[{i}]", f"poses_prior[{i}]", f"prior_diagonal_sigmas"],
))
# 2/ Odometry factors
if use_factors['odometry']:
for i in range(num_poses-1):
factors.append(Factor(
residual=odometry_residual,
keys=[f"poses[{i}]", f"poses[{i+1}]", f"odoms[{i}]", "odometry_diagonal_sigmas"],
))
# 3/ Loop factors
if use_factors['loop']:
for ii, loops_index_pair in enumerate(loops_indexes):
idx_from = loops_index_pair[0]
idx_to = loops_index_pair[1]
factors.append(Factor(
residual=odometry_residual,
keys=[f"poses[{idx_from}]", f"poses[{idx_to}]", f"loops[{ii}]", "loop_diagonal_sigmas"],
))
- ์์ ์ฝ๋์์ ๋ณด๋ค์ํผ,
residual=
์ ํจ์๋ฅผ ์ ๋ ฅํด์ฃผ๊ณ ,keys=
์ ์ด ํจ์์ ์ธ์๋ก ๋ค์ด๊ฐ ๋ณ์๋ค์ ์ฐจ๋ก๋ก ๋์ดํด์ฃผ๋ฉด ๋๋ค.- ๊ทธ๋ฐ๋ฐ ์ด stringfied๋ ๋ณ์์ด๋ฆ๋ค์ ์ด๋์ ์๋๊ฐ? ๋ค์๊ณผ ๊ฐ์ด Values ๋ผ๋ ๊ณณ์๋ค๊ฐ ๋ค ๋ด์์ฃผ๋ฉด ๋๋ค.
initial_values = Values( # poses=[sf.Pose3.identity()] * num_poses, # may not converge. poses=initial_poses, poses_prior=poses_prior, prior_diagonal_sigmas=prior_diagonal_sigmas, odoms=odoms_obs, odometry_diagonal_sigmas=odometry_diagonal_sigmas, loops=loops_obs, loop_diagonal_sigmas=loop_diagonal_sigmas, )
- ์ฐ๋ณ์ ์๋ ๊ฒ์ด ์ฐ๋ฆฌ๊ฐ ์ฌ์ ์ ์ ์ํด๋ ๋ณ์๋ค์ด๋ฉฐ
- ์ข๋ณ์ ์ฌ์ฉํ ์ด๋ฆ์ด key๋ก ์ฐ์ด๊ฒ ๋๋ค.
- ๊ทธ๋ฐ๋ฐ ์ด stringfied๋ ๋ณ์์ด๋ฆ๋ค์ ์ด๋์ ์๋๊ฐ? ๋ค์๊ณผ ๊ฐ์ด Values ๋ผ๋ ๊ณณ์๋ค๊ฐ ๋ค ๋ด์์ฃผ๋ฉด ๋๋ค.
- ๊ทธ๋ฌ๋ฉด ์ต์ข
์ ์ผ๋ก
Optimizer
class๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ์ฝ๋ค. ์๋์ ๊ฐ์ด ํด์ฃผ๋ฉด ๋!optimizer = Optimizer( factors=factors, optimized_keys=[f"poses[{i}]" for i in range(num_poses)], debug_stats=True, )
- ์ฌ๊ธฐ์๋ ์ต์ ํํ ๋ณ์์ ๋ํ key๋ง ๋ ๋ฃ์ด์ฃผ๊ฒ ๋๋๋ฐ, ์ด ๋ถ๋ถ์ ์ข Pytorch์ ๋ฎ์ ๊ฒ ๊ฐ๊ธฐ๋ ํ๋ค.
Results
- ์ฌ์ค์ด ๋ ์ญ์ ๊ธธ์๋๋ฐ, ๊ฒฐ๊ณผ๋ ๋ค์๊ณผ ๊ฐ๋ค.
Without a Loop constraint
- odometry factors ์ ๋ํด ground-truth ๋๋น perturbation noise ๋ฅผ ์์ด์ฃผ์๊ธฐ ๋๋ฌธ์ (see
perturb_alpha
in the codes), loop closure factor ์์ด๋ ์๋์ ๊ฐ์ด trajectory์ drift ๊ฐ ๋์ ๋๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
With only a single Loop constraint
- ํํธ, ์ฒ์ ๋ ธ๋์ ๋ง์ง๋ง ๋ ธ๋ ์ฌ์ด์ loop closure factor ๋ฅผ ๋จ ํ๋๋ง ๋ฃ์ด์ฃผ๋๋ผ๋, ์๋์ ๊ฐ์ด ์ ์ฒด trajectory์ shape ์ด ๋ง์ด ๊ฐ์ ๋๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
- ์ฌ์ค ์ํ์ ์ผ๋ก ๋น์ฐํ ๊ฒฐ๊ณผ์ด๋คใ
ใ
๊ทธ๋๋ ์๋ก์ด ํด์ ์ด์ฉํด์ ๊ฐ๋จํ๊ฒ ์ด๋ ๊ฒ ์ค์ตํด๋ณด๋ ๊ฒ์ ์ฐธ ์ฌ๋ฏธ์๋ค.
- ์ด๋ฐ ๋ฅ์ (๊ฝค ์์ ์ฝ๋๋ฒ ์ด์ค ๊ท๋ชจ์) PGO ๋ SLAM๊ณ์ hello-world ๋ผ๊ณ ํ ์ ์์ง ์์๊น?
๊ฒฐ๋ก
- SymForce ๋ก Pose-graph Optimization ์ ์ํํด๋ณด์๋ค.
- Pose3 ๋ค์ ๋ํด unary (prior factor) or binary (between factor) constraints ๋ฅผ ๊ฑธ์ด์ฃผ๊ธฐ ์ํด์, measurement ์ estimated (direct value or error value) ์ฌ์ด์ error ๋ฅผ ๊ณ์ฐํด์ผ ํ๋ค. ์ด๋ฅผ ๊ณ์ฐํ๊ธฐ ์ํด์ local_coordinates ์ด๋ผ๋ ํจ์๋ฅผ ์ฌ์ฉํ๋ค. ์ด๋ฐ ํ๋ก์ธ์ค๋ฅผ
tangent space optimization
๋ผ๊ณ ๋ ๋ถ๋ฅธ๋ค.
- Pose3 ๋ค์ ๋ํด unary (prior factor) or binary (between factor) constraints ๋ฅผ ๊ฑธ์ด์ฃผ๊ธฐ ์ํด์, measurement ์ estimated (direct value or error value) ์ฌ์ด์ error ๋ฅผ ๊ณ์ฐํด์ผ ํ๋ค. ์ด๋ฅผ ๊ณ์ฐํ๊ธฐ ์ํด์ local_coordinates ์ด๋ผ๋ ํจ์๋ฅผ ์ฌ์ฉํ๋ค. ์ด๋ฐ ํ๋ก์ธ์ค๋ฅผ
- ps. ๊ทธ ์ธ์.. simularity ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด epsilon์ ์ ์ฌ์ฉํด์ฃผ์ด์ผ ํ๋ค.
TODO
- ์ง์ ์ ์ํ toy example ์ ๋ํด์ ํด๋ณด์์ผ๋, SLAM ๋
ผ๋ฌธ๋ค์์ ๋ง์ด ์ฐ์ด๋ ๋ฐ์ดํฐ์
์ ๋ํด์๋ ์คํ์ ํด๋ณด์.
- ์ด ๋ ์๋ชป๋ (์ฌ์ค์ loop๊ฐ ์๋๋ฐ) loop closure factors ๋ค์ด ๋ง์ด ์ฝ์ ๋ ๋, ์ด๋ป๊ฒ ๋ฐฉ์ดํ ์ ์๋์ง ์์๋ณด์.