Project 03 · Image processing

Finding game trails from the air, without AI

Game trails are thin, winding lines of packed dirt. From 80 metres up, a drone can see them. Here's how a handful of classic image filters turn an off-season map into trail lines and funnels you can load into your mapping app, and how to tell whether it actually works.

Four panels: a synthetic drone image of a meadow; the thin-line filter output where winding trails, a fence and an ATV track all glow; the skeleton with straight and short pieces shown in pink as dropped; and the final trails in orange with meeting points circled
Figure 1. The test image through the four main stages: the drone image, the thin-line filter, the cleaned-up skeleton (pink pieces are thrown away), and the final trails with their meeting points circled.

Read the law first

Before anything technical: many provinces and states restrict using aircraft, drones included, to locate, track or hunt game. The restrictions are often tied to a window before and during the season, and some places ban it outright. Aviation rules add their own limits on where, how high and how far you can fly.

What this tool is for. Off-season habitat mapping on land you have permission to fly, under your aviation authority's rules. Check your hunting regulations before you fly, and again before you hunt.

What a trail looks like from above

A drone flying at 60 to 120 metres with an ordinary camera captures a few centimetres of ground per pixel. Free software like WebODM stitches hundreds of overlapping photos into one big, map-accurate image called an orthomosaic.1 At that resolution, a well-used deer trail 20 to 60 centimetres wide is a line three to ten pixels across.

Look at enough of these images and three things give trails away:

None of those needs machine learning to measure. Each one maps onto a well-understood image-processing step, and the pipeline is just those steps in order.

A trail is less green, long and thin, and it winds. Each property becomes one filter.

Step 1: less green

Farm and forestry imaging often uses near-infrared cameras to measure plant health. Trail Finder sticks to ordinary RGB, because that's what most drones carry. The standard RGB-only measure is Excess Green, worked out for every pixel:2

ExG = 2·G − R − B high on plants, low on dirt and dry grass

A raw ExG map has a problem: it also changes slowly across the image, from a drier hillside, a patch of different grass or the shadow of a cloud. Those slow changes would swamp the narrow dip a trail makes. So the tool subtracts a heavily blurred copy of the map from itself, which leaves only local dips in green: places noticeably less green than their surroundings.

trail signal = blur(ExG, wide) − ExG

Step 2: long and thin

The trail signal is still full of blobs: bare patches, bushes' shadows, the odd rock. What makes a trail different is its shape, and the tool for finding shapes like that is a ridge filter.

Picture the trail signal as a landscape, with trails as long, low ridges. Along a ridge the height barely changes. Across it, the height drops off sharply on both sides. Mathematically, that's about the image's second derivatives, captured in a 2×2 matrix at every pixel called the Hessian. Its two eigenvalues measure the curvature in the sharpest and gentlest directions. A line has one large curvature (across) and one near zero (along). A blob is curved in every direction, and flat ground in none.

The Sato filter turns that into a single "how line-like is this pixel" score.3 It was designed to find blood vessels in medical scans, which have exactly the same shape problem. Trail Finder runs it at three widths, about 0.3, 0.6 and 1 metre, and keeps the strongest response. That way both narrow deer trails and wider, well-worn ones are picked up.

Step 3: from pixels to lines

A threshold turns the filter output into "trail or not" for every pixel. A single threshold is a bad compromise: set it high and faint stretches of real trail break up; set it low and random speckle gets in. So the tool uses two (hysteresis). Pixels above the high threshold start a trail. Pixels above only the low threshold are kept if they connect to one that's above the high threshold. A faint stretch of a strong trail survives; an isolated faint speck doesn't.

Next, skeletonization thins every detected strip to a line one pixel wide along its centre. That turns a blotchy mask into a network of paths that can be measured, simplified and exported as map lines. Two clean-ups follow: twigs shorter than 2 metres are pruned off the network, and whole pieces shorter than 15 metres are dropped, because real trails go somewhere.

Step 4: it winds

This is the step that makes the difference. A fence line or a two-rut ATV track can look exactly like a trail in colour: a narrow strip of different vegetation. In the test images they're made that way on purpose, so straightness is the only clue.

The tool runs a probabilistic Hough transform on the skeleton, a classic method that finds straight line segments by letting every pixel vote for the lines it could lie on.4 Any straight run longer than 20 metres that never strays more than 15 centimetres from its line is treated as man-made. Then every piece along that line, across the whole image, is removed.

That last rule came from a failure. An earlier version judged each piece of skeleton on its own: is this branch straight? But wherever a trail crosses a fence, the skeleton splits the fence into short pieces at the crossing. Each short piece was too short to look convincingly straight, and fence fragments leaked through. Removing the entire line fixed it.

The cost is a known blind spot: a game trail that runs dead straight for 20 metres, along a fence for instance, gets thrown out with the fence. The 20-metre length is a setting you can raise if that's common where you hunt.

Step 5: funnels and confidence

Where three or more trails come together within about 6 metres, the tool marks a meeting point. On the ground, these are the funnels, creek crossings, saddles and fence gaps that concentrate movement. They're often the best stand sites on the whole map, and the hardest to spot by eye in a big image.

