Pontos-chave personalizados e métricas OKS
Projete um esquema de pontos-chave para objetos não humanos, crie e verifique o YAML do conjunto de dados e defina sigmas do OKS conforme a precisão com que cada ponto pode ser posicionado.
A pose não serve apenas para pessoas. As pontas dos garfos de uma empilhadeira, os cantos da etiqueta de um pacote, o focinho e a cauda de um animal — qualquer objeto com pontos estáveis e identificáveis pode compor um conjunto de dados de pose. O esquema que você define no primeiro dia é permanente: cada rótulo, cada par de inversão e cada métrica depende dele, e alterá-lo depois significa rotular tudo de novo. Nesta lição, você definirá o esquema corretamente antes que alguém rotule mil imagens e verá exatamente como a métrica o avaliará.
Prático
Projete o esquema de pontos-chave#
Pontos-chave bem definidos compartilham três propriedades: inequívocos (dois anotadores colocam o ponto no mesmo lugar), estáveis (o ponto existe em todas as poses do objeto) e úteis (algum cálculo subsequente usa o ponto). Considere uma empilhadeira vista por uma câmera no teto, quando a pergunta do produto é "os garfos estão levantados enquanto o veículo se move?":
| Índice | Ponto-chave | Por que existe | Ponto correspondente na inversão |
|---|---|---|---|
| 0 | left_fork_tip | extensão e direção dos garfos | 1 |
| 1 | right_fork_tip | extensão e direção dos garfos | 0 |
| 2 | mast_top | altura dos garfos em relação ao mastro | 2 (ele próprio) |
| 3 | mast_base | ponto de referência no chassi | 3 (ele próprio) |
Cada ponto justifica sua inclusão ao alimentar o cálculo: as pontas dos garfos indicam a direção, e o par do mastro indica a altura. Avalie cada ponto candidato com os mesmos critérios:
Evite adicionar pontos "por precaução": cada um consome tempo de rotulagem em todas as instâncias, e pontos que ninguém usa só acrescentam ruído à métrica. Conjuntos de dados públicos mostram a variedade: Tiger-Pose usa 12 pontos sem visibilidade, Dog-Pose, 24, e Hand Keypoints, 21:


Escreva o YAML do conjunto de dados#
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 a ordem dos pontos para pessoas e ferramentas, por classe.flip_idxprecisa desfazer a si mesmo: aplicá-lo duas vezes retorna cada índice ao próprio valor.kpt_oks_sigmasé opcional e pondera tanto a perda de pontos-chave durante o treinamento quanto o OKS durante a validação; se estiver presente, deve conter um valor positivo por ponto-chave ou a execução será interrompida com umValueError.
Na Ultralytics Platform, o mesmo esquema é um modelo de esqueleto. Um novo modelo pode partir de um modelo existente (Começar de…), para que você renomeie e remova pontos em vez de posicionar cada ponto-chave manualmente — mantenha a ordem dos índices da tabela do esquema:

Verifique os rótulos antes do treinamento#
Conjuntos de dados de pose podem falhar sem aviso: se faltar um ponto-chave em uma linha, todos os valores seguintes ficam deslocados; e uma visibilidade igual a 3, resultado de um exportador com defeito, é interpretada como qualquer outro número. Este verificador carrega o YAML com o próprio carregador da Ultralytics e valida cada linha. Ele funciona sem alterações com COCO8-pose; basta substituir pelo caminho do seu YAML:
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] começa no primeiro indicador de visibilidade (classe, quatro valores da caixa e, em seguida, x y v) e considera depois disso um valor a cada três.
Como o OKS avalia um ponto-chave#
O mAP de pose substitui a IoU da caixa pela Similaridade de Pontos-Chave de Objetos (OKS). Para cada ponto-chave rotulado, o OKS converte a distância em pixels entre a previsão e o rótulo em uma pontuação de 0 a 1, dimensionada pelo tamanho do objeto e pelo sigma desse ponto-chave, e calcula a média dos pontos-chave rotulados. O mAP50-95 de pose calcula a média da precisão nos limiares de OKS de 0.50 a 0.95, exatamente como o mAP de caixa faz com os limiares de IoU; o guia de métricas de desempenho explica o mecanismo compartilhado.
A maneira mais fácil de entender os sigmas é medi-los. Este script avalia a mesma previsão da empilhadeira com dois sigmas, usando a função kpt_iou chamada pelo 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.10Em uma empilhadeira de 200 × 120 pixels, uma previsão com erro de 10 pixels não atinge o limiar de OKS de 0.50 com sigma 0.025 e passa por todos os limiares até 0.90 com sigma 0.10. Mesmo modelo, mesmo erro, mAP de pose bem diferente.
| Sigma | Efeito | Uso |
|---|---|---|
| Pequeno (≈ 0.025–0.05) | um erro de poucos pixels já reduz a pontuação | pontos bem definidos: olhos, pontas dos garfos, cantos |
| Grande (≈ 0.08–0.10) | tolerância ampla | pontos imprecisos: quadris, meio de um mastro |
| Padrão | Sigmas COCO para kpt_shape: [17, 3]; caso contrário, 1/K para cada ponto | comece por aqui e depois meça |
Os sigmas devem refletir a consistência com que os anotadores posicionam cada ponto. Faça a medição: peça a duas pessoas que rotulem as mesmas 50 imagens e compare as distâncias por ponto-chave. Pontos com maior divergência entre anotadores devem receber sigmas maiores; caso contrário, a métrica penaliza o modelo pelo ruído humano.
Alterar kpt_oks_sigmas altera o mAP de pose sem alterar o modelo, como mostra a tabela acima. Define os sigmas antes da primeira medição de referência, regista-os com cada resultado e nunca compares execuções validadas com valores diferentes.
Mostrar solução
Exemplo de um pacote em uma esteira transportadora, visto de cima, quando a pergunta é "a etiqueta está virada para cima e em que direção está girada?":
| Índice | Ponto-chave | Finalidade | Ponto correspondente na inversão | Sigma |
|---|---|---|---|---|
| 0 | label_top_left | orientação da etiqueta | 1 | 0.04 |
| 1 | label_top_right | orientação da etiqueta | 0 | 0.04 |
| 2 | label_bottom_right | orientação da etiqueta | 3 | 0.04 |
| 3 | label_bottom_left | orientação da 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 passa na verificação de ser sua própria inversa: 0 → 1 → 0 e 2 → 3 → 2. Uma etiqueta espelhada troca esquerda e direita, mas mantém a parte de cima e a de baixo; portanto, o canto superior esquerdo corresponde ao superior direito, não ao inferior direito.
Para encontrar a tolerância, defina sigma=[0.04] * 4 em oks.py e aumente os deslocamentos; a caixa da etiqueta define a escala, então use uma caixa do tamanho de uma etiqueta de pacote típica nas suas imagens. O deslocamento em que o OKS cai pela primeira vez abaixo de 0.5 é o maior erro que a precisão dos seus anotadores permite ao modelo.