TAI BUI
Back to curriculum
BuildPython

Instance Segmentation — Mask R-CNN

Add a small mask branch to a Faster R-CNN detector and you have instance segmentation. RoIAlign is the part that actually matters, and it's trickier than it looks.

TIME: ~75 minutesPREREQUISITES: Phase 4 Lesson 06 (YOLO), Phase 4 Lesson 07 (U-Net)

Learning Objectives

  • Trace the Mask R-CNN architecture end-to-end: backbone, FPN, RPN, RoIAlign, box head, mask head
  • Implement RoIAlign from scratch and explain why RoIPool is no longer used
  • Use the torchvision `maskrcnn_resnet50_fpn_v2` pretrained model for production-quality instance masks and read its output format correctly
  • Fine-tune Mask R-CNN on a small custom dataset by replacing the box and mask heads and keeping the backbone frozen

The Problem

Semantic segmentation gives you one mask per class. Instance segmentation gives you one mask per object, even when two objects share a class. Counting individuals, tracking across frames, and measuring things (the bounding box of each brick in a wall, each cell in a microscope image) all demand instance segmentation.

Mask R-CNN (He et al., 2017) solved this by reframing instance segmentation as detection-plus-a-mask. The design was so clean that for the next five years almost every instance segmentation paper was a Mask R-CNN variant, and the torchvision implementation is still the production default for small to medium datasets.

The hard engineering problem is sampling: how do you crop a fixed-size feature region out of a proposal box whose corners do not align with pixel boundaries? Getting that wrong costs tenths of a mAP point everywhere. RoIAlign is the answer.

Warm-up0/3
1. A semantic segmentation network like U-Net labels every pixel with a class, but two overlapping objects of the same class still merge into one blob. What additional capability would let a system tell those two objects apart?Warm-up
Two overlapping rounded shapes both labelled object, drawn as one touching blob, with an arrow to a dashed box marked question mark, tell apart.
Two touching objects of the same class rendered as a single shared-label blob.
2. A detector proposes a box with floating-point corners, like (34.7, 51.3) to (98.2, 142.9), the same kind of coordinates a YOLO-style detector outputs. If you round all four numbers to the nearest integer before using the box, what is the most direct consequence?Warm-up
A box with float corners 34.7, 51.3, 98.2, 142.9, an instruction to round every number to the nearest integer, and an arrow down to a dashed box marked question mark, rounded box.
A detector's float-coordinate box, before rounding is applied.
3. A detector scores thousands of candidate boxes, and many of them overlap heavily on the same object. Which standard step keeps the best box and removes the near-duplicates?Warm-up
Four overlapping outlined rectangles in a cascade, each in a different dash pattern, with a legend on the right matching each pattern to a score of 0.92, 0.88, 0.81 or 0.76, next to a dashed box marked question mark, keep which.
Illustrative overlapping box scores on one object, with the keep-or-drop step unresolved.
Check: The Problem0/2
1. What is Mask R-CNN?Define
A code box loading torchvision's maskrcnn_resnet50_fpn_v2 with its default COCO weights, above two badges reading parameters 46M and COCO classes 91.
One concrete Mask R-CNN: the pretrained torchvision model and the two figures Build It Step 3 prints.
2. You must measure the bounding box of each brick in photos of a brick wall, and many bricks touch. Which approach fits?Design
A brick-wall pattern of staggered rows in a single color, every brick sharing its edges with the next and none marked out from the rest.
A brick wall where neighbouring bricks share edges, before any per-brick boundary is drawn.

The Concept

The architecture

Mermaid diagram

Five pieces to understand:

  1. Backbone: ResNet-50 or ResNet-101, pretrained on ImageNet, producing a hierarchy of feature maps at strides 4, 8, 16, 32.
  2. FPN (Feature Pyramid Network): top-down and lateral connections that give every level C channels of semantic-rich features, so detection can query the FPN level that matches the object's size.
  3. RPN (Region Proposal Network): a small conv head that, at every anchor position, predicts "is there an object here?" and "how do I refine the box?" It produces ~1,000 proposals per image.
  4. RoIAlign: samples a fixed-size (e.g. 7x7) feature patch from any box on any FPN level, using bilinear sampling with no quantisation.
  5. Heads: a two-layer box head that refines the box and picks a class, and a small conv head that outputs a 28x28 binary mask for each proposal.

Why RoIAlign, not RoIPool

