Sitemap
Leonardo Cruciani

Leonardo Cruciani

Data Scientist Consultant. My real projects are under NDA - imagine something impressive. Here I write about side projects and nerdy rabbit holes.

Dart(s) Vader: How I Taught a Webcam to Keep Score

8 min readSep 15, 2026

--

One static camera, no stickers on the board, and a small neural network that finds the tips well enough to automate dart scoring.

Press enter or click to view image in full size
Picture from Behance

Darts is a beautiful sport r̶u̶i̶n̶e̶d̶… interrupted every few throws by arithmetic. You throw a treble 19, a single 7 and something that bounced off the wire, and then you stand there subtracting 83 from 327 while your friends watch and silently judge your maths. Commercial auto-scorers fix this with a ring of three or more cameras around the board and expensive yearly plans.

I had a dartboard, a webcam, a mid-range GPU and a strong conviction that one camera should be enough (in most of the cases).

The result is DARTS VADER, open source: it locks onto a standard board from almost any angle, finds the dart tips with a small neural network, and runs a full x01 game in a fullscreen web app.

Press enter or click to view image in full size
The app reviewing a turn: the live camera view, a zoomed tile per dart and the score.

Measuring the board, learning the tips

Scoring a dart splits cleanly into two problems with very different personalities:

Knowing where the board is turns out to be pure geometry — a regulation board is specified down to the millimetre, so once its position is known, everything else follows from trigonometry.

Knowing where the tips are is the opposite kind of problem: darts come in every colour and shape, overlap each other, cast shadows, get seen from behind — that’s perception, and perception is where a neural network earns its keep. So the project rests on one rule:

Measure what you can, and only learn what truly has to be learned.

The board’s position is captured as a homography, a small matrix that maps any pixel on the board’s plane to real millimetres. I get there in two steps: a rough colour-based guess from the red and green rings, refined by matching the rectified image against the board’s rings and wires until the fit stops improving. On a handheld, very oblique 4K video the board locked in every one of 519 frames, accurate to about half a millimetre, in under 60 milliseconds.

Press enter or click to view image in full size
Fitted geometry on a fixed webcam (left), on a handheld, very oblique phone video (centre), and centered (right).

That matrix is also why only the tip matters: a dart’s barrel floats a few centimetres above the board, so the homography places it slightly wrong, but the tip is jammed into the surface and genuinely lies on the plane it was fitted to. Convert that one point to millimetres and the rest is just checking which ring and sector it falls in, with no separate calibration needed:

# once the board homography is known, scoring is geometry
x_mm, y_mm = board.to_model([tip_px])[0] # image pixels -> board millimetres
hit = score_model_point(x_mm, y_mm, board.rings) # radius -> ring, angle -> sector
print(hit.label, hit.score) # "T20" 60

The only blind spot is rotational, since the board looks identical every 36° (every two sections): the system asks the player to confirm the 20 once and works out every later rotation by matching the printed number ring against that first reading, right in 165 of 165 sessions I tested it against.

Finding the tips

The hard part — and the most interesting one, imo — is TipNet, a small network I trained to look at the board and predict a heatmap: instead of guessing three fixed coordinates, it lays a grid over the image and, for every cell, says how likely a tip sits there, plus a small nudge for exactly where inside that cell. I tried guessing coordinates directly first and it kept breaking, for a simple reason: the camera watches continuously, so any given frame can show zero, one, two or three tips depending on how far into the turn play has got, and a network built to output “tip 1, tip 2, tip 3” has no clean way to say “there’s only one dart right now”. A heatmap doesn’t have that problem — empty cells just stay dark, a tip the model is unsure about shows up as a faint smudge instead of a confidently wrong point, and counting the tips is just counting the bright spots.

Get Leonardo Cruciani’s stories in your inbox

Join Medium for free to get updates from this writer.

Under the hood it’s a small, off-the-shelf vision backbone (ResNet-18) that turns the photo into features, and a light decoder that turns those features back into that fine grid — one channel for “is a tip here”, two more for the precise nudge. Finding the tips at the end is then almost embarrassingly simple: pick the brightest spots on the grid, read their tiny offset, and convert to a real pixel position. The whole network is 11.6 million parameters and runs in under 7 milliseconds per frame, easily fast enough to keep up with a live camera.

