Phase 04 - Lesson 21
Detecção de Keypoints e Estimativa de Pose
This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.
Uma pose é um conjunto ordenado de keypoints. Um detector de keypoints é um regressor de heatmaps. Todo o resto é contabilidade.
Tipo: Build Linguagens: Python Pré-requisitos: Fase 4 Lição 06 (Detecção), Fase 4 Lição 07 (U-Net) Tempo: ~45 minutos
Objetivos de Aprendizagem
- Distinguir estimativa de pose top-down e bottom-up e indicar quando cada uma é usada
- Regredir heatmaps para K keypoints com um alvo Gaussiano por keypoint e extrair as coordenadas dos keypoints na inferência
- Explicar os Part Affinity Fields (PAFs) e como os pipelines bottom-up associam keypoints em instâncias
- Usar o MediaPipe Pose ou o MMPose para estimativa de keypoints em produção e entender seu formato de saída
O Problema
Tarefas de keypoints se escondem sob muitos nomes: pose humana (17 articulações do corpo), landmarks faciais (68 ou 478 pontos), mão (21 pontos), pose de animais, pose de objetos robóticos, landmarks anatômicos médicos. Cada uma delas compartilha a mesma estrutura: detectar K pontos discretos em um objeto e produzir suas coordenadas (x, y).
A estimativa de pose é a base de captura de movimento, apps de fitness, análise esportiva, controle por gestos, animação, prova de roupas em AR e preensão robótica. O caso 2D está maduro; a pose 3D (estimar posições de articulações em coordenadas de mundo a partir de uma única câmera) é a fronteira atual da pesquisa.
A questão de engenharia é escala. Uma pose de uma única pessoa em uma única imagem é um problema de 20ms. Pose de múltiplas pessoas em uma multidão a 30 fps é um problema diferente, com arquiteturas diferentes.
O Conceito
Top-down vs bottom-up
flowchart LR
subgraph TD["Pipeline top-down"]
A1["Detectar caixas de pessoas"] --> A2["Recortar cada caixa"]
A2 --> A3["Modelo de keypoints por caixa<br/>(HRNet, ViTPose)"]
end
subgraph BU["Pipeline bottom-up"]
B1["Uma passada sobre a imagem"] --> B2["Todos os heatmaps de keypoints<br/>+ campo de associação"]
B2 --> B3["Agrupar keypoints em<br/>instâncias (matching guloso)"]
end
style TD fill:#dbeafe,stroke:#2563eb
style BU fill:#fef3c7,stroke:#d97706
- Top-down — detectar as pessoas primeiro e, em seguida, rodar um modelo de keypoints por pessoa em cada recorte. A maior acurácia; escala linearmente com o número de pessoas.
- Bottom-up — uma única passada para frente prevê todos os keypoints mais um campo de associação; agrupe-os. Tempo constante independente do tamanho da multidão.
Top-down (HRNet, ViTPose) é o líder em acurácia; bottom-up (OpenPose, HigherHRNet) é o líder em throughput para cenas com aglomeração.
Regressão de heatmaps
Em vez de regredir (x, y) diretamente, preveja um heatmap H x W por keypoint com um blob Gaussiano centrado na localização verdadeira.
target[k, y, x] = exp(-((x - cx_k)^2 + (y - cy_k)^2) / (2 sigma^2))
Na inferência, o argmax de cada heatmap é a localização prevista do keypoint.
Por que heatmaps funcionam melhor que regressão direta: a estrutura espacial da rede (o mapa de features convolucional) se alinha naturalmente com a saída espacial. Alvos Gaussianos também regularizam — um pequeno erro de localização produz uma perda pequena, não zero.
Localização sub-pixel
O argmax fornece coordenadas inteiras. Para precisão sub-pixel, refine ajustando uma parábola ao argmax e seus vizinhos, ou use a conhecida direção de offset (dx, dy) = 0.25 * (heatmap[y, x+1] - heatmap[y, x-1], ...).
Part Affinity Fields (PAFs)
O truque do OpenPose para associação bottom-up. Para cada par de keypoints conectados (por exemplo, ombro esquerdo até cotovelo esquerdo), preveja um campo de 2 canais que codifica o vetor unitário apontando de um para o outro. Para associar um ombro ao seu cotovelo, integre o PAF ao longo da linha que conecta os pares candidatos; o par com a maior integral é o escolhido.
For each connection (limb):
PAF channels: 2 (unit vector x, y)
Line integral: sum over sample points of (PAF . line_direction)
Higher integral = stronger match
Elegante e escala para multidões de tamanho arbitrário sem recortes por pessoa.
Keypoints do COCO
O dataset padrão de pose corporal: 17 keypoints por pessoa, com PCK (Percentage of Correct Keypoints) e OKS (Object Keypoint Similarity) como métricas. OKS é o análogo do IoU para keypoints e é o que o mAP@OKS do COCO reporta.
2D vs 3D
- Pose 2D — coordenadas de imagem; resolvida com qualidade de produção (MediaPipe, HRNet, ViTPose).
- Pose 3D — coordenadas de mundo / câmera; ainda é pesquisa ativa. Abordagens comuns:
- Elevar previsões 2D para 3D com um pequeno MLP (VideoPose3D).
- Regressão 3D direta a partir da imagem (PyMAF, MHFormer).
- Setups multi-vista (CMU Panoptic) para ground truth.
Construindo
Passo 1: Alvo de heatmap Gaussiano
import numpy as np
import torch
def gaussian_heatmap(size, cx, cy, sigma=2.0):
yy, xx = np.meshgrid(np.arange(size), np.arange(size), indexing="ij")
return np.exp(-((xx - cx) ** 2 + (yy - cy) ** 2) / (2 * sigma ** 2)).astype(np.float32)
hm = gaussian_heatmap(64, 32, 32, sigma=2.0)
print(f"peak: {hm.max():.3f} at ({hm.argmax() % 64}, {hm.argmax() // 64})")
Heatmaps por keypoint empilhados ao longo de um eixo de canal formam o tensor de alvo completo.
Passo 2: Cabeça de keypoint minúscula
Um modelo no estilo U-Net que produz K canais de heatmap.
import torch.nn as nn
import torch.nn.functional as F
class TinyKeypointNet(nn.Module):
def __init__(self, num_keypoints=4, base=16):
super().__init__()
self.down1 = nn.Sequential(nn.Conv2d(3, base, 3, 2, 1), nn.ReLU(inplace=True))
self.down2 = nn.Sequential(nn.Conv2d(base, base * 2, 3, 2, 1), nn.ReLU(inplace=True))
self.mid = nn.Sequential(nn.Conv2d(base * 2, base * 2, 3, 1, 1), nn.ReLU(inplace=True))
self.up1 = nn.ConvTranspose2d(base * 2, base, 2, 2)
self.up2 = nn.ConvTranspose2d(base, num_keypoints, 2, 2)
def forward(self, x):
h1 = self.down1(x)
h2 = self.down2(h1)
h3 = self.mid(h2)
u1 = self.up1(h3)
return self.up2(u1)
Entrada (N, 3, H, W), saída (N, K, H, W). A perda é o MSE por pixel contra os alvos Gaussianos.
Passo 3: Inferência — extrair coordenadas dos keypoints
def heatmap_to_coords(heatmaps):
"""
heatmaps: (N, K, H, W)
returns: (N, K, 2) float coordinates in image pixels
"""
N, K, H, W = heatmaps.shape
hm = heatmaps.reshape(N, K, -1)
idx = hm.argmax(dim=-1)
ys = (idx // W).float()
xs = (idx % W).float()
return torch.stack([xs, ys], dim=-1)
coords = heatmap_to_coords(torch.randn(2, 4, 32, 32))
print(f"coords: {coords.shape}") # (2, 4, 2)
Uma linha na inferência. Para refinamento sub-pixel, interpole ao redor do argmax.
Passo 4: Dataset sintético de keypoints
Simples: desenhe quatro pontos em uma tela branca e aprenda a prevê-los.
def make_synthetic_sample(size=64):
img = np.ones((3, size, size), dtype=np.float32)
rng = np.random.default_rng()
kps = rng.integers(8, size - 8, size=(4, 2))
for cx, cy in kps:
img[:, cy - 2:cy + 2, cx - 2:cx + 2] = 0.0
hms = np.stack([gaussian_heatmap(size, cx, cy) for cx, cy in kps])
return img, hms, kps
Fácil o suficiente para um modelo minúsculo aprender em um minuto.
Passo 5: Treinamento
model = TinyKeypointNet(num_keypoints=4)
opt = torch.optim.Adam(model.parameters(), lr=3e-3)
for step in range(200):
batch = [make_synthetic_sample() for _ in range(16)]
imgs = torch.from_numpy(np.stack([b[0] for b in batch]))
hms = torch.from_numpy(np.stack([b[1] for b in batch]))
pred = model(imgs)
# Upsample pred to full resolution
pred = F.interpolate(pred, size=hms.shape[-2:], mode="bilinear", align_corners=False)
loss = F.mse_loss(pred, hms)
opt.zero_grad(); loss.backward(); opt.step()
Usando
- MediaPipe Pose — o estimador de pose de produção do Google; entrega runtimes WebGL + mobile com latência sub-10ms.
- MMPose (OpenMMLab) — base de código de pesquisa abrangente; toda arquitetura SOTA com pesos pré-treinados.
- YOLOv8-pose — a pose multi-pessoa em tempo real mais rápida, com uma única passada para frente.
- transformers HumanDPT / PoseAnything — abordagens vision-language mais recentes para pose de vocabulário aberto (qualquer objeto, qualquer conjunto de keypoints).
Entregando
Esta lição produz:
outputs/prompt-pose-stack-picker.md— um prompt que escolhe MediaPipe / YOLOv8-pose / HRNet / ViTPose dados a latência, o tamanho da multidão e a necessidade de 2D vs 3D.outputs/skill-heatmap-to-coords.md— uma skill que escreve a rotina sub-pixel de heatmap para coordenada usada por todo modelo de pose de produção.
Exercícios
- (Fácil) Treine o modelo minúsculo de keypoints no dataset sintético de 4 pontos. Reporte o erro L2 médio entre os keypoints previstos e os verdadeiros após 200 passos.
- (Médio) Adicione refinamento sub-pixel: dada a posição do argmax, ajuste uma parábola 1D ao longo de x e y a partir dos pixels vizinhos. Reporte o ganho de acurácia vs o argmax inteiro.
- (Difícil) Construa um dataset sintético de 2 pessoas em que cada imagem mostra duas instâncias do padrão de 4 keypoints. Treine um pipeline bottom-up com PAFs que prevê qual keypoint pertence a qual instância e avalie o OKS.
Termos-Chave
| Termo | O que as pessoas dizem | O que realmente significa |
|---|---|---|
| Keypoint | "Um landmark" | Um ponto específico e ordenado em um objeto (articulação, canto, feature) |
| Pose | "O esqueleto" | Um conjunto ordenado de keypoints pertencentes a uma instância |
| Top-down | "Detectar e depois pose" | Pipeline de dois estágios: detector de pessoas + modelo de keypoints por recorte; a maior acurácia |
| Bottom-up | "Pose primeiro, agrupar depois" | Previsão de todos os keypoints em uma passada + agrupamento; tempo constante no tamanho da multidão |
| Heatmap | "Alvo Gaussiano" | Tensor H x W por keypoint com pico na localização verdadeira; o alvo de regressão preferido |
| PAF | "Part Affinity Field" | Campo de vetor unitário de 2 canais que codifica direções de membros; usado para agrupar keypoints em instâncias |
| OKS | "IoU de keypoints" | Object Keypoint Similarity; a métrica do COCO para pose |
| HRNet | "High-Resolution Net" | A arquitetura top-down dominante para keypoints; preserva features de alta resolução do início ao fim |
Leitura Adicional
- OpenPose (Cao et al., 2017) — bottom-up com PAFs; ainda o melhor texto sobre a abordagem
- HRNet (Sun et al., 2019) — a arquitetura top-down de referência
- ViTPose (Xu et al., 2022) — ViT puro como backbone de pose; SOTA atual em muitos benchmarks
- MediaPipe Pose — pose em tempo real de produção; o stack implantado mais rápido em 2026