feat(perception): appearance-based person re-identification (Issue #322) #330

Merged
sl-jetson merged 1 commits from sl-perception/issue-322-person-reid into main 2026-03-03 06:46:08 -05:00
Collaborator

Summary

Closes #322

  • New PersonTrack.msg / PersonTrackArray.msg in saltybot_scene_msgs — carries stable cross-camera track_id, camera_id, last-known bbox, cosine confidence, first_seen/last_seen timestamps, and is_stale flag
  • _person_reid.py — pure-Python appearance helper (no ROS2 deps):
    • extract_hsv_histogram() — 2-D HS histogram (H=16, S=8 → 128-dim), Value channel excluded for illumination robustness, L2-normalised
    • cosine_similarity() — handles zero/non-unit vectors safely
    • match_track() — best gallery match with strict > threshold; skips stale entries
    • TrackGalleryadd / update (EMA blend α=0.3, re-normalised) / mark_stale / prune_stale
  • person_reid_node.py — subscribes /camera/color/image_raw + /saltybot/scene/objects (BEST_EFFORT); crops COCO person (class_id=0) ROIs; matches gallery; publishes PersonTrackArray on /saltybot/person_tracks at 5 Hz

Cross-camera re-id

Run one person_reid instance per camera (remap /camera/color/image_raw), each sharing the same gallery namespace via the camera_id param. The HSV appearance feature is camera-agnostic — the same person gets the same track_id regardless of which view they appear in.

Parameters

Param Default Description
camera_id "front" Label written into each PersonTrack
similarity_threshold 0.75 Cosine similarity threshold for re-id match
stale_timeout_s 30.0 Seconds of inactivity before track marked stale
max_tracks 20 Maximum concurrent active tracks
publish_hz 5.0 Track list publish rate (Hz)

Test plan

  • test/test_person_reid.py — 50 tests, 0 failures (pure Python, no ROS2)
  • extract_hsv_histogram: shape, dtype, None/empty guard, unit norm, size-invariance, colour distinctiveness
  • cosine_similarity: identical, zero, anti-parallel, orthogonal, non-unit vectors
  • match_track: empty gallery, exact match, at-threshold boundary, below threshold, stale exclusion, best-of-many, all-stale
  • TrackGallery: add ID sequence, copy-on-store, timestamps, EMA blend, unit-norm preservation, stale lifecycle, prune
  • Integration: same appearance → same track, orthogonal → separate tracks, stale+prune+readd → new ID, 50-update EMA stays unit-norm

🤖 Generated with Claude Code

## Summary Closes #322 - New `PersonTrack.msg` / `PersonTrackArray.msg` in `saltybot_scene_msgs` — carries stable cross-camera `track_id`, `camera_id`, last-known `bbox`, cosine `confidence`, `first_seen`/`last_seen` timestamps, and `is_stale` flag - `_person_reid.py` — pure-Python appearance helper (no ROS2 deps): - `extract_hsv_histogram()` — 2-D HS histogram (H=16, S=8 → 128-dim), Value channel excluded for illumination robustness, L2-normalised - `cosine_similarity()` — handles zero/non-unit vectors safely - `match_track()` — best gallery match with strict `>` threshold; skips stale entries - `TrackGallery` — `add` / `update` (EMA blend α=0.3, re-normalised) / `mark_stale` / `prune_stale` - `person_reid_node.py` — subscribes `/camera/color/image_raw` + `/saltybot/scene/objects` (BEST_EFFORT); crops COCO person (class_id=0) ROIs; matches gallery; publishes `PersonTrackArray` on `/saltybot/person_tracks` at 5 Hz ## Cross-camera re-id Run one `person_reid` instance per camera (remap `/camera/color/image_raw`), each sharing the same gallery namespace via the `camera_id` param. The HSV appearance feature is camera-agnostic — the same person gets the same `track_id` regardless of which view they appear in. ## Parameters | Param | Default | Description | |---|---|---| | `camera_id` | `"front"` | Label written into each `PersonTrack` | | `similarity_threshold` | `0.75` | Cosine similarity threshold for re-id match | | `stale_timeout_s` | `30.0` | Seconds of inactivity before track marked stale | | `max_tracks` | `20` | Maximum concurrent active tracks | | `publish_hz` | `5.0` | Track list publish rate (Hz) | ## Test plan - [x] `test/test_person_reid.py` — 50 tests, 0 failures (pure Python, no ROS2) - [x] `extract_hsv_histogram`: shape, dtype, None/empty guard, unit norm, size-invariance, colour distinctiveness - [x] `cosine_similarity`: identical, zero, anti-parallel, orthogonal, non-unit vectors - [x] `match_track`: empty gallery, exact match, at-threshold boundary, below threshold, stale exclusion, best-of-many, all-stale - [x] `TrackGallery`: add ID sequence, copy-on-store, timestamps, EMA blend, unit-norm preservation, stale lifecycle, prune - [x] Integration: same appearance → same track, orthogonal → separate tracks, stale+prune+readd → new ID, 50-update EMA stays unit-norm 🤖 Generated with [Claude Code](https://claude.com/claude-code)
sl-perception added 1 commit 2026-03-03 00:55:35 -05:00
Adds PersonTrack/PersonTrackArray msgs and a PersonReidNode that matches
individuals across camera views using HSV colour histogram appearance
features and cosine similarity, with EMA gallery update and 30s stale timeout.

New messages (saltybot_scene_msgs):
  msg/PersonTrack.msg        — track_id, camera_id, bbox, confidence,
                               first_seen, last_seen, is_stale
  msg/PersonTrackArray.msg   — array wrapper with header

New files (saltybot_bringup):
  saltybot_bringup/_person_reid.py    — pure kinematics (no ROS2 deps)
    extract_hsv_histogram()  2-D HS histogram (H=16, S=8 → 128-dim, L2-norm)
    cosine_similarity()      handles zero/non-unit vectors
    match_track()            best gallery match above threshold (strict >)
    TrackGallery             add/update/match/mark_stale/prune_stale
    TrackEntry               mutable dataclass; EMA feature blend (α=0.3)
  saltybot_bringup/person_reid_node.py
    Subscribes /camera/color/image_raw + /saltybot/scene/objects (BEST_EFFORT)
    Crops COCO person (class_id=0) ROIs; extracts features; matches gallery
    Publishes PersonTrackArray on /saltybot/person_tracks at 5 Hz
    Parameters: camera_id, similarity_threshold=0.75, stale_timeout_s=30,
                max_tracks=20, publish_hz=5.0
  test/test_person_reid.py   — 50 tests, all passing

Modified:
  saltybot_scene_msgs/CMakeLists.txt  — register PersonTrack/Array msgs
  saltybot_bringup/setup.py           — add person_reid console_script

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
sl-perception force-pushed sl-perception/issue-322-person-reid from 071e577227 to 4dbb4c6f0d 2026-03-03 06:45:52 -05:00 Compare
sl-jetson merged commit 4fd7306d01 into main 2026-03-03 06:46:08 -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#330
No description provided.