Capítulo 5: Detecção e Segmentação#

Este capítulo apresenta dois grandes problemas da visão computacional moderna: detecção de objetos (localizar e classificar instâncias dentro de uma imagem) e segmentação (delimitar regiões pixel a pixel). Começamos pelo YOLO26, o detector em tempo real mais recente da família YOLO; passamos por um detector baseado em Transformers (RF-DETR), que aborda a detecção por um caminho arquitetural diferente; e seguimos para o Segment Anything (SAM), o modelo de segmentação fundacional da Meta.

Tutorial do YOLO26#

Uma introdução rápida ao YOLO26 da Ultralytics, incluindo instalação e inferência com modelos pré-treinados.

O YOLO26 é a versão mais recente da família YOLO da Ultralytics e foi projetada com uma filosofia edge-first: simplificar a arquitetura para que o mesmo modelo rode bem em CPU, GPU e aceleradores embarcados. Três decisões marcam essa geração:

  • Detecção sem NMS (non-max suppression): o YOLO26 introduz uma cabeça de detecção one-to-one que prevê diretamente um conjunto fixo de hipóteses, eliminando o pós-processamento de NMS e simplificando a exportação para ONNX, TensorRT, CoreML, TFLite e OpenVINO.

  • Treino reformulado: novas técnicas como Progressive Loss Balancing (ProgLoss), Small-Target-Aware Label Assignment (STAL) e o otimizador MuSGD melhoram a estabilidade do treino e a precisão em objetos pequenos.

  • Desempenho em CPU: até 43% mais rápido em CPU que o YOLO11 com a mesma precisão, o que torna o modelo prático para deployment em hardware modesto (Raspberry Pi, Jetson Nano).

Suporta as cinco tarefas clássicas da família: detecção, segmentação de instâncias, estimativa de pose, oriented bounding boxes (OBB) e classificação.

Instalação e Modelo Pré-treinado#

Nesta seção instalamos a biblioteca e carregamos um modelo YOLO26 já treinado, pronto para uso. Vamos começar pela parte mais direta (rodar inferência com pesos pré-treinados) e, mais adiante no capítulo, veremos como treinar o modelo com dados próprios.

Instalação

Primeiro, instale a biblioteca ultralytics, que contém a implementação do YOLO26:

!pip install ultralytics

Modelos pré-treinados

A Ultralytics disponibiliza pesos já treinados no COCO (80 classes), prontos para uso imediato. Basta escolher o tamanho do modelo conforme o equilíbrio desejado entre velocidade e precisão:

  • yolo26n.pt: versão nano, leve e otimizada para dispositivos com recursos limitados, ideal para testes rápidos e prototipagem.

  • Variantes maiores (yolo26s.pt, yolo26m.pt, yolo26l.pt, yolo26x.pt) trocam velocidade por mais precisão.

Carregar um modelo pré-treinado é simples:

from ultralytics import YOLO

# Baixa (na primeira vez) e carrega os pesos pré-treinados
model = YOLO("yolo26n.pt")

Com o modelo carregado, já podemos rodar inferência diretamente, como nos exemplos a seguir.

Exemplo de Detecção de Objetos#

Com o modelo pré-treinado carregado, já podemos detectar objetos em uma imagem. Para não repetir o mesmo código de visualização em cada tarefa, definimos primeiro uma função auxiliar que roda o modelo e exibe a imagem original ao lado do resultado:

from ultralytics import YOLO
from ultralytics.utils import ASSETS  # imagens de exemplo que vêm com o pacote
import cv2
import matplotlib.pyplot as plt

def inferir_e_exibir(model, image_path, titulo="Resultado"):
    """Roda o modelo na imagem e mostra original vs. resultado lado a lado."""
    results = model(image_path)

    # OpenCV lê em BGR; convertemos para RGB para exibir no matplotlib
    img = cv2.cvtColor(cv2.imread(str(image_path)), cv2.COLOR_BGR2RGB)
    # results[0].plot() devolve a imagem anotada (também em BGR)
    result_img = cv2.cvtColor(results[0].plot(), cv2.COLOR_BGR2RGB)

    fig, axs = plt.subplots(1, 2, figsize=(15, 7))
    axs[0].imshow(img);        axs[0].set_title("Imagem Original"); axs[0].axis("off")
    axs[1].imshow(result_img); axs[1].set_title(titulo);            axs[1].axis("off")
    plt.tight_layout()
    plt.show()

Como imagem de teste usamos a bus.jpg, que já acompanha a biblioteca ultralytics (em ASSETS), então não é preciso baixar nada. Começando pela detecção:

# Modelo de detecção
model = YOLO("yolo26s.pt")

# Imagem de exemplo embutida no pacote ultralytics
from ultralytics.utils import ASSETS
rua = ASSETS / "bus.jpg"

inferir_e_exibir(model, rua, "Resultado da Detecção")
Resultado da detecção com YOLO26: caixas e rótulos sobre um ônibus e pessoas

Exemplo de Segmentação#

A mesma função serve para segmentação: basta trocar para um modelo -seg e reutilizar a mesma imagem.

# Modelo de segmentação de instâncias
model = YOLO("yolo26n-seg.pt")

# Mesma imagem do exemplo anterior
from ultralytics.utils import ASSETS
rua = ASSETS / "bus.jpg"

