Backyard wildlife camera trap pipeline
for ingestion, species identification, individual re-identification, and local dashboard
This is a fun DIY project - for serious camera trap software with similar objectives, see AddaxAI.
See CONTRIBUTING.md for environment setup.
Run the setup command to set the data root (where images and the database will be stored) and initialise the database:
crittercam setupYou will be prompted for:
- Data root — path to your external drive; images and the database are stored here
- Country code — ISO 3166-1 alpha-3 code (e.g.
USA) for SpeciesNet geofencing; improves accuracy by filtering out species that don't occur in your region - State/province — abbreviation (e.g.
CT) for finer-grained geofencing
The config is written to ~/.config/crittercam/config.toml and can be updated by running crittercam setup again.
After offloading your SD card to a local directory, run:
crittercam ingest --source /path/to/offloaded/imagesIf --deployment-id is not supplied, an interactive prompt lists your existing deployments
and offers to create a new one. When creating a new deployment, camera make and model are
pre-filled from the EXIF data of the first image in the source directory.
To skip the prompt by specifying a deployment directly:
crittercam ingest --source /path/to/offloaded/images --deployment-id 1To override the configured data root for a single run:
crittercam ingest --source /path/to/offloaded/images --data-root /path/to/dataAfter ingesting, run species classification on all pending images:
crittercam classifyOn first run, SpeciesNet will automatically download model weights (~1 GB) from Kaggle — no separate download step or credentials are required. Subsequent runs use the cached weights.
Each image produces:
- A detection row in the database with species label, confidence score, and bounding box
- A thumbnail at
derived/YYYY/MM/DD/<filename>_thumb.jpg - A padded crop at
derived/YYYY/MM/DD/<filename>_det001.jpg(if an animal was detected)
Overrides — geofencing and crop padding can be adjusted per-run without changing the config:
crittercam classify --country USA --admin1-region CT --crop-padding 0.20After classifying, run individual re-identification on all pending detections:
crittercam identifyOn first run, MegaDescriptor-L-384 weights will download automatically from HuggingFace (~1.5 GB). Subsequent runs use the cached weights.
For each detection crop, this computes an embedding vector and uses gallery-based nearest-neighbor matching (cosine similarity) to assign detections to known individuals. New individuals are created when no gallery match exceeds the threshold.
To target a single species:
crittercam identify --species "domestic cat"To experiment with the similarity threshold without re-computing embeddings (fast — skips the model entirely and just re-runs the matching step):
crittercam identify --skip-embedding --threshold 0.6The default threshold is 0.5, calibrated on domestic cat detections. Human-confirmed assignments
(merges, name corrections) are preserved as gallery anchors across re-matching runs.
Merge individuals that the algorithm split incorrectly — all reassigned detections are marked as human-confirmed and will survive future re-matching. Individual IDs are shown in the dashboard detail panel (click any crop to open it):
crittercam merge-individuals 3 7 12 # merges #7 and #12 into #3 (the lowest id)Name an individual to give it a display nickname in the dashboard:
crittercam name-individual 3 "Mittens"crittercam build-ui # compiles the React app into crittercam/web/ui/dist/
crittercam serve # serves API + built UI from a single Uvicorn processThen visit http://localhost:8000. See CONTRIBUTING.md for the hot-reloading dev server setup.

