Ultralytics YOLO27: Iscriviti alla lista d'attesa.
Get Started
Creare dataset YOLO ad alte prestazioni·Etichettatura·Lezione 5/10
Lezioneprincipiante

Controllo qualità del dataset

Verifica le etichette, il bilanciamento delle classi, i duplicati e la copertura degli split con uno script; chiedi a un modello di segnalare le probabili etichette mancanti e correggi i problemi prima del training.

Correggere le etichette di solito aumenta la precisione più che ottimizzare gli iperparametri. Spesso i team trascorrono settimane a regolare le impostazioni per guadagnare uno o due punti di mAP, per poi scoprire durante un pomeriggio di verifica che una parte di una classe era stata etichettata in modo errato fin dall'inizio. Il controllo qualità rileva ciò che la guida per l'annotazione non ha individuato, prima che un'esecuzione di training trasformi gli errori nelle etichette in un numero di cui nessuno dovrebbe fidarsi.

Esito

Esegui un controllo automatico delle etichette e una revisione assistita dal modello degli elementi sospetti, correggi i problemi rilevati e annota il risultato in brevi note di QC.

Percorso rapido
Se sai già come muoverti, ecco la versione breve.
  1. Automatizza i conteggi: istanze per classe, percentuale di immagini di sfondo, classi non valide, duplicati e classi assenti dal train.

  2. Chiedi a un modello preaddestrato di segnalare le rilevazioni sicure senza un'etichetta corrispondente e controllale a occhio.

  3. Correggi lo sbilanciamento delle classi nell'ordine giusto: prima raccogli altri dati, poi aumenta il sovracampionamento e, come ultima risorsa, applica pesi alla loss.

  4. Annota ciò che hai controllato e corretto.

Pratico

I sette controlli#

Esegui tutti e sette i controlli prima della prima esecuzione di training e di nuovo dopo ogni lotto di annotazione importante. La guida alla preelaborazione dei dati annotati ne approfondisce alcuni.

#ControlloCosa rilevaCome trovarlo
1Etichette mancantiOggetti ignorati da chi ha annotatoRevisione degli elementi sospetti qui sotto; campione visivo
2Classi errate o non valideClassi scambiate, ID non presenti nell'elenco delle classiScript di verifica; matrice di confusione dopo una prima esecuzione
3Riquadri troppo larghiRiquadri con margini o bordi tagliatiCampione visivo; segnalazioni di riquadri troppo piccoli dallo script di verifica
4DuplicatiCopie esatte e fotogrammi quasi identiciScript di verifica per le copie esatte; vista Clustering di Ultralytics Platform, colorata in base allo split, per i quasi duplicati
5Sbilanciamento delle classiClassi molto al di sotto degli obiettivi indicati nelle specificheScript di verifica; scheda Grafici della Platform
6Copertura degli split e data leakageClassi assenti da uno split; una clip presente in due splitScript di verifica; split raggruppati (lezione successiva)
7Percentuale di immagini di sfondoTroppe poche o troppe immagini vuoteScript di verifica; obiettivo: 0–10%

Automatizza il conteggio#

I controlli 2, 4, 5, 6 e 7 riguardano conteggi, quindi automatizzali con uno script. Questo controllo legge un file YAML per i dati di rilevamento, i cui train: e val: indicano le cartelle delle immagini. Conta le istanze per classe e le immagini di sfondo in ogni split, segnala gli ID di classe non validi, i riquadri troppo piccoli, le righe delle etichette duplicate e le immagini identiche byte per byte, ed elenca le classi presenti in val ma mai in train:

import hashlib
from collections import Counter
from pathlib import Path

from ultralytics.data.utils import IMG_FORMATS, check_det_dataset, img2label_paths

data = check_det_dataset("coco8.yaml")  # downloads COCO8 on first run; swap in your data YAML
names, seen = data["names"], {}

for split in ("train", "val"):
    images = sorted(p for p in Path(data[split]).rglob("*.*") if p.suffix[1:].lower() in IMG_FORMATS)
    instances, hashes, problems, background = Counter(), Counter(), [], 0
    for img, label in zip(images, img2label_paths([str(p) for p in images])):
        hashes[hashlib.md5(img.read_bytes()).hexdigest()] += 1
        rows = [r.split() for r in Path(label).read_text().splitlines() if r.strip()] if Path(label).exists() else []
        background += not rows
        if len(rows) != len({tuple(r) for r in rows}):
            problems.append(f"{img.name}: duplicate label rows")
        for r in rows:
            cls, w, h = int(r[0]), float(r[3]), float(r[4])
            if cls not in names:
                problems.append(f"{img.name}: class {cls} is not in names")
            elif w * h < 1e-4:
                problems.append(f"{img.name}: tiny {names[cls]} box")
            instances[cls] += 1
    seen[split] = set(instances)
    duplicates = sum(n - 1 for n in hashes.values())
    print(f"{split}: {len(images)} images, {background} background, {duplicates} duplicate images")
    for cls, n in instances.most_common():
        print(f"  {names.get(cls, cls):>12}  {n}")
    for problem in problems:
        print("  !", problem)

