Ultralytics YOLO27: Trag dich in die Warteliste ein.
Get Started
LektionFortgeschrittene

Benutzerdefinierte Keypoints und OKS-Metriken

Entwirf ein Keypoint-Schema für nicht menschliche Objekte, erstelle und überprüfe das Dataset-YAML und lege OKS-Sigmas anhand der Platzierungsgenauigkeit der einzelnen Punkte fest.

Pose ist nicht nur für Personen geeignet. Gabelspitzen an einem Gabelstapler, Ecken eines Paketetiketts, Nase und Schwanz eines Tiers – jedes Objekt mit stabilen, eindeutig benennbaren Punkten eignet sich für einen Pose-Datensatz. Das Schema, das du am ersten Tag festlegst, bleibt dauerhaft bestehen: Jedes Label, jedes Spiegelungspaar und jede Metrik hängt davon ab. Eine spätere Änderung bedeutet, alles neu zu labeln. In dieser Lektion legst du das Schema fest, bevor jemand tausend Bilder labelt, und erfährst genau, wie die Metrik es bewertet.

Lernergebnis

Erstelle ein Dataset-YAML für ein benutzerdefiniertes Keypoint-Schema mit Spiegelungspaaren und OKS-Sigmas, einen Label-Prüfer, der echte Daten fehlerfrei verarbeitet, und eine Vorhersage dazu, wie viele Pixel Abweichung jedes Sigma toleriert.

Schnellkurs
Wenn du dich bereits auskennst, findest du hier die Kurzfassung.
  1. Wähle 3–12 Punkte aus, die eindeutig sind, in jeder Pose vorkommen und von nachgelagertem Code verwendet werden.

  2. Lege die Reihenfolge fest und erstelle dann flip_idx, indem du jeweils die linken und rechten Gegenstücke vertauschst; mittige Punkte werden sich selbst zugeordnet.

  3. Füge kpt_oks_sigmas hinzu, wenn sich die Punkte darin unterscheiden, wie genau Annotatoren sie platzieren können.

  4. Führe vor dem Training einen Label-Prüfer für die Spaltenanzahl, Sichtbarkeitswerte und ein selbstinverses flip_idx aus.

Praxisnah

Das Keypoint-Schema entwerfen#

Gute Keypoints haben drei Eigenschaften gemeinsam: eindeutig (zwei Annotatoren platzieren den Punkt an derselben Stelle), stabil (der Punkt kommt in jeder Pose des Objekts vor) und nützlich (eine nachgelagerte Berechnung verwendet ihn). Betrachte einen Gabelstapler aus der Perspektive einer Decken-Kamera. Die Produktfrage lautet: „Sind die Gabeln angehoben, während der Stapler fährt?“:

IndexKeypointGrund für die AufnahmeSpiegelungspartner
0left_fork_tipReichweite und Richtung der Gabeln1
1right_fork_tipReichweite und Richtung der Gabeln0
2mast_topHöhe der Gabeln relativ zum Hubmast2 (sich selbst)
3mast_baseBezugspunkt am Fahrgestell3 (sich selbst)

Jeder Punkt ist enthalten, weil er in die Berechnung einfließt: Die Gabelspitzen geben die Richtung an, das Mastpaar die Höhe. Prüfe jeden möglichen Punkt anhand derselben Kriterien:

Widerstehe der Versuchung, Punkte „für alle Fälle“ hinzuzufügen: Jeder einzelne kostet bei jedem Beispiel Labeling-Zeit, und Punkte, die niemand verwendet, fügen der Metrik nur Rauschen hinzu. Öffentliche Datensätze zeigen die Bandbreite: Tiger-Pose verwendet 12 Punkte ohne Sichtbarkeit, Dog-Pose 24 und Hand Keypoints 21:

Hand-Keypoint-Schema mit 21 nummerierten Orientierungspunkten, von 0 am Handgelenk über die Gelenke jedes Fingers bis zur Spitze des kleinen Fingers bei 20

Dog-Pose-Beispiele: ein Welpe mit einer Hunde-Bounding-Box und farbigen Keypoints an Gesicht, Beinen und Körper

Das Dataset-YAML erstellen#

path: forklift-pose
train: images/train
val: images/val

kpt_shape: [4, 3] # 4 keypoints, each x, y, visibility
flip_idx: [1, 0, 2, 3] # fork tips swap, mast points stay
kpt_oks_sigmas: [0.05, 0.05, 0.08, 0.08] # tips are sharp, mast ends are fuzzier

names:
  0: forklift

kpt_names:
  0:
    - left_fork_tip
    - right_fork_tip
    - mast_top
    - mast_base
  • kpt_names dokumentiert die Reihenfolge der Punkte für Personen und Werkzeuge, jeweils pro Klasse.
  • flip_idx muss seine eigene Zuordnung aufheben: Bei zweimaliger Anwendung wird jeder Index wieder sich selbst zugeordnet.
  • kpt_oks_sigmas ist optional und gewichtet sowohl den Keypoint-Verlust beim Training als auch OKS bei der Validierung. Falls vorhanden, muss es einen positiven Wert pro Keypoint enthalten, andernfalls wird der Lauf mit einem ValueError abgebrochen.

Auf der Ultralytics-Plattform entspricht dasselbe Schema einer Skelettvorlage. Eine neue Vorlage kann von einer vorhandenen ausgehen (Vorlage verwenden…). So kannst du Punkte umbenennen und entfernen, statt jeden Keypoint von Hand zu platzieren – behalte die Indexreihenfolge aus deiner Schema-Tabelle bei:

Dialog „Neue Skelettvorlage“ auf der Ultralytics-Plattform: Ein 17-Punkte-Skelett einer Person wurde von einer vorhandenen Vorlage übernommen; die aufgelisteten Keypoints reichen von nose bis left_wrist und können umbenannt werden

Labels vor dem Training prüfen#

Fehler in Pose-Datensätzen bleiben oft unbemerkt: Fehlt in einer Zeile ein Keypoint, verschieben sich alle nachfolgenden Werte; ein Sichtbarkeitswert von 3 aus einem fehlerhaften Export wird wie jede andere Zahl eingelesen. Dieser Prüfer lädt das YAML mit dem eigenen Loader von Ultralytics und validiert jede Zeile. Er funktioniert unverändert mit COCO8-pose; ersetze den YAML-Pfad durch deinen eigenen:

from pathlib import Path

from ultralytics.data.utils import check_det_dataset

data = check_det_dataset("coco8-pose.yaml")  # resolves paths, downloads if needed
k, dims = data["kpt_shape"]
flip = data.get("flip_idx", [])
assert not flip or sorted(flip) == list(range(k)), "flip_idx must list every keypoint index once"
assert all(flip[flip[i]] == i for i in range(len(flip))), "flip_idx must undo itself: pairs swap, center points stay"

expected = 5 + k * dims  # class + 4 box values + k keypoints of `dims` values
bad = 0
for label in Path(data["path"], "labels").rglob("*.txt"):
    for n, row in enumerate(label.read_text().splitlines(), 1):
        values = row.split()
        if len(values) != expected:
            bad += 1
            print(f"{label.name}:{n} has {len(values)} values, expected {expected}")
        elif dims == 3 and not {float(v) for v in values[7::3]} <= {0, 1, 2}:
            bad += 1
            print(f"{label.name}:{n} has a visibility flag outside 0/1/2")
print(f"kpt_shape={data['kpt_shape']}, flip_idx ok, {bad} bad rows")
kpt_shape=[17, 3], flip_idx ok, 0 bad rows

values[7::3] beginnt beim ersten Sichtbarkeitswert (Klasse, vier Box-Werte, dann x y v) und nimmt anschließend jeden dritten Wert.

So bewertet OKS einen Keypoint#

Pose mAP ersetzt die Box-IoU durch Object-Keypoint-Ähnlichkeit (OKS). Für jeden gelabelten Keypoint wandelt OKS den Pixelabstand zwischen Vorhersage und Label in einen Wert zwischen 0 und 1 um, skaliert nach der Objektgröße und dem Sigma des jeweiligen Keypoints, und bildet anschließend den Durchschnitt über die gelabelten Keypoints. Pose mAP50-95 ist der Durchschnitt der Präzision bei OKS-Schwellenwerten von 0,50 bis 0,95 – genau wie Box-mAP über IoU-Schwellenwerte. Der Leitfaden zu Leistungsmetriken behandelt die gemeinsame Berechnungsgrundlage.

Sigmas lassen sich am einfachsten verstehen, indem man sie misst. Dieses Skript bewertet dieselbe Gabelstapler-Vorhersage mit zwei Sigmas und verwendet dabei die Funktion kpt_iou, die der Pose-Validator aufruft:

import torch

from ultralytics.utils.metrics import kpt_iou

# One labeled forklift, 4 keypoints (x, y, visibility) in pixels, inside a 200 x 120 px box
label = torch.tensor([[[40.0, 30, 2], [40, 90, 2], [170, 20, 2], [170, 100, 2]]])
area = torch.tensor([200 * 120 * 0.53])  # Ultralytics scales box area by 0.53 to approximate object area

for offset in (2, 5, 10, 20):
    pred = label.clone()
    pred[..., 0] += offset  # every predicted keypoint lands `offset` pixels to the right
    sharp = kpt_iou(label, pred, area, sigma=[0.025] * 4).item()
    loose = kpt_iou(label, pred, area, sigma=[0.10] * 4).item()
    print(f"{offset:2d} px off: OKS {sharp:.2f} with sigma 0.025, {loose:.2f} with sigma 0.10")
 2 px off: OKS 0.94 with sigma 0.025, 1.00 with sigma 0.10
 5 px off: OKS 0.67 with sigma 0.025, 0.98 with sigma 0.10
10 px off: OKS 0.21 with sigma 0.025, 0.91 with sigma 0.10
20 px off: OKS 0.00 with sigma 0.025, 0.67 with sigma 0.10

Bei einem 200 × 120 Pixel großen Gabelstapler verfehlt eine Vorhersage mit 10 Pixel Abweichung den OKS-Schwellenwert 0,50 bei einem Sigma von 0,025 und besteht alle Schwellenwerte bis 0,90 bei einem Sigma von 0,10. Dasselbe Modell, derselbe Fehler, aber eine ganz andere Pose-mAP.

SigmaAuswirkungVerwendung für
Klein (≈ 0,025–0,05)Schon wenige Pixel Abweichung kosten Punktescharf abgegrenzte Punkte: Augen, Gabelspitzen, Ecken
Groß (≈ 0,08–0,10)großzügige Toleranzunscharf abgegrenzte Punkte: Hüften, Mitte eines Masts
StandardCOCO-Sigmas für kpt_shape: [17, 3]; andernfalls 1/K für jeden PunktHier beginnen und dann messen

Sigmas sollten widerspiegeln, wie einheitlich Annotatoren jeden Punkt platzieren. Miss die Abweichung: Lass zwei Personen dieselben 50 Bilder labeln und vergleiche die Abstände für jeden Keypoint. Für Punkte, bei denen sie voneinander abweichen, sind größere Sigmas angebracht. Andernfalls bestraft die Metrik das Modell für menschliche Ungenauigkeit.

Metriken sind nur innerhalb desselben Schemas vergleichbar

Eine Änderung von kpt_oks_sigmas verändert die Pose-mAP, ohne das Modell zu verändern, wie die Tabelle oben zeigt. Lege die Sigmas vor deiner ersten Baseline fest, dokumentiere sie bei jedem Ergebnis und vergleiche niemals Läufe, die mit unterschiedlichen Werten validiert wurden.

Ausprobieren

Entwirf ein Keypoint-Schema für ein Objekt aus deinem eigenen Anwendungsbereich: Liste 3–8 Keypoints mit jeweils einer kurzen Begründung, einem Spiegelungspartner und einem Sigma auf. Schreibe das YAML, führe check_pose_labels.py mit COCO8-pose und anschließend mit einigen eigenen Labels aus und verwende oks.py, um herauszufinden, wie viele Pixel Abweichung dein präzisester Punkt toleriert, bevor OKS unter 0,5 fällt.

Abgeschlossen, wenn
Du hast die Lektion abgeschlossen, wenn alle folgenden Punkte zutreffen.
  • Deine Schema-Tabelle benennt jeden Keypoint, seinen Zweck, seinen Spiegelungspartner und sein Sigma.

  • Dein YAML enthält kpt_shape, ein selbstinverses flip_idx und (falls verwendet) ein positives Sigma pro Keypoint. Der Prüfer meldet in deiner Stichprobe 0 fehlerhafte Zeilen.

  • Du kannst angeben, ab welcher Pixelabweichung dein präzisester Keypoint bei OKS 0,5 nicht mehr zählt.

Lösung anzeigen

Beispiel für ein Paket auf einem Förderband, von oben betrachtet. Die Frage lautet: „Zeigt das Etikett nach oben, und in welche Richtung ist es gedreht?“:

IndexKeypointZweckSpiegelungspartnerSigma
0label_top_leftAusrichtung des Etiketts10.04
1label_top_rightAusrichtung des Etiketts00.04
2label_bottom_rightAusrichtung des Etiketts30.04
3label_bottom_leftAusrichtung des Etiketts20.04
kpt_shape: [4, 3]
flip_idx: [1, 0, 3, 2]
kpt_oks_sigmas: [0.04, 0.04, 0.04, 0.04]

flip_idx besteht die Prüfung auf Selbstinversität: 0 → 1 → 0 und 2 → 3 → 2. Ein gespiegeltes Etikett vertauscht links und rechts, behält aber oben und unten bei. Daher wird oben links mit oben rechts gepaart, nicht mit unten rechts.

Um die Toleranz zu bestimmen, setze sigma=[0.04] * 4 in oks.py und erweitere die Abweichungswerte. Die Box des Etiketts bestimmt den Maßstab. Verwende daher eine Box, die der Größe eines typischen Paketetiketts in deinen Bildern entspricht. Die Abweichung, bei der OKS erstmals unter 0,5 fällt, ist der größte Fehler, den die Genauigkeit deiner Annotatoren dem Modell erlaubt.

Wie es weitergeht

Als Nächstes befassen wir uns mit Objekten, bei denen die Ausrichtung wichtig ist, deren Form aber ein einfaches Rechteck ist: mit orientierten Bounding-Boxes.