AI Hints
Home›Computer Vision Tutorial›Object Detection
Computer Vision · Lesson 20 of 111

Training on Your Own Data

Labels, formats, and YOLO.

⏱ 2 min read▶ Video at 32:55🎓 Free
Watch this lesson in the Computer Vision courseWatch on YouTube ↗Full Computer Vision course

The biggest difference between a demo and a product is your own data. No public dataset contains Orchard Vision's apples, on its own conveyor, in its own lighting. So you'll need your own labelled images, and the good news is that you don't need very many to start.

Training on Your Own Data
From the video: Training on Your Own Data
  • No dataset has your apples
  • Your conveyor, your lighting
  • You don't need many to start

The label format

YOLO's labels are simple text files, with one line per object. The class, then the centre, width, and height, all as fractions of the image size. That means they don't depend on resolution. We turn one label back into pixels. Run it. That's the whole format. Tools such as Label Studio and Roboflow let you draw these boxes, and export this format.

labels.pypython
label = "0 0.512 0.430 0.210 0.300"
image_w, image_h = 640, 480

cls, cx, cy, w, h = label.split()
cx, cy, w, h = float(cx), float(cy), float(w), float(h)

x1 = (cx - w / 2) * image_w
y1 = (cy - h / 2) * image_h
x2 = (cx + w / 2) * image_w
y2 = (cy + h / 2) * image_h

print("Class:", cls)
print("Pixels:", round(x1), round(y1), round(x2), round(y2))
Output
Class: 0
Pixels: 260 134 395 278

Training a custom detector

Here's a complete training run. To keep it self contained, we draw fifty fake images, with round red apples, and write the labels. A small yaml file tells YOLO where everything is. Then one line trains it. Run it. We start from pretrained weights, for six quick epochs, on a small dataset. And the score climbs high. It's a simple task, but the very same code trains on your real apple photos, and that's what makes this so useful.

train.pypython
import cv2
import numpy as np
from pathlib import Path
from ultralytics import YOLO

rng = np.random.default_rng(0)
root = Path("apples")
for split, n in [("train", 40), ("val", 10)]:
    (root / "images" / split).mkdir(parents=True)
    (root / "labels" / split).mkdir(parents=True)
    for i in range(n):
        img = np.full((320, 320, 3), 40, np.uint8) + rng.integers(0, 30, (320, 320, 3), dtype=np.uint8)
        rows = []
        for _ in range(rng.integers(1, 4)):
            r = int(rng.integers(20, 45)); x = int(rng.integers(r, 320 - r)); y = int(rng.integers(r, 320 - r))
            cv2.circle(img, (x, y), r, (40, 40, 220), -1)
            rows.append(f"0 {x / 320} {y / 320} {2 * r / 320} {2 * r / 320}")
        cv2.imwrite(str(root / "images" / split / f"{i}.jpg"), img)
        (root / "labels" / split / f"{i}.txt").write_text("\n".join(rows))

(root / "data.yaml").write_text(f"path: {root.resolve()}\ntrain: images/train\nval: images/val\nnames:\n  0: apple\n")

model = YOLO("yolo11n.pt")
out = model.train(data=str(root / "data.yaml"), epochs=6, imgsz=320, batch=8, device="cpu",
                  workers=0, verbose=False, plots=False, project="runs", name="a")
print("mAP at 0.5:", round(float(out.box.map50), 3))
Output
      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size

        3/6         0G     0.6657     0.6343     0.9528         18        320: 0% ──────────── 0/5  0.4s
        3/6         0G     0.5727     0.6996     0.8943         13        320: 20% ━━────────── 1/5 1.4s/it 0.8s<5.5s

                   all         10         21      0.975          1      0.995      0.931

      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size

        4/6         0G     0.5838     0.5662      1.046         17        320: 0% ──────────── 0/5  0.4s
        4/6         0G     0.6874     0.5662     0.9537         16        320: 20% ━━────────── 1/5 1.4s/it 0.9s<5.7s

                   all         10         21          1      0.797      0.945      0.808

      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size

        5/6         0G     0.6968     0.7312     0.9707         17        320: 0% ──────────── 0/5  0.4s
        5/6         0G     0.8654     0.7376     0.9385         17        320: 20% ━━────────── 1/5 1.4s/it 0.9s<5.8s

                   all         10         21      0.995          1      0.995       0.88

      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size

        6/6         0G     0.7784     0.5927     0.9454         15        320: 0% ──────────── 0/5  0.5s
        6/6         0G     0.6586     0.5812      0.891         16        320: 20% ━━────────── 1/5 1.4s/it 0.9s<5.8s

                   all         10         21      0.995          1      0.995       0.88

6 epochs completed in 0.004 hours.
Optimizer stripped from /privateruns/detect/runs/a/weights/last.pt, 5.5MB
Optimizer stripped from /privateruns/detect/runs/a/weights/best.pt, 5.5MB

Validating /privateruns/detect/runs/a/weights/best.pt...
Ultralytics 8.4.197 🚀 Python-3.13.5 torch-2.15.0 CPU (Apple M3 Pro)
YOLO11n summary (fused): 101 layers, 2,597,051 parameters, 0 gradients, 6.4 GFLOPs

                   all         10         21      0.974          1      0.995      0.931
Speed: 0.1ms preprocess, 12.6ms inference, 0.0ms loss, 0.2ms postprocess per image
mAP at 0.5: 0.995

Tips for real projects

Collect images from the real camera, in the real place, across different lights and days. Start with a hundred or two hundred images per class. Label consistently, and write down the rules. Keep a test set from a different day. Use image sizes large enough for your smallest object. And after a first run, look at the failures, and collect more of those. That's how models really improve.

Real camera, real placematch deployment
Label consistentlywrite down rules
Test on another dayavoid leakage
Look at the failuresthen collect more

Summary

Your own data beats everything else. AppleEye can now find apples. But for grading, a box isn't enough. We need the exact shape of each bruise. Next, labelling each pixel.

Key takeaways
  • Labels are normalised text
  • One line trains the model
  • Failures tell you what to collect
Next up: Segmentation — labelling every pixel