Ultralytics YOLO27: Iscriviti alla lista d'attesa.
Get Started
Fondamenti della visione artificiale·Dataset ed etichette·Lezione 7/10
Lezioneprincipiante

Qualità delle annotazioni

Scopri cosa contiene un'etichetta YOLO, controlla i file delle etichette per individuare righe errate e mantieni la coerenza tra gli annotatori con una linea guida e una sessione di calibrazione misurata.

Un modello addestrato su etichette incoerenti apprende l'incoerenza. Cinquantamila riquadri disegnati seguendo una regola chiara valgono più di mezzo milione disegnati secondo venti interpretazioni personali, e rietichettare è quasi sempre più economico che usare un modello più grande. Questa lezione ti fornisce un verificatore delle etichette, una linea guida di una pagina e un valore che misura l'accordo tra annotatori.

Esito

Controlla i file delle etichette di un dataset, scrivi una linea guida di una pagina per una classe e misura l'accordo tra due annotatori.

Percorso rapido
Se sai già come muoverti, ecco la versione breve.
  1. Individua la riga dell'etichetta per la tua attività: riquadro, poligono, keypoint o angoli.

  2. Controlla ogni file delle etichette: numero di colonne, ID delle classi, coordinate comprese tra 0 e 1, riquadri duplicati.

  3. Linea guida di una pagina con un'immagine di esempio per ogni regola e un elenco di ciò che non va etichettato.

  4. Prima, una sessione di calibrazione: tutti etichettano le stesse 50 immagini; aumenta la scala solo quando l'accordo supera la soglia.

  5. I revisori respingono le annotazioni indicando un motivo; non le correggono mai senza segnalarlo.

Pratico

Cos'è davvero un'etichetta#

Nel formato YOLO, ogni immagine ha un file .txt corrispondente, con una riga per ogni oggetto. Le coordinate sono normalizzate tra 0 e 1 rispetto alla larghezza e all'altezza dell'immagine, così le etichette restano valide quando l'immagine viene ridimensionata. La struttura della riga dipende dall'attività:

TaskUn'etichettaNote
Rilevamentoclass x_center y_center width heightCentro e dimensioni del riquadro
Segmentazione di istanzeclass x1 y1 x2 y2 ... xn ynContorno del poligono
Stima della posaclass x y w h px1 py1 v1 ... pxn pyn vnRiquadro, poi keypoint con visibilità facoltativa
OBBclass x1 y1 x2 y2 x3 y3 x4 y4Quattro angoli del riquadro ruotato
ClassificazioneNessun file delle etichetteImmagini suddivise in una cartella per classe
Segmentazione semanticaUna maschera PNG per immagineOgni valore dei pixel è un indice di classe
Stima della profonditàUn PNG o NPY di profondità per immagineOgni valore dei pixel è una distanza

Gli indici delle classi partono da 0 e corrispondono ai nomi nel YAML del dataset. Un'immagine senza oggetti non richiede un file delle etichette: è così che funzionano le immagini di sfondo. Le pagine dei dataset di rilevamento, semantico e di profondità contengono esempi pratici.

Controlla i file delle etichette#

Le righe errate passano inosservate: una coordinata superiore a 1, un ID di classe non definito nel YAML o lo stesso oggetto racchiuso due volte diventano rumore che il modello cerca di apprendere. Un breve script di controllo li individua prima dell'addestramento:

from itertools import combinations
from pathlib import Path

from ultralytics.data.utils import check_det_dataset

def iou(a, b):
    """IoU of two normalized (x_center, y_center, width, height) boxes."""
    ax1, ay1, ax2, ay2 = a[0] - a[2] / 2, a[1] - a[3] / 2, a[0] + a[2] / 2, a[1] + a[3] / 2
    bx1, by1, bx2, by2 = b[0] - b[2] / 2, b[1] - b[3] / 2, b[0] + b[2] / 2, b[1] + b[3] / 2
    inter = max(0, min(ax2, bx2) - max(ax1, bx1)) * max(0, min(ay2, by2) - max(ay1, by1))
    return inter / (a[2] * a[3] + b[2] * b[3] - inter)

def check_labels(text, names):
    """Return a list of problems in one YOLO detection label file."""
    problems, boxes = [], []
    for i, row in enumerate(text.split("\n"), 1):
        values = row.split()
        if not values:
            continue
        if len(values) != 5:
            problems.append(f"row {i}: {len(values)} values, expected 5")
        elif int(values[0]) not in names:
            problems.append(f"row {i}: unknown class {values[0]}")
        elif not all(0 <= float(v) <= 1 for v in values[1:]):
            problems.append(f"row {i}: coordinates outside 0-1")
        else:
            boxes.append((i, int(values[0]), [float(v) for v in values[1:]]))
    for (i, ca, a), (j, cb, b) in combinations(boxes, 2):
        if ca == cb and iou(a, b) > 0.8:
            problems.append(f"row {j}: duplicate of row {i} (IoU {iou(a, b):.2f})")
    return problems

