feat: RealSense obstacle detection (Issue #611) #623
Loading…
x
Reference in New Issue
Block a user
No description provided.
Delete Branch "sl-perception/issue-611-obstacle-detect"
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?
Summary
New package
saltybot_obstacle_detect— RANSAC ground-plane fitting on D435i depth images, BFS obstacle clustering, MarkerArray centroids, PointCloud2 non-ground points, and safety_zone e-stop integration.Pipeline (30 Hz)
min_obstacle_h=0.05 m < h < max_obstacle_h=0.80 mPublished topics
/saltybot/obstacles/saltybot/obstacles/cloud/saltybot/obstacles/alertSafety zone integration
When
depth_estop_enabled: true, DANGER obstacles publish zeroTwisttodepth_estop_topic(default/cmd_vel_input). This feeds into thesaltybot_safety_zonecmd_vel chain, giving independent depth-based stopping for obstacles the LIDAR beam misses (low surfaces, floor-level hazards).Pure-math modules (no ROS2, fully testable)
ground_plane.py: RANSAC + SVD fit, height_above_plane, obstacle_mask, ground_maskobstacle_clusterer.py: 4-connected BFS on 2D occupancy grid, ObstacleCluster dataclassTests
test_ground_plane.py: 10 tests — flat/tilted ground, normal orientation, unit vector, height computation, obstacle/ground mask accuracytest_obstacle_clusterer.py: 8 tests — single/dual cluster, sort order, empty input, min_pts filter, centroid accuracy, range clippingCloses #611
New package saltybot_obstacle_detect — RANSAC ground plane fitting on D435i depth images with 2D grid BFS obstacle clustering. ground_plane.py (pure Python + numpy): fit_ground_plane(pts, n_iter=50, inlier_thresh_m=0.06): RANSAC over 3D point cloud in camera optical frame (+Z forward). Samples 3 points, fits plane via cross-product, counts inliers, refines via SVD on best inlier set. Orients normal toward -Y (upward in world). Returns (normal, d). height_above_plane(pts, plane): signed h = d - n·p (h>0 = above ground). obstacle_mask(pts, plane, min_h, max_h): min_obstacle_h_m < h < max_h. ground_mask(pts, plane, thresh): inlier classification. obstacle_clusterer.py (pure Python + numpy): cluster_obstacles(pts, heights, cell_m=0.30, min_pts=5): projects obstacle 3D points onto (X,Z) bird's-eye plane, discretises into grid cells, runs 4-connected BFS flood-fill, returns ObstacleCluster list sorted by forward distance. ObstacleCluster: centroid(3), radius_m, height_m, n_pts + distance_m/lateral_m properties. obstacle_detect_node.py (ROS2 node 'obstacle_detect'): - Subscribes: /camera/depth/camera_info (latched, once), /camera/depth/image_rect_raw (BEST_EFFORT, 30Hz float32 depth). - Pipeline: stride downsample (default 8x → 80x60) → back-project to 3D → RANSAC ground plane (temporally blended α=0.3) → obstacle mask (min_h=0.05m, max_h=0.80m) → BFS clustering → alert classification. - Publishes: /saltybot/obstacles (MarkerArray): SPHERE markers colour-coded DANGER(red)/WARN(yellow)/CLEAR(green) + distance TEXT labels. /saltybot/obstacles/cloud (PointCloud2): xyz float32 non-ground pts. /saltybot/obstacles/alert (String JSON): alert_level, closest_m, obstacle_count, per-obstacle {x,y,z,radius_m,height_m,level}. - Safety zone integration (depth_estop_enabled=false by default): DANGER → zero Twist to depth_estop_topic (/cmd_vel_input) feeds into safety_zone's cmd_vel chain for independent depth e-stop. config/obstacle_detect_params.yaml: all tuneable parameters with comments. launch/obstacle_detect.launch.py: single node with params_file arg. test/test_ground_plane.py: 10 unit tests (RANSAC correctness, normal orientation, height computation, inlier/obstacle classification). test/test_obstacle_clusterer.py: 8 unit tests (single/dual cluster, distance sort, empty, min_pts filter, centroid accuracy, range clip). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>