# How Multi-Person Tracking Works with Hungarian-Lite Assignment in RuView

> Discover how RuView's Hungarian-lite algorithm achieves real-time multi-person tracking on ESP-32-S3 devices with sub-5 ms latency by matching Wi-Fi CSI variance.

- Repository: [rUv/RuView](https://github.com/ruvnet/RuView)
- Tags: internals
- Published: 2026-03-08

---

**RuView tracks up to four people in real time using a greedy "Hungarian-lite" algorithm that matches Wi-Fi CSI variance signatures to persistent slots, delivering sub-5 ms latency on ESP-32-S3 edge devices.**

RuView's Wi-Fi-based sensing pipeline relies on multi-person tracking with Hungarian-lite assignment to maintain identity stability across frames without the computational overhead of classical optimization algorithms. Implemented in the [`sig_mincut_person_match.rs`](https://github.com/ruvnet/RuView/blob/main/sig_mincut_person_match.rs) module, this approach uses greedy minimum-cost matching on compact 8-dimensional feature vectors, enabling deterministic tracking on memory-constrained WASM-edge environments.

## Understanding the Hungarian-Lite Assignment Strategy

The classical **Hungarian algorithm** solves the assignment problem in O(N³) time, guaranteeing globally optimal matching between detections and tracks. **Hungarian-lite** replaces this with a greedy O(N²) approach that repeatedly selects the lowest-cost available pair without backtracking.

In [`sig_mincut_person_match.rs`](https://github.com/ruvnet/RuView/blob/main/sig_mincut_person_match.rs), the `greedy_assign` function implements this by iterating through the cost matrix to find the minimum Euclidean distance between a detected person and an active slot, marking both as assigned, and continuing until all detections are matched or distances exceed `MAX_MATCH_DISTANCE`. While potentially suboptimal for large N, this method is deterministic, requires no dynamic memory allocation, and executes in under 5 ms on ESP-32-S3 microcontrollers—critical for RuView's real-time requirements where `MAX_PERSONS` is capped at 4.

## Core Components of the PersonMatcher

### Feature Extraction and Signature Vectors

Each detected person is represented by a compact **feature vector** consisting of the top-8 variance values from their CSI sub-carriers. In [`sig_mincut_person_match.rs`](https://github.com/ruvnet/RuView/blob/main/sig_mincut_person_match.rs), the constant `FEAT_DIM` is set to 8:

```rust
const FEAT_DIM: usize = 8;

```

These variance patterns serve as unique fingerprints for each individual's movement and position relative to the Wi-Fi antennas, enabling the tracker to distinguish between multiple people even when they cross paths or move erratically.

### Cost Matrix Construction

The algorithm builds a **cost matrix** using Euclidean (L₂) distances between newly extracted feature vectors and the stored signatures of active slots. The implementation calculates this via `l2_distance(&current[d], &self.slots[s].signature)` for each detection-slot pair.

This distance metric quantifies the similarity between a current observation and a tracked identity, where values below `MAX_MATCH_DISTANCE` (5.0) indicate valid matches and higher values trigger the creation of new tracks or slot recycling.

### Slot Management and EMA Updates

RuView maintains exactly four **person slots** (`MAX_PERSONS = 4`), each storing a signature, frame counter, and stability metrics. When a match occurs, the slot's signature updates using an **exponential moving average (EMA)** with `SIG_ALPHA = 0.15`:

```rust
slot.signature[f] = SIG_ALPHA * current_features[p][f] + (1.0 - SIG_ALPHA) * slot.signature[f];

```

This smoothing prevents sudden jumps caused by temporary occlusion or noise. Slots time out after `ABSENT_TIMEOUT = 100` consecutive empty frames, releasing resources for new detections.

## Implementation in sig_mincut_person_match.rs

The tracking logic resides in [`rust-port/wifi-densepose-rs/crates/wifi-densepose-wasm-edge/src/sig_mincut_person_match.rs`](https://github.com/ruvnet/RuView/blob/main/rust-port/wifi-densepose-rs/crates/wifi-densepose-wasm-edge/src/sig_mincut_person_match.rs). Key operational constants include:

- `const MAX_PERSONS: usize = 4;` – Hard limit on concurrent tracks
- `const FEAT_DIM: usize = 8;` – Feature vector dimensionality  
- `const MAX_MATCH_DISTANCE: f32 = 5.0;` – Rejection threshold for spurious matches
- `const ABSENT_TIMEOUT: u16 = 100;` – Frames before slot release
- `const SIG_ALPHA: f32 = 0.15;` – EMA smoothing factor

The `PersonMatcher::process_frame` method drives the pipeline, emitting structured events including `EVENT_PERSON_ID_ASSIGNED`, `EVENT_PERSON_ID_SWAP`, and `EVENT_MATCH_CONFIDENCE` that downstream applications consume for real-time analytics.

## Practical Code Examples

### Basic Frame Processing

Initialize the matcher and process CSI frames to obtain person IDs:

```rust
use wifi_densepose_wasm_edge::sig_mincut_person_match::PersonMatcher;

// Create the matcher with 4-person capacity
let mut matcher = PersonMatcher::new();

// Simulated CSI data (32 subcarriers)
let amplitudes = [1.0_f32; 32];
let variances = [0.2_f32; 32];
let n_persons = 2; // Two detections this frame

// Process frame and retrieve events
let events = matcher.process_frame(&amplitudes, &variances, n_persons);

for (event_type, value) in events {
    match event_type {
        EVENT_PERSON_ID_ASSIGNED => println!("New ID: {:.2}", value),
        EVENT_PERSON_ID_SWAP => println!("Swap detected: {:.2}", value),
        EVENT_MATCH_CONFIDENCE => println!("Confidence: {:.2}", value),
        _ => {}
    }
}

```

### Accessing Stable Signatures

Check if a tracked person has stabilized and retrieve their current signature:

```rust
let slot_idx = 0;
if matcher.is_person_stable(slot_idx) {
    if let Some(sig) = matcher.person_signature(slot_idx) {
        println!("Stable signature for slot {}: {:?}", slot_idx, sig);
        // Returns array of 8 f32 values (FEAT_DIM)
    }
}

```

### Integration with Full Pose Tracking

For applications requiring 17-keypoint pose estimation, combine the CSI-based matcher with the Kalman-based `PoseTracker`:

```rust
// Obtain IDs from CSI matching first
let csi_ids = matcher.active_persons();

// PoseTracker uses full Hungarian algorithm for keypoint associations
let mut pose_tracker = PoseTracker::new();
let detections: Vec<PoseDetection> = // ... from DensePose model

// Process with full tracking
pose_tracker.predict_all(&detections, &csi_ids);

```

The `PoseTracker` in [`pose_tracker.rs`](https://github.com/ruvnet/RuView/blob/main/pose_tracker.rs) implements the classical O(N³) Hungarian algorithm for up to 10 persons, suitable for higher-level pose data where computational constraints are less severe than the ESP-32-S3 target environment.

## Performance Characteristics and Hardware Constraints

The Hungarian-lite design specifically targets **WASM-edge environments** and microcontrollers like the ESP-32-S3. By avoiding the O(N³) complexity of the full Hungarian algorithm and limiting `MAX_PERSONS` to 4, the implementation achieves:

- **Deterministic latency**: Consistently under 5 ms per frame on ESP-32-S3
- **Memory efficiency**: Fixed-size arrays eliminate heap allocations during tracking
- **Power efficiency**: Reduced CPU cycles extend battery life in portable deployments

When tracking requirements exceed four people or involve complex pose keypoints, the system falls back to the full `PoseTracker` implementation in [`pose_tracker.rs`](https://github.com/ruvnet/RuView/blob/main/pose_tracker.rs), which trades computational overhead for assignment optimality.

## Summary

- RuView implements **multi-person tracking with Hungarian-lite assignment** in [`sig_mincut_person_match.rs`](https://github.com/ruvnet/RuView/blob/main/sig_mincut_person_match.rs) to enable real-time Wi-Fi sensing on constrained edge devices.
- The algorithm uses **greedy minimum-cost matching** on a 4×N cost matrix of Euclidean distances between 8-dimensional CSI variance signatures, avoiding the O(N³) complexity of classical methods.
- **Exponential moving averages** with α=0.15 smooth signature updates, while hard thresholds (`MAX_MATCH_DISTANCE = 5.0`) and timeout counters (`ABSENT_TIMEOUT = 100`) filter spurious matches and release stale tracks.
- The implementation emits structured events (`EVENT_PERSON_ID_ASSIGNED`, `EVENT_PERSON_ID_SWAP`, `EVENT_MATCH_CONFIDENCE`) for downstream integration and achieves sub-5 ms latency on ESP-32-S3 hardware.

## Frequently Asked Questions

### What is the difference between Hungarian-lite and the full Hungarian algorithm?

The **full Hungarian algorithm** solves the assignment problem in O(N³) time, guaranteeing globally optimal matching between detections and tracks. **Hungarian-lite** uses a greedy O(N²) approach that repeatedly selects the lowest-cost available pair without backtracking. While potentially suboptimal for large N, this method is deterministic, requires no dynamic memory allocation, and executes in under 5 ms on ESP-32-S3 microcontrollers—critical for RuView's real-time requirements where `MAX_PERSONS` is capped at 4.

### Why does RuView limit tracking to four people?

RuView constrains simultaneous tracking to **four people** (`MAX_PERSONS = 4`) to maintain real-time performance on ESP-32-S3 and other edge devices. This limit ensures the greedy assignment algorithm operates on a small, fixed-size cost matrix (maximum 4×4), eliminating dynamic memory allocation and keeping computational latency below 5 ms per frame. For scenarios requiring more than four people, RuView provides the separate `PoseTracker` in [`pose_tracker.rs`](https://github.com/ruvnet/RuView/blob/main/pose_tracker.rs), which supports up to 10 persons using the full Hungarian algorithm but requires significantly more computational resources.

### How does the exponential moving average prevent ID switches?

The **exponential moving average (EMA)** with `SIG_ALPHA = 0.15` smooths the signature updates for each tracked slot, preventing sudden jumps caused by temporary occlusion, noise, or brief signature similarities between people. By blending only 15% of the new observation with 85% of the historical signature, the tracker maintains stable identity representations even when individuals cross paths or move erratically. This temporal consistency, combined with the `MAX_MATCH_DISTANCE` threshold of 5.0, significantly reduces the likelihood of identity swaps compared to frame-to-frame nearest-neighbor matching without smoothing.

### Can I use the full pose tracker instead of the CSI-based matcher?

Yes, RuView provides the **`PoseTracker`** in [`pose_tracker.rs`](https://github.com/ruvnet/RuView/blob/main/pose_tracker.rs) as an alternative for applications requiring full 17-keypoint pose estimation or tracking more than four people. While the CSI-based `PersonMatcher` uses Hungarian-lite assignment on variance signatures for edge efficiency, `PoseTracker` implements the classical O(N³) Hungarian algorithm on Kalman-filtered keypoint states, supporting up to 10 persons. You can integrate both trackers by first obtaining person IDs from the CSI matcher, then passing them to `PoseTracker` for pose trajectory maintenance, though this requires significantly more computational resources and is typically deployed on less constrained hardware than the ESP-32-S3 target environment.