broken = "0 0.50 0.50 0.20 0.30\n0 0.51 0.50 0.20 0.30\n3 0.40 0.40 0.10 0.10\n0 1.20 0.50 0.10 0.10\n0 0.50 0.50 0.10"
print("\n".join(check_labels(broken, {0: "pill"})))

data = check_det_dataset("medical-pills.yaml")
files = sorted(Path(data["path"], "labels").rglob("*.txt"))
found = sum(len(check_labels(f.read_text(), data["names"])) for f in files)
print(f"medical-pills: {found} problems in {len(files)} label files")
row 3: unknown class 3
row 4: coordinates outside 0-1
row 5: 4 values, expected 5
row 2: duplicate of row 1 (IoU 0.90)
medical-pills: 0 problems in 115 label files

L'esempio creato a mano mostra ogni errore rilevato dallo script; il dataset reale supera il controllo senza problemi. Eseguilo dopo ogni gruppo di annotazioni e prima di ogni sessione di addestramento: bastano pochi secondi.

Perché le etichette divergono#

Editor di annotazione di Ultralytics Platform a schermo intero: scatoloni di cartone su robot da magazzino delineati con poligoni e l'elenco delle classi aperto a fianco

Osserva il pannello delle classi in questo editor: i 14 poligoni degli scatoloni sono salvati sotto Grapes, mentre una classe Box compare più in basso nell'elenco; inoltre, plane e Plane esistono come due classi separate. Un verificatore non può individuare nessuno dei due problemi, perché ogni riga è valida, ma un revisore che confronta le etichette con la linea guida li noterebbe entrambi in pochi secondi. La maggior parte degli errori di etichettatura è di questo tipo: file validi, decisioni sbagliate.

Raramente gli annotatori etichettano male per disattenzione. Le loro annotazioni sono incoerenti perché le regole sono ambigue e ciascuno fa una supposizione ragionevole ma diversa:

  • Dove finisce il riquadro? Include l'ombra, una borsa trasportata, il carico sulle forche di un muletto?
  • Troncamento. Una persona per metà fuori dall'inquadratura è ancora person? Quale frazione deve essere visibile?
  • Occlusione. Se è visibile solo una ruota di un'auto, va etichettata o ignorata?
  • Confini tra classi. Un pick-up è car o truck? Uno scooter è bicycle o other?
  • Casi da non etichettare. Riflessi, poster e schermi che mostrano l'oggetto.

Ogni scelta va bene, purché tutti facciano la stessa.

Una linea guida di una pagina#

Deve stare su una pagina, includere un'immagine di esempio per ogni regola e presentare per primi i casi limite:

## Class: forklift

Include:
- Forklifts with at least 30% of the body visible, moving or parked, any size or color.

Do not label:
- Pallet jacks (no overhead mast) → class `pallet_jack`.
- Reflections in glass or polished floors, and forklifts on posters or screens.

Box rules:
- Include the mast and the full visible body.
- Exclude the load (pallets, boxes) — those have their own classes.
- For partly occluded forklifts, box the visible part only.

Misura l'accordo durante una sessione di calibrazione#

Prima di passare da un annotatore a venti, tutti etichettano le stesse 50 immagini e confronti i risultati:

L'accordo è la quota di oggetti che entrambi gli annotatori hanno disegnato con la stessa classe e un IoU di almeno 0,5: la versione pratica dell'affidabilità inter-valutatore:

def iou(a, b):
    """IoU of two (x1, y1, x2, y2) pixel boxes."""
    w = max(0, min(a[2], b[2]) - max(a[0], b[0]))
    h = max(0, min(a[3], b[3]) - max(a[1], b[1]))
    inter = w * h
    return inter / ((a[2] - a[0]) * (a[3] - a[1]) + (b[2] - b[0]) * (b[3] - b[1]) - inter)

def agreement(boxes_a, boxes_b, threshold=0.5):
    """Share of objects both annotators drew with the same class and IoU >= threshold."""
    unmatched, matched = list(boxes_b), 0
    for cls_a, box_a in boxes_a:
        best = max(unmatched, key=lambda b: iou(box_a, b[1]) if b[0] == cls_a else -1, default=None)
        if best and best[0] == cls_a and iou(box_a, best[1]) >= threshold:
            unmatched.remove(best)
            matched += 1
    return matched / max(len(boxes_a), len(boxes_b))

# Two annotators label the same calibration image
alice = [("forklift", (100, 200, 400, 600)), ("person", (450, 220, 520, 480)), ("person", (600, 230, 660, 470))]
bob = [("forklift", (100, 120, 400, 600)), ("person", (452, 218, 522, 478)), ("pallet_jack", (600, 230, 660, 470))]
print(f"agreement: {agreement(alice, bob):.0%}")
agreement: 67%

Quel 67% nasconde due tipi di disaccordo. Il riquadro del muletto di Bob parte 80 pixel più in alto — ha incluso la punta del montante — ma ha comunque un IoU di 0,83, quindi conta come accordo; se il tuo prodotto misura l'altezza, definisci meglio la regola per il riquadro. Il terzo oggetto è un vero conflitto: Alice dice person, Bob dice pallet_jack. Serve una regola più chiara e un'immagine di esempio, poi un'altra sessione. Conserva le 50 immagini di calibrazione: diventeranno il test di onboarding per ogni nuovo annotatore.

I revisori rifiutano, non correggono di nascosto

Un revisore che corregge silenziosamente le etichette elimina il ciclo di feedback e chi annota non impara mai cosa ha sbagliato. Rifiuta l'etichetta indicando il motivo e lascia che sia l'annotatore a correggerla. È più lento per le prime cento immagini, ma molto più rapido in seguito.

Cosa monitorare#

MetricaCosa indica
Produttività (oggetti all'ora)Capacità e costi
Tasso di rifiutoSe la qualità sta peggiorando
Accordo su un insieme di sovrapposizione condivisoSe gli annotatori continuano ad applicare le stesse regole

Quando l'accordo diminuisce, qualcosa è cambiato: un nuovo annotatore, una nuova fotocamera, un gruppo di immagini ambiguo, la stanchezza. Quando in seguito il modello fallisce in una condizione specifica, rimanda quelle stesse immagini all'etichettatura: è il ciclo che l'apprendimento attivo automatizza e da cui derivano la maggior parte dei miglioramenti di accuratezza nel mondo reale. Esplora e filtra gli errori nei tuoi dataset su Ultralytics Platform e consulta Controllo qualità dei dataset e Buone pratiche di annotazione per applicare questo processo su larga scala.

Provalo

Esegui lo script di controllo sulla cartella delle etichette del tuo dataset. Poi scrivi una linea guida di una pagina per la classe più ambigua, con un'immagine di esempio per ogni regola e un elenco di ciò che non va etichettato. Dai la linea guida e 10 immagini non etichettate a un collega senza spiegargli nulla, etichetta le stesse immagini tu e calcola l'accordo con lo script. Registra il punteggio e ogni regola modificata a causa di un disaccordo.

Criteri di completamento
La lezione è completata quando tutte le seguenti condizioni sono soddisfatte.
  • Lo script di controllo non segnala problemi nelle tue etichette oppure hai corretto tutti quelli individuati.

  • Hai una linea guida di una pagina con un esempio per ogni regola e un elenco di ciò che non va etichettato.

  • Hai un punteggio di accordo per due annotatori e un elenco delle regole che hai modificato in base al risultato.

Mostra la soluzione

Una linea guida per la classe pill di medical-pills, scritta dopo una sessione di calibrazione su 10 immagini:

## Class: pill

Include:
- Every tablet inside a blister cavity, including half-visible ones at the image edge.
- Tablets seen through clear plastic, even when glare covers part of them.

Do not label:
- Empty cavities, torn foil, or tablets outside the pack.
- Reflections of tablets in the foil.

Box rules:
- Box the tablet itself, not the plastic bubble around it.
- If two tablets touch, draw two boxes.

Come interpretare il punteggio di accordo: supponiamo che la prima sessione ottenga 82%. Ordina gli oggetti non corrispondenti in base alla causa. Sui blister, i due problemi più comuni sono i riquadri disegnati intorno alla bolla di plastica invece che alla compressa (risolto con la prima regola sui riquadri e un'immagine di esempio) e le compresse coperte dai riflessi ignorate da un annotatore (risolto con la seconda regola di inclusione). Ripeti la prova su 10 immagini nuove; aumenta la scala solo quando il punteggio supera la soglia del 90%.

Prossimi passi

Prossimo passo: suddividere i dati etichettati in modo che le tue metriche siano affidabili, iniziando da una fuga di dati nascosta proprio in questo dataset.

Accedi per salvare i progressi