The original Fast R-CNN used RoIPool, which splits a proposal box into a grid, takes the maximum feature in each cell, and rounds all coordinates to integers. That rounding misaligns the feature map from the input pixel coordinates by up to a full feature-map pixel: barely noticeable on a 224x224 image, catastrophic once the feature map is 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

RoIAlign lifts mask AP by 3-4 points on COCO for free. Every detector that cares about localisation now uses it, from YOLOv7-seg to RT-DETR to Mask2Former.

The RPN in one paragraph

At every position of a feature map, place K anchor boxes of different sizes and shapes. Predict an objectness score for each anchor and a regression offset to turn the anchor into a better-fitting box. Keep the top ~1,000 boxes by score, apply NMS at IoU 0.7, and hand the survivors to the heads. The RPN trains against its own mini-loss, the same structure as the YOLO loss from Lesson 6, just with two classes: object or no object.

The mask head

For each proposal, after RoIAlign, the mask head is a tiny FCN: four 3x3 convs, a 2x deconv, a final 1x1 conv that produces num_classes output channels at 28x28 resolution. Only the channel corresponding to the predicted class is kept; the others are ignored. This decouples mask prediction from classification.

Upsample the 28x28 mask to the proposal's original pixel size to produce the final binary mask.

Losses

Mask R-CNN has five losses added together:

L = L_rpn_cls + L_rpn_box + L_box_cls + L_box_reg + L_mask
  • L_rpn_cls and L_rpn_box: objectness and box regression for the RPN's proposals.
  • L_box_cls: cross-entropy over (C+1) classes, including background, on the head's classifier.
  • L_box_reg: smooth L1 on the head's box refinement.
  • L_mask: per-pixel binary cross-entropy on the 28x28 mask output.

Each loss has its own default weight; the torchvision implementation exposes them as constructor arguments.

Output format

torchvision.models.detection.maskrcnn_resnet50_fpn_v2 returns a list of dicts, one per image:

{
    "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,
}

The mask is already at full image resolution; the 28x28 head output has been upsampled internally.

Check: The Concept0/14
1. What does the FPN (Feature Pyramid Network) do inside Mask R-CNN?Define
Four stacked boxes of decreasing width labelled stride 4, 8, 16 and 32, with an arrow into a dashed box marked question mark, the FPN's job.
The backbone's four stride levels, the input the FPN is given, with the FPN's own role left unresolved.
2. What is the RPN (Region Proposal Network)?Define
A feature-map grid with three anchor rectangles of different sizes and aspect ratios drawn around one position, next to a card listing the lesson's anchor setup.
The anchor boxes the RPN works from, drawn at one position of a feature map.
3. What does RoIAlign produce from a proposal box?Define
A grid of feature-map cells with the lesson's example proposal box drawn over it, its corners landing inside cells rather than on the gridlines, and an arrow into a dashed box marked question mark, what RoIAlign returns.
The lesson's example proposal box sitting across feature-map cell boundaries, with what RoIAlign returns left unresolved.
4. What is RoIPool, and why is it considered obsolete?Define
Two cards side by side, Fast R-CNN before 2017 with crop step RoIPool and Mask R-CNN in 2017 with crop step RoIAlign, above a dashed box marked question mark, what RoIPool does.
RoIPool and RoIAlign named as the crop step of their own era, with what RoIPool does left unresolved.
5. What is Mask AP?Define
A solid outlined shape marked ground-truth mask with a dashed outlined shape marked predicted mask laid over it, and an arrow into a dashed box marked question mark, what Mask AP measures.
One object's predicted mask against its ground-truth mask, with what the metric measures left unresolved.
6. What does the mask head output for each proposal, and what is kept at inference?Define
A chain of four boxes reading RoIAlign patch, four 3 by 3 convs, a 2 times deconv and a 1 by 1 conv, with an arrow down into a dashed box marked question mark, output and what is kept.
The mask head's layer stack for one proposal, with its output and what inference keeps left unresolved.
7. Why does Mask R-CNN use RoIAlign instead of RoIPool to crop features from a proposal box?Explain
One proposal box branching into two boxes labelled RoIPool and RoIAlign, each followed by its own dashed box marked question mark, how it crops.
The two candidate crop steps for one proposal box, with what each one does left unresolved on both sides.
8. Why does rounding box coordinates hurt much more on a stride-32 feature map than on a 224x224 image?Explain
Two equally sized squares, one labelled one stride-4 cell and one labelled one stride-32 cell, each marked with an unknown number of input pixels.
One feature-map cell at each of the two strides, with the input width each one covers left unresolved.
9. Mask R-CNN's mask head predicts a separate 28x28 mask for every class, for each proposal. Why per class rather than one shared mask?Explain
Three boxes labelled cat, dog and bird, each holding a differently shaped blob marked 28x28, above a dashed box marked question mark, why one per class.
Illustrative per-class mask channels for one proposal, with the reason for having one per class left unresolved.
10. The FPN inside Mask R-CNN gives features at strides 4, 8, 16 and 32. Why not use a single level for every object?Explain
Four squares of the same size with progressively coarser grids, labelled stride 4, 8, 16 and 32, above a dashed box marked question mark, why keep all four.
The four FPN levels drawn as grids that halve in resolution at each step, with the reason for keeping all four left unresolved.
11. After scoring anchors at every position, what does the RPN do before handing proposals to the heads?Explain
Nine boxes in a grid, each labelled with an objectness score between 0.11 and 0.81, next to a dashed box marked question mark, selection step.
Illustrative scored anchors, with the proposal-selection step that follows left unresolved.
12. Mask R-CNN's total training loss adds up five terms: L_rpn_cls, L_rpn_box, L_box_cls, L_box_reg and L_mask. Which two come from the RPN?Explain
Five equal boxes labelled L_rpn_cls, L_rpn_box, L_box_cls, L_box_reg and L_mask in a row, with a dashed box below marked question mark, which 2 are from the RPN.
The five loss terms that sum to the total loss, with none marked as belonging to a particular part yet.
13. What resolution are the masks in the model's prediction dict, and why?Explain
A box labelled mask head output, 28x28, with an arrow into a dashed box marked question mark, returned mask shape.
The mask head's own 28x28 output, with the shape the model finally returns left unresolved.
14. You want crisper mask boundaries on large objects and can spend more memory. Which change fits the Mask R-CNN design?Design
A box reading mask head today, 28x28 output, next to a box reading budget, more memory to spend, above a dashed box marked question mark, change to make.
The mask head's current output size and the spare memory budget, with the change to make left unresolved.

