Computer vision

Climbing Beta

I wanted to point a camera at a bouldering wall and get back a sequence of moves. Finding the holds is a vision problem, so that half is a trained model. Working out where to put your hands and feet is a search over a finite set of positions, so that half is a graph search.

View repository
  • Python
  • YOLO11s-seg
  • ONNX
  • WebGPU
  • Dijkstra
Timeline
Public repo · Aug 2026 – present
Role
Independent developer
Scope
Instance segmentation, colour clustering, stance-graph search
Status
Live phone app · detection in the browser
Four limbs resting on holds on a wall, with the right hand moving to a higher hold.LHRHLFRFone limb moves
A stance assigns one hold to each limb. Every edge in the search moves exactly one of them, and pays for the distance travelled, the strain on the body, the balance of the centre of mass, and the quality of the hold.

What it does

You give it a photo of a wall. A fine-tuned YOLO11s-seg model finds the holds, colour clustering in CIELAB works out which ones belong to the same route, and a shortest-path search over four-limb stances returns a sequence of moves. The first full one was 14 moves up an 8-hold green route, picked out of 121 detected holds. On 106 held-out images containing 6,356 holds the detector reaches 0.877 mask mAP50.

There is also a phone app, live at climbing-beta-theta.vercel.app, that runs the detector in the browser, so the photo never leaves the phone. On a 24-megapixel wall photo it finds the same 152 holds as the Python pipeline, in 0.67 seconds.

What was hard

Two modelling mistakes cost me the most time. The search kept stacking all four limbs on one hold, because coincident contact points drive the strain and balance costs to zero. Then I set the maximum hand-to-foot span to 0.9 times height and deadlocked 7 of the 10 routes I tested, when reaching overhead off a foothold is closer to 1.25 times your height.

Clustering the route was its own fight. Hold colours run in a continuum from orange through yellow to pink, so single linkage chained straight across the gaps and merged 43 of the 76 holds on one wall into a single route.

Then getting the detector onto a phone. PyTorch alone is 558 MB, over Vercel’s 500 MB limit, so I exported the model to ONNX and ran it in the page over WebGPU. Ultralytics’ own browser library crashed on this model, so I wrote the letterboxing and NMS myself.

Where it goes next

I never got scale out of the photo by itself. Gym walls are drilled on a regular T-nut grid, so the bolt holes should work as a ruler, but I tried twice across three walls and neither method found them. So scale is an input: you mark two points a known distance apart, or four corners of the bolt grid, and the phone app measures a route that way.

The app can now save a tape-measure check alongside the photo and the taps, so next is collecting those for the first real error figure the geometry has had. After that, tuning the reach limits against routes a person has actually climbed. One of them currently rejects 91% of candidate moves and nothing has tested whether that is right.

The detector still picks up things that are not holds. A calibrated wall plane now removes the ones it can rule out, but a brush on the mat in front of a vertical wall still gets through.

0.877Mask mAP50 on held-out images
6,356Held-out holds evaluated
14Moves in the first full sequence

Curious about something here? Ask me about it

Next projectFaultline Agent reliability · TypeScript