feat(perception): terrain roughness estimator via Gabor + LBP (Issue #296) #305

Merged
sl-jetson merged 1 commits from sl-perception/issue-296-terrain-rough into main 2026-03-02 21:35:42 -05:00
Collaborator

Summary

  • Add _terrain_roughness.py: RoughnessResult NamedTuple; gabor_energy() with 4-orientation × 2-wavelength (5px, 10px) quadrature Gabor bank — DC removal via image mean subtraction prevents false energy on uniform surfaces; lbp_variance() using 8-point radius-1 LBP in vectorised NumPy slice comparisons (no sklearn dep); estimate_roughness() crops to bottom roi_frac of frame, blends normalised Gabor energy and LBP variance to a [0, 1] roughness score
  • Add terrain_rough_node.py: subscribes /camera/color/image_raw (BEST_EFFORT), publishes Float32 /saltybot/terrain_roughness at 2 Hz (configurable via publish_hz); roi_frac param default 0.40 (bottom 40% = floor region)
  • Register terrain_roughness console script in setup.py
  • 37/37 unit tests pass (pure Python, no ROS2 required)

Implementation note

Standard Gabor filters (ψ=0) are not zero-mean when truncated at a finite kernel size, so they produce non-zero responses on constant images. DC removal (img -= img.mean() before filtering) is the standard fix used in texture analysis literature, and is applied here.

Test plan

  • python3 -m pytest test/test_terrain_roughness.py -v → 37 passed
  • Launch on Jetson: ros2 run saltybot_bringup terrain_roughness
  • Verify /saltybot/terrain_roughness ≈ 0.0 on a smooth tile floor
  • Verify /saltybot/terrain_roughness > 0.5 on gravel/carpet
  • Confirm roi_frac limits analysis to bottom portion of frame

🤖 Generated with Claude Code

## Summary - Add `_terrain_roughness.py`: `RoughnessResult` NamedTuple; `gabor_energy()` with 4-orientation × 2-wavelength (5px, 10px) quadrature Gabor bank — DC removal via image mean subtraction prevents false energy on uniform surfaces; `lbp_variance()` using 8-point radius-1 LBP in vectorised NumPy slice comparisons (no sklearn dep); `estimate_roughness()` crops to bottom `roi_frac` of frame, blends normalised Gabor energy and LBP variance to a [0, 1] roughness score - Add `terrain_rough_node.py`: subscribes `/camera/color/image_raw` (BEST_EFFORT), publishes `Float32 /saltybot/terrain_roughness` at 2 Hz (configurable via `publish_hz`); `roi_frac` param default 0.40 (bottom 40% = floor region) - Register `terrain_roughness` console script in `setup.py` - 37/37 unit tests pass (pure Python, no ROS2 required) ## Implementation note Standard Gabor filters (ψ=0) are **not zero-mean** when truncated at a finite kernel size, so they produce non-zero responses on constant images. DC removal (`img -= img.mean()` before filtering) is the standard fix used in texture analysis literature, and is applied here. ## Test plan - [ ] `python3 -m pytest test/test_terrain_roughness.py -v` → 37 passed - [ ] Launch on Jetson: `ros2 run saltybot_bringup terrain_roughness` - [ ] Verify `/saltybot/terrain_roughness` ≈ 0.0 on a smooth tile floor - [ ] Verify `/saltybot/terrain_roughness` > 0.5 on gravel/carpet - [ ] Confirm `roi_frac` limits analysis to bottom portion of frame 🤖 Generated with [Claude Code](https://claude.com/claude-code)
sl-perception added 1 commit 2026-03-02 21:13:12 -05:00
- Add _terrain_roughness.py: RoughnessResult NamedTuple; gabor_energy() with
  4-orientation × 2-wavelength (5px, 10px) quadrature Gabor bank, DC removal
  via image mean subtraction (prevents false high energy on uniform surfaces);
  lbp_variance() using 8-point radius-1 LBP in vectorised numpy slice
  comparisons (no sklearn); estimate_roughness() with bottom roi_frac crop,
  normalised blend roughness = 0.5*(gabor/500) + 0.5*(lbp/5000) clipped [0,1]
- Add terrain_rough_node.py: subscribes /camera/color/image_raw (BEST_EFFORT),
  publishes Float32 /saltybot/terrain_roughness at 2Hz (configurable via
  publish_hz param); roi_frac param default 0.40 (bottom 40% = floor region)
- Register terrain_roughness console script in setup.py
- 37/37 unit tests pass (no ROS2 required)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
sl-jetson merged commit becd0bc717 into main 2026-03-02 21:35:42 -05:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: seb/saltylab-firmware#305
No description provided.