Build It

Step 1: RoIAlign from scratch

This is the one component of Mask R-CNN that is simpler to understand as code than as prose.

python
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)

Every number is at a bilinearly-sampled position. No rounding, no quantisation, no dropped gradients.

Step 2: Compare to torchvision's RoIAlign

Before trusting a hand-written RoIAlign anywhere real, check it against torchvision's own implementation.

python
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}")

With sampling_ratio=1 and aligned=True, the two match to within 1e-5.

Step 3: Load a pretrained Mask R-CNN

Skip training from zero. torchvision ships a Mask R-CNN already trained on COCO, so loading it gets you production-quality masks before you write a single training loop.

python
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 parameters, 91 classes (COCO). The first class (id 0) is background; everything the model actually detects starts at id 1.

Step 4: Run inference

Before wiring this into a pipeline, check exactly what shape the model hands back for each prediction key.

python
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)}")

The mask tensor is shape (N, 1, H, W). Threshold at 0.5 to get a binary mask per object:

python
binary_masks = (p['masks'] > 0.5).squeeze(1)  # (N, H, W) boolean

Step 5: Swap the heads for a custom class count

The common fine-tuning recipe: reuse the backbone, FPN, and RPN; replace the two classifier heads.

python
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 must include the background class, so a dataset with 4 object classes uses num_classes=5.

Step 6: Freeze what does not need training

On small datasets, freeze the backbone and the FPN. Only the RPN objectness + regression and the two heads learn.

python
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:,}")

On 500-image datasets this is the difference between convergence and overfitting.

