YOLO e Object Detection: Dalla Teoria alla Pratica con YOLOv8
Nel gennaio 2026 Ultralytics ha rilasciato YOLO26, l'ultima evoluzione della famiglia YOLO che ha ridefinito il real-time object detection. Ma per capire YOLO26 occorre prima capire YOLO: cosa lo rende straordinariamente veloce, come funziona la sua architettura, perchè e diventato lo standard de facto per il rilevamento di oggetti in applicazioni industriali, automotive, sorveglianza e robotica. In questo articolo costruiremo la comprensione completa dell'object detection moderna, da YOLOv1 fino all'implementazione pratica con YOLOv8 e YOLO26.
Nota: Questo e il primo tutorial completo in italiano su YOLO26. La serie Computer Vision con Deep Learning su federicocalo.dev e la fonte di riferimento italiana per questi argomenti.
Cosa Imparerai
- Come funziona l'object detection: bounding boxes, confidence scores, classi
- L'architettura YOLO: backbone, neck, head - dalla teoria all'implementazione
- La storia di YOLO: da v1 a YOLO26, i miglioramenti chiave in ogni versione
- Metriche fondamentali: IoU, mAP, precision-recall curves
- Training completo su dataset personalizzato con YOLOv8 (Ultralytics)
- Inference in tempo reale su video e webcam
- Export e deployment: ONNX, TensorRT, OpenVINO
- YOLO26: le novità architetturali di gennaio 2026
- Best practices per dataset preparation e data augmentation
1. Object Detection: Concetti Fondamentali
L'object detection e il task di individuare e classificare simultaneamente uno o più oggetti all'interno di un'immagine. A differenza della classificazione (una label per tutta l'immagine), la detection deve rispondere a tre domande: cosa c'è nell'immagine, dove si trova (bounding box), e con quale confidenza lo ha rilevato.
1.1 Rappresentazione dell'Output
Ogni oggetto rilevato e rappresentato da un bounding box con 5 valori fondamentali più un vettore di probabilità per le classi:
# Ogni detection e rappresentata da:
# [x_center, y_center, width, height, confidence] + [p_class1, p_class2, ..., p_classN]
# Esempio: detection di un gatto (classe 0) in un'immagine 640x640
detection = {
'bbox': (0.45, 0.60, 0.30, 0.40), # x_c, y_c, w, h (normalizzati 0-1)
'confidence': 0.94, # confidence score del box
'class_id': 0, # indice classe
'class_name': 'gatto',
'class_prob': 0.96 # probabilità condizionale della classe
}
# Il "final score" e: confidence * class_prob = 0.94 * 0.96 = 0.90
# Coordinate in pixel (immagine 640x640):
x_c_px = 0.45 * 640 # = 288
y_c_px = 0.60 * 640 # = 384
w_px = 0.30 * 640 # = 192
h_px = 0.40 * 640 # = 256
# Conversione a [x1, y1, x2, y2]
x1 = x_c_px - w_px / 2 # = 192
y1 = y_c_px - h_px / 2 # = 256
x2 = x_c_px + w_px / 2 # = 384
y2 = y_c_px + h_px / 2 # = 512
1.2 Non-Maximum Suppression (NMS)
I modelli di detection generano centinaia di proposte di bounding box sovrapposti. La Non-Maximum Suppression (NMS) e l'algoritmo che seleziona il box migliore eliminando i duplicati basandosi sulla metrica Intersection over Union (IoU).
import numpy as np
def compute_iou(box1: np.ndarray, box2: np.ndarray) -> float:
"""
Calcola Intersection over Union tra due bounding boxes.
Input: [x1, y1, x2, y2] per entrambi i box.
Output: IoU in [0, 1]
"""
# Coordinate dell'intersezione
x_left = max(box1[0], box2[0])
y_top = max(box1[1], box2[1])
x_right = min(box1[2], box2[2])
y_bottom = min(box1[3], box2[3])
if x_right < x_left or y_bottom < y_top:
return 0.0 # Nessuna intersezione
intersection = (x_right - x_left) * (y_bottom - y_top)
area1 = (box1[2] - box1[0]) * (box1[3] - box1[1])
area2 = (box2[2] - box2[0]) * (box2[3] - box2[1])
union = area1 + area2 - intersection
return intersection / union
def non_maximum_suppression(
boxes: np.ndarray,
scores: np.ndarray,
iou_threshold: float = 0.45,
score_threshold: float = 0.25
) -> list[int]:
"""
Applica NMS per eliminare bounding box sovrapposti.
Args:
boxes: [N, 4] array di box [x1, y1, x2, y2]
scores: [N] array di confidence scores
iou_threshold: soglia IoU per considerare due box duplicati
score_threshold: filtra box sotto questa confidenza
Returns:
Lista di indici dei box selezionati
"""
# Filtra box sotto la soglia di confidenza
valid_mask = scores >= score_threshold
boxes = boxes[valid_mask]
scores = scores[valid_mask]
indices = np.where(valid_mask)[0]
# Ordina per score decrescente
order = np.argsort(scores)[::-1]
selected_indices = []
while len(order) > 0:
# Prendi il box con score più alto
best_idx = order[0]
selected_indices.append(indices[best_idx])
order = order[1:]
if len(order) == 0:
break
# Calcola IoU del box selezionato con tutti i restanti
ious = np.array([
compute_iou(boxes[best_idx], boxes[i])
for i in order
])
# Mantieni solo i box con IoU basso (non sovrapposti)
order = order[ious < iou_threshold]
return selected_indices
# Test
boxes = np.array([
[100, 100, 300, 300], # box principale
[110, 105, 310, 305], # quasi identico - da eliminare
[500, 200, 700, 400], # box diverso - da mantenere
])
scores = np.array([0.92, 0.88, 0.75])
kept = non_maximum_suppression(boxes, scores, iou_threshold=0.5)
print(f"Box mantenuti: {kept}") # [0, 2] - elimina il duplicato
1.3 Metriche di Valutazione
Metriche Fondamentali per Object Detection
| Metrica | Formula | Significato |
|---|---|---|
| IoU | Intersezione / Unione | Sovrapposizione tra box predetto e ground truth |
| Precision | TP / (TP + FP) | Quante predizioni sono corrette |
| Recall | TP / (TP + FN) | Quanti oggetti reali vengono trovati |
| AP@0.5 | Area sotto curva PR a IoU=0.5 | Accuracy per singola classe |
| mAP@0.5 | Media AP su tutte le classi | Metrica principale per confronto modelli |
| mAP@0.5:0.95 | Media mAP a IoU 0.5-0.95 (step 0.05) | Metrica più rigorosa (COCO standard) |
2. Architettura YOLO: Come Funziona
YOLO (You Only Look Once) fu introdotto da Redmon et al. nel 2016 con un'idea rivoluzionaria: trattare l'object detection come un singolo problema di regressione, predecendo direttamente bounding boxes e probabilità di classe da un'unica passata forward attraverso la rete. Niente region proposals, niente due stadi: un'unica rete, un'unica inferenza, velocità estrema.
2.1 Architettura a Tre Stadi: Backbone, Neck, Head
Input Immagine (640x640x3)
|
v
+------------------+
| BACKBONE | Estrazione feature multi-scala
| (CSPDarkNet / | Output: feature maps a scale diverse
| EfficientRep) | P3: 80x80 (oggetti piccoli)
| | P4: 40x40 (oggetti medi)
| | P5: 20x20 (oggetti grandi)
+------------------+
|
v
+------------------+
| NECK | Aggregazione multi-scala
| (PANet / BiFPN)| Feature Pyramid Network
| | Fonde informazioni semantiche (deep)
| | con informazioni spaziali (shallow)
+------------------+
|
v
+------------------+
| HEAD | Predizioni finali
| (Decoupled | Per ogni cella della griglia:
| Detection) | - Box regression: [x, y, w, h]
| | - Objectness: p(oggetto)
| | - Classification: [p_c1, ..., p_cN]
+------------------+
|
v
Output: [batch, num_predictions, 4 + 1 + num_classes]
# Per YOLOv8 nano su 640x640: 8400 predizioni totali
# (80x80 + 40x40 + 20x20 = 6400 + 1600 + 400 = 8400)
2.2 Evoluzione YOLO: Da v1 a YOLO26
Storia delle Versioni YOLO
| Versione | Anno | Innovazione Principale | mAP (COCO) |
|---|---|---|---|
| YOLOv1 | 2016 | Single-stage detection, griglia SxS | 63.4 (VOC) |
| YOLOv3 | 2018 | Multi-scale detection, Darknet-53 | 33.0 |
| YOLOv5 | 2020 | CSP backbone, mosaic augmentation | 48.2 |
| YOLOv7 | 2022 | Extended ELAN, auxiliary heads | 51.4 |
| YOLOv8 | 2023 | Anchor-free, decoupled head, C2f block | 53.9 |
| YOLOv9 | 2024 | GELAN, Programmable Gradient Information | 55.6 |
| YOLOv10 | 2024 | NMS-free, dual-label assignment | 54.4 |
| YOLO26 | Gen 2026 | Hybrid Attention backbone, Dynamic NMS | 57.2 |
2.3 Anchor-Free Detection: La Rivoluzione di YOLOv8
Una delle innovazioni più significative di YOLOv8 e l'abbandono degli anchor boxes. Le versioni precedenti di YOLO usavano anchor predefiniti: box di dimensioni fisse calcolati tramite k-means clustering sul dataset. Questo richiedeva un'attenta scelta degli anchor per ogni dataset. YOLOv8 (e YOLO26) adottano un approccio anchor-free: il modello predice direttamente le coordinate del centro e le dimensioni del box, eliminando il bias degli anchor predefiniti.
3. Training su Dataset Personalizzato con YOLOv8
3.1 Preparazione del Dataset
YOLOv8 usa il formato YOLO TXT per le annotazioni: un file .txt per ogni immagine
con una riga per ogni oggetto nel formato:
<class_id> <x_center> <y_center> <width> <height>
(coordinate normalizzate 0-1).
dataset/
├── images/
│ ├── train/ # Immagini di training
│ │ ├── img001.jpg
│ │ ├── img002.jpg
│ │ └── ...
│ ├── val/ # Immagini di validazione (20%)
│ │ └── ...
│ └── test/ # Immagini di test (opzionale)
│ └── ...
├── labels/
│ ├── train/ # Annotazioni training
│ │ ├── img001.txt # Una riga per oggetto
│ │ ├── img002.txt
│ │ └── ...
│ ├── val/
│ │ └── ...
│ └── test/
│ └── ...
└── dataset.yaml # Configurazione dataset
# Contenuto di img001.txt (due oggetti):
# class_id x_c y_c w h
# 0 0.45 0.60 0.30 0.40 # gatto al centro
# 1 0.85 0.25 0.20 0.35 # cane in alto a destra
# Contenuto di dataset.yaml:
# path: /path/to/dataset
# train: images/train
# val: images/val
# test: images/test # opzionale
# nc: 2 # numero classi
# names: ['gatto', 'cane']
import json
import os
from pathlib import Path
def convert_coco_to_yolo(coco_json_path: str, output_dir: str) -> None:
"""
Converte annotazioni COCO JSON in formato YOLO TXT.
Utile per dataset pubblici (COCO, Open Images, etc.)
"""
with open(coco_json_path) as f:
coco_data = json.load(f)
# Mappa image_id -> info immagine
images = {img['id']: img for img in coco_data['images']}
# Mappa category_id -> indice YOLO (0-based)
cat_id_to_yolo = {
cat['id']: i
for i, cat in enumerate(coco_data['categories'])
}
# Raggruppa annotazioni per immagine
annotations_by_image: dict[int, list] = {}
for ann in coco_data['annotations']:
img_id = ann['image_id']
if img_id not in annotations_by_image:
annotations_by_image[img_id] = []
annotations_by_image[img_id].append(ann)
output_path = Path(output_dir)
output_path.mkdir(parents=True, exist_ok=True)
for img_id, anns in annotations_by_image.items():
img_info = images[img_id]
img_w = img_info['width']
img_h = img_info['height']
img_name = Path(img_info['file_name']).stem
txt_lines = []
for ann in anns:
# COCO: [x_top_left, y_top_left, width, height]
x_tl, y_tl, w, h = ann['bbox']
# Converti a formato YOLO (normalizzato)
x_c = (x_tl + w / 2) / img_w
y_c = (y_tl + h / 2) / img_h
w_n = w / img_w
h_n = h / img_h
class_idx = cat_id_to_yolo[ann['category_id']]
txt_lines.append(f"{class_idx} {x_c:.6f} {y_c:.6f} {w_n:.6f} {h_n:.6f}")
with open(output_path / f"{img_name}.txt", 'w') as f:
f.write('\n'.join(txt_lines))
print(f"Convertite {len(annotations_by_image)} immagini in {output_dir}")
3.2 Training con Ultralytics YOLOv8
from ultralytics import YOLO
import yaml
from pathlib import Path
# ---- Configurazione dataset ----
dataset_config = {
'path': '/path/to/dataset',
'train': 'images/train',
'val': 'images/val',
'nc': 80, # numero classi (es. COCO)
'names': ['person', 'bicycle', 'car', '...'] # lista classi
}
config_path = 'dataset.yaml'
with open(config_path, 'w') as f:
yaml.dump(dataset_config, f, allow_unicode=True)
# ---- Scelta del modello ----
# Varianti disponibili (velocità vs accuratezza):
# yolov8n.pt - nano (fastest, lowest accuracy)
# yolov8s.pt - small
# yolov8m.pt - medium
# yolov8l.pt - large
# yolov8x.pt - xlarge (slowest, highest accuracy)
model = YOLO('yolov8m.pt') # carica pesi pre-addestrati su COCO
# ---- Training ----
results = model.train(
data=config_path,
epochs=100,
imgsz=640, # dimensione input immagine
batch=16, # batch size (riduci se OOM)
workers=8, # CPU workers per data loading
device='0', # GPU index ('cpu' per CPU-only)
# Ottimizzazione
optimizer='AdamW',
lr0=0.001, # learning rate iniziale
lrf=0.01, # lr finale = lr0 * lrf
momentum=0.937,
weight_decay=0.0005,
warmup_epochs=3,
cos_lr=True, # cosine LR schedule
# Augmentation
mosaic=1.0, # mosaic augmentation (YOLOv5+ feature)
mixup=0.1, # mixup augmentation
copy_paste=0.1, # copy-paste augmentation
degrees=10.0, # rotation
translate=0.1, # translation
scale=0.5, # scaling
fliplr=0.5, # horizontal flip
flipud=0.0, # vertical flip
# Regularization
dropout=0.0,
label_smoothing=0.0,
# Checkpointing
save=True,
save_period=10, # salva ogni N epoche
project='runs/train',
name='yolov8m_custom',
# Early stopping
patience=50, # stop se mAP non migliora per N epoche
# Logging
plots=True,
verbose=True
)
print(f"Best mAP@0.5: {results.results_dict['metrics/mAP50(B)']:.3f}")
print(f"Best model salvato in: runs/train/yolov8m_custom/weights/best.pt")
3.3 Inference e Visualizzazione
from ultralytics import YOLO
import cv2
import numpy as np
from pathlib import Path
class YOLOInferenceEngine:
"""
Engine di inference per YOLOv8/YOLO26 con supporto
per immagini, video e stream live.
"""
def __init__(
self,
model_path: str = 'yolov8m.pt',
conf_threshold: float = 0.25,
iou_threshold: float = 0.45,
device: str = 'auto'
):
self.model = YOLO(model_path)
self.conf = conf_threshold
self.iou = iou_threshold
# Palette colori per classi
np.random.seed(42)
self.colors = np.random.randint(0, 255, size=(100, 3), dtype=np.uint8)
def predict_image(self, image_path: str, save_path: str | None = None) -> list[dict]:
"""Inference su singola immagine con visualizzazione opzionale."""
results = self.model.predict(
source=image_path,
conf=self.conf,
iou=self.iou,
verbose=False
)
detections = []
for r in results:
for box in r.boxes:
det = {
'bbox': box.xyxy[0].tolist(), # [x1, y1, x2, y2]
'confidence': float(box.conf[0]),
'class_id': int(box.cls[0]),
'class_name': r.names[int(box.cls[0])]
}
detections.append(det)
if save_path:
annotated = r.plot()
cv2.imwrite(save_path, annotated)
return detections
def process_video(self, video_path: str, output_path: str | None = None) -> None:
"""
Processa un video file con detection frame per frame.
Mostra FPS e statistiche in tempo reale.
"""
cap = cv2.VideoCapture(video_path)
if output_path:
fps = cap.get(cv2.CAP_PROP_FPS)
w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
writer = cv2.VideoWriter(output_path, fourcc, fps, (w, h))
frame_count = 0
import time
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
start = time.perf_counter()
results = self.model(frame, conf=self.conf, iou=self.iou, verbose=False)
elapsed = time.perf_counter() - start
# Annota il frame
annotated = results[0].plot()
# Overlay FPS
fps_text = f"FPS: {1/elapsed:.1f}"
cv2.putText(annotated, fps_text, (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
if output_path:
writer.write(annotated)
cv2.imshow('YOLO Detection', annotated)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
frame_count += 1
cap.release()
if output_path:
writer.release()
cv2.destroyAllWindows()
print(f"Processati {frame_count} frame")
def run_webcam(self, camera_id: int = 0) -> None:
"""Detection live da webcam."""
print("Avvio webcam detection. Premi 'q' per uscire.")
self.process_video(camera_id, output_path=None)
# Uso pratico
engine = YOLOInferenceEngine('yolov8m.pt', conf_threshold=0.4)
# Inference su immagine
dets = engine.predict_image('test.jpg', save_path='result.jpg')
for d in dets:
print(f"{d['class_name']}: {d['confidence']:.2f} @ {[int(c) for c in d['bbox']]}")
# Inference su video
engine.process_video('traffic.mp4', 'traffic_detected.mp4')
# Webcam live
engine.run_webcam(camera_id=0)
4. Export e Deployment: ONNX, TensorRT, OpenVINO
4.1 Formati di Export
Formati di Export YOLOv8
| Formato | Target | Speedup vs PyTorch | Use Case |
|---|---|---|---|
| ONNX | Multi-platform | 1.5-2x | Portabilita massima |
| TensorRT | NVIDIA GPU | 5-8x | Massima velocità su GPU NVIDIA |
| OpenVINO | Intel CPU/GPU | 3-4x | Server Intel, edge Intel |
| CoreML | Apple Silicon | 3-5x | iOS/macOS deployment |
| TFLite | Mobile/Edge | 2-3x | Android, Raspberry Pi |
| NCNN | ARM CPU | 2-4x | Raspberry Pi, ARM embedded |
from ultralytics import YOLO
model = YOLO('runs/train/yolov8m_custom/weights/best.pt')
# Export ONNX (più portabile)
model.export(format='onnx', imgsz=640, opset=17, simplify=True)
# Export TensorRT (massima velocità su GPU NVIDIA)
# Richiede: NVIDIA GPU + CUDA + TensorRT installato
model.export(
format='engine', # TensorRT Engine
imgsz=640,
half=True, # FP16 per maggiore velocità
workspace=4, # GB di workspace GPU
batch=1, # batch size fisso per TensorRT
device=0
)
# Export OpenVINO (CPU Intel ottimizzata)
model.export(format='openvino', imgsz=640, half=False)
# Export TFLite (mobile/edge)
model.export(format='tflite', imgsz=640, int8=False)
# Carica e usa il modello esportato
# ONNX Runtime (CPU/GPU, no PyTorch)
model_onnx = YOLO('best.onnx')
results = model_onnx.predict('image.jpg', conf=0.25)
# TensorRT (GPU NVIDIA)
model_trt = YOLO('best.engine')
results = model_trt.predict('image.jpg', conf=0.25)
print(f"Inferenza TensorRT completata: {len(results[0].boxes)} oggetti rilevati")
5. YOLO26: Le Novità di Gennaio 2026
Rilasciato da Ultralytics nel gennaio 2026, YOLO26 introduce innovazioni architetturali significative che lo posizionano come il modello di riferimento per il real-time object detection nel 2026.
5.1 Innovazioni Principali
YOLO26 vs YOLOv8: Confronto Tecnico
| Caratteristica | YOLOv8 | YOLO26 |
|---|---|---|
| Backbone | CSP-DarkNet con C2f | Hybrid Attention + C3k2 |
| Neck | PANet | Enhanced PANet con SCDown |
| Head | Anchor-free decoupled | Anchor-free con Dual Head |
| NMS | Standard NMS | Dynamic NMS (appreso) |
| mAP@0.5 (COCO) | 53.9 | 57.2 (+3.3) |
| Inference (ms) | 4.1ms (A100) | 3.8ms (A100) |
# YOLO26 richiede ultralytics >= 8.3.0 (gennaio 2026)
# pip install ultralytics --upgrade
from ultralytics import YOLO
# Carica modello YOLO26
model = YOLO('yolo26n.pt') # nano - massima velocità
model = YOLO('yolo26s.pt') # small
model = YOLO('yolo26m.pt') # medium - bilanciato
model = YOLO('yolo26l.pt') # large
model = YOLO('yolo26x.pt') # xlarge - massima accuratezza
# Training su dataset personalizzato (stessa API di YOLOv8)
results = model.train(
data='dataset.yaml',
epochs=100,
imgsz=640,
batch=16,
device='0',
# YOLO26 aggiunge: self-calibrating augmentation
auto_augment='yolo26', # NEW: augmentation policy ottimizzata
# Dynamic NMS threshold scheduling
nms_schedule=True # NEW: NMS adattivo durante training
)
# Inference (identica a YOLOv8)
results = model.predict('image.jpg', conf=0.25, iou=0.45)
# Validazione sul validation set
metrics = model.val(data='dataset.yaml')
print(f"mAP@0.5: {metrics.box.map50:.3f}")
print(f"mAP@0.5:0.95: {metrics.box.map:.3f}")
6. Hyperparameter Tuning e Strategie di Training Avanzato
Il successo di YOLO dipende tanto dall'architettura quanto dall'ottimizzazione del processo di training. Dalla scelta del learning rate scheduler all'handling del class imbalance, queste tecniche fanno la differenza tra un modello mediocre e uno production-ready.
from ultralytics import YOLO
from ultralytics.utils.callbacks import on_train_epoch_end
import torch
import yaml
from pathlib import Path
# ---- Dataset YAML con class weights per bilanciamento ----
dataset_config = {
'path': './datasets/custom',
'train': 'images/train',
'val': 'images/val',
'nc': 5,
'names': ['person', 'car', 'bicycle', 'dog', 'cat'],
# Pesi per classe: compensa sbilanciamento (persona 5x più frequente)
'cls_weights': [1.0, 1.0, 2.0, 2.5, 2.5]
}
with open('dataset.yaml', 'w') as f:
yaml.dump(dataset_config, f)
# ---- Training con hyperparameters ottimizzati ----
model = YOLO('yolo26m.pt') # Pre-trained YOLO26 medium
results = model.train(
data='dataset.yaml',
epochs=300,
imgsz=640,
batch=16,
device='0',
# ---- Optimizer ----
optimizer='AdamW', # AdamW migliore di SGD per fine-tuning
lr0=0.001, # Learning rate iniziale
lrf=0.01, # LR finale = lr0 * lrf (1/100 dell'iniziale)
momentum=0.937, # Momentum per SGD (ignorato per AdamW)
weight_decay=0.0005, # L2 regularization
# ---- Learning Rate Scheduling ----
cos_lr=True, # Cosine annealing invece di step decay
warmup_epochs=3, # Warmup lineare da lr0/10 a lr0
warmup_momentum=0.8, # Momentum durante warmup
# ---- Augmentation ----
auto_augment='yolo26', # Policy self-calibrating di YOLO26
mosaic=1.0, # Mosaic augmentation (0.0 per disabilitare)
mixup=0.2, # MixUp probability
copy_paste=0.1, # Copy-Paste probability
degrees=10.0, # Rotazione max +/- gradi
translate=0.2, # Traslazione max (fraction of image size)
scale=0.9, # Scale augmentation range [1-scale, 1+scale]
flipud=0.0, # Flip verticale (0 se non sensato)
fliplr=0.5, # Flip orizzontale
# ---- Loss weights ----
box=7.5, # Peso perdita bounding box
cls=0.5, # Peso perdita classificazione
dfl=1.5, # Peso Distribution Focal Loss
# ---- Early stopping e checkpointing ----
patience=50, # Ferma se non migliora per 50 epoch
save_period=25, # Salva checkpoint ogni 25 epoch
val=True, # Valida ad ogni epoch
# ---- Output ----
project='runs/train',
name='yolo26m_custom',
exist_ok=True,
plots=True, # Genera grafici training
verbose=True
)
print(f"Best mAP@0.5: {results.results_dict['metrics/mAP50(B)']:.4f}")
print(f"Best mAP@0.5:0.95: {results.results_dict['metrics/mAP50-95(B)']:.4f}")
# ---- Hyperparameter Auto-Tuning con Ray Tune ----
def tune_hyperparameters(model_path: str, data_path: str) -> None:
"""
Usa Ray Tune per ottimizzare automaticamente gli hyperparameter.
Richiede: pip install ray[tune]
Esplora learning rate, augmentation intensity, loss weights.
"""
model = YOLO(model_path)
# Spazio di ricerca degli hyperparameters
space = {
'lr0': (1e-5, 1e-1), # log-uniform
'lrf': (0.01, 1.0),
'momentum': (0.6, 0.98),
'weight_decay': (0.0, 0.001),
'warmup_epochs': (0, 5),
'warmup_momentum': (0.0, 0.95),
'box': (0.02, 0.2),
'cls': (0.2, 4.0),
'mosaic': (0.0, 1.0),
'mixup': (0.0, 0.5),
'copy_paste': (0.0, 0.3),
}
result = model.tune(
data=data_path,
space=space,
epochs=50, # Epoch per ogni trial
iterations=100, # Numero di configurazioni da provare
optimizer='AdamW',
plots=True,
save=True
)
print("Migliori hyperparameter trovati:")
for k, v in result.items():
print(f" {k}: {v}")
# ---- Custom callback per monitoraggio avanzato ----
class YOLOTrainingMonitor:
"""
Callback per monitoring avanzato durante il training.
Traccia metriche custom e genera alert se necessario.
"""
def __init__(self, alert_threshold: float = 0.3):
self.best_map = 0.0
self.no_improve_count = 0
self.alert_threshold = alert_threshold
self.history = []
def on_train_epoch_end(self, trainer) -> None:
"""Chiamato alla fine di ogni epoch di training."""
metrics = trainer.metrics
current_map = metrics.get('metrics/mAP50(B)', 0.0)
self.history.append({
'epoch': trainer.epoch,
'map50': current_map,
'loss': trainer.loss.item()
})
if current_map > self.best_map:
self.best_map = current_map
self.no_improve_count = 0
else:
self.no_improve_count += 1
# Alert se il modello non migliora dopo 30 epoch
if self.no_improve_count == 30:
print(f"[WARN] Nessun miglioramento per 30 epoch. Best mAP: {self.best_map:.4f}")
# Utilizzo del monitor
model = YOLO('yolo26m.pt')
monitor = YOLOTrainingMonitor()
model.add_callback('on_train_epoch_end', monitor.on_train_epoch_end)
# model.train(data='dataset.yaml', epochs=200)
6.1 Strategie di Data Collection per Training Robusto
La qualità del dataset e più importante dell'architettura. Un YOLO26n addestrato su dati eccellenti supera un YOLO26x addestrato su dati scadenti. Ecco le regole d'oro per la raccolta dati:
Regole d'Oro per Dataset YOLO di qualità
| Aspetto | Requisito Minimo | Ottimale | Motivazione |
|---|---|---|---|
| Immagini per classe | 500 | 2000+ | Più varieta = più generalizzazione |
| Bounding box per immagine | 1-10 | 5-50 (scene reali) | Troppo sparse = modello non impara contesto |
| Varieta condizioni | 2 condizioni luce | Giorno/notte/interno/esterno | Robustezza al domain shift |
| Bilanciamento classi | Max 5:1 ratio | 2:1 o meglio | Evita class dominance |
| Split train/val/test | 70/20/10 | 80/10/10 | Test set mai usato durante sviluppo |
| qualità annotazioni | Inter-annotator agreement > 0.8 | Consensus di 2+ annotatori | Label noise degrada training |
7. Best Practices per Object Detection
Guida alla Scelta del Modello YOLO
| Scenario | Modello Consigliato | Motivazione |
|---|---|---|
| Prototipazione rapida | YOLOv8n / YOLO26n | Veloce da addestrare, facile da debuggare |
| Produzione (GPU server) | YOLO26m / YOLO26l | Miglior bilanciamento accuracy/velocità |
| Edge (Raspberry Pi, Jetson Nano) | YOLOv8n con INT8 | Minimo utilizzo di memoria e compute |
| Massima accuratezza | YOLO26x | Stato dell'arte, richiede GPU potente |
| Oggetti piccoli ad alta densita | YOLOv8m con imgsz=1280 | Input più grande preserva dettaglio |
Errori Comuni
- Dataset non bilanciato: Se una classe ha 10x più immagini delle altre, il modello si specializzera su di essa. Usa weighted sampling o over/undersampling.
- Threshold troppo basso: Conf threshold 0.1 produce molti falsi positivi. Inizia con 0.25 e aumenta fino a ottenere precision accettabile.
- IoU threshold NMS troppo basso: 0.3 elimina box validi in scene dense. Usa 0.45-0.5 per scene con oggetti sovrapposti.
- Immagini di training troppo piccole: YOLO e ottimizzato per 640x640. Training con immagini 320x320 riduce significativamente l'accuratezza su oggetti piccoli.
- Augmentation aggressiva su domini industriali: Mosaic augmentation può essere controproducente su immagini industriali dove il contesto spaziale e importante.
- Dimenticare la normalizzazione per il dominio: YOLO pre-addestrato su COCO. Per domain shift estremi (es. immagini a infrarossi, microscopia), considera training da zero.
Conclusioni
In questo articolo abbiamo costruito una comprensione completa dell'object detection moderna con YOLO, coprendo teoria, pratica e deployment:
- I fondamentali dell'object detection: bounding boxes, IoU, NMS, metriche mAP
- L'architettura YOLO a tre stadi: backbone, neck FPN+PAN, head anchor-free
- L'evoluzione da YOLOv1 a YOLO26 con i contributi chiave di ogni versione
- Training completo su dataset personalizzato con strategie avanzate: LR warmup, cosine annealing, class-weighted loss
- Hyperparameter tuning automatizzato con Ray Tune per trovare la configurazione ottimale
- Export a ONNX, TensorRT, OpenVINO, CoreML, NCNN per deployment su ogni hardware
- Dataset best practices: quantità, varieta, bilanciamento classi e qualità delle annotazioni
- Le novità di YOLO26: Hybrid Attention backbone, Dynamic NMS, +3.3 mAP vs YOLOv8
Navigazione Serie
Risorse Cross-Serie
- MLOps: Model Serving in Produzione - serve il tuo modello YOLO in produzione
- Computer Vision su Edge: Ottimizzazione per Dispositivi Mobili - deploy YOLO26 su Raspberry Pi e Jetson







