feat(vo): visual odometry fallback — CUDA optical flow + EKF fusion + slip failover (Issue #157) #164
Loading…
x
Reference in New Issue
Block a user
No description provided.
Delete Branch "sl-perception/issue-157-visual-odom"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Issue #157 — Visual Odometry Fallback
Summary
saltybot_visual_odom— one package, two nodes, three core modules + unit testsSparsePyrLKOpticalFlow+ FAST detector/rtabmap/odomjump detectionodom→base_linkTF at 30 HzArchitecture
GPU Acceleration
cv2.cuda.FastFeatureDetector_createcv2.cuda.SparsePyrLKOpticalFlowEKF Fusion
[px, py, θ, v, ω](unicycle)σ_v=0.05 m/s→ 10× inflated during slipσ_v=0.08 m/s→ 3× inflated when VO has low feature count[px, py, θ]— soft-corrects drift after loop closuresSlip Failover
Subscribes to
/saltybot/terrainJSON (is_slipping,slip_ratio) fromsaltybot_terrain_adaptation. When slipping:Loop Closure Drift Correction
When
/rtabmap/odomjumps >loop_closure_thr_m(default 0.3m) from EKF estimate, callsKalmanOdomFilter.update_rtabmap()with the optimised pose. Alpha=0.4 — soft-correct, not a hard snap.Test Plan
colcon build --packages-select saltybot_visual_odompytest jetson/ros2_ws/src/saltybot_visual_odom/test/→ 8 tests passros2 launch saltybot_visual_odom visual_odom.launch.pyros2 topic hz /saltybot/visual_odom→ ~30 Hzros2 topic hz /saltybot/odom_fused→ ~30 Hzros2 run tf2_tools view_frames→odom→base_linkpresentros2 topic echo /saltybot/visual_odom_status→vo_valid: trueis_slipping: trueto/saltybot/terrain→ status showsactive_source: vo_primary🤖 Generated with Claude Code
New package: saltybot_visual_odom (13 files, ~900 lines) Nodes: visual_odom_node — D435i IR1 stereo VO at 30 Hz CUDA: SparsePyrLKOpticalFlow + FastFeatureDetector (GPU) CPU fallback: calcOpticalFlowPyrLK + goodFeaturesToTrack Essential matrix (5-pt RANSAC) + depth-aided metric scale forward-backward consistency check on tracked points Publishes /saltybot/visual_odom (Odometry) odom_fusion_node — 5-state EKF [px, py, θ, v, ω] (unicycle model) Fuses: wheel odom (/saltybot/rover_odom or tank_odom) + visual odom (/saltybot/visual_odom) Slip failover: /saltybot/terrain JSON → 10× wheel noise on slip Loop closure: /rtabmap/odom jump > 0.3m → EKF soft-correct TF: publishes odom → base_link at 30 Hz Publishes /saltybot/odom_fused + /saltybot/visual_odom_status Modules: optical_flow_tracker.py — CUDA/CPU sparse LK tracker with re-detection, forward-backward consistency, ROI masking stereo_vo.py — Essential matrix decomposition, camera→base_link frame rotation, depth median scale recovery, loop closure soft-correct, accumulated SE(3) pose kalman_odom_filter.py — 5-state EKF: predict (unicycle), update_wheel, update_visual, update_rtabmap (absolute pose); Joseph-form covariance for numerical stability Tests: test/test_kalman_odom.py — 8 unit tests for EKF + StereoVO (no ROS deps) Topic/TF map: /camera/infra1/image_rect_raw → visual_odom_node /camera/depth/image_rect_raw → visual_odom_node /saltybot/visual_odom ← visual_odom_node (30 Hz) /saltybot/rover_odom → odom_fusion_node /saltybot/terrain → odom_fusion_node (slip signal) /rtabmap/odom → odom_fusion_node (loop closure) /saltybot/odom_fused ← odom_fusion_node (30 Hz) odom → base_link TF ← odom_fusion_node (30 Hz) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>