Check: Build It0/15
1. torchvision's Mask R-CNN prediction dict has `labels` that start at 1, not 0. Why?Define
A table of three detections with label ids 1, 18 and 64 and their scores, above a dashed box marked question mark, why the numbering starts at 1.
Illustrative labels from one prediction dict, none of them 0, with the reason for the numbering left unresolved.
2. When fine-tuning on a custom dataset, why can the backbone, FPN and RPN be reused while both predictors are replaced?Explain
Three boxes labelled Backbone plus FPN, RPN, and Box plus mask predictors, each with its own dashed question-mark box below asking whether it is tied to the class count.
The three groups of components, with whether each depends on the class count left unresolved for all three.
3. Build It Step 2 compares a hand-written RoIAlign against torchvision's `roi_align` and passes `aligned=True` explicitly. Why not just rely on torchvision's default?Explain
The lesson's own torchvision roi_align call, with the aligned=True argument highlighted on its own line.
The call from Build It Step 2, where aligned is passed explicitly instead of being left to the library.
4. In `roi_align_single` with `spatial_scale=1 / 16.0`, a box has x1=160 and x2=320. What are x1 and x2 after the line that rescales the box?Apply
The signature and docstring of the lesson's roi_align_single with spatial_scale set to one sixteenth, next to a card giving x1 as 160 and x2 as 320, and a dashed box marked question mark comma question mark, x1 x2 after it runs.
The lesson's own roi_align_single signature and the box coordinates before its rescale line runs.
5. `roi_align_single` is called with a feature map of shape (256, 50, 50) and `output_size=7`. What shape does it return?Apply
A box labelled feature, 256, 50, 50, with an arrow into a dashed box marked question mark, return shape.
The function's input feature shape, with the returned shape left unresolved.
6. Build It Step 2 runs the hand-written RoIAlign and torchvision's `roi_align` with `aligned=True` and `sampling_ratio=1`. What does the printed max|diff| show?Apply
The comparison code calling roi_align_single and torchvision's roi_align with aligned=True and sampling_ratio=1, above a dashed box marked question mark, printed max absolute diff.
The lesson's own comparison code between the two RoIAlign implementations, with the printed difference left unresolved.
7. An image tensor of shape (3, 400, 600) is run through the pretrained model, and it returns N=2 detections. After `binary_masks = (p['masks'] > 0.5).squeeze(1)`, what is the shape of `binary_masks`?Apply
Three lines of code building a 3 by 400 by 600 image tensor, running it through the model for two detections and thresholding the masks, above a dashed box marked question mark, binary_masks shape.
The stem's own call sequence, with the shape of binary_masks left unresolved.
8. The model's `masks` output has shape (N, 1, H, W) with float values in [0, 1]. How do you turn this into a binary mask per object?Apply
Seven cells holding values from 0.05 to 0.95, shaded smoothly brighter as the value rises, above a dashed box marked question mark, foreground or not.
A row of mask pixel values between 0 and 1, with the foreground decision left unresolved.
9. Your custom dataset has 6 real object classes. When you swap Mask R-CNN's box and mask heads for your own predictor, what num_classes should you pass?Apply
Six boxes labelled class 1 through class 6, next to a dashed box marked question mark, num_classes.
Six real object classes, with the num_classes argument to pass left unresolved.
10. After `custom = freeze_backbone_and_fpn(custom)`, which parameters still have `requires_grad=True`?Apply
The two call lines from the lesson's freeze step, above four groups labelled backbone, FPN, RPN and box plus mask heads, each with its own dashed box marked question mark, requires_grad.
The freeze step as the lesson calls it, with each group's requires_grad flag left unresolved.
11. You fine-tune Mask R-CNN on 500 images and validation mAP plateaus while train loss keeps dropping. What is the first thing to try?Analyze
A line chart over 20 epochs with train loss steadily falling and val mAP rising early then going flat, on a 500-image training run.
Illustrative training curves: train loss keeps falling while val mAP plateaus, on a 500-image dataset.
12. You port the pretrained weights into another framework, assuming the original used the aligned RoIAlign convention. Boxes look almost right, but they sit a fraction of a pixel off and mask edges are slightly blurred. What should you check first?Analyze
A box with a dashed outline sitting slightly off its expected outline, marked off by a fraction of a pixel, next to a circle with a faint blurred-looking edge, marked mask edge slightly blurred.
The symptom after porting the weights: boxes sit slightly off and mask edges look blurred, with no cause named yet.
13. You fine-tune with the backbone and FPN frozen. The total loss keeps dropping, yet both the boxes and the masks stay noisy. Which signal should you inspect?Analyze
A frozen backbone and FPN box next to a chart of total loss falling smoothly over epochs, with a note that boxes and masks still stay noisy.
A falling total loss with a frozen backbone and FPN, while the noisy-output problem remains unexplained.
14. A dataset has 4 object classes labeled 1 to 4, and you build the model with `num_classes=4`. Which mistake explains the trouble with class 4?Analyze
A row of dataset labels 1, 2, 3 and 4 above a row of predictor output slots 0, 1, 2 and 3, next to a dashed box marked question mark, why class 4 breaks.
The dataset's label values next to the output slots num_classes=4 allocates, with the cause of the trouble left unresolved.
15. You have 50 labeled images with 2 object classes and want a working instance segmenter quickly. Which setup fits?Design
A five by ten grid of small tiles marked 50 labeled images, next to a card naming the two classes in the data and a dashed box marked question mark, setup to use.
The 50-image, two-class dataset the question starts from, with the setup to use left unresolved.