inferir_e_exibir(model, rua, "Resultado da Segmentação")
Resultado da segmentação de instâncias com YOLO26: máscaras sobre um ônibus e pessoas

Exemplo de Estimativa de Pose#

Para estimativa de pose, usamos a imagem zidane.jpg (também embutida no pacote), que mostra pessoas em pé.

# Modelo de estimativa de pose
model = YOLO("yolo26n-pose.pt")

# Outra imagem de exemplo do pacote, com pessoas
from ultralytics.utils import ASSETS
pessoas = ASSETS / "zidane.jpg"

inferir_e_exibir(model, pessoas, "Resultado da Pose")
Resultado da estimativa de pose com YOLO26: esqueleto de keypoints sobre pessoas em pé

Aplicação em um Vídeo#

A detecção também pode ser aplicada quadro a quadro em um vídeo. Para testar, use o vídeo de exemplo abaixo (uma cena curta de rua, gerada para este material):

Download do vídeo de teste

O código abaixo lê o vídeo de entrada, roda o YOLO26 em cada quadro e grava um novo vídeo com as detecções desenhadas:

from ultralytics import YOLO
import cv2

# Modelo de detecção
model = YOLO("yolo26n.pt")

# Caminhos dos vídeos de entrada e saída
input_video_path = "video_teste.mp4"
output_video_path = "video_resultado.mp4"

# Abrir o vídeo de entrada
cap = cv2.VideoCapture(input_video_path)

# Configurar o vídeo de saída com a mesma taxa de quadros e resolução da entrada
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
out = cv2.VideoWriter(output_video_path, fourcc, cap.get(cv2.CAP_PROP_FPS),
                      (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))))

while cap.isOpened():
    ret, frame = cap.read()
    if not ret:
        break

    # Detectar objetos no quadro (verbose=False evita imprimir um log por quadro)
    results = model(frame, verbose=False)

    # Quadro com as detecções desenhadas
    annotated_frame = results[0].plot()

    # Gravar o quadro anotado no vídeo de saída
    out.write(annotated_frame)

# Liberar os recursos
cap.release()
out.release()
cv2.destroyAllWindows()

print(f"Vídeo com detecção salvo em: {output_video_path}")

Treinamento com Dados Próprios#

Até aqui usamos um modelo pré-treinado no COCO para fazer inferência. Quando as classes do seu problema não estão entre as 80 do COCO, o caminho é treinar (ou fazer fine-tuning) o modelo com seus próprios dados. Agora que você já viu o modelo em ação, vamos a esse passo.

Dados de exemplo: o coco8

Para uma demonstração rápida usamos o coco8, um subconjunto minúsculo do famoso dataset COCO (Common Objects in Context). Ele tem apenas 8 imagens (4 para treino e 4 para validação), com as anotações no formato YOLO (um arquivo .txt por imagem, contendo a classe e a caixa delimitadora normalizada de cada objeto). É baixado automaticamente na primeira vez que você treina com data='coco8.yaml', então serve para validar o fluxo de treino em segundos, sem precisar de um dataset grande.

Embora tenha poucas imagens, o coco8 herda o espaço de 80 classes do COCO, as mesmas que os modelos pré-treinados que usamos na inferência reconhecem. Elas cobrem categorias do dia a dia, que podemos agrupar assim:

  • Pessoas e animais: person, bird, cat, dog, horse, sheep, cow, elephant, bear, zebra, giraffe

  • Veículos: bicycle, car, motorcycle, airplane, bus, train, truck, boat

  • Trânsito e rua: traffic light, fire hydrant, stop sign, parking meter, bench

  • Acessórios e esporte: backpack, umbrella, handbag, tie, suitcase, frisbee, skis, snowboard, sports ball, kite, baseball bat, baseball glove, skateboard, surfboard, tennis racket

  • Cozinha e comida: bottle, wine glass, cup, fork, knife, spoon, bowl, banana, apple, sandwich, orange, broccoli, carrot, hot dog, pizza, donut, cake

  • Móveis e casa: chair, couch, potted plant, bed, dining table, toilet

  • Eletrônicos: tv, laptop, mouse, remote, keyboard, cell phone

  • Eletrodomésticos e objetos: microwave, oven, toaster, sink, refrigerator, book, clock, vase, scissors, teddy bear, hair drier, toothbrush

Abaixo, quatro imagens do coco8 com as anotações (caixas e classes) que acompanham o dataset:

Quatro imagens do dataset coco8 com as caixas e nomes de classe anotados

O arquivo de configuração (data.yaml)

O que diz ao YOLO onde estão as imagens e quais são as classes é um arquivo .yaml. O coco8.yaml é um bom modelo para entender o formato:

path: coco8            # diretório raiz do dataset
train: images/train    # imagens de treino (relativo a 'path')
val: images/val        # imagens de validação (relativo a 'path')
test:                  # imagens de teste (opcional)

names:                 # mapeamento índice -> nome da classe
  0: person
  1: bicycle
  2: car
  # ... (as 80 classes do COCO)

São quatro elementos:

  • path: a pasta raiz do dataset (pode ser um caminho absoluto, como /home/usuario/meu_dataset, ou relativo ao diretório de datasets do Ultralytics).

  • train e val: as pastas de imagens de treino e validação, relativas a path. Cada imagem deve ter um arquivo de rótulo .txt correspondente, na pasta labels/ no mesmo nível de images/.

  • names: a lista de classes, na ordem dos índices usados nos rótulos.

