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

  1. (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.
  2. (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.
  3. (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

0 lifetime access. Curriculum based on AI Engineering from Scratch by Rohit Ghumare (MIT, used under attribution).