Use It

The full training loop for Mask R-CNN in torchvision is 40 lines, and it barely changes between tasks. Swap datasets and go.

python
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()}

The targets list must have per-image dicts with boxes, labels, and masks (as (num_instances, H, W) binary tensors). The model returns a dict of five losses during training and a list of predictions during eval, keyed on model.training.

The pycocotools evaluator produces mAP@IoU=0.5:0.95 for both boxes and masks; you need both numbers to know whether the box head or the mask head is the bottleneck.

Check: Use It0/3
1. Why do you need both a box AP number and a mask AP number from the evaluator?Explain
Two dashed boxes side by side, each marked question mark, one labelled box AP and one labelled mask AP, with no values filled in.
Two unresolved evaluator numbers, box AP and mask AP, for the same model.
2. You call `model.eval()` and then `model(images)` with no targets. What comes back?Apply
The lines model.eval() and out equals model of images with no targets, above a dashed box marked question mark, what out holds.
A call to the model in eval mode with no targets, and the return value left unresolved.
3. On the same model, box AP is high but mask AP is much lower. Which component do you investigate first?Analyze
Two badges, box AP at 0.74 and mask AP at 0.39 in a different color, on the same model.
An illustrative box AP and mask AP pair with a wide gap between them, on one model.

Ship It

This lesson produces:

  • outputs/prompt-instance-vs-semantic-router.md, a prompt that asks three questions and picks instance vs semantic vs panoptic plus the exact model to start with.
  • outputs/skill-mask-rcnn-head-swapper.md, a skill that generates the 10 lines of code for swapping heads on any torchvision detection model, given the new num_classes.
Author's Note

The place people actually get stuck on Mask R-CNN isn't the architecture, it's RoIAlign's aligned flag, and the trap runs backward from what you'd guess. torchvision's roi_align defaults to aligned=False, and maskrcnn_resnet50_fpn_v2's pooler never overrides that default, so the pretrained model you load out of the box runs the unaligned convention internally. Build It Step 2 passes aligned=True explicitly because that is the corrected behavior, not the shipped one. Assume the opposite, that the pretrained model already uses the aligned convention, and you get boxes that look almost right: off by a fraction of a pixel everywhere, mask boundaries blurred just enough that the cause is hard to trace back. If you ever port weights between frameworks or reimplement RoIAlign yourself, check that flag before touching anything else, and don't trust your memory of which way the default goes.

The other trap is freezing too little. Freezing the backbone and FPN on a small dataset (Build It, Step 6) is the right call, but it's easy to leave the RPN undertrained at the same time and not notice, because the total loss still drops. A weak RPN feeds noisy proposals to both heads no matter how well you tune them. Watch the RPN's own loss term, not just the sum.

Exercises

  1. (Easy) Verify your RoIAlign against torchvision.ops.roi_align on 100 random boxes. Report the max absolute difference. Also run RoIPool (pre-2017 behaviour) and show it diverges by ~1-2 feature-map pixels on boxes near the border.
  2. (Medium) Fine-tune maskrcnn_resnet50_fpn_v2 on a 50-image custom dataset (any two classes: balloons, fish, pothole, logos). Freeze the backbone, train for 20 epochs, report mask [email protected].
  3. (Hard) Replace Mask R-CNN's mask head with one that predicts at 56x56 instead of 28x28. Measure mAP@IoU=0.75 before and after. Explain why the gain (or lack of one) matches the expected boundary-precision / memory trade-off.

Key Terms

TermWhat people sayWhat it actually means
Mask R-CNN"Detection plus masks"Faster R-CNN + a small FCN head that predicts a 28x28 mask per proposal per class
FPN"Feature pyramid"Top-down + lateral connections that give every stride level C channels of semantic-rich features
RPN"Region proposer"A small conv head that produces ~1000 object/no-object proposals per image
RoIAlign"No-rounding crop"Bilinearly samples a fixed-size feature grid from any float-coordinate box
RoIPool"Pre-2017 crop"Same purpose as RoIAlign but rounds box coordinates; obsolete
Mask AP"Instance mAP"Average precision computed with mask IoU instead of box IoU; the COCO instance segmentation metric
Binary mask head"Per-class mask"Predicts one binary mask per class for each proposal; only the predicted class's channel is kept
Background class"Class 0"The catch-all "no object" class; indices for real classes start at 1

Further Reading