A estrutura de pastas esperada é:

meu_dataset/
├── images/
│   ├── train/   # imagens de treino (.jpg, .png)
│   └── val/     # imagens de validação
└── labels/
    ├── train/   # um .txt por imagem (mesmo nome)
    └── val/

Tip

Ao baixar um dataset de fora (por exemplo, do Kaggle ou do Roboflow), procure justamente por esse data.yaml. Muitos já vêm com ele pronto. Basta ajustar o path para o local onde você descompactou os arquivos e passar esse caminho em data=. Se o dataset não estiver no formato YOLO, será preciso converter as anotações antes.

Para treinar com um dataset próprio, é só apontar para o seu YAML:

# Em vez de coco8.yaml, use o caminho do seu arquivo de configuração
model.train(data="/caminho/para/meu_dataset/data.yaml", epochs=3)

Exemplo de Treinamento

from ultralytics import YOLO

# Carregar o modelo YOLO26 nano como ponto de partida
model = YOLO("yolo26n.pt")

# Treinar o modelo com o conjunto COCO8 por 3 épocas
model.train(data='coco8.yaml', epochs=3)

Resumo do Log Gerado

Durante o treinamento, o YOLO26 exibe um log com informações úteis. A tabela abaixo resume os principais elementos:

Campo

Descrição

epoch

Época atual / total

gpu_mem

Memória de GPU usada (GB)

box_loss

Erro na predição das caixas delimitadoras

cls_loss

Erro na classificação dos objetos

dfl_loss

Erro na regressão refinada das caixas (Distribution Focal Loss)

instances

Número de objetos anotados no batch

size

Tamanho das imagens de entrada

metrics/precision

Precisão: taxa de detecções corretas

metrics/recall

Revocação: taxa de objetos corretamente detectados

metrics/mAP50

Média da precisão com IoU de 0.5

metrics/mAP50-95

Média da precisão com IoU de 0.5 a 0.95 (mais rigoroso e completo)

Essas métricas ajudam a monitorar o desempenho do modelo. Idealmente, as perdas (losses) devem diminuir e as métricas (mAP, precision, recall) devem aumentar ao longo das épocas.

Configurações avançadas do YOLO26#

Abaixo, apresento uma tabela atualizada com as principais configurações avançadas que podem ser utilizadas tanto no treinamento quanto na predição com o YOLO26, abrangendo todas as tarefas suportadas.

Tabela de Configurações Avançadas do YOLO26

Parâmetro

Descrição

Valor Padrão

Treinamento

Predição

Tarefas Suportadas

imgsz

Tamanho da imagem (pode ser um valor único ou uma tupla).

640

Todas

batch

Tamanho do lote por GPU durante o treinamento.

16

Todas

epochs

Número de épocas para o treinamento.

100

Todas

optimizer

Otimizador utilizado (e.g., SGD, Adam, AdamW, RMSProp).

‘auto’

Todas

lr0

Taxa de aprendizado inicial.

0.01

Todas

momentum

Momentum para otimizadores baseados em momentum.

0.937

Todas

weight_decay

Decaimento de peso (regularização L2).

0.0005

Todas

warmup_epochs

Número de épocas de aquecimento para o ajuste gradual da taxa de aprendizado.

3

Todas

patience

Número de épocas sem melhoria antes de interromper o treinamento antecipadamente.

100

Todas

pretrained

Utilizar pesos pré-treinados (True, False ou caminho para o modelo).

True

Todas

conf

Threshold de confiança para filtrar detecções.

0.25

Detecção, Segmentação, Pose, OBB

iou

Limiar de IoU do NMS (só no modo end2end=False; o YOLO26 é NMS-free por padrão).

0.7

Detecção, Segmentação, Pose, OBB

max_det

Número máximo de detecções por imagem.

300

Detecção, Segmentação, Pose, OBB

save_period

Intervalo de épocas para salvar checkpoints durante o treinamento.

-1

Todas

augment

Aplicar aumentos de dados durante a inferência.

False

Todas

visualize

Visualizar mapas de características intermediários para depuração.

False

Todas

flipud

Probabilidade de aplicar flip vertical durante o aumento de dados.

0.0

Todas

fliplr

Probabilidade de aplicar flip horizontal durante o aumento de dados.

0.5

Todas

degrees

Ângulo máximo para rotação aleatória durante o aumento de dados.

0.0

Todas

translate

Fração máxima para translação aleatória durante o aumento de dados.

0.1

Todas

scale

Fator de escala para aumento de dados.

0.5

Todas

shear

Ângulo máximo para cisalhamento durante o aumento de dados.

0.0

Todas

hsv_h, hsv_s, hsv_v

Ajustes de matiz, saturação e valor para aumento de dados no espaço HSV.

0.015, 0.7, 0.4

Todas

copy_paste

Aplicar técnica de copy-paste durante o aumento de dados.

0.0

Detecção, Segmentação

mask_ratio

Threshold para binarização de máscaras em segmentação.

0.5

Segmentação

kpt_thr

Threshold de confiança para keypoints em estimativa de pose.

0.2

Pose Estimation

resume

Retomar treinamento a partir de um checkpoint salvo.

False

Todas

project

Nome do diretório de projeto para salvar resultados.

‘runs’

Todas

name

Nome da execução específica dentro do projeto.

‘exp’

Todas

exist_ok

Permitir sobrescrever diretórios existentes.