Each trail line then gets a confidence score from three things: how strongly the ridge filter responded along it, how long its connected network is, and how many meeting points it runs into. The GeoJSON output lists lines in confidence order, with the meeting points as separate features. That's a ranked list of places to walk, not a guarantee, and the score is a ranking rather than a probability.

From trails to stand sites

A trail map is a list of places deer walk. The next question is where to sit, and the answer changes through the day with the wind. A stand that's perfect while the wind comes from the northwest can blow your scent straight down the funnel once the afternoon thermals turn it around. standsites.py takes the trails and meeting points and a set of wind windows, and suggests stands for each window.

Four map panels of a 400 by 300 metre block with a creek-bottom trail, two bedding trails meeting it at a funnel and a trail from the south; each panel shows the chosen stands as numbered white squares with blue scent wedges for that time window's wind; the 09:00 to 10:30 switching window has no stands
Figure 2. Stand sites on a made-up block, one panel per wind window. Each stand's scent (blue) misses every trail and the funnel. While the wind could be either northwest or southeast, nowhere is clean.
  1. Candidates. Every point 10 to 30 metres from a trail: close enough to shoot, not so close you're on top of it. Each is scored by how much trail lies within range, with a bonus for each meeting point.
  2. Scent cone. For each window, the wind carries scent downwind in a cone 25 degrees either side, plus 10 for swirl, out to 150 metres. A candidate is rejected if more than 2 metres of trail, or any meeting point, falls inside it.
  3. Pick. The best few safe stands per window, at least 30 metres apart, plus any that stay clean all day.

The wind windows can be typed in, or read from a SlopeShift forecast. SlopeShift learns how long after first direct sun the thermals switch at a particular stand, then predicts the switch for coming days, with an earliest-to-latest band. That band becomes an "either" window: a stand only counts as safe if it's clean for both directions. In practice that often means there's no safe stand during the switch, which is useful to know before you walk in.

A window with no safe stand is an answer too: don't be walking in during the switch.

What already exists. Several hunting apps draw a scent cone from the forecast wind for a stand you place yourself, including HuntStand, ScoutLook and HuntWise.5 This works the other way round: it proposes the stands, from trails found in your own imagery, and can use wind measured at the stand instead of a regional forecast. We didn't find a tool that does that. The scent model is deliberately simple, a straight cone, and real scent eddies around cover and terrain, so treat the suggestions as places to look on the ground.

Measuring it honestly

To know whether a trail finder works, you need images where the true trails are known exactly. Hand-labelling real drone maps is slow and was out of reach for a first version, so the project renders synthetic ones. make_sample.py draws an aerial meadow with textured grass, brush, dry patches and tree crowns with shadows. It adds five faint winding trails, three of which meet at a funnel, plus a fence and an ATV track. It also writes the true trail lines to a file.

A detected point within 1 metre of a true trail's centreline counts as correct. Two numbers summarize each scene:

Bar chart of precision and recall for the committed sample and twelve synthetic test scenes; precision ranges from about 0.84 to 1.0 and recall from about 0.83 to 0.97, all above the build-failure thresholds
Figure 3. Precision and recall on the committed sample and 12 scenes generated from random seeds the settings were never tuned on. The build fails if the averages drop below the dashed lines.
ScenesPrecisionRecallFence/ATV keptFunnel found
Committed sample0.990.960%0.2 m off
12 fresh scenes, mean0.950.917% worstall within 3.5 m

Those numbers are good, and they flatter the tool, for a simple reason: the same person wrote the scene generator and the detector. Whatever the generator gets wrong about real fields, the detector was never asked to handle. Real imagery has tall grass hiding trails, cattle paths, wind-flattened crops, deer beds and stitching seams. Expect lower numbers on a real flight until the settings are tuned on real data.

The synthetic numbers are a floor for the code's correctness, not a forecast for your field.

Flying for trails

Limits, and when to bring in AI

A neural network would probably beat these filters on real imagery eventually. It would also need a few hundred hand-labelled trail segments from real flights before it could learn anything. The plan is to collect those first, then train a small segmentation network (a U-Net with a lightweight MobileNet encoder) that runs on a laptop CPU. It would be scored with the same evaluation, so it has to beat the classic pipeline on equal terms before it replaces it.

Notes

  1. WebODM and the underlying OpenDroneMap are free, open-source photogrammetry tools that turn overlapping drone photos into orthomosaics and elevation models. ↩
  2. Excess Green was introduced for separating plants from soil in colour images: D. M. Woebbecke et al., "Color indices for weed identification under various soil, residue, and lighting conditions," Transactions of the ASAE 38(1), 1995. ↩
  3. Y. Sato et al., "Three-dimensional multi-scale line filter for segmentation and visualization of curvilinear structures in medical images," Medical Image Analysis 2(2), 1998. Trail Finder uses the scikit-image implementation. ↩
  4. The probabilistic Hough transform: J. Matas, C. Galambos and J. Kittler, "Robust detection of lines using the progressive probabilistic Hough transform," Computer Vision and Image Understanding 78(1), 2000. Implemented in scikit-image as probabilistic_hough_line. ↩
  5. For example HuntStand's scent cone, ScoutLook's scent-cone graphic (Mossy Oak/ScoutLook) and HuntWise WindCast. ↩