Optical flow is the cheapest way into GPS-denied flight, and the most limited. A small downward camera watches the ground slide past, a rangefinder measures height, and the autopilot turns that into velocity. No map, no absolute position, no companion computer. Good enough to hover indoors and move slowly. Not a mission system.
This is part of the GPS-denied navigation series: six solutions compared. See also the GPS-denied overview and the ArduPilot optical flow setup guide.
How the module works
A small downward-facing camera (typically a PixArt PMW3901-class sensor) compares successive frames and measures how fast ground texture moves across the image — like an optical mouse facing the floor. On its own that gives only an angular rate, so a downward rangefinder (laser time-of-flight or lidar, e.g. TFmini / TF-Luna) supplies height. Height × angular rate = ground velocity. A built-in gyro/IMU compensates for vehicle tilt. The autopilot’s EKF fuses that velocity with the IMU: the estimator outputs horizontal velocity and a dead-reckoned relative position, plus altitude from the rangefinder.
ArduPilot: set EK3_SRC1_VELXY = 5 (OpticalFlow) and EK3_SRC1_POSXY = 0 (ignore GPS); height from the rangefinder, e.g. EK3_SRC1_POSZ = 2. Flow sensors use FLOW_TYPE = 5 for MAVLink. ArduPilot deliberately caps climb at the rangefinder’s configured maximum when flow is the only horizontal source, so the EKF failsafe doesn’t trigger as the vehicle out-climbs the sensor.
PX4: EKF2 fuses optical flow and the rangefinder (EKF2_RNG_* parameters; EKF2_HGT_REF = 2 for rangefinder height). A separate downward rangefinder is mandatory — barometer is not allowed. Flow arrives via the OPTICAL_FLOW_RAD MAVLink topic, range via DISTANCE_SENSOR.
What it can and can’t do: Loiter / PosHold indoors works. True missions, RTL, and other automatic modes do not — they need a global position, which flow never provides. Position is relative to takeoff and drifts over time.
DIY space: modules and prices
| Module | What it is | Connection | Price (verified) |
|---|---|---|---|
| MicoAir MTF-01 | PMW3901 flow + ToF range, 8 m | UART, MAVLink, MSP | $32.99 (PyroDrone, in stock) |
| MicoAir MTF-01P | Same, 12 m laser range | UART, MAVLink, 100 Hz | $41.99 (PyroDrone) |
| MicoAir MTF-02P | 6 m range, 1.5 g, works outdoors | UART, MAVLink / MSP | $21.99 (PyroDrone) |
| ARK Flow | PAW3902 flow + ToF (8 m) + BMI088 IMU, USA-made, NDAA-compliant | DroneCAN | $320 |
| CubePilot HereFlow | PMW3901, 1.2 g, NDAA-compliant | DroneCAN | $85 |
| Holybro H-Flow | PAA3905 + ToF / IMU | DroneCAN | $45 |
| Matek 3901-L0X | PMW3901 + VL53L0X lidar, 2 m range | UART | $28 |
ArduPilot setup for the MTF-01: SERIALn_BAUD = 115, SERIALn_PROTOCOL = 1, FLOW_TYPE = 5, RNGFND1_TYPE = 10, RNGFND1_ORIENT = 25 (downward), then EK3_SRC1_VELXY = 5, EK3_SRC1_POSXY = 0. Gotcha: on ArduPilot 4.5.0+, an MTF-01 may not be recognized unless its mav_id is changed from 1 in MicoAssistant and SERIALn_OPTIONS = 1024 is set. Verify in Mission Planner that flow quality and range data are non-zero before trusting Loiter.
The rangefinder matters as much as the flow sensor. The TF-Luna is the common pairing: a small ToF lidar that gives the height the velocity math needs. The bracket that holds it level and clear of the landing gear is a printed part — see Mounting the Sensors for GPS-Denied Flight.

Commercial and industrial use
DJI’s Vision Positioning System is the mass-market precedent: downward cameras + ultrasonic sensors (Phantom 3/4, Inspire 2) give indoor hover of ±0.1 m vertical / ±0.3 m horizontal, at 0.3–3 m (Phantom 3) to 10 m (Inspire 2) altitude, requiring a clearly patterned surface and adequate light. Same physics as DIY flow, productized.
In industry, flow is the recommended aid for warehouse hovering and low-altitude inspection where GPS is absent: a hover and transition aid, not a mapping system. On the government side, ARK Flow is the NDAA-compliant benchmark — and the price premium for compliance is steep, roughly 8–22× the Chinese benchmark modules.
Strengths and limits
Strengths: cheapest GPS-denied aid ($22–$42 for a MicoAir 2-in-1); grams of weight; computation happens on the sensor (up to 100 Hz), so no companion computer; native EKF support in PX4 and ArduPilot; good enough for stable hover and slow indoor translation.
Limits: needs visible ground texture and light — blank concrete, still water, fresh snow, and darkness all fail it; strictly low altitude (rangefinder-limited, and ArduPilot caps climb at rangefinder max); no absolute position, no map, no repeatable waypoints; position is dead-reckoned, so error grows with distance and time.
Failure modes: vibration (an optical flow sensor cannot rescue a vibrating airframe); rangefinder dropout leaves the EKF with no height reference and Z drifts on pure IMU — a documented PX4 forum case had TFmini dropouts make a drone shoot upward; sunlight saturating the sensor; out-climbing the rangefinder. ArduPilot’s docs warn that bad flow estimates give little warning before the copter leans to maximum.
How it compares
- Visual-inertial odometry gives full 3D pose and local-frame missions, but needs a companion computer, calibration, and $1,400+ of hardware — flow is the $30 hover aid, VIO is the autonomy stack.
- Lidar SLAM builds a map and works in the dark, but costs more, weighs more, and needs real compute. Flow is lighter and cheaper; lidar is more capable.
- UWB positioning gives absolute, drift-free position, but needs surveyed anchors installed first. Flow needs nothing installed — it works anywhere with texture and light.
- Dead reckoning alone is what flow is fused with: flow is the velocity measurement that keeps dead reckoning honest for minutes instead of seconds.
- Terrain matching gives an absolute fix that doesn’t drift, but needs preloaded maps. Flow needs no maps at all.