False

Todas

Nota: na inferência padrão o YOLO26 é end-to-end (sem NMS), então o parâmetro iou acima só tem efeito ao ativar a cabeça legada one-to-many com end2end=False.

Tarefas Suportadas pelo YOLO26

O YOLO26 é uma estrutura versátil que suporta as seguintes tarefas de visão computacional:

  • Detecção de Objetos: Identificação e localização de objetos em imagens ou vídeos.

  • Segmentação de Instâncias: Delineamento preciso dos contornos de objetos detectados.

  • Classificação de Imagens: Categorização de imagens em classes predefinidas.

  • Estimativa de Pose: Detecção e rastreamento de pontos-chave em corpos humanos.

  • Detecção Orientada (OBB): Detecção de objetos com orientação rotacional para maior precisão.

Cada uma dessas tarefas pode ser executada utilizando modelos específicos do YOLO26, como yolo26n.pt para detecção, yolo26n-seg.pt para segmentação, yolo26n-cls.pt para classificação, yolo26n-pose.pt para estimativa de pose e yolo26n-obb.pt para detecção orientada.

Entendendo o IoU (Intersection over Union)#

Quando um detector como o YOLO26 prevê uma caixa para um objeto, precisamos de uma forma de medir quão boa é essa previsão em relação à caixa correta (anotada manualmente). O IoU (Intersection over Union) é a métrica padrão para isso: ele compara duas caixas e devolve um número entre 0 e 1 indicando o quanto elas se sobrepõem.

A ideia é simples: dividir a área em comum das duas caixas pela área total que elas ocupam juntas.

\[ IoU = \frac{\text{Área da Interseção}}{\text{Área da União}} \]
  • Interseção (∩): a região onde as duas caixas se sobrepõem.

  • União (∪): a área coberta por pelo menos uma das caixas (a soma das duas, sem contar a sobreposição em dobro).

Tres paineis ilustrando o IoU: duas caixas, a intersecao em verde e a uniao em roxo

O valor resultante é fácil de interpretar:

  • IoU = 0: as caixas não se tocam (previsão totalmente errada de posição).

  • IoU = 1: as caixas são idênticas (previsão perfeita).

  • valores intermediários: quanto mais perto de 1, melhor o alinhamento.

Na prática, o IoU aparece em dois momentos centrais da detecção de objetos:

  • Avaliação (métrica): para decidir se uma detecção “acertou”, compara-se a caixa prevista com a verdadeira; normalmente considera-se acerto quando o IoU passa de um limiar (por exemplo, 0,5). É a base de métricas como o mAP.

  • Pós-processamento (NMS): quando o modelo gera várias caixas sobrepostas para o mesmo objeto, o IoU é usado para identificar e remover as redundantes, mantendo apenas a melhor.

Curiosidade: o IoU é uma aplicação do Índice de Jaccard, criado para medir a similaridade entre conjuntos. Aqui, os “conjuntos” são as áreas das duas caixas.

Agora que entendemos o conceito, vamos implementá-lo na prática.

Da Precisão ao mAP#

O IoU nos diz quando uma única detecção está bem posicionada. Mas para avaliar um modelo inteiro, em muitas imagens e várias classes, precisamos resumir tudo em um número. Esse número é o mAP (mean Average Precision), a métrica mais usada em detecção de objetos e a que aparece no log de treino do YOLO26.

Para chegar nele, partimos de dois conceitos básicos:

  • Precisão (precision): das caixas que o modelo previu, quantas estavam certas? Mede o quanto evitamos falsos positivos.

  • Revocação (recall): dos objetos que realmente existiam, quantos o modelo encontrou? Mede o quanto evitamos falsos negativos.

Uma detecção é contada como acerto (verdadeiro positivo) quando sua classe está correta e o IoU com a caixa verdadeira passa de um limiar (tipicamente 0,5). É aqui que o IoU entra na conta.

AP: a área sob a curva precision-recall

Ao variar o limiar de confiança do modelo (de muito exigente a muito permissivo), precisão e revocação mudam: exigir muita confiança aumenta a precisão mas reduz a revocação, e vice-versa. Plotando um valor contra o outro, obtemos a curva precision-recall. A Average Precision (AP) de uma classe é a área sob essa curva, um único número entre 0 e 1 que resume o equilíbrio entre acertar e não deixar passar.

Curva precision-recall com a area sob a curva destacada, representando a Average Precision

De AP para mAP

  • O AP é calculado por classe. O mAP é a média dos APs de todas as classes (o “m” de mean). Assim, um modelo que vai bem em “pessoa” mas mal em “bicicleta” tem o mAP penalizado.

  • O limiar de IoU usado muda o resultado, por isso existem duas formas comuns:

    • mAP@50 (ou mAP50): usa um único limiar de IoU de 0,5. Mais permissivo.

    • mAP@50-95 (ou mAP50-95): calcula o mAP em dez limiares de IoU (de 0,5 a 0,95, de 0,05 em 0,05) e tira a média. É mais rigoroso, pois exige caixas bem ajustadas, e é a métrica principal usada para comparar modelos no COCO.

Em resumo: IoU mede uma caixa, AP resume uma classe, e mAP resume o modelo inteiro. São exatamente as colunas metrics/mAP50 e metrics/mAP50-95 que vimos no log de treinamento. Quanto mais perto de 1, melhor o detector.

Exercícios#

Antes de seguir para outras arquiteturas, é hora de praticar. Os dois exercícios a seguir cobrem os dois lados do que vimos até aqui: medir detecções com o IoU e criar as anotações que servem de verdade para essa medida.

Cálculo do IoU (Intersection over Union)#

Agora é a sua vez: implemente uma função que calcula o IoU entre duas caixas.

Representação das Caixas

As caixas são representadas por seus cantos superior esquerdo e inferior direito:

[x1_a, y1_a, x2_a, y2_a, x1_b, y1_b, x2_b, y2_b]

Ou seja:

  • x1_a, y1_a: coordenadas do canto superior esquerdo da Caixa A

  • x2_a, y2_a: coordenadas do canto inferior direito da Caixa A

  • x1_b, y1_b: coordenadas do canto superior esquerdo da Caixa B

  • x2_b, y2_b: coordenadas do canto inferior direito da Caixa B

Exemplo

dados = [2, 2, 5, 5, 4, 3, 7, 6]
  • Caixa A: (2, 2) até (5, 5)

  • Caixa B: (4, 3) até (7, 6)

AI

Tarefa

  • Calcule a área de interseção entre as duas caixas.

  • Calcule a área da união.

  • Use a fórmula do IoU para obter o valor final.

  • (Opcional, mas altamente recomendado): plote as duas caixas em um gráfico 2D para visualizar claramente a interseção.

Dica: use bibliotecas como matplotlib para desenhar as caixas e facilitar a interpretação.

Função a implementar

def calcular_iou(dados):
  """
  Calcula o Intersection over Union entre duas caixas a partir de uma lista com 8 valores.

  Parâmetros:
    dados: lista com 8 elementos no formato
        [x1_a, y1_a, x2_a, y2_a, x1_b, y1_b, x2_b, y2_b]

  Retorno:
    IoU: float
  """
  # Sua implementação aqui
  pass

Testes

# Teste 1
Entrada: 2, 2, 5, 5, 4, 3, 7, 6
Saída: 0.12

# Teste 2
Entrada: 1, 1, 4, 4, 1, 1, 4, 4
Saída: 1.00

# Teste 3
Entrada: 0, 0, 6, 6, 2, 2, 4, 4
Saída: 0.11

# Teste 4
Entrada: 0, 0, 2, 2, 3, 3, 5, 5
Saída: 0.0

Criando seu Próprio Dataset com o Roboflow#

Até aqui você usou modelos e avaliou resultados. Agora vai para o outro lado do processo: criar os dados que alimentam um modelo. Anotar é uma das etapas mais importantes (e trabalhosas) de qualquer projeto de visão computacional. A qualidade das anotações limita a qualidade do modelo.

Neste exercício você vai montar um pequeno dataset de pessoas a partir do video_teste.mp4 usado nos exemplos de detecção, e anotá-lo de três formas diferentes no Roboflow, uma ferramenta gratuita e baseada no navegador.

Passo 1: Extrair quadros do vídeo

Um vídeo é uma sequência de imagens. Para anotar, primeiro extraímos alguns quadros como imagens individuais. O código abaixo salva 1 quadro por segundo do vídeo em uma pasta frames/:

import cv2
import os

os.makedirs("frames", exist_ok=True)

cap = cv2.VideoCapture("video_teste.mp4")
fps = cap.get(cv2.CAP_PROP_FPS)
passo = int(fps)  # 1 quadro por segundo

i = salvos = 0
while cap.isOpened():
    ret, frame = cap.read()
    if not ret:
        break
    if i % passo == 0:
        cv2.imwrite(f"frames/frame_{salvos:03d}.jpg", frame)
        salvos += 1
    i += 1

cap.release()
print(f"{salvos} quadros salvos em frames/")

Passo 2: Anotar no Roboflow

  1. Crie uma conta gratuita em roboflow.com e inicie um novo projeto.

  2. Faça upload dos quadros da pasta frames/.

  3. Anote a classe person em cada imagem, repetindo o processo nos três tipos de tarefa:

    • Detecção (Object Detection): desenhe uma bounding box ao redor de cada pessoa.

    • Segmentação (Instance Segmentation): contorne o formato de cada pessoa com um polígono.

    • Pose (Keypoint Detection): marque os pontos-chave do corpo (cabeça, ombros, cotovelos, joelhos, etc.).

  4. Gere uma versão do dataset e use o Export do Roboflow no formato YOLO.

Tip

Repare que o Roboflow exporta exatamente a estrutura que vimos na seção de treinamento: pastas images/ e labels/ mais um data.yaml. Ou seja, o que você anotar aqui pode ser treinado diretamente com model.train(data="data.yaml").

Reflexão

  • Quantos quadros foram necessários para ter variedade suficiente de poses e posições?

  • Qual das três anotações foi mais trabalhosa? Por quê?

  • Como a anotação se conecta com o IoU? (Dica: a caixa que você desenha é a “verdade” contra a qual o IoU do modelo será medido.)

Detecção com Transformers: RF-DETR#

O YOLO26 resolve a detecção com uma rede totalmente convolucional. Existe, porém, uma segunda grande linhagem de detectores, nascida de uma ideia diferente: tratar a detecção como um problema de conjunto e resolvê-lo com Transformers, a mesma arquitetura de atenção que revolucionou o processamento de linguagem natural.

