Salta al contenuto principale

03 - YOLO26: Rilevamento Oggetti Real-Time

Guida completa a YOLO26: Rilevamento Oggetti Real-Time

Cosa ne pensi di questo articolo?

03 - YOLO26: Rilevamento Oggetti Real-Time
Computer VisionFoto:Ruben BoekelooPexels

In Breve

Guida completa a YOLO26: Rilevamento Oggetti Real-Time

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:

Formato Output YOLO
# 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).

IoU e NMS da Zero
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

Architettura YOLO (Schema Generale)
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).

Struttura Dataset YOLO
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']
Conversione Annotazioni da COCO a YOLO
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

Training Completo 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

Inference su Immagini, Video e Webcam
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
Export a Tutti i Formati con YOLOv8
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: Utilizzo con Ultralytics
# 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.

Training Avanzato: LR Scheduling, Warmup, Early Stopping e Callbacks
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

Risorse Cross-Serie

Prova Questi Strumenti

Strumenti gratuiti correlati a questo argomento.

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti