Benutzerdefinierten Datensatz vorbereiten
Ordne Bilder und Beschriftungen im Ultralytics-YOLO-Format an, wandle Pixel-Bounding-Boxes in normalisierte Beschriftungszeilen um und finde fehlerhafte Beschriftungen, bevor du trainierst.
Ein falsch benannter Ordner oder eine nicht normalisierte Beschriftung lässt das Training nicht abstürzen. Ultralytics überspringt die fehlerhaften Bilder und macht weiter. So trainierst du mit einem Teil deiner Daten, erhältst ein mittelmäßiges Modell und verlierst einen Nachmittag damit, die Ursache an der falschen Stelle zu suchen.
In dieser Lektion geht es um die praktische Umsetzung eines Erkennungsdatensatzes: Ordner, Beschriftungszeilen, Konvertierung und Prüfungen. Du arbeitest mit dem echten Datensatz mit 115 medizinischen Pillenbildern, mit dem auch der Rest dieses Kurses trainiert. Wie du entscheidest, was du sammelst und wie du es beschriftest, wird unter Bewährte Methoden für die Annotation behandelt.
Praxisnah
Übungsdatensatz abrufen#

Die folgende Struktur kann auch im Dialogfeld „New Dataset“ der Ultralytics Platform als ZIP-Datei hochgeladen werden. Ein hier vorbereiteter Datensatz lässt sich also unverändert hochladen. Lade 92 Trainings- und 23 Validierungsbilder von Pillen in Blisterpackungen herunter und entpacke sie nach my_dataset:
curl -L -o medical-pills.zip https://github.com/ultralytics/assets/releases/download/v0.0.0/medical-pills.zip
unzip -q medical-pills.zip -d my_datasetmy_dataset/
├── images/
│ ├── train/ Frame_0.jpg, Frame_100.jpg, ... (92 images)
│ └── val/ Frame_144.jpg, ... (23 images)
└── labels/
├── train/ Frame_0.txt, Frame_100.txt, ... (92 files)
└── val/ Frame_144.txt, ... (23 files)Es gibt kein Manifest. Ultralytics findet jede Beschriftung, indem /images/ im Bildpfad durch /labels/ und die Dateierweiterung durch .txt ersetzt wird. Daher liest images/val/Frame_144.jpg die Datei labels/val/Frame_144.txt. Daraus folgen zwei Dinge:
- Die Ordner müssen genau
imagesundlabelsheißen und nebeneinander liegen. - Ein Bild mit einer leeren oder fehlenden Beschriftungsdatei ist ein Hintergrundbild: gültig und nützlich, um dem Modell beizubringen, was es nicht erkennen soll – aber nur, wenn das beabsichtigt ist.
Beschriftungszeile lesen#

Jede Zeile steht für ein Objekt: zuerst ein ganzzahliger Klassenindex, dann Mittelpunkt und Größe der Bounding-Box als Bruchteile der Bildbreite und -höhe. Eine echte Zeile aus labels/train/Frame_0.txt:
0 0.330729 0.816204 0.103125 0.1212960: Klassenindex 0, den die YAML-Dateipillzuordnet.0.330729 0.816204: Der Mittelpunkt der Box liegt bei 33 % der Bildbreite und 82 % der Bildhöhe.0.103125 0.121296: Die Box ist etwa 10 % der Bildbreite und 12 % der Bildhöhe groß.
Beispiel: Pixel-Bounding-Box umwandeln#
Beschriftungswerkzeuge exportieren häufig Pixelkoordinaten der Ecken (x1, y1, x2, y2). Teile sie durch die Bildgröße. Die Person ganz links im 810×1080 großen bus.jpg aus der letzten Lektion erstreckt sich von (49, 398) bis (240, 902):
| Feld | Formel | Wert |
|---|---|---|
x_center | (x1 + x2) / 2 / Breite | (49 + 240) / 2 / 810 = 0.178395 |
y_center | (y1 + y2) / 2 / Höhe | (398 + 902) / 2 / 1080 = 0.601852 |
width | (x2 − x1) / Breite | 191 / 810 = 0.235802 |
height | (y2 − y1) / Höhe | 504 / 1080 = 0.466667 |
Die meisten Beschriftungswerkzeuge und Tools wie Roboflow können direkt im YOLO-Format exportieren. Für COCO JSON folge dem Leitfaden zum Training mit COCO JSON; das Repository JSON2YOLO behandelt weitere Formate. Für Pascal-VOC-XML lässt sich die obige Formel in ein kurzes Skript übertragen:
import xml.etree.ElementTree as ET
from pathlib import Path
CLASSES = ["person", "bus"] # index = class id; must match names in data.yaml
def voc_to_yolo(xml_file: Path, out_dir: Path) -> None:
root = ET.parse(xml_file).getroot()
w, h = int(root.find("size/width").text), int(root.find("size/height").text)
lines = []
for obj in root.iter("object"):
name = obj.find("name").text
if name not in CLASSES:
continue
box = obj.find("bndbox")
x1, y1, x2, y2 = (float(box.find(k).text) for k in ("xmin", "ymin", "xmax", "ymax"))
xc, yc, bw, bh = (x1 + x2) / 2 / w, (y1 + y2) / 2 / h, (x2 - x1) / w, (y2 - y1) / h
lines.append(f"{CLASSES.index(name)} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}")
out_dir.mkdir(parents=True, exist_ok=True)
(out_dir / f"{xml_file.stem}.txt").write_text("\n".join(lines) + "\n")
voc_to_yolo(Path("bus.xml"), Path("labels"))
print(Path("labels/bus.txt").read_text())Bei Ausführung auf einem bus.xml mit den Personen- und Bus-Bounding-Boxes aus der vorherigen Lektion gibt das Skript das berechnete Beispiel und den Bus aus:
0 0.178395 0.601852 0.235802 0.466667
1 0.495679 0.454167 0.991358 0.482407Vor dem Training prüfen#
Zeichne einige Beschriftungsdateien auf den zugehörigen Bildern ein. Eine verschobene Klasse, vertauschte x- und y-Koordinaten oder Boxen um Schatten sind mit bloßem Auge erkennbar, in Metriken aber unsichtbar:
from ultralytics.data.utils import visualize_image_annotations
visualize_image_annotations(
"my_dataset/images/train/Frame_0.jpg",
"my_dataset/labels/train/Frame_0.txt",
{0: "pill"},
)Lass Ultralytics anschließend alle Dateien prüfen. Beim ersten Einlesen eines Datensatzes durchsucht das Programm die Beschriftungen und entscheidet für jede Zeile, ob das jeweilige Bild verwendbar ist:
Die Prüfzeile zeigt das Ergebnis an: 23 images, 0 backgrounds, 0 corrupt steht für einen fehlerfreien Datensatz. Alles andere weist auf einen der unten aufgeführten Fehler hin.
Diese Fehler vermeiden#
Symptom: ignoring corrupt image/label: non-normalized or out of bounds coordinates [634. 881. 198. 163.] und eine Anzahl ungleich null für corrupt. Ursache: Pixelwerte statt Bruchteile. Lösung: Teile durch Bildbreite und Bildhöhe. Jeder Labelwert über 1 bedeutet, dass die Box nie normalisiert wurde.
Symptom: yolo val durchsucht 0 images, 23 backgrounds, meldet No labels found als Warnung und bricht das Training mit ValueError: train: No labels found ab – hier, weil der Label-Ordner annotations hieß. Lösung: Benenne die Ordner exakt images und labels und verwende übereinstimmende Dateinamen.
Symptom: Label class 1 exceeds dataset class count 1. Possible class labels are 0-0. Ursache: Das Exportwerkzeug hat die Klassen ab 1 nummeriert. Lösung: YOLO-Klassen beginnen bei 0; ziehe im Konvertierungsskript 1 ab oder korrigiere die Exporteinstellung.
Wenn die Boxen um eine halbe Boxhöhe nach unten und nach rechts verschoben gezeichnet werden, hast du x1 y1 w h statt x_center y_center w h geschrieben. Eine Datei zu zeichnen deckt das in Sekunden auf; keine Prüfung beim Durchsuchen kann das leisten.
Systematische Prüfungen für einen ganzen Datensatz findest du unter Qualitätskontrolle von Datensätzen. Integrierte Datensätze wie coco8, coco128 und african-wildlife eignen sich als Vorlagen für die Ordnerstruktur; die Übersicht der Datensätze führt sie alle auf. Der Trainer ergänzt deine Beschriftungen um Datenerweiterung. Eine kurze Datenbereinigung zahlt sich daher in jeder Epoche aus.
Lösung anzeigen
for s in train val; do
echo "$s: $(ls my_dataset/images/$s | wc -l) images, $(ls my_dataset/labels/$s | wc -l) labels"
done
# train: 92 images, 92 labels
# val: 23 images, 23 labelsfrom pathlib import Path
for txt in Path("my_dataset/labels").rglob("*.txt"):
for line in txt.read_text().splitlines():
values = [float(v) for v in line.split()[1:]]
if any(v < 0 or v > 1 for v in values):
print(txt, line)Wenn das Skript nichts ausgibt, sind alle Koordinaten normalisiert. Gleiche Anzahlen pro Split bedeuten, dass jedes Bild eine Beschriftungsdatei hat; die Prüfzeile der nächsten Lektion bestätigt, dass keine davon beschädigt ist.