Essa linhagem começou com o DETR (Detection Transformer), da Meta, em 2020. Em vez de gerar milhares de caixas candidatas e depois filtrá-las, o DETR prevê diretamente um conjunto fixo de objetos, casando cada previsão com um objeto real durante o treino. Disso vêm duas consequências importantes:

  • Fim do NMS: como cada objeto é previsto uma única vez, não há caixas redundantes para remover, e o modelo se torna end-to-end de verdade. (Curiosamente, o YOLO26 chega ao mesmo destino, a detecção sem NMS, partindo de uma arquitetura convolucional.)

  • Visão global da imagem: o mecanismo de atenção deixa cada região da imagem “conversar” com todas as outras, o que ajuda em cenas com muitos objetos ou com oclusão.

O ponto fraco do DETR original era a lentidão e a dificuldade de treino. O RF-DETR, da Roboflow, é uma família recente que resolve isso: parte de um backbone DINOv2 pré-treinado, roda em tempo real e está entre os detectores mais precisos no COCO, com a vantagem de transferir muito bem para domínios específicos (justamente o cenário de quem anota o próprio dataset, como no exercício anterior).

Tip

YOLO e RF-DETR resolvem a mesma tarefa por caminhos opostos. O YOLO é uma CNN refinada por anos de engenharia incremental em busca de velocidade; o RF-DETR parte de um Transformer com visão global da cena. Na prática, vale testar os dois no seu problema: o YOLO costuma liderar em latência bruta, enquanto o RF-DETR tende a brilhar em precisão e na transferência para domínios fora do COCO.

Instalação e Inferência#

Para comparar diretamente com o YOLO, vamos rodar o RF-DETR na mesma imagem (bus.jpg) usada no exemplo de detecção. Primeiro, a instalação:

!pip install rfdetr supervision

A inferência devolve um objeto Detections da biblioteca supervision, que também cuida da anotação visual. A lista COCO_CLASSES traduz o índice numérico de cada classe para o nome legível:

from rfdetr import RFDETRMedium
from rfdetr.util.coco_classes import COCO_CLASSES
import supervision as sv
import matplotlib.pyplot as plt
from PIL import Image
from urllib.request import urlopen
import numpy as np

# Variantes: RFDETRNano, RFDETRSmall, RFDETRMedium, RFDETRBase, RFDETRLarge
# (de mais rápido a mais preciso). Aqui usamos a Medium, pré-treinada no COCO.
model = RFDETRMedium()

# Mesma imagem do exemplo de detecção com YOLO (carregada direto do repositório oficial)
url = "https://raw.githubusercontent.com/ultralytics/ultralytics/main/ultralytics/assets/bus.jpg"
imagem = Image.open(urlopen(url)).convert("RGB")

# Inferência: só mantém detecções com confiança >= 0.5
deteccoes = model.predict(imagem, threshold=0.5)

# Monta os rótulos "classe confiança" e desenha caixas + textos sobre a imagem
rotulos = [f"{COCO_CLASSES[c]} {p:.2f}"
           for c, p in zip(deteccoes.class_id, deteccoes.confidence)]
anotada = sv.BoxAnnotator().annotate(np.array(imagem).copy(), deteccoes)
anotada = sv.LabelAnnotator(smart_position=True).annotate(anotada, deteccoes, rotulos)

# Exibe original vs. resultado, lado a lado
fig, axs = plt.subplots(1, 2, figsize=(15, 7))
axs[0].imshow(imagem);  axs[0].set_title("Imagem Original");                 axs[0].axis("off")
axs[1].imshow(anotada); axs[1].set_title("Resultado da Detecção (RF-DETR)"); axs[1].axis("off")
plt.tight_layout()
plt.show()
Resultado da detecção com RF-DETR: caixas e rótulos sobre um ônibus e pessoas, ao lado da imagem original

Na mesma cena, o RF-DETR encontra o ônibus e as pessoas com confiança alta, assim como o YOLO26. A diferença está por baixo: as caixas saem diretamente da previsão de conjunto do Transformer, sem nenhuma etapa de NMS. Vale rodar os dois modelos nas suas próprias imagens e comparar tanto a qualidade das detecções quanto o tempo de inferência.

Segment Anything (SAM)#

Em visão computacional, o SAM (Segment Anything), desenvolvido pela Meta AI, tornou-se o modelo de base para segmentação. A ideia segue o mesmo princípio que consagrou os LLMs no processamento de linguagem natural: um único modelo, treinado em grande escala, capaz de segmentar praticamente qualquer objeto em qualquer imagem a partir de prompts simples: um clique, uma caixa, uma máscara aproximada ou, nas versões mais recentes, uma descrição em texto.

A versão atual, SAM 3, estende essa capacidade para vídeos e introduz segmentação aberta por vocabulário (open-vocabulary), permitindo descrever em linguagem natural aquilo que se quer segmentar. A Meta disponibiliza um playground oficial onde é possível experimentar o modelo diretamente no navegador, sem instalação:

Playground oficial do SAM 3: https://aidemos.meta.com/segment-anything

Rodando o SAM 3 no Colab#

Além do playground, o SAM 3 está disponível na biblioteca transformers da Hugging Face, o que permite rodá-lo direto no Colab. Vamos usar o recurso central do modelo, a segmentação por conceito (Promptable Concept Segmentation): em vez de um clique ou uma caixa, passamos uma palavra (um substantivo) e o SAM 3 segmenta todas as instâncias daquele conceito na imagem. Para fechar o capítulo de forma coerente, vamos segmentar o conceito person na mesma bus.jpg dos exemplos de detecção, fazendo a transição natural de detecção (caixas) para segmentação (máscaras).

