Phase 04 - Lesson 08
Segmentacao de Instancias — Mask R-CNN
Adicione um pequeno ramo de mascara a um detector Faster R-CNN e voce tem segmentacao de instancias. A parte dificil e o RoIAlign, e e mais dificil do que parece.
Type: Build + Learn Languages: Python Prerequisites: Fase 4 Licao 06 (YOLO), Fase 4 Licao 07 (U-Net) Time: ~75 minutos
Objetivos de Aprendizagem
- Tracar a arquitetura do Mask R-CNN de ponta a ponta: backbone, FPN, RPN, RoIAlign, cabeca de caixa, cabeca de mascara
- Implementar o RoIAlign do zero e explicar por que o RoIPool nao e mais usado
- Usar o modelo pre-treinado
maskrcnn_resnet50_fpn_v2do torchvision para mascaras de instancia de qualidade de producao e ler corretamente o formato de sua saida - Fazer fine-tuning do Mask R-CNN em um pequeno conjunto de dados personalizado substituindo as cabecas de caixa e de mascara e mantendo o backbone congelado
O Problema
A segmentacao semantica fornece uma mascara por classe. A segmentacao de instancias fornece uma mascara por objeto, mesmo quando dois objetos compartilham uma classe. Contar individuos, rastrear ao longo de quadros e medir coisas (a caixa delimitadora de cada tijolo em uma parede, cada celula em uma imagem de microscopio) tudo exige segmentacao de instancias.
O Mask R-CNN (He et al., 2017) resolveu isso ao reformular a segmentacao de instancias como deteccao mais uma mascara. O design era tao limpo que, pelos cinco anos seguintes, quase todo artigo de segmentacao de instancias foi uma variante do Mask R-CNN, e a implementacao do torchvision ainda e o padrao de producao para conjuntos de dados pequenos a medios.
O problema de engenharia dificil e a amostragem: como voce recorta uma regiao de features de tamanho fixo de uma caixa de proposta cujos cantos nao se alinham com os limites dos pixels? Errar nisso custa decimos de ponto de mAP em todos os lugares. O RoIAlign e a resposta.
O Conceito
A arquitetura
flowchart LR
IMG["Entrada"] --> BB["ResNet<br/>backbone"]
BB --> FPN["Rede de<br/>Piramide de Features"]
FPN --> RPN["Rede de<br/>Proposta de<br/>Regioes"]
FPN --> RA["RoIAlign"]
RPN -->|"top-K propostas"| RA
RA --> BH["Cabeca de caixa<br/>(classe + refino)"]
RA --> MH["Cabeca de mascara<br/>(conv 14x14)"]
BH --> NMS["NMS"]
MH --> NMS
NMS --> OUT["caixas +<br/>classes + mascaras"]
style BB fill:#dbeafe,stroke:#2563eb
style FPN fill:#fef3c7,stroke:#d97706
style RPN fill:#fecaca,stroke:#dc2626
style OUT fill:#dcfce7,stroke:#16a34a
Cinco pecas para entender:
- Backbone — ResNet-50 ou ResNet-101 treinado no ImageNet. Produz uma hierarquia de mapas de features em strides 4, 8, 16, 32.
- FPN (Rede de Piramide de Features) — conexoes top-down + laterais que dao a cada nivel C canais de features semanticamente ricas. A deteccao consulta o nivel da FPN que corresponde ao tamanho do objeto.
- RPN (Rede de Proposta de Regioes) — uma pequena cabeca conv que, em cada posicao de ancora, preve "existe um objeto aqui?" e "como eu refino a caixa?". Produz ~1000 propostas por imagem.
- RoIAlign — amostra uma regiao de features de tamanho fixo (por exemplo, 7x7) de qualquer caixa em qualquer nivel da FPN. Amostragem bilinear, sem quantizacao.
- Cabecas — cabeca de caixa de duas camadas que refina a caixa e escolhe uma classe, mais uma pequena cabeca conv que produz uma mascara binaria
28x28para cada proposta.
Por que RoIAlign, e nao RoIPool
O Fast R-CNN original usava o RoIPool, que divide uma caixa de proposta em uma grade, pega a feature maxima em cada celula e arredonda todas as coordenadas para inteiros. Esse arredondamento desalinha o mapa de features das coordenadas de pixel de entrada em ate um pixel inteiro do mapa de features — pequeno em uma imagem de 224x224, catastrofico quando o mapa de features e de stride 32.
RoIPool:
box (34.7, 51.3, 98.2, 142.9)
round -> (34, 51, 98, 142)
split grid -> round each cell boundary
misalignment accumulates at every step
RoIAlign:
box (34.7, 51.3, 98.2, 142.9)
sample at exact float coordinates using bilinear interpolation
no rounding anywhere
O RoIAlign aumenta o AP de mascara em 3-4 pontos no COCO de graca. Todo detector que se importa com a localizacao agora o usa — YOLOv7 seg, RT-DETR, Mask2Former, todos.
A RPN em um paragrafo
Em cada posicao de um mapa de features, coloque K caixas de ancora de diferentes tamanhos e formatos. Preveja uma pontuacao de objetividade para cada ancora e um deslocamento de regressao para transformar a ancora em uma caixa melhor ajustada. Mantenha as ~1.000 melhores caixas por pontuacao, aplique NMS em IoU 0.7 e entregue os sobreviventes as cabecas. A RPN e treinada com sua propria mini-perda — a mesma estrutura da perda do YOLO da Licao 6, apenas com duas classes (objeto / nao objeto).
A cabeca de mascara
Para cada proposta (apos o RoIAlign), a cabeca de mascara e uma pequena FCN: quatro convs 3x3, um deconv 2x, uma conv final 1x1 que produz num_classes canais de saida na resolucao 28x28. Apenas o canal correspondente a classe prevista e mantido; os outros sao ignorados. Isso desacopla a previsao de mascara da classificacao.
Faca upsample da mascara 28x28 para o tamanho original em pixels da proposta para produzir a mascara binaria final.
Perdas
O Mask R-CNN tem quatro perdas somadas:
L = L_rpn_cls + L_rpn_box + L_box_cls + L_box_reg + L_mask
L_rpn_cls,L_rpn_box— objetividade + regressao de caixa para as propostas da RPN.L_box_cls— entropia cruzada sobre (C+1) classes (incluindo fundo) no classificador da cabeca.L_box_reg— smooth L1 no refinamento de caixa da cabeca.L_mask— entropia cruzada binaria por pixel na saida da mascara 28x28.
Cada perda tem seu proprio peso padrao; a implementacao do torchvision os expoe como argumentos do construtor.
Formato de saida
torchvision.models.detection.maskrcnn_resnet50_fpn_v2 retorna uma lista de dicts, um por imagem:
{
"boxes": (N, 4) in (x1, y1, x2, y2) pixel coordinates,
"labels": (N,) class IDs, 0 = background so indices are 1-based,
"scores": (N,) confidence scores,
"masks": (N, 1, H, W) float masks in [0, 1] — threshold at 0.5 for binary,
}
A mascara ja esta na resolucao total da imagem. A saida 28x28 da cabeca foi upsampled internamente.
Construa
Passo 1: RoIAlign do zero
Este e o unico componente do Mask R-CNN que e mais simples de entender como codigo do que como prosa.
import torch
import torch.nn.functional as F
def roi_align_single(feature, box, output_size=7, spatial_scale=1 / 16.0):
"""
feature: (C, H, W) single-image feature map
box: (x1, y1, x2, y2) in original image pixel coordinates
output_size: side of the output grid (7 for box head, 14 for mask head)
spatial_scale: reciprocal of the feature map stride
"""
C, H, W = feature.shape
x1, y1, x2, y2 = [c * spatial_scale - 0.5 for c in box]
bin_w = (x2 - x1) / output_size
bin_h = (y2 - y1) / output_size
grid_y = torch.linspace(y1 + bin_h / 2, y2 - bin_h / 2, output_size)
grid_x = torch.linspace(x1 + bin_w / 2, x2 - bin_w / 2, output_size)
yy, xx = torch.meshgrid(grid_y, grid_x, indexing="ij")
gx = 2 * (xx + 0.5) / W - 1
gy = 2 * (yy + 0.5) / H - 1
grid = torch.stack([gx, gy], dim=-1).unsqueeze(0)
sampled = F.grid_sample(feature.unsqueeze(0), grid, mode="bilinear",
align_corners=False)
return sampled.squeeze(0)
Cada numero esta em uma posicao amostrada bilinearmente. Sem arredondamento, sem quantizacao, sem gradientes descartados.
Passo 2: Compare com o RoIAlign do torchvision
from torchvision.ops import roi_align
feature = torch.randn(1, 16, 50, 50)
boxes = torch.tensor([[0, 10, 20, 100, 90]], dtype=torch.float32) # (batch_idx, x1, y1, x2, y2)
ours = roi_align_single(feature[0], boxes[0, 1:].tolist(), output_size=7, spatial_scale=1/4)
theirs = roi_align(feature, boxes, output_size=(7, 7), spatial_scale=1/4, sampling_ratio=1, aligned=True)[0]
print(f"shape ours: {tuple(ours.shape)}")
print(f"shape theirs: {tuple(theirs.shape)}")
print(f"max|diff|: {(ours - theirs).abs().max().item():.3e}")
Com sampling_ratio=1 e aligned=True, os dois coincidem dentro de 1e-5.
Passo 3: Carregue um Mask R-CNN pre-treinado
import torch
from torchvision.models.detection import maskrcnn_resnet50_fpn_v2, MaskRCNN_ResNet50_FPN_V2_Weights
model = maskrcnn_resnet50_fpn_v2(weights=MaskRCNN_ResNet50_FPN_V2_Weights.DEFAULT)
model.eval()
print(f"params: {sum(p.numel() for p in model.parameters()):,}")
print(f"classes (including background): {len(model.roi_heads.box_predictor.cls_score.out_features * [0])}")
46M de parametros, 91 classes (COCO). A primeira classe (id 0) e fundo; tudo o que o modelo realmente detecta comeca no id 1.
Passo 4: Execute a inferencia
with torch.no_grad():
x = torch.randn(3, 400, 600)
predictions = model([x])
p = predictions[0]
print(f"boxes: {tuple(p['boxes'].shape)}")
print(f"labels: {tuple(p['labels'].shape)}")
print(f"scores: {tuple(p['scores'].shape)}")
print(f"masks: {tuple(p['masks'].shape)}")
O tensor de mascara tem formato (N, 1, H, W). Aplique um limiar de 0.5 para obter uma mascara binaria por objeto:
binary_masks = (p['masks'] > 0.5).squeeze(1) # (N, H, W) boolean
Passo 5: Troque as cabecas por uma contagem de classes personalizada
A receita comum de fine-tuning: reutilize o backbone, a FPN e a RPN; substitua as duas cabecas classificadoras.
from torchvision.models.detection.faster_rcnn import FastRCNNPredictor
from torchvision.models.detection.mask_rcnn import MaskRCNNPredictor
def build_custom_maskrcnn(num_classes):
model = maskrcnn_resnet50_fpn_v2(weights=MaskRCNN_ResNet50_FPN_V2_Weights.DEFAULT)
in_features = model.roi_heads.box_predictor.cls_score.in_features
model.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes)
in_features_mask = model.roi_heads.mask_predictor.conv5_mask.in_channels
hidden_layer = 256
model.roi_heads.mask_predictor = MaskRCNNPredictor(in_features_mask, hidden_layer, num_classes)
return model
custom = build_custom_maskrcnn(num_classes=5)
print(f"custom cls_score.out_features: {custom.roi_heads.box_predictor.cls_score.out_features}")
num_classes deve incluir a classe de fundo, entao um conjunto de dados com 4 classes de objeto usa num_classes=5.
Passo 6: Congele o que nao precisa de treinamento
Em conjuntos de dados pequenos, congele o backbone e a FPN. Apenas a objetividade + regressao da RPN e as duas cabecas aprendem.
def freeze_backbone_and_fpn(model):
# torchvision Mask R-CNN packs the FPN inside `model.backbone` (as
# `model.backbone.fpn`), so iterating `model.backbone.parameters()` covers
# both the ResNet feature layers and the FPN lateral/output convs.
for p in model.backbone.parameters():
p.requires_grad = False
return model
custom = freeze_backbone_and_fpn(custom)
trainable = sum(p.numel() for p in custom.parameters() if p.requires_grad)
print(f"trainable after freeze: {trainable:,}")
Em conjuntos de dados de 500 imagens, essa e a diferenca entre convergencia e overfitting.
Use
O loop de treinamento completo para o Mask R-CNN no torchvision tem 40 linhas e nao muda de forma significativa entre tarefas — troque os conjuntos de dados e va em frente.
def train_step(model, images, targets, optimizer):
model.train()
loss_dict = model(images, targets)
losses = sum(loss for loss in loss_dict.values())
optimizer.zero_grad()
losses.backward()
optimizer.step()
return {k: v.item() for k, v in loss_dict.items()}
A lista targets deve ter dicts por imagem com boxes, labels e masks (como tensores binarios (num_instances, H, W)). O modelo retorna um dict de quatro perdas durante o treinamento e uma lista de previsoes durante a avaliacao, com base em model.training.
O avaliador pycocotools produz mAP@IoU=0.5:0.95 tanto para caixas quanto para mascaras; voce precisa dos dois numeros para saber se a cabeca de caixa ou a cabeca de mascara e o gargalo.
Entregue
Esta licao produz:
outputs/prompt-instance-vs-semantic-router.md— um prompt que faz tres perguntas e escolhe entre instancia, semantica ou panoptica, mais o modelo exato para comecar.outputs/skill-mask-rcnn-head-swapper.md— uma skill que gera as 10 linhas de codigo para trocar cabecas em qualquer modelo de deteccao do torchvision, dada a novanum_classes.
Exercicios
- (Facil) Verifique seu RoIAlign contra
torchvision.ops.roi_alignem 100 caixas aleatorias. Reporte a diferenca absoluta maxima. Tambem execute o RoIPool (comportamento pre-2017) e mostre que ele diverge em ~1-2 pixels do mapa de features em caixas proximas a borda. - (Medio) Faca fine-tuning do
maskrcnn_resnet50_fpn_v2em um conjunto de dados personalizado de 50 imagens (quaisquer duas classes: baloes, peixes, buracos, logos). Congele o backbone, treine por 20 epocas, reporte o AP de mascara@0.5. - (Dificil) Substitua a cabeca de mascara do Mask R-CNN por uma que preveja em 56x56 em vez de 28x28. Meca o mAP@IoU=0.75 antes e depois. Explique por que o ganho (ou a falta dele) corresponde ao trade-off esperado entre precisao de borda e memoria.
Termos-Chave
| Termo | O que as pessoas dizem | O que realmente significa |
|---|---|---|
| Mask R-CNN | "Deteccao mais mascaras" | Faster R-CNN + uma pequena cabeca FCN que preve uma mascara 28x28 por proposta por classe |
| FPN | "Piramide de features" | Conexoes top-down + laterais que dao a cada nivel de stride C canais de features semanticamente ricas |
| RPN | "Propositor de regioes" | Uma pequena cabeca conv que produz ~1000 propostas objeto/nao-objeto por imagem |
| RoIAlign | "Recorte sem arredondamento" | Amostra bilinearmente uma grade de features de tamanho fixo de qualquer caixa com coordenadas em float |
| RoIPool | "Recorte pre-2017" | Mesmo proposito que o RoIAlign mas arredonda coordenadas de caixa; obsoleto |
| Mask AP | "mAP de instancia" | Precisao media calculada com IoU de mascara em vez de IoU de caixa; a metrica de segmentacao de instancias do COCO |
| Cabeca de mascara binaria | "Mascara por classe" | Preve uma mascara binaria por classe para cada proposta; apenas o canal da classe prevista e mantido |
| Classe de fundo | "Classe 0" | A classe abrangente "nenhum objeto"; os indices para classes reais comecam em 1 |
Leitura Complementar
- Mask R-CNN (He et al., 2017) — o artigo; a secao 3 sobre RoIAlign e a leitura critica
- FPN: Feature Pyramid Networks (Lin et al., 2017) — o artigo da FPN; todo detector moderno a usa
- torchvision Mask R-CNN tutorial — a referencia para o loop de fine-tuning
- Detectron2 model zoo — implementacoes de producao com pesos treinados para quase todas as variantes de deteccao e segmentacao