Four stages, on your own webcam. Each one works, each one breaks, and each one's breakage picks the next. At the end the motion is a grid of numbers your turtles can read.
The cheapest possible motion detector: keep the last frame, subtract it from this one, and look at what's left. Anything that didn't move cancels to zero.
Try it: hold still — the screen goes black. The camera only exists where something changes.
Not an opinion. Below are the same two frames of a bar. On the left they play in order; on the right, in the opposite order. Same frames, opposite motion. Watch what the frame difference does.
A hand moving left and a hand moving right light up identically. Do it now, in front of the camera: wave left, then wave right. You cannot tell the two apart on screen, and neither can the computer — the proof above is why. To get direction we have to stop asking “did this pixel change?” and start asking “where did this patch go?”
Flick the lights, or cover the lens. The whole frame lights up and the “moving pixels” readout jumps toward 100%. Nothing moved. Every method on this page — all four stages — assumes brightness constancy: that a point on an object keeps the same brightness from one frame to the next. When the lights change, that assumption is false, and the flow lies.
Stop looking at single pixels. Cut the frame into small patches. For each patch, go hunting in the next frame for the spot that looks most like it — the place where the pixels differ least (the sum of squared differences). How far you had to move to find it is the motion.
Slow — and here's the number. Every patch runs a full search: patches × positions × pixels. Drag search radius from ±1 to ±9 and watch comparisons this frame climb by more than an order of magnitude, with the milliseconds right behind it. Doubling how fast a hand you can track costs you four times the work. That's the trade this method can't get out of.
Haywire on flat regions — and they're on screen right now, in orange. Point the camera at a blank wall. A patch of blank wall matches every position in the search window equally well, so “best match” is decided by sensor noise. Those orange arrows are real output from a correct implementation. They are not a bug on this page — they're what the method actually knows, which is nothing. A flat patch has no information about its own motion.
So: we need something that doesn't search at all, and we need to face the flat-patch problem honestly rather than paint it orange.
No searching. Measure how fast brightness changes across space (Ix side-to-side, Iy up-and-down) and how fast it changes in time (It). Then plain algebra: if I know how fast brightness changes across space, and how fast it changes in time, I can solve for how fast things are moving. That's Lucas-Kanade, and it's the fast way.
Through a small window, you can only see the motion that is perpendicular to an edge. Motion along an edge is invisible — the edge slides along itself and nothing appears to change. This is not a limitation of this page, of your camera, or of Lucas-Kanade. It is a limitation of looking through a small hole, and your own visual system has it too.
Press it five times with the aperture closed: the view never changes. Then hit reveal — five completely different motions.
Lucas-Kanade beats the aperture problem only when the little window contains enough different directions of edge — a corner, a texture, a speck. Where it doesn't, the math says so out loud: the matrix becomes singular, the smallest eigenvalue collapses toward zero, and there is no unique answer to be had. Those windows are orange on the screen above, and shrinking the window slider makes more of them. Same disease as Stage 2's flat wall, now with a diagnosis.
We have direction, at speed, with an honest confidence value on every vector. So what is it good for in a room full of NetLogo teachers?
Those gradients only describe the neighborhood right around a pixel, so the algebra only holds if things moved a little between frames — about a pixel or two. Wave your hand fast: the vectors go to nonsense. Real implementations fight this with an image pyramid — run the flow on a shrunk-down copy (where a big motion becomes a small one), then refine. That pyramid is exactly what this page doesn't have, and it's the honest reason fast motion breaks it.
Take the flow and average it down onto a grid. Now every cell holds two numbers. A grid of cells, each holding numbers, with agents walking around on top reading them — you already know what that is. You've been writing it all week.
Try it: switch to Turtles and wave. You are pushing agents around with your hand.
This is a NetLogo world. Each patch holds two numbers — flow-x
and flow-y. Your turtles can read them:
ask turtles [ set heading atan flow-x flow-y fd 1 ]
That's the whole bridge. A webcam is now a force field, and everything you know about agent-based modeling applies to it.
;; Each patch remembers the motion the camera saw there. patches-own [ flow-x flow-y ] to go ask turtles [ let fx [flow-x] of patch-here let fy [flow-y] of patch-here ;; only ride patches that actually saw something move if (fx * fx + fy * fy) > 0.01 [ set heading atan fx fy ;; NetLogo: atan x y — 0 is north, clockwise fd 1 ] ] tick end to paint-flow ;; the color-wheel view, in NetLogo ask patches [ let speed sqrt (flow-x * flow-x + flow-y * flow-y) set pcolor ifelse-value (speed < 0.1) [ gray ] [ scale-color red speed 0 4 ] ] end
An image counts y downward from the top-left corner. NetLogo counts y upward. So when you pipe camera flow into a NetLogo model, flip the sign of flow-y on the way in — otherwise your turtles will chase your hand in a perfect mirror image and you will lose an afternoon to it. (This page already flipped it for you in the snippet above; the arrows on the canvas are drawn in image coordinates.)
Getting the numbers across is the part left open: the usual routes are a small local web
server posting values into NetLogo Web, the Python extension, or — the low-tech classroom answer —
write a frame of flow values to a CSV and file-read it. That's a real branch, and nobody
has walked it yet on this page.
This is a simplified, real-time browser implementation written to be read, not to win a benchmark. It is not OpenCV's Farnebäck, and it is not a production Lucas-Kanade pyramid. It runs on 160 × 120 grayscale and upscales the drawing. Small motions only. No pyramid, no sub-pixel refinement, no temporal smoothing, no outlier rejection.
Everything it gets wrong, it gets wrong for reasons worth teaching: brightness constancy (Stage 1), small displacement (Stage 3), the aperture problem, and flat regions. Those last two are inherent to the problem — a production library has exactly the same two holes in its knowledge. It just hides them better.