← Autonomy lab

02 / 06 · VFH+ with VFH* look-ahead · 2D lidar

Vector Field Histogram

Every tick: how far can the drone fly in each of 72 directions, which of those are open, and which opening leads somewhere a few metres on.

Unseen worlds, known map
56 / 56
Unseen worlds, 2D lidar
56 / 56
Closest to a pillar, offline
0.8 m
Planner tick, Gazebo
< 15 ms
Gazebo · x500 with 270° 2D lidar · the July version; the new one is not recorded yet

How one control tick works

Five questions, twenty times a second

01 · How far each way?

72 bins of 5°

Each bin holds the distance the drone could fly along it before touching an obstacle grown by its own radius plus a safety margin (the dotted circles), done exactly as a ray-disc test.

The margin grows with speed, using the larger of measured and commanded speed, because PX4 is still catching up with the command.

02 · Which ways are open?

Two thresholds

A bin closes when its distance drops below 1.6 m and reopens only beyond 2.5 m (the two rings), so a narrow gap does not flicker open and shut.

The July version summed obstacle density per bin, which meant one thing for a known cylinder and another for ten lidar returns on the same pillar. Distance per bin means the same whatever the sensor sends.

03 · Candidates

Where an opening points

The centre of each narrow opening; for a wide one, a direction 20° in from each side, plus the goal if it lies between them.

04 · Look before choosing

VFH*: a small A*

With more than one candidate, it steps 1.5 m along each, redoes the histogram from there, and carries on: up to 40 expansions, 6 steps deep. A path costs its length plus 0.3 m per 5° of turning.

The branch being flown is kept unless another is 0.5 m shorter. Without the look-ahead: 45 of 56 held-out worlds instead of 56.

05 · How fast?

Only as fast as it can stop

No faster than it can still stop short of what lies ahead, given PX4's reaction time. With the lidar, it also does not move toward a direction outside the sensor's 270° view: every lidar crash in the lagged sim was a drone backing into the blind spot.

openblockedchosenflown

Try it

Pick a goal, watch it choose

Click or tap to move the goal. Switch the look-ahead off to see plain VFH+ choose on the spot. A 2D re-implementation of vfh_core.py on the known map, same defaults, modelled PX4 lag.

The histogram, written out

Every tick, for each of 72 bins of 5°. Numbers are the defaults in vfh_core.py; for an obstacle of radius r, D is the distance to its centre and φ its offset from the bin's bearing.

free distance along bin k

dk = minobstacles D cos φ − √(R2 − D2 sin2 φ),  for |φ| ≤ arcsin(R / D)

with

R = r + 0.35 + 0.25 + 0.4 · max(|v|, |vcmd|)
enlargeprop tips, hover margin, and 0.4 m more per m/s
blockedk = dk < 1.6  or  (blocked last tick and dk ≤ 2.5)
hysteresisVFH+ Eq. 7, two thresholds in metres
opening ≤ 8 bins → its centre; wider → 4 bins in from each side, + the goal bin if between
candidatesVFH+ Eq. 12–13
f = path length + 0.3 m per 5° turned + |x − g|
look-aheadA* over 1.5 m steps, 40 expansions, 6 deep; switch only if 0.5 m shorter
5 Δ(c, kgoal) + 2 Δ(c, kv) + 2 Δ(c, kprev)
no look-aheadplain VFH+ cost (Eq. 15), Δ in bins
v = min(1.0, vstop(dk* − 0.6), |g − p|),  vstop(r) = −aT + √((aT)2 + 2ar)
speedT = 0.95 s, a = 1 m/s²; dk* measured with the bare radius, no speed margin

What differs from the papers

PaperHereWhy
Mapcertainty grid around the robotcylinder list, or lidar returns kept 10 sthe memory does the grid's job for the lidar
Bin valuesummed c²(a − b·d²)free distance along the binsame meaning whatever the sensor returns
Robot dynamicsmasked histogram from turning radiusspeed-dependent margin, turning cost, braking limita multirotor turns on the spot; it cannot change velocity quickly
Look-ahead costangular costs, discount λmetres of path, distance-to-goal estimateranks branches when the budget runs out
Out of budgetn/abest estimate per branch, 0.5 m switch hysteresisstable choice while the lidar memory changes
Goal binn/astays open if what blocks it is past the goala pillar just behind the goal used to close the way

Results

Offline, lagged point-drone sim. Tuned on forest seeds 0–39 plus six layouts, scored on seeds 100–149 plus the same layouts (56 worlds). Clean = reached without coming within 0.35 m of a pillar.
known mapworst clearance2D lidarworst clearance
July51/56 clean0.59 m41/56 clean, 2 crashes−0.18 m
now56/56 clean0.89 m56/56 clean0.80 m
now, no look-ahead45/560.62 m51/560.80 m
now, no speed margin56/560.37 m56/560.61 m
now, no lidar memory55/560.80 m
Gazebo, headless, measured against the simulator's true pose. Time in NAVIGATE, closest approach of the drone's centre to a pillar surface.
worldknown map, now2D lidar, now
course16 s, 1.40 m16 s, 0.78 m
gate16 s, 1.45 m16 s, 1.63 m
corridor18 s, 1.41 m17 s, 1.65 m
trap16 s, 1.40 m16 s, 0.72 m
cup18 s, 1.35 m18 s, 1.65 m
wall20 s, 1.38 m21 s, 1.54 m
forest929 s, 1.11 m26 s, 1.53 m
forest1024 s, 1.32 m24 s, 1.08 m
forest1425 s, 1.27 m23 s, 1.59 m
forest2225 s, 1.04 m16 s, 0.17 m (reflown twice: 1.00 m, 0.72 m)

All 20 flights of the second batch reached the goal. The lidar cannot see the 1.2 m pillars at all, so in lidar mode they are cleared by altitude alone; in one forest22 flight PX4's height estimate drifted 0.5 m and the drone passed 0.17 m over one.

What the drone sees

RViz from the July version: the direction fan around the drone, red for blocked, and the path flown.

RViz · direction fan around the drone · cyan: flown path

Python · numpy · ROS 2 Humble · PX4 v1.14 · Gazebo Garden · 270° 2D lidar