Press enter or click to view image in full size
TipNet on a photo from a camera position never used for training. Heatmap, three sharp peaks; decoded tips (pink) on hand labels (green).

Training it needed a loss built for a very lopsided grid, where only a handful of cells out of thousands are ever “tip” and the rest are background — plain cross-entropy would just learn to predict nothing everywhere. It also needed a softer idea of “correct”: instead of labelling one single cell as the tip and everything else as wrong, the true label is a small Gaussian bump centred on the tip, fading out over the neighbouring cells. That way a prediction one cell off isn’t punished as harshly as one on the far side of the board, which matters because the grid itself is coarser than a millimetre of dart tip. A focal loss borrowed from CenterNet builds on that Gaussian label, weighting hard-wrong cells far more than easy-right ones:

# per pixel, y in [0,1] is the Gaussian label, p is the sigmoid prediction
if y == 1:
loss = -(1 - p) ** alpha * log(p) # focus on hard positives
else:
loss = -(1 - y) ** beta * p ** alpha * log(1 - p) # down-weight near-misses

Here is exactly what one full run looked like: sixty epochs on about 2,200 training images, mostly DeepDarts — a public dataset of dartboard photos with hand-labelled tip positions, which I also use as a benchmark — topped up with my own hand-labelled sessions from this exact camera, weighted three times heavier in the mix since at first there were so few of them.

Press enter or click to view image in full size
60 epochs from the training log. Left: focal + offset loss. Right: recall, precision and the share of images with every tip right, on the held-out validation split

The shape of that curve is a small lesson on its own. In the first epoch the network barely predicts anything, so precision looks great, since the handful of guesses it does make are usually right, while recall is terrible, because it’s ignoring most of the board. Loss and recall improve together for the first ten epochs or so, then the run spends its last thirty epochs squeezing out single percentage points, noisily, because the validation set is small enough that one hard photo can move the curve.

On the DeepDarts validation split, a tip counts as found if it lands within 10 mm of the truth, and TipNet gets 96.7% of them, with 97.4% precision and every tip right in 87% of images. Point it at my own webcam, from angles it never trained on, and it scores 11 of 15 darts right in one test and 33 of 42 in another — a real gap between benchmark and living room, not a bug: DeepDarts was shot from a handful of set-ups that don’t cover every angle, and a millimetre near a wire is a coin flip against a 1.5 mm mean error however good the model gets.

Press enter or click to view image in full size

A model that is right about four darts in five would start a pub fight, not settle one.

Playing darts is labelling data

Four out of five is not a finished model, it’s a model that’s hungry for more than two evenings with a phone can give it, so I built the app around feeding it for free. The camera calls each dart the moment it looks stable, and after the third one the turn freezes on a still photo for review — press Enter if it’s right, click to fix the one that isn’t. Every confirmed turn is saved with its photo and the exact tip positions, so playing a normal game of darts quietly produces exactly the kind of labelled data the model needs, from exactly the camera angles it will actually be used from. Every so often I will fine-tune the last checkpoint on whatever has piled up since, rather than starting over, and because the model sits behind a clean interface (tips in board millimetres, with a confidence) — a better one can drop in later without touching anything else.

Press enter or click to view image in full size
Live play. Darts appear on the camera view and on the mini board as soon as they are stable.

The dashboard, built with a lot of help from AI

The fullscreen interface — dark theme, sound effects, a full end-of-match statistics screen — was built largely with an AI coding assistant from my descriptions, since it’s what makes a darts night fun rather than where the hard problems live. One happy accident: in double-out games it now measures how many millimetres each miss was off the finishing double by, and I am consistently low and left.

Press enter or click to view image in full size
End-of-match statistics: every dart on a synthetic board with the grouping circle, averages, and how the turn scores were distributed.

Code, training tools and instructions to run it on your own webcam: github.com/cruleon/darts_vader. If you try it, I’d love to know how many darts it gets right from your angle.

The tip model is pre-trained on the DeepDarts dataset: W. McNally, P. Walters, K. Vats, A. Wong and J. McPhee, “DeepDarts: Modeling Keypoints as Objects for Automatic Scorekeeping in Darts using a Single Camera”, CVPR Workshops 2021.

--

--

Written by Leonardo Cruciani

Data Scientist Consultant. My real projects are under NDA - imagine something impressive. Here I write about side projects and nerdy rabbit holes.