Note

O facebook/sam3 é um modelo gated (de acesso controlado). Antes de rodar: (1) abra a página do modelo no Hugging Face, faça login e aceite os termos de uso (para os modelos SAM a liberação costuma ser imediata); (2) autentique o Colab uma vez executando o notebook_login() da célula abaixo e colando um token de acesso (gerado em Settings → Access Tokens no Hugging Face).

Primeiro, instalamos as bibliotecas (o SAM 3 exige uma versão recente do transformers):

!pip install -U transformers supervision

Em seguida, a autenticação no Hugging Face:

from huggingface_hub import notebook_login

notebook_login()

Agora a inferência. O Sam3Processor prepara a imagem e o texto; o modelo devolve as máscaras, que pós-processamos no tamanho original da imagem e anotamos com a biblioteca supervision (a mesma usada no RF-DETR):

import torch
import numpy as np
import supervision as sv
import matplotlib.pyplot as plt
from PIL import Image
from urllib.request import urlopen
from transformers import Sam3Model, Sam3Processor

# Carrega o SAM 3 (modelo gated: requer acesso aprovado + login acima)
device = "cuda" if torch.cuda.is_available() else "cpu"
model = Sam3Model.from_pretrained("facebook/sam3").to(device)
processor = Sam3Processor.from_pretrained("facebook/sam3")

# Mesma imagem dos exemplos de detecção
url = "https://raw.githubusercontent.com/ultralytics/ultralytics/main/ultralytics/assets/bus.jpg"
imagem = Image.open(urlopen(url)).convert("RGB")

# Prompt de conceito: segmenta TODAS as instâncias de "person"
inputs = processor(images=imagem, text="person", return_tensors="pt").to(device)
with torch.no_grad():
    outputs = model(**inputs)

# Pós-processamento: máscaras, caixas e scores no tamanho original da imagem
resultado = processor.post_process_instance_segmentation(
    outputs,
    threshold=0.5,
    mask_threshold=0.5,
    target_sizes=inputs.get("original_sizes").tolist(),
)[0]
print(f"{len(resultado['masks'])} instâncias de 'person' encontradas")

# Converte para o formato da supervision e desenha as máscaras + rótulos
deteccoes = sv.Detections(
    xyxy=resultado["boxes"].cpu().numpy(),
    mask=resultado["masks"].cpu().numpy().astype(bool),
    confidence=resultado["scores"].cpu().numpy(),
)
rotulos = [f"person {p:.2f}" for p in deteccoes.confidence]
anotada = sv.MaskAnnotator(color_lookup=sv.ColorLookup.INDEX).annotate(np.array(imagem).copy(), deteccoes)
anotada = sv.LabelAnnotator(smart_position=True, color_lookup=sv.ColorLookup.INDEX).annotate(anotada, deteccoes, rotulos)

# Exibe original vs. resultado, lado a lado
fig, axs = plt.subplots(1, 2, figsize=(15, 7))
axs[0].imshow(imagem);  axs[0].set_title("Imagem Original");                  axs[0].axis("off")
axs[1].imshow(anotada); axs[1].set_title("Segmentação por Conceito (SAM 3)"); axs[1].axis("off")
plt.tight_layout()
plt.show()
Segmentação por conceito com SAM 3: máscaras coloridas sobre cada pessoa da cena, ao lado da imagem original

Ao executar no Colab (com GPU), o SAM 3 devolve uma máscara para cada pessoa da cena, e não apenas a caixa que a envolve. É a diferença entre detecção (onde está o objeto) e segmentação (quais pixels são o objeto), agora a partir de um simples prompt de texto, sem nenhuma classe pré-definida.

Tip

Como o SAM 3 trabalha com vocabulário aberto (open-vocabulary), basta trocar o texto em text="..." para segmentar qualquer outro conceito, sem treinar nada. Experimente prompts como "bus", "backpack" ou "shoe", ou frases curtas com adjetivo, como "blue bus".

Localização por Linguagem: LocateAnything (NVIDIA)#

Os detectores que vimos até aqui (YOLO26, RF-DETR) localizam objetos de um conjunto de classes, e o SAM 3 segmenta instâncias de um conceito. O LocateAnything, da NVIDIA, dá um passo adicional: é um modelo de visão-linguagem (Vision-Language Model, VLM) que localiza objetos a partir de uma descrição em linguagem natural qualquer, incluindo referring expressions (frases que apontam para uma instância específica, como “a pessoa de casaco branco”).

Por baixo, ele combina um codificador visual (Moon-ViT) com um decodificador de linguagem (Qwen2.5) e introduz o Parallel Box Decoding (PBD): em vez de gerar as coordenadas das caixas token a token, prevê as caixas em paralelo, ganhando velocidade e precisão em IoU alto. O modelo cobre desde detecção genérica até grounding de elementos de interface (GUI), OCR e leitura de documentos, e foi treinado em larga escala (12M de imagens, 138M de consultas).

Note

O nvidia/LocateAnything-3B não é gated (download livre), mas é distribuído sob a NVIDIA License for non-commercial use: permitido apenas para pesquisa acadêmica e fins não comerciais. Por ser um modelo de 3 bilhões de parâmetros, rode o exemplo no Colab com GPU.