print("in val but never in train:", sorted(names[c] for c in seen["val"] - seen["train"] if c in names))
train: 4 images, 0 background, 0 duplicate images
        orange  4
          bowl  3
       giraffe  2
...
val: 4 images, 0 background, 0 duplicate images
        person  10
         horse  2
...
in val but never in train: ['dog', 'elephant', 'horse', 'person', 'umbrella']

L'ultima riga è il risultato del controllo. person ha 10 istanze in val e nessuna in train, quindi val valuta una classe su cui il modello non è mai stato addestrato. COCO8 è un set di 8 immagini per i test rapidi, quindi in questo caso è previsto; nel tuo dataset significa che devi correggere lo split o raccogliere altri dati prima del training. Quando ci sono problemi, vengono stampati sotto il rispettivo split come ! 000000000025.jpg: class 99 is not in names o ! 000000000009.jpg: duplicate label rows.

Chiedi a un modello di segnalare le etichette sospette#

Le etichette mancanti sono l'errore più dannoso e il più difficile da individuare scorrendo le immagini. Un modello preaddestrato può segnalarle: una rilevazione sicura senza alcuna etichetta nelle vicinanze indica un'etichetta mancante o un errore del modello, e vale la pena controllare entrambi i casi. Questo script funziona su COCO128:

from pathlib import Path

import torch

from ultralytics import YOLO
from ultralytics.data.utils import check_det_dataset, img2label_paths
from ultralytics.utils.metrics import box_iou
from ultralytics.utils.ops import xywh2xyxy

data = check_det_dataset("coco128.yaml")  # swap in your data YAML
model = YOLO("yolo26n.pt")  # a pretrained model, or your own first fine-tune
images = sorted(Path(data["train"]).glob("*.jpg"))

suspects = []
for result, label in zip(model(images, conf=0.6, stream=True, verbose=False), img2label_paths(map(str, images))):
    rows = [r.split() for r in Path(label).read_text().splitlines() if r.strip()] if Path(label).exists() else []
    boxes = xywh2xyxy(torch.tensor([[float(v) for v in r[1:5]] for r in rows]).reshape(-1, 4))
    iou = box_iou(result.boxes.xyxyn, boxes)  # model boxes vs labeled boxes
    for j, (cls, conf) in enumerate(zip(result.boxes.cls.int().tolist(), result.boxes.conf.tolist())):
        if not rows or iou[j].max() < 0.3:  # confident detection with no label anywhere near it
            suspects.append((conf, Path(result.path).name, model.names[cls]))

print(f"{len(suspects)} confident detections with no matching label; top 5:")
for conf, name, cls in sorted(suspects, reverse=True)[:5]:
    print(f"  {conf:.2f}  {cls:<12} {name}")
4 confident detections with no matching label; top 5:
  0.77  donut        000000000438.jpg
  0.75  person       000000000531.jpg
  0.71  donut        000000000438.jpg
  0.66  bottle       000000000436.jpg

Su 128 immagini, lo script riduce il controllo a tre. Aprendole si trovano entrambi i tipi di risultati:

  • 000000000438.jpg mostra un riquadro con una dozzina di ciambelle, in cui due rilevazioni sicure non si sovrappongono ad alcuna ciambella etichettata: possibili etichette mancanti o posizionate male.
  • 000000000531.jpg segnala una «persona» che in realtà è l'ombra del fotografo su un campo da tennis. L'etichetta è corretta e il modello ha sbagliato; è proprio per questo che una persona deve controllare ogni elemento sospetto, invece di aggiungerlo automaticamente.

Abbassa conf per ampliare la ricerca e, quando ne avrai uno, usa il tuo primo modello ottimizzato, perché conosce le tue classi.

Leggi i grafici del dataset#

Scheda Grafici di Ultralytics Platform per il dataset medical-pills: Split Distribution mostra 80 immagini di train e 20 di validazione; Top Classes mostra pill con 1,755 annotazioni e tablet con 0

Su Ultralytics Platform, la scheda Grafici del dataset visualizza la distribuzione degli split, il conteggio delle classi e le statistiche dei riquadri (documentazione dei dataset). Questa schermata mostra già un problema rilevato dal controllo qualità: l'elenco contiene due classi, ma tablet ha 0 annotazioni su 1,755. Una classe senza etichette non può essere appresa. Etichettala oppure rimuovila dall'elenco delle classi prima del training.

Correggi lo sbilanciamento delle classi nell'ordine giusto#

   forklift  ████████████████████████ 8000 instances
   person    ████████████████ 5400
   pallet    ██ 700                              ← red flag

Una differenza come questa produce un modello sicuro nelle rilevazioni dei carrelli elevatori, ma che non rileva i pallet. Tre soluzioni, in ordine:

  1. Raccogli e annota più dati della classe minoritaria. È sempre la soluzione migliore: punta ad almeno 1.500 immagini e 10.000 istanze per ogni classe.
  2. Aumenta il sovracampionamento solo nel train. Inserisci più volte nell'elenco dei file di train le immagini che contengono la classe minoritaria, così ogni epoca le vede più spesso. Non includere le copie in val e test.
  3. Applica pesi alla loss. L'argomento di training cls_pw (0.0 per impostazione predefinita, fino a 1.0 per una ponderazione completa inversamente proporzionale alla frequenza) aumenta il peso della loss di classificazione per le classi rare. Ricorri a questa soluzione solo dopo i punti 1 e 2.
Non eliminare mai le immagini difficili per alzare il punteggio

Rimuovere dal set di validazione immagini sfocate, affollate o ambigue fa aumentare mAP senza migliorare il modello: lo rende solo meno affidabile. Se un'immagine è stata davvero acquisita durante la produzione, tienila e correggine le etichette. Rimuovi solo i veri valori anomali (errore 9) e i duplicati esatti.

Scrivi note di QC#

Bastano poche righe per rispondere alla domanda «che cosa hai controllato?» quando, mesi dopo, te lo chiederanno un cliente o un auditor:

Dataset: forklift-v1   Audit date: 2026-05-09
Images: 4,820   Labels: 18,442   Background: 380 (7.9%)
Audit script: 3 invalid class IDs fixed, 41 duplicate label rows removed
Suspect review: 62 flagged, 19 missing labels added, 43 model errors ignored
Visual sample: 120 images, 4 mislabeled (3.3%), fixed
Split check: grouped by camera-day, every class in every split
Imbalance: pallet 2.5x below person; collecting more next round
Ready to train: yes
Provalo

Indica i tuoi file YAML dei dati con qc_audit.py e suspect_labels.py (oppure usa COCO8 e COCO128) ed esegui gli script. Correggi ogni problema segnalato, apri tutte le immagini sospette e stabilisci se si tratta di un errore nell'etichetta o nel modello, poi esegui di nuovo entrambi gli script. Risultato: qc-notes.md con i conteggi prima e dopo le correzioni.

Criteri di completamento
La lezione è completata quando tutte le seguenti condizioni sono soddisfatte.
  • Lo script di verifica non segnala classi non valide, righe delle etichette duplicate o classi assenti dal train.

  • Ogni elemento sospetto rilevato durante la revisione assistita dal modello è stato aperto e risolto correggendo l'etichetta o identificando un errore del modello.

  • Le note di QC riportano ciò che è stato controllato, ciò che è stato rilevato e ciò che è stato corretto.

Mostra la soluzione

Note di QC per le due esecuzioni dimostrative di questa lezione:

Dataset: COCO8 + COCO128 demo   Audit date: 2026-05-09
qc_audit.py (COCO8): 0 invalid classes, 0 duplicate rows, 0 duplicate images
  FINDING: dog, elephant, horse, person, umbrella appear in val only
  ACTION: re-split by group or collect these classes into train before training
suspect_labels.py (COCO128, conf 0.6): 4 suspects in 3 images
  000000000438.jpg: 2 donuts without matching labels -> relabel the donut box
  000000000531.jpg: "person" is the photographer's shadow -> model error, label kept
  000000000436.jpg: bottle -> open and decide
Ready to train: no, fix the val-only classes first

Nota l'ultima riga. Un dataset in cui una classe compare solo in val non supera il controllo qualità, anche se ogni singola etichetta è corretta, perché la metrica di quella classe misura qualcosa che il modello non ha mai avuto la possibilità di apprendere.

Prossimi passi

Con le etichette corrette, la lezione successiva suddivide il dataset in modo che i punteggi di validazione riflettano la produzione invece della memoria.

Accedi per salvare i progressi