Puntos clave personalizados y métricas OKS
Diseña un esquema de puntos clave para objetos no humanos, escribe y comprueba el YAML del conjunto de datos y define sigmas OKS según la precisión con la que se puede colocar cada punto.
La pose no se limita a las personas. Las puntas de las horquillas de una carretilla elevadora, las esquinas de la etiqueta de un paquete, el hocico y la cola de un animal: cualquier objeto con puntos estables e identificables puede formar un conjunto de datos de pose. El esquema que escribas el primer día será permanente: cada etiqueta, cada par de volteo y cada métrica dependen de él, y cambiarlo más adelante implica volver a etiquetarlo todo. En esta lección definirás correctamente el esquema antes de que nadie etiquete mil imágenes y verás exactamente cómo se evaluará con la métrica.
Práctico
Diseña el esquema de puntos clave#
Los buenos puntos clave comparten tres propiedades: inequívocos (dos anotadores colocan el punto en el mismo sitio), estables (el punto existe en todas las poses que adopta el objeto) y útiles (algún cálculo posterior los utiliza). Pensemos en una carretilla elevadora vista desde una cámara cenital, donde la pregunta del producto es «¿están elevadas las horquillas mientras se mueve la carretilla?»:
| Índice | Punto clave | Por qué existe | Punto correspondiente al reflejar |
|---|---|---|---|
| 0 | left_fork_tip | Extensión y dirección de las horquillas | 1 |
| 1 | right_fork_tip | Extensión y dirección de las horquillas | 0 |
| 2 | mast_top | Altura de las horquillas respecto al mástil | 2 (sí mismo) |
| 3 | mast_base | Punto de referencia en el chasis | 3 (sí mismo) |
Cada punto se justifica porque contribuye al cálculo: las puntas de las horquillas indican la dirección y el par del mástil indica la altura. Evalúa cada punto candidato con los mismos criterios:
Evita añadir puntos «por si acaso»: cada uno cuesta tiempo de etiquetado en cada instancia, y los puntos que nadie utiliza solo añaden ruido a la métrica. Los conjuntos de datos públicos muestran la variedad: Tiger-Pose utiliza 12 puntos sin visibilidad, Dog-Pose, 24, y Hand Keypoints, 21:


Escribe el YAML del conjunto de datos#
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_basekpt_namesdocumenta el orden de cada clase, tanto para personas como para herramientas.flip_idxdebe deshacerse a sí mismo: al aplicarlo dos veces, cada índice vuelve a sí mismo.kpt_oks_sigmases opcional y pondera tanto la pérdida de puntos clave durante el entrenamiento como OKS durante la validación; si se incluye, debe contener un valor positivo por cada punto clave o la ejecución se detiene con unValueError.
En Ultralytics Platform, el mismo esquema se utiliza como plantilla de esqueleto. Una plantilla nueva puede partir de una existente (Partir de…), así que renombras y eliminas puntos en lugar de colocar cada punto clave a mano; mantén el orden de los índices de la tabla del esquema:

Comprueba las etiquetas antes del entrenamiento#
Los conjuntos de datos de pose pueden fallar sin avisar: si falta un punto clave en una fila, todos los valores posteriores se desplazan, y un valor de visibilidad de 3 procedente de un exportador defectuoso se interpreta como cualquier otro número. Este comprobador carga el YAML con el cargador propio de Ultralytics y valida todas las filas. Funciona tal cual con COCO8-pose; cambia la ruta del YAML por la tuya:
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 rowsvalues[7::3] empieza en el primer indicador de visibilidad (clase, cuatro valores del cuadro y, después, x y v) y toma cada tercer valor a partir de ahí.
Cómo evalúa OKS un punto clave#
La mAP de pose sustituye la IoU de los cuadros por la similitud de puntos clave de objetos (OKS). Para cada punto clave etiquetado, OKS convierte la distancia en píxeles entre la predicción y la etiqueta en una puntuación de 0 a 1, escalada según el tamaño del objeto y el sigma de ese punto clave, y luego calcula la media de los puntos clave etiquetados. La mAP50-95 de pose calcula la media de la precisión en umbrales OKS de 0.50 a 0.95, igual que la mAP de cuadros en los umbrales IoU; la guía de métricas de rendimiento explica el sistema común.
La forma más sencilla de entender los sigmas es medirlos. Este script evalúa la misma predicción de una carretilla elevadora con dos sigmas mediante la función kpt_iou que utiliza el validador de pose:
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.10En una carretilla elevadora de 200 × 120 píxeles, una predicción con 10 píxeles de error no supera el umbral OKS de 0.50 con un sigma de 0.025, pero supera todos los umbrales hasta 0.90 con un sigma de 0.10. Mismo modelo, mismo error, pero una mAP de pose muy distinta.
| Sigma | Efecto | Uso |
|---|---|---|
| Pequeño (≈ 0.025–0.05) | unos pocos píxeles de error ya reducen la puntuación | Puntos precisos: ojos, puntas de horquillas, esquinas |
| Grande (≈ 0.08–0.10) | Tolerancia amplia | Puntos difusos: caderas, centro de un mástil |
| Predeterminado | Sigmas de COCO para kpt_shape: [17, 3]; de lo contrario, 1/K para cada punto | Empieza por aquí y luego mide |
Los sigmas deben reflejar la consistencia con la que los anotadores colocan cada punto. Mídela: pide a dos personas que etiqueten las mismas 50 imágenes y compara las distancias de cada punto clave. Los puntos en los que discrepen deben tener sigmas mayores; de lo contrario, la métrica penaliza al modelo por el ruido humano.
Cambiar kpt_oks_sigmas cambia el mAP de pose aunque el modelo no cambie, como muestra la tabla anterior. Fija los sigmas antes de establecer la primera línea de base, regístralos con cada resultado y no compares nunca ejecuciones validadas con valores distintos.
Mostrar solución
Ejemplo de un paquete en una cinta transportadora, visto desde arriba, donde la pregunta es «¿la etiqueta está boca arriba y en qué dirección está girada?»:
| Índice | Punto clave | Finalidad | Punto correspondiente al reflejar | Sigma |
|---|---|---|---|---|
| 0 | label_top_left | Orientación de la etiqueta | 1 | 0.04 |
| 1 | label_top_right | Orientación de la etiqueta | 0 | 0.04 |
| 2 | label_bottom_right | Orientación de la etiqueta | 3 | 0.04 |
| 3 | label_bottom_left | Orientación de la etiqueta | 2 | 0.04 |
kpt_shape: [4, 3]
flip_idx: [1, 0, 3, 2]
kpt_oks_sigmas: [0.04, 0.04, 0.04, 0.04]flip_idx supera la comprobación de involución: 0 → 1 → 0 y 2 → 3 → 2. Una etiqueta reflejada intercambia izquierda y derecha, pero mantiene arriba y abajo, así que la esquina superior izquierda se corresponde con la superior derecha, no con la inferior derecha.
Para encontrar la tolerancia, define sigma=[0.04] * 4 en oks.py y amplía los desplazamientos; el cuadro de la etiqueta determina la escala, así que utiliza un cuadro del tamaño de una etiqueta de paquete típica de tus imágenes. El desplazamiento en el que OKS baja por primera vez de 0.5 es el error máximo que la precisión de tus anotadores permite al modelo.