Lidar obstacle avoidance for outdoor followers
The UWB ranging tells the wagon where you are. The pursuit loop turns that into smooth motion. But neither one of them knows about the fire hydrant between you and the wagon. A follower that tracks you perfectly and drives straight into a curb is not a follower. It is a guided missile. The missing piece is a sensor that watches the path, and a layer of logic that decides when to ignore the pursuit loop and steer around something instead.
Why lidar, not cameras
An outdoor follower needs to detect obstacles at walking speed and react within one or two control cycles. That rules out anything that depends on good lighting. Cameras wash out in direct sun, go blind in fog, and need significant compute to turn pixels into distance. Ultrasonic sensors work in the dark but have a wide beam angle, poor angular resolution, and short range, maybe 3 meters. Active infrared depth sensors wash out in sunlight.
A 2D lidar solves most of this. It spins a laser in a horizontal plane, measuring time of flight at each angular step, and returns a full 360-degree scan of distances. No dependence on light. Accurate to about 3 cm. The scan we use fires 20,000 points per second with an angular resolution down to 0.18 degrees at 10 Hz. That means a fresh, dense picture of everything around the wagon ten times a second.
The limitation is in the name: it is 2D. The laser sweeps a single horizontal slice, typically 10 to 30 cm off the ground. Anything shorter than that (curbs, steps, low edges) is invisible. Anything taller than the beam (overhanging branches, signage) is also invisible. It sees the world as a flat ring, and that flat ring is enough for most paths but not all of them.
A 2D lidar gives you a flat snapshot of a three-dimensional world. It is enough to keep the wagon off most things. It is not enough to keep it off everything.
The two classic approaches: VFH and DWA
Once you have a scan, you need to decide where to go. There are two algorithms that show up in almost every mobile robot textbook, and each one has a different idea about how to make that decision.
VFH: pick the clearest gap
Vector Field Histogram works in polar coordinates. It chops the 360-degree scan into angular sectors, say 72 bins of 5 degrees each. For each sector it sums up how many obstacles are in it and how close they are. The result is a histogram: a bar chart where tall bars mean “blocked” and short bars mean “open.” The algorithm then picks the open sector closest to the target heading and steers toward it.
VFH is fast, simple, and works well in sparse environments. Its weakness is that it only looks at angular clearance. The standard version does not model the wagon’s own dynamics – its speed, acceleration, or turning radius. Later variants like VFH+ add robot-radius expansion and curvature masking, but the core idea is still angular. In a tight corridor basic VFH can pick a gap that the wagon physically cannot fit through at its current speed, or oscillate between two equally open sectors.
DWA: simulate before you commit
Dynamic Window Approach takes a different route. Instead of scanning sectors, it samples candidate velocities (linear and angular), simulates a short trajectory for each one, and scores how that trajectory would play out. The score combines three things: how close the simulated path comes to obstacles, how well it aligns with the target heading, and how fast it moves toward the goal.
DWA respects the wagon’s dynamics because it only samples velocities the motors can actually reach within one cycle. It handles narrow passages better than VFH because it “plays forward” the consequences of a turn before committing. The cost is compute: simulating dozens of trajectories every 50 ms is heavier than summing histogram bins.
On a wagon carrying 50 to 100 kg of payload, with a modest VCU, full DWA at 20 Hz is tight. We run a pruned version: fewer candidate velocities, shorter simulation horizon, and a pre-filtered scan that drops points beyond 5 meters. The result is close enough to full DWA that the difference is academic, but it fits the compute budget.
Neither one is enough alone
Here is the honest part. We tried VFH alone, and it worked fine on open paths with scattered obstacles. The wagon weaved through cones, parked bikes, and the occasional trash can. But in a dense environment, a market crowd, a narrow gate, a row of chairs, VFH would pick a gap and then discover the wagon was too wide for it at speed. It would stall, re-pick, stall again.
We tried DWA alone, and it handled dense scenes better. But on an open path it wasted cycles simulating trajectories that were all basically “go straight,” and it was slower to react to a sudden obstacle, a person stepping out, a dog darting across, because it had to simulate and score before it could swerve.
VFH is fast but blind to dynamics. DWA is thorough but slow. The answer is not to pick one. It is to use both, at different times.
What works is a hybrid. On open ground, where the histogram is mostly empty, run the fast VFH path. It costs almost nothing and the answer is obvious: go toward the person. When the histogram starts filling up, when obstacles get close, switch to the trajectory-level logic that simulates before it commits. The switch is triggered by a density threshold on the VFH histogram within the target direction window: if more than 30% of the sectors in that window are blocked, hand off to the heavier planner. We use hysteresis on the threshold – switch to DWA at 30% but only switch back to VFH when density drops below 20%, to prevent the two algorithms from toggling rapidly at the boundary.
# hybrid avoidance: fast path or careful path
density = blocked_sectors(target_dir, window=60deg) / sectors_in_window
if density < 0.3:
turn = vfh_heading(scan, target_bearing) # fast
v_dwa = v # pass through pursuit speed
else:
turn, v_dwa = dwa_search(scan, target_bearing, v) # careful
v_safe = min(v_dwa, max_safe_speed(scan, turn))
The last line caps forward speed based on how much room the chosen path has. Even if the pursuit loop says "go fast," the avoidance layer will not let the wagon outdrive its stopping distance. If the nearest obstacle in the chosen direction is 0.8 meters away, the wagon cannot be doing 1.5 m/s, because it cannot stop in time. This is also why the DWA simulation horizon does not need to be very long: the speed cap guarantees the wagon never enters a regime where it cannot stop within the planning window.
How it plugs into the pursuit loop
The avoidance layer sits between the pursuit loop and the motor driver. It receives the desired velocity and heading from the pursuit loop, and it has the authority to override either one:
# pursuit loop produces desired motion
v_desired, turn_desired = pursuit_loop(r, theta, target_distance)
# avoidance layer modifies or vetoes
v_safe, turn_safe = avoid(v_desired, turn_desired, lidar_scan)
drive_motors(v_safe, turn_safe)
If the path to the person is clear, the avoidance layer passes the pursuit output through unchanged. The wagon does not even know the layer is there. If something is in the way, the layer adjusts the heading to steer around it while keeping the speed as close to the pursuit target as the obstacle allows. If the path is completely blocked, it halts the wagon and holds position until either the obstacle moves or the person walks far enough that a new path opens.
The key design decision is that the avoidance layer never changes the target distance. It only modifies velocity and heading. The pursuit loop keeps running, keeps computing the ideal gap. The avoidance layer just decides how to get there safely. This separation means a following problem and an obstacle problem never get tangled. The one weakness of reactive avoidance is local minima: a U-shaped wall or a dead-end where every direction points back at an obstacle. In practice this is rare in following scenarios because the person keeps moving and provides a fresh target, but in a static environment the wagon could stall. The fix is a short local costmap that remembers where the wagon has already tried.
The outdoor problems you cannot find in a lab
A lab is flat, clean, and predictable. Outdoors is none of those things. Here is what actually broke:
Glass doors and windows
The 905 nm laser passes through glass. The scan shows nothing where a glass wall is. The wagon drives straight into it. The fix is not in the lidar. It is in not trusting a clear path at full speed near buildings, and adding a simple bumper contact sensor as a last resort.
Thin posts and chair legs
At 0.18 degree resolution, a 3 cm chair leg at 5 meters subtends about 1.9 angular bins. That is enough to register, but at the faster 20 Hz scan rate (0.36 degree bins) it drops to under one bin and can be filtered out as noise. The wagon does not see it until it is much closer, by which point it is too late to swerve smoothly. We lowered the discard threshold so that a single-bin return is tracked as a potential obstacle rather than thrown out as noise.
Curb edges and drops
The 2D scan sits at a fixed height. A curb that is 10 cm tall is below the beam. The wagon rolls off it. There is no clean answer to this with a 2D lidar alone. The practical mitigation is an IMU that detects the tilt when a wheel drops and triggers an immediate stop, but that is reactive, not preventive - by the time the IMU fires, a wheel is already off the edge. followwagon does not currently have a dedicated downward cliff sensor, which means drops and curbs remain a known limitation that the user needs to watch for. This is high on the list for the next hardware revision.
Moving obstacles
A dog, a child, another person crossing the path. The scan sees them, but the avoidance layer does not know they are moving. It treats a running dog the same as a parked bike. By the time the trajectory simulation runs, the dog has moved. We mitigate this by running the avoidance layer at the same 20 Hz as the pursuit loop and keeping the simulation horizon short, under 0.5 seconds. If the obstacle moves out of the way in that window, the next cycle catches it. A more robust approach would estimate obstacle velocity across frames and predict future positions, but that adds significant compute and is still on our to-do list.
Outdoor obstacle avoidance is not one algorithm. It is a layer that borrows from several, knows its own limits, and leans on other sensors when the lidar runs out.
What this means for the kit
Every followwagon carries a 360-degree lidar. It is the most expensive single component on the wagon. It is also the component that makes the difference between a wagon that follows you and a wagon that follows you safely. The ranging gets you to the person. The pursuit loop gets you there smoothly. The lidar gets you there in one piece.
Strip the lidar out and the wagon still follows. But it follows blind. On a clear path that is fine. On anything else, it is a liability.