Dynamic Vision Sensor Obstacle Avoidance: Looming Detection and Time-to-Collision on a Raspberry Pi 5
Course Project: Dynamic Vision Sensors
Authors: Paweł Jerzyna, Piotr Grzyb, Marcin Dworak
Method: Event-based looming detection with kinematic TTC estimation, implemented from scratch in C++17 with ARM NEON SIMD
This repository delivers a bio-inspired optical avoidance system designed for resource-constrained edge hardware (Raspberry Pi 5) coupled with a neuromorphic event-based camera (Prophesee GenX320). This code for now is working only with recorded videos, launching it in real-time doesn't work.
⚠️ Live camera capture on the Raspberry Pi 5 is not yet operational. The detection pipeline is validated offline. All benchmarks below were measured on real sensor recordings replayed on the target hardware.⚠️
Dense DNNs and Contrast Maximization saturate the CPU and desynchronization the event-stream on edge devices. Instead, this C++ pipeline mimics the looming detection and escape reflexes of flying insects. It extracts dynamic 2D bounding boxes via ARM NEON SIMD and evaluates a kinematic Time-to-Collision (TTC) surface expansion metric.
- ⚡ Mean Core Latency: ~0.13 ms per 10 ms slice (1.3% of the single-core real-time budget).
- 🚀 Real-Time Margin: 82× factor (26.6 s of events (7.14 M) processed in 350 ms CPU).
- 🛡️ Zero Packet Loss: 0% slice deadline overruns; sustained deterministic 100 Hz output cadence.
- 💻 CPU load: Consumes < 2% single-core CPU load, leaving headroom for PX4 / ArduPilot integration.
📄 Performance metrics and analysis of computational and parametric complexity: docs/performance.md
The pipeline processes asynchronous EVT3 event streams DetectionPipeline engine.
To eliminate uncorrelated background thermal noise and hot pixels, each incoming event
An event survives filtering if and only if:
Surviving events are mapped across three spatial sectors: Left (
Rather than recovering explicit 3D metric depth, the system measures the optical footprint expansion rate (
A safety-critical collision alert is triggered whenever:
All figures below come from a single benchmark run on the artifacts committed
in docs/output/ - recording wykryty_ruch.raw, 320 × 320,
7 143 802 events over 26.62 s, replayed on a Raspberry Pi 5.
| Pipeline stage | Mean | p99 | Max | Scaling |
|---|---|---|---|---|
| Spatiotemporal Filter (per SDK packet) | 10.8 µs | 30.1 µs | 4123 µs ⁽¹⁾ |
|
| Geometric Tracker (per 10 ms slice) | 13.9 µs | 89.3 µs | 1102 µs ⁽¹⁾ |
|
| TTC Estimator (per slice) | 0.76 µs | 16.7 µs | 51 µs |
|
| Tracker + TTC (per slice) | 14.7 µs | 92.9 µs | 1102 µs | — |
⁽¹⁾ The tail is environmental, not algorithmic. Filter latency is
uncorrelated with packet size (
| Value | |
|---|---|
| Total CPU time | 325 ms for 26.62 s of events |
| Mean cost per 10 ms slice | 0.122 ms (= 324 832 µs / 2663 slices, filter + tracker + TTC) |
| Real-time factor | 82× |
| Throughput | 22.0 Mev/s |
| Slice deadline overruns (> 10 ms) | 0.00% (0 / 2663) |
| Single-core utilization | 1.22% |
| Filter rejection rate | 27.6% (7.14 M → 5.17 M events) |
| Peak filter buffer / TTC history | 9830 events / 7 samples |
| Value | Filter rejection | Tracker mean | Slice p99 | Throughput | Detections | Danger slices |
|---|---|---|---|---|---|---|
| 1 (default) | 27.6% | 18.1 µs | 120.3 µs | 13.7 Mev/s | 2496 | 375 |
| 2 | 40.3% | 12.0 µs | 90.1 µs | 16.4 Mev/s | 2402 | 412 |
| 3 | 51.4% | 9.0 µs | 70.7 µs | 17.7 Mev/s | 2233 | 387 |
A more aggressive filter speeds up the tracker and tightens the latency tail at the cost of detections. Throughput here is lower than in a clean single run.
Ordered by priority. Items marked 🔴 block the project's core claim; 🟡 improve correctness or rigour; 🟢 extend capability.
The processing pipeline sustains an 82× real-time margin on recorded streams, but the live path has never been brought up on Raspberry Pi 5 — until it is, every claim in this repository is an offline one. Requires diagnosing Metavision live-camera initialization, then re-measuring end-to-end latency including sensor I/O and USB transfer, which the current benchmark deliberately excludes.
- Fix the TTC constant. Projected area scales as
$Z^{-2}$ , so$TTC = 2A/\dot{A}$ , not$A/\dot{A}$ — the current formula reports half the true time-to-collision. Rescaling$\tau_{\text{danger}}$ from 0.45 s to 0.90 s leaves behaviour bit-identical (verified: max TTC among expanding slices is 0.300 s, so both thresholds sit on the same plateau). - Make the TTC threshold meaningful. The relative-growth gate bounds TTC
at
$T_w(1+\rho_{\min})/\rho_{\min} = 0.300$ s, so$\tau_{\text{danger}}$ currently rejects nothing —expandingimpliesdangerin 375/375 cases. Either lower it to ≈ 0.15–0.20 s or drop it and document the growth gates as the actual decision rule. - Alert hysteresis. The
dangerflag averages 12 transitions per second, with a median episode of a single 10 ms slice (88 of 160 episodes last one slice). An N-of-M debounce or minimum alert duration is a prerequisite for any control-loop integration. - Labelled evaluation set. No ground truth exists, so precision, recall
and PR/ROC curves cannot be computed and no accuracy claim is currently
defensible.
tools/param_sweep.pyalready accepts a--labelsfile (start_us,end_us); it just needs the labels. - Validate beyond one recording. Thresholds are tuned on a single 26.6 s clip. Varying lighting, textures, approach angles and object sizes is needed before any generalization claim.
- Eliminate the
build_map()clear. Zeroing the full 320×320 map every SDK packet costs ≈ 2.7 billion operations per run and dominates filter cost — packets average only 270 events. A timestamp-stamped map removes the clear entirely; results are unchanged. - Replace
FrameSlicer::pop_ready'serase(begin()).O(R)per pop on astd::vectordegrades toO(R²)when catching up on a backlog. Astd::dequeor a read index fixes it. - Benchmark warm-up. Latency maxima are startup artifacts — discarding 500 packets drops the filter maximum from 4123 µs to 533 µs with no change to mean, median or p99.
- IMU fusion. Ego-motion during banking and pitch produces global optical expansion indistinguishable from an approaching obstacle. Continuous-time gyro/accelerometer integration would cancel it and remove a whole class of false positives — the largest correctness gap for airborne use.
- Multi-obstacle clustering. Replace the single bounding box per sector with connected-component tracking, so scenes with several independent hazards are handled rather than collapsed into one box.
- Closed-loop flight integration. Bridge the decision engine to PX4 / ArduPilot over MAVLink, translating TTC warnings into evasive thrust vectors. Depends on live capture, hysteresis, and IMU fusion.
Special thanks to the Embedded Vision Systems Group at the AGH University of Krakow for providing hardware access, testing facilities, and research guidance throughout this project.