A instalação segue as versões fixadas pelo cartão do modelo (repare no transformers==4.57.1; no Colab, pode ser necessário reiniciar a sessão em Runtime → Restart session depois de instalar):

!pip install opencv-python-headless==4.11.0.86 transformers==4.57.1 numpy==1.25.0 Pillow==11.1.0 peft torchvision decord==0.6.0 lmdb==1.7.5

O código abaixo organiza a inferência numa pequena classe: ela carrega o modelo uma vez e, a cada consulta, monta a mensagem (imagem + texto), aplica o chat template, gera a resposta e extrai as caixas. As coordenadas saem normalizadas no intervalo [0, 1000] e são convertidas para pixels:

import re
import torch
from transformers import AutoModel, AutoTokenizer, AutoProcessor


class LocateAnythingWorker:
    """Carrega o modelo uma vez e responde a consultas de localização."""

    def __init__(self, model_path, device="cuda", dtype=torch.bfloat16):
        self.device, self.dtype = device, dtype
        self.tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
        self.processor = AutoProcessor.from_pretrained(model_path, trust_remote_code=True)
        self.model = AutoModel.from_pretrained(
            model_path, torch_dtype=dtype, trust_remote_code=True,
        ).to(device).eval()

    @torch.no_grad()
    def predict(self, image, question, max_new_tokens=2048):
        # 1) Monta a mensagem com imagem + texto
        messages = [{"role": "user", "content": [
            {"type": "image", "image": image},
            {"type": "text", "text": question},
        ]}]
        # 2) Aplica o chat template e processa a imagem
        text = self.processor.py_apply_chat_template(
            messages, tokenize=False, add_generation_prompt=True)
        images, videos = self.processor.process_vision_info(messages)
        inputs = self.processor(
            text=[text], images=images, videos=videos, return_tensors="pt"
        ).to(self.device)
        # 3) Gera a resposta (generation_mode="hybrid" equilibra velocidade e precisão)
        resposta = self.model.generate(
            pixel_values=inputs["pixel_values"].to(self.dtype),
            input_ids=inputs["input_ids"],
            attention_mask=inputs["attention_mask"],
            image_grid_hws=inputs.get("image_grid_hws", None),
            tokenizer=self.tokenizer,
            max_new_tokens=max_new_tokens,
            use_cache=True,
            generation_mode="hybrid",
            temperature=0.7, do_sample=True, top_p=0.9, repetition_penalty=1.1,
        )
        return resposta[0] if isinstance(resposta, tuple) else resposta

    @staticmethod
    def parse_boxes(answer, image_width, image_height):
        """Extrai as caixas <box>...</box> e converte de [0, 1000] para pixels."""
        boxes = []
        for m in re.finditer(r"<box><(\d+)><(\d+)><(\d+)><(\d+)></box>", answer):
            x1, y1, x2, y2 = [int(g) for g in m.groups()]
            boxes.append([x1 / 1000 * image_width,  y1 / 1000 * image_height,
                          x2 / 1000 * image_width,  y2 / 1000 * image_height])
        return boxes

Com a classe pronta, fazemos uma consulta por referring expression na mesma bus.jpg: em vez de “todas as pessoas”, pedimos uma pessoa específica pela descrição. As caixas são desenhadas com a supervision (a mesma lib do RF-DETR):

import numpy as np
import supervision as sv
import matplotlib.pyplot as plt
from PIL import Image
from urllib.request import urlopen

worker = LocateAnythingWorker("nvidia/LocateAnything-3B")

# Mesma imagem dos exemplos anteriores
url = "https://raw.githubusercontent.com/ultralytics/ultralytics/main/ultralytics/assets/bus.jpg"
imagem = Image.open(urlopen(url)).convert("RGB")

# Referring expression: aponta para UMA instância pela descrição
consulta = "Locate the person wearing a white coat."
resposta = worker.predict(imagem, consulta)

# Converte a resposta em caixas (em pixels) e desenha
largura, altura = imagem.size
caixas = LocateAnythingWorker.parse_boxes(resposta, largura, altura)
deteccoes = sv.Detections(xyxy=np.array(caixas, dtype=float).reshape(-1, 4))
anotada = sv.BoxAnnotator(color_lookup=sv.ColorLookup.INDEX).annotate(
    np.array(imagem).copy(), deteccoes)

# Exibe original vs. resultado, lado a lado
fig, axs = plt.subplots(1, 2, figsize=(15, 7))
axs[0].imshow(imagem);  axs[0].set_title("Imagem Original");                            axs[0].axis("off")
axs[1].imshow(anotada); axs[1].set_title("Localização por Linguagem (LocateAnything)"); axs[1].axis("off")
plt.tight_layout()
plt.show()

Ao executar no Colab, o LocateAnything desenha a caixa apenas sobre a pessoa que corresponde à descrição, ignorando as demais. É a diferença em relação aos detectores anteriores: aqui a “classe” é uma frase livre, e o modelo entende a quem ela se refere.

Tip

A consulta é texto livre: troque a frase em consulta para localizar outras coisas, como "Locate all the people." (detecção de todas as instâncias), "Locate the blue bus.", ou mesmo elementos de interface e texto em capturas de tela. É o mesmo modelo, sem treinar nada.

Referências e Conteúdo Extra#