YOLO & Darknet for Camera Traps
A working understanding of what YOLO does, how Darknet relates to it, and how to run, train, and use both for camera-trap detection and classification.
1. What YOLO is, in one paragraph
YOLO ("You Only Look Once") is a family of single-stage object detectors. Where older detectors (R-CNN, Faster R-CNN) first proposed regions and then classified them, YOLO does both in a single forward pass through a convolutional network. The image is divided into a grid; at each grid cell, the network predicts (a) a small number of bounding boxes, (b) their objectness scores, and (c) a class probability vector. This makes YOLO fast enough to run in real time on a laptop GPU, which is why it dominates camera-trap pipelines.
2. YOLO vs Darknet vs Ultralytics — what is what?
| Name | What it is | Use it for |
|---|---|---|
| Darknet | The original C/CUDA training framework written by Joseph Redmon and extended by AlexeyAB. Backs YOLOv1–v4. | Reproducing older camera-trap papers (e.g., many MegaDetector v3 / v4 variants). |
| Ultralytics YOLO | A PyTorch-based reimplementation, currently at YOLOv8 / YOLO11. Much friendlier API. | What we'll use for almost everything in this course. |
| MegaDetector | A YOLOv5-trained model for 3 classes: animal, person, vehicle. Excellent on camera traps worldwide. | Always run first on a new camera-trap dataset — gives you a strong baseline before you train anything custom. |
megadetector Python package. The Day-2 lecture
will demo a Darknet build for those who care.
3. Installing Ultralytics
pip install ultralytics
# Verify
yolo checks
4. Inference: run a pretrained detector in 3 lines
The Ultralytics CLI is the fastest way to detect:
# Use YOLOv8n (nano, fast) on a folder of Oregon Critters images
yolo detect predict \
model=yolov8n.pt \
source=/data/oregon_critters/images/site_A \
conf=0.25 \
save=True \
project=runs save_dir=site_A
You'll see annotated images in runs/site_A/. To do the same thing in Python —
and capture the results as structured data — use the API:
Annotated detection script
"""
detect_oregon_critters.py
Run a pretrained YOLO model on every image in a directory and write
a tidy CSV of detections that can be merged with site metadata.
Usage:
python detect_oregon_critters.py \
--images /data/oregon_critters/images/site_A \
--out site_A_detections.csv \
--model yolov8n.pt \
--conf 0.25
"""
import argparse
import csv
from pathlib import Path
from ultralytics import YOLO
def main():
p = argparse.ArgumentParser()
p.add_argument("--images", required=True, help="Directory of .JPG files")
p.add_argument("--out", required=True, help="Output CSV path")
p.add_argument("--model", default="yolov8n.pt",
help="Path or name of YOLO weights")
p.add_argument("--conf", type=float, default=0.25,
help="Minimum confidence to keep a detection")
args = p.parse_args()
# Loads the weights once; downloads from Ultralytics if it's a known name.
model = YOLO(args.model)
# Stream=True yields one Result per image without loading them all into RAM.
image_paths = sorted(Path(args.images).glob("**/*.JPG"))
print(f"Found {len(image_paths)} images")
with open(args.out, "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["image", "class_id", "class_name",
"confidence", "x1", "y1", "x2", "y2"])
for result in model.predict(source=image_paths,
conf=args.conf,
stream=True,
verbose=False):
# result.boxes is a tensor of detections for this image.
# Each row: [x1, y1, x2, y2, conf, class_id]
img_name = Path(result.path).name
for box in result.boxes:
cls_id = int(box.cls.item())
cls_nm = model.names[cls_id]
xy = box.xyxy[0].tolist()
writer.writerow([img_name, cls_id, cls_nm,
round(box.conf.item(), 3),
*[round(v, 1) for v in xy]])
print(f"Wrote detections to {args.out}")
if __name__ == "__main__":
main()
5. Training your own YOLO
Ultralytics expects this folder layout:
oregon_critters_yolo/
├── images/
│ ├── train/ # 80% of your JPEGs
│ └── val/ # 20%
├── labels/
│ ├── train/ # one .txt per image, same basename
│ └── val/
└── data.yaml # the dataset descriptor
Each .txt label file has one line per object, normalized to image dimensions:
# class_id x_center y_center width height (all in [0, 1])
0 0.512 0.443 0.180 0.260
2 0.760 0.522 0.140 0.310
And data.yaml looks like:
path: /data/oregon_critters_yolo
train: images/train
val: images/val
names:
0: deer
1: black_bear
2: cougar
3: coyote
Training script (annotated)
"""
train_oregon_critters.py
Fine-tune YOLOv8 on a 4-class Oregon Critters subset.
Designed to run on a single GPU in ~30 minutes for the small subset
used in Lab 4.
"""
from ultralytics import YOLO
# Start from yolov8s.pt (small) rather than nano — the species discrimination
# task benefits noticeably from extra capacity, and it still trains fast.
model = YOLO("yolov8s.pt")
# A single .train() call kicks off the entire pipeline:
# - sets up the optimizer (AdamW by default)
# - reads data.yaml
# - builds the train/val DataLoaders with built-in augmentation
# - runs the epoch loop, logging mAP@0.5 and mAP@0.5:0.95
# - saves the best checkpoint to runs/detect/<name>/weights/best.pt
results = model.train(
data="oregon_critters_yolo/data.yaml",
epochs=50,
imgsz=640, # input resolution; bigger = slower but better small-animal recall
batch=16, # drop to 8 if you OOM
patience=10, # early-stop if val mAP stalls
name="oregon_critters_v1",
pretrained=True, # we ARE fine-tuning, not training from scratch
optimizer="AdamW",
cos_lr=True, # cosine LR schedule helps for small datasets
augment=True, # mosaic + flips + HSV; turn off near end if overfitting
seed=42, # reproducibility
)
# Evaluate on the val set with the best checkpoint
metrics = model.val()
print("mAP@0.5 :", round(metrics.box.map50, 3))
print("mAP@0.5:.95:", round(metrics.box.map, 3))
6. Reading training curves honestly
- train/box_loss should fall and then plateau. If it's still falling at the last epoch you stopped too early.
- val/box_loss should track training loss for a while, then start to rise.
That rise is overfitting — take the checkpoint from before the rise (Ultralytics calls
this
best.ptautomatically). - mAP@0.5 is the friendliest metric to report to non-ML colleagues, but mAP@0.5:0.95 is the standard for papers.
- Look at
confusion_matrix.pngin the run directory — if cougar and bobcat are getting confused with each other but everything else is fine, you have a clear story to tell.
7. Classification on detected crops
For fine-grained species ID, a common pattern is "detect with MegaDetector then classify the crops". You can do this in Ultralytics by using a classification model on the cropped detections from a detection model.
"""
classify_crops.py
Pipe: detector finds animals; classifier names them.
"""
from ultralytics import YOLO
from PIL import Image
det = YOLO("yolov8n.pt") # generic animal detector
cls = YOLO("oregon_critters_cls/best.pt") # YOUR fine-tuned classifier
for result in det.predict(source="data/site_B", stream=True, conf=0.3):
img = Image.open(result.path).convert("RGB")
for box in result.boxes:
x1, y1, x2, y2 = [int(v) for v in box.xyxy[0].tolist()]
crop = img.crop((x1, y1, x2, y2))
c = cls.predict(crop, verbose=False)[0]
species = cls.names[int(c.probs.top1)]
confidence = float(c.probs.top1conf)
print(result.path, species, round(confidence, 2), [x1, y1, x2, y2])
8. The Darknet route (for the curious)
If you need to read or reproduce a paper that used Darknet directly:
# Clone AlexeyAB's maintained fork
git clone https://github.com/AlexeyAB/darknet.git
cd darknet
# Edit Makefile: set GPU=1 CUDNN=1 OPENCV=1 if you have CUDA installed,
# otherwise leave defaults for CPU-only.
make -j8
# Download YOLOv4 weights
wget https://github.com/AlexeyAB/darknet/releases/download/darknet_yolo_v3_optimal/yolov4.weights
# Run detection on one image
./darknet detector test cfg/coco.data cfg/yolov4.cfg yolov4.weights \
-ext_output predictions.jpg
# (predictions.jpg is written; bounding boxes are also printed to stdout)
Darknet training uses an obj.data file pointing to train.txt and
valid.txt — the same idea as Ultralytics' data.yaml, just less
polished. For new work you should prefer Ultralytics; for old papers, Darknet is fine.
9. Evaluation metrics, briefly
| Metric | What it tells you | What it hides |
|---|---|---|
| Precision | Of the boxes I called positive, how many were right? | Doesn't penalize missed animals. |
| Recall | Of all the real animals, how many did I find? | Doesn't penalize false alarms. |
| mAP@0.5 | Average precision averaged over classes, treating a box as correct if IoU≥0.5 with truth. | Insensitive to localization fineness. |
| mAP@0.5:0.95 | Same but averaged over IoU thresholds 0.5, 0.55, ..., 0.95. | Harder to interpret; standard in papers. |