Skip to content

About

A CNN trained from scratch to track an object nearly the same color as its background, on a bad camera, from ~180 hand-labeled frames.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tracking an object that's the same color as its background, on a bad camera, from scratch

The goal was simple to state and hard to solve: know where a small animal is in a tank at all times, and when it isn't visible, know its last seen position — attributed to a real landmark (near the filter, in the plants, near a fixed marker), not just "somewhere." The camera was an iPad — limited resolution, inconsistent lighting, and a container that reflects a warm color back at everything in it. Worse: the animal's own coloring shifts with depth and light, so it isn't even a fixed target.

Motion tracking was the first attempt, and it failed for a specific, measurable reason — it caught a filter's bubbles and the ripples they made across the surface, which move far more reliably than the animal does. Color tracking failed for a different, measurable reason: under the tank's lighting, the animal and the water it swims through are separated by a color difference (ΔE ≈ 6.8) that sits barely above the threshold most people can even perceive (JND ≈ 2.3) — an orange fish in a red bucket is nearly the same color as its own background. A blend of motion and color did better than either alone, but still wasn't reliable enough to trust unattended.

What actually worked: a small convolutional network, trained from scratch — no pretrained weights, no object-detection framework — on frames hand-labeled one at a time, with a real feedback loop. Capture a frame, mark where the animal actually is, retrain, watch it try again on new frames, correct what it got wrong, repeat. The dataset grew exactly where the model was weakest, not where it was already right.

The result

  • 24,837 parameters, ~107KB on disk, ~22ms inference on CPU. No GPU required, no cloud call, no pretrained backbone.
  • 181 hand-labeled frames across six labeling sessions were enough to get a working detector — because the negatives (frames where the animal is confirmed absent) turned out to matter as much as the positive boxes. See train.py's docstring for why.
  • Honest, two-regime accuracy, reported without flattering itself: in clear water, the detector found the animal correctly on 9 of 14 frames (64%). In heavy blur, bubbles, or at the far edge of the frame, it found it on 0 of 25 (0%). Absence detection (correctly saying "not visible") was solid: 5/5, and 16/16 before that. This isn't a general-purpose animal tracker — it's a detector that knows exactly the conditions it works in, and says so honestly rather than guessing outside them.
  • A confidence gate tuned from real labeled data, not guessed: the model refuses to draw a box at all when it isn't sure, rather than producing a confident wrong answer. Tuned properly, it went 19/19 correct with zero wrong answers on a true holdout batch it had never seen during training — silent on the hard frames rather than guessing.

What's actually in this repo

  • model.py — the network architecture and the inference function, including the confidence gate and the reasoning behind its exact thresholds.
  • train.py — the training script, and six real engineering lessons learned the hard way while building it (documented in the file itself): why negatives are the highest-yield label type, why a shuffled train/val split lies to you, why a validation frame built from the model's own accepted prediction measures nothing about position, why selecting a checkpoint on validation performance destroys validation's entire purpose, why synthetic data was never used, and why a training run should never overwrite the model it's replacing without a backup.

No training images are included. No footage, no photos — just the model, the code, and the documented reasoning.

Honest limits

Trained on one camera, one container, one lighting setup, this network does not generalize — trained on an earlier session and validated on a later one (or the reverse), it fails completely, because it memorizes the specific scene rather than learning a general prior for "what the target looks like." That's the expected, honest tradeoff for a network this small trained on this little real data with no synthetic augmentation. The fix isn't more augmentation — it's more real labeled frames spanning more real conditions, which is exactly what the active-learning loop in train.py supports.

Originally built to track a pet fish in an aquarium setup — the mechanism turned out to be more interesting than the application.

About

A CNN trained from scratch to track an object nearly the same color as its background, on a bad camera, from ~180 hand-labeled frames.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages