Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Vector Field Histogram obstacle avoidance (PX4 + ROS 2 + Gazebo)

Reactive obstacle avoidance for a PX4 multirotor using the classic Vector Field Histogram (Borenstein & Koren, 1991). The drone builds a polar histogram of obstacle density around itself every control tick, picks the most open direction that still heads toward the goal, and flies it — no global path, no map, purely reactive.

Learning project. WIP testing in a complex sim world.sdf

Demo

VFH

The demo has been accelerated

rviz

Rviz version

installation

You can check out HOLO-DWA (same environment, DWA algorithm, a detailed installation of env).

How VFH works here

obstacles (fixed cylinders | live LiDAR pillars)
      │
      ▼
1. polar histogram   split 360° into sectors (5°→72 bins); each obstacle adds
                     density  max(0, a − b·d_surface)  over the bins it
                     subtends (enlarged by the robot radius). Nearer/wider =
                     denser and over more bins.
2. smoothing         circular moving average → kills single-bin jitter
3. valleys           runs of bins below `threshold` = candidate openings
4. steering          the opening nearest the goal bearing; goal-open → straight
                     at goal, else wide opening steers s_max/2 in from the near
                     edge, narrow opening steers to its centre
5. velocity setpoint heading = steer bearing, speed ∝ how open ahead, z holds
                     cruise altitude

It is 2D: the histogram is a horizontal slice at cruise altitude. An obstacle counts only if it reaches the slice, so the low (1.2 m) cylinders are correctly seen straight through at 2 m — honest 2D, not faked 3D fly-over. (3D is a documented future extension, see WORKLOG.)

Layout

VFH/
├── run.sh                     one-shot tmux launcher (PX4+gz+agent+bridge+nodes)
├── WORKLOG.md                 decisions, findings, tuning
├── logs/                      flight CSVs
└── VFH_OA/                    ament_python package (vfh_oa)
    ├── vfh_oa/
    │   ├── vfh_core.py            the algorithm — pure numpy, no ROS
    │   ├── world_spec.py          single source of truth for the worlds
    │   ├── obstacle_sensor_node.py  fixed cylinders | live LiDAR pillars
    │   └── vfh_planner_node.py    reactive planner + PX4 offboard flight
    ├── config/vfh_params.yaml
    ├── launch/vfh_nodes.launch.py
    ├── worlds/                    generated SDF (gen_world.py)
    ├── rviz/vfh.rviz
    ├── test/test_vfh_core.py      23 unit tests
    └── tools/
        ├── viz_vfh.py             matplotlib: scene + polar histogram
        ├── sim_offline.py         no-ROS reactive-loop sim (tuning)
        ├── gen_world.py           world_spec → SDF
        └── check_log.py           flight CSV summary

vfh_core.py has no ROS dependency — it is unit-tested and driven offline before ever touching Gazebo, because the histogram and valley search are the parts of VFH easiest to get wrong.

Quick start

# 1. build
cd ~/ws && colcon build --packages-select vfh_oa --symlink-install
source install/setup.bash

# 2. verify the algorithm with no ROS at all
cd src/VFH/VFH_OA
python3 -m pytest test/                       # 23 tests
python3 tools/viz_vfh.py --world vfh_forest   # scene + polar histogram PNGs
python3 tools/sim_offline.py --sweep          # every world × sensing mode
python3 tools/gen_world.py                    # (re)generate the SDF worlds

# 3. fly it (headless or with a GUI)
cd ~/ws/src/VFH
./run.sh                        # vfh_test, fixed obstacles, default goal
WORLD=vfh_forest ./run.sh       # the wall course
OBST_SOURCE=lidar ./run.sh      # drive VFH off the real 2D LiDAR scan
./run.sh 8 -3                   # custom goal (Gazebo ENU x y)
./run.sh kill                   # tear down

# 4. summarize the flight
python3 VFH_OA/tools/check_log.py --world vfh_test

run.sh env overrides: WORLD (vfh_test|vfh_forest), OBST_SOURCE (fixed|lidar), V_MAX, HEADLESS=1, RVIZ, PX4_DIR.

Interfaces

topic dir type note
/fmu/out/vehicle_odometry in px4_msgs/VehicleOdometry position, velocity, attitude (NED)
/lidar in sensor_msgs/LaserScan 2D LiDAR (lidar mode), bridged from gz
/vfh/obstacles internal std_msgs/Float32MultiArray rows (n,e,radius,top) NED
/fmu/in/trajectory_setpoint out px4_msgs/TrajectorySetpoint velocity setpoint, NaN passthrough
/vfh/planner_markers out visualization_msgs/MarkerArray polar histogram spokes + steer/goal arrows
/vfh/flown_path, /vfh/obstacle_markers out Path / MarkerArray RViz

Internal frame is PX4 NED; goals are given in Gazebo world ENU and converted on the way in. Bearing convention θ = atan2(east, north) (0 = North, +π/2 = East).

Key parameters (config/vfh_params.yaml)

param default meaning
sector_angle 5.0° histogram resolution (72 bins)
density_a, density_b 7.0, 1.0 density = a − b·d_surface; blocks out to 5 m
threshold 2.0 bins below this are free (candidate valleys)
smooth_window 5 moving-average window
robot_radius 0.5 m histogram enlargement (prop + safety)
wide_valley 9 bins wide/narrow opening cutoff (s_max)
v_max 1.0 m/s cruise speed
goal_threshold 0.5 m goal acceptance

Tuned offline in sim_offline.py, not in Gazebo — see WORKLOG.

The VFH vs APF local-minimum case

vfh_forest puts a wall of five cylinders straight across the direct line to the goal. A potential-field planner (APF_OA) stalls at the wall's centre where attraction and repulsion cancel. VFH sees the wall blocking ahead and a free opening past its finite end, and steers there — it rounds the wall instead of wedging. This is the goal.md Step 5(a) comparison, reproducible offline with python3 tools/sim_offline.py --world vfh_forest.

Results

Headless PX4 SITL + Gazebo flights (./run.sh, all four combinations):

world mode result length min clearance stuck
vfh_test fixed REACHED 12.9 m 0.97 m 0
vfh_test lidar REACHED 15.0 m 1.36 m 0
vfh_forest fixed REACHED 17.4 m 1.11 m 0
vfh_forest lidar REACHED 20.4 m 1.40 m 0

Every course reached with zero stuck episodes; the forest wall is rounded in both fixed and lidar mode. Real-flight path lengths match the offline sim and clearances are a touch better (PX4 smooths the velocity setpoints).

Offline sim (tools/sim_offline.py --sweep, perfect velocity tracking):

world        mode   result   time   len    clear
vfh_test     fixed  REACHED  13.4s  12.7m  0.79m
vfh_test     lidar  REACHED  13.4s  12.7m  0.79m
vfh_forest   fixed  REACHED  17.4s  17.2m  0.86m
vfh_forest   lidar  REACHED  17.4s  17.2m  0.86m

About

Reactive obstacle avoidance for a PX4 multirotor using the classic Vector Field Histogram (Borenstein & Koren, 1991). The drone builds a polar histogram of obstacle density around itself every control tick, picks the most open direction that still heads toward the goal, and flies it

Topics

Resources

Stars

Watchers

Forks

Contributors

Languages