Mask R-CNN : Guide complet — Détection et Segmentation d’Instances
Résumé — Mask R-CNN, proposé par He, Gkioxari, Dollár et Girshick de Facebook AI Research en 2017, est une extension de Faster R-CNN qui ajoute une branche de segmentation pour produire des masques pixel-parfait pour chaque objet détecté. C’est l’algorithme de référence pour la segmentation d’instances: il détecte chaque objet individuellement dans une image, le classe, et dessine son contour exact au pixel près. La contribution clé est RoI Align, qui remplace le RoI Pooling approximatif de Faster R-CNN par une interpolation bilinéaire exacte, éliminant les décalages de masquage.
Principe mathématique
1. Architecture générale
Mask R-CNN = Faster R-CNN + Branche de Masque:
- Backbone + FPN: un ResNet-FPN extrait des features multi-échelle
- Region Proposal Network (RPN): propose ~1000 Régions d’Intérêt (RoIs) via des anchor boxes
- RoI Align: extrait des features de taille fixe (7×7) pour chaque RoI exactement
- Branches: classification + bounding box + masque binaire (m×m par classe)
2. RoI Align — la contribution clé
Le RoI Pooling de Faster R-CNN quantifie les coordonnées des RoIs en entiers, créant des décalages jusqu’à 0.5 pixel. Pour la segmentation, c’est catastrophique: le masque est décalé.
RoI Align supprime cette quantification et utilise l’interpolation bilinéaire:
Pour chaque point d’échantillonnage (x, y) dans le feature map extrait:
– On calcule les 4 pixels voisins les plus proches: p1, p2, p3, p4
– On interpole: V(x,y) = w1·V(p1) + w2·V(p2) + w3·V(p3) + w4·V(p4)
Où les poids w_i sont calculés par la distance relative de (x,y) à chaque pixel.
En pratique, on échantillonne 4 points par cellule de bin (typiquement 2×2 = 4 points) et on prend le max de chaque cellule.
3. Loss totale
L = L_cls + L_box + L_mask
Où:
– L_cls: Cross-Entropy pour la classification de la classe
– L_box: Smooth L1 loss pour la régression de bounding box (4 coordonnées)
– L_mask: BCE (Binary Cross-Entropy) par pixel pour chaque clé de classe (K masks de m×m)
Crucialement, L_mask est calculé indépendamment pour chaque classe: le masque de la classe k utilise uniquement la sortie k du branch de masque. Cela permet au modèle de générer des masques pour toutes les classes sans compétition entre elles. La classe correcte est sélectionnée au moment de l’inférence.
4. Feature Pyramid Network (FPN)
Le FPN extrait des features à différentes échelles spatiales:
– P2: 1/4 de la résolution d’entrée (objets petits)
– P3: 1/8 (objets moyens)
– P4: 1/16 (grands objets)
– P5: 1/32 (très grands objets)
Chaque niveau du FPN est utilisé par le RPN pour générer des propositions adaptées à la taille de l’objet.
Intuition
Faster R-CNN dit « il y a un chat ici » avec une boîte englobante. Mask R-CNN fait un pas de plus: il dit exactement quels pixels appartiennent au chat et quels pixels sont l’arrière-plan. C’est la différence entre entourer un objet avec un feutre gros (bounding box) et le colorier précisément avec un pinceau fin (masque).
L’amélioration clé (RoI Align), c’est comme passer d’un artiste qui dessine à main levée (approximation) à un artiste qui utilise une règle et un compas (précision pixel-parfait). Cette précision est essentielle quand on veut savoir exactement où finit le pixel du chat et où commence le pixel du canapé.
Implémentation Python
1. Inférence avec Mask R-CNN pré-entraîné
import torch
import torchvision
from torchvision.models.detection import maskrcnn_resnet50_fpn, MaskRCNN_ResNet50_FPN_Weights
from PIL import Image
import matplotlib.pyplot as plt
import numpy as np
# Chargement du modèle pré-entraîné sur COCO
weights = MaskRCNN_ResNet50_FPN_Weights.DEFAULT
model = maskrcnn_resnet50_fpn(weights=weights, num_classes=91)
model.eval()
# Chargement d'une image
img = Image.open("photo.jpg").convert("RGB")
transform = weights.transforms()
img_tensor = transform(img)
# Inférence
with torch.no_grad():
predictions = model([img_tensor])[0]
# Affichage des détections
COCO_CATEGORIES = weights.meta["categories"]
threshold = 0.7
fig, ax = plt.subplots(1, 1, figsize=(12, 8))
ax.imshow(np.array(img))
for i in range(len(predictions["scores"])):
if predictions["scores"][i] < threshold:
continue
box = predictions["boxes"][i].cpu().numpy()
mask = predictions["masks"][i, 0].cpu().numpy()
label = COCO_CATEGORIES[predictions["labels"][i]]
score = predictions["scores"][i].item()
# Boîte englobante
from matplotlib.patches import Rectangle
rect = Rectangle((box[0], box[1]), box[2]-box[0], box[3]-box[1],
edgecolor="lime", facecolor="none", linewidth=2)
ax.add_patch(rect)
# Masque semi-transparent
ax.contour(mask, levels=[0.5], colors="cyan", linewidths=1.5, alpha=0.7)
# Étiquette
ax.text(box[0], box[1]-5, f"{label}: {score:.2f}", color="white",
bbox=dict(facecolor="red", alpha=0.7, pad=2), fontsize=10)
plt.axis("off")
plt.tight_layout()
plt.savefig("mask_rcnn_detections.png", dpi=150)
plt.show()
2. Entraînement sur dataset personnalisé
import torch
import torchvision
from torchvision.models.detection import maskrcnn_resnet50_fpn, MaskRCNN_ResNet50_FPN_Weights
from torchvision.datasets import VOCDetection
class PennFudanDataset(torch.utils.data.Dataset):
"""Dataset personnalisé pour Mask R-CNN avec masques."""
def __init__(self, root, transforms=None):
self.root = root
self.transforms = transforms
self.imgs = sorted(os.listdir(os.path.join(root, "PNGImages")))
self.masks = sorted(os.listdir(os.path.join(root, "PedMasks")))
def __getitem__(self, idx):
img_path = os.path.join(self.root, "PNGImages", self.imgs[idx])
mask_path = os.path.join(self.root, "PedMasks", self.masks[idx])
img = Image.open(img_path).convert("RGB")
mask = Image.open(mask_path)
mask = np.array(mask)
# Extraire les instances individuelles du masque
obj_ids = np.unique(mask)[1:] # Ignorer le fond (0)
masks = (mask == np.expand_dims(obj_ids, axis=(1, 2))).astype(np.uint8)
boxes = []
for i in range(len(obj_ids)):
pos = np.where(masks[i])
xmin = np.min(pos[1]); xmax = np.max(pos[1])
ymin = np.min(pos[0]); ymax = np.max(pos[0])
boxes.append([xmin, ymin, xmax, ymax])
target = {
"boxes": torch.as_tensor(boxes, dtype=torch.float32),
"labels": torch.ones((len(obj_ids),), dtype=torch.int64),
"masks": torch.as_tensor(masks),
"image_id": torch.tensor([idx])
}
if self.transforms:
img, target = self.transforms(img, target)
return img, target
def __len__(self):
return len(self.imgs)
# Entraînement
model = maskrcnn_resnet50_fpn(weights=None, num_classes=2)
params = [p for p in model.parameters() if p.requires_grad]
optimizer = torch.optim.SGD(params, lr=0.005, momentum=0.9, weight_decay=0.0005)
lr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)
dataset = PennFudanDataset("PennFudanPed/")
loader = torch.utils.data.DataLoader(dataset, batch_size=2, shuffle=True, collate_fn=lambda x: tuple(zip(*x)))
for epoch in range(10):
model.train()
total_loss = 0
for images, targets in loader:
loss_dict = model(images, targets)
losses = sum(loss for loss in loss_dict.values())
optimizer.zero_grad()
losses.backward()
optimizer.step()
total_loss += losses.item()
lr_scheduler.step()
print(f'Epoch {epoch}: Loss {total_loss/len(loader):.4f}')
Hyperparamètres
| Hyperparamètre | Valeur typique | Description |
|---|---|---|
| backbone | ResNet-50-FPN | Extraction de features (ResNet-50 + FPN) |
| num_classes | 91 (COCO) | Nombre de classes (+1 pour le fond) |
| mask_threshold | 0.5 | Seuil de binarisation pour les masques |
| anchor_sizes | (32, 64, 128, 256, 512) | Tailles des anchor boxes dans le RPN |
| rpn_pre_nms_top_n | 1000 | Nombre de propositions avant NMS |
| box_score_thresh | 0.05 | Score minimum pour retenir une détection |
Avantages
- Précision pixel-parfait : RoI Align élimine les décalages de quantification, donnant des masques 10-100x plus précis que les méthodes précédentes.
- Multi-tâches : Détection + classification + segmentation en une seule passe.
- Générique : Fonctionne sur n’importe quelle classe d’objets, pas seulement les COCO classes.
- Framework mature : Intégré dans torchvision, facile à utiliser avec des datasets personnalisés.
Limites
- Lent à l’inférence : ~5-10 FPS sur GPU, insuffisant pour le temps réel.
- Gourmand en mémoire : Le FPN + les masques nécessitent beaucoup de VRAM.
- Petits objets : Les très petits objets (< 32×32 pixels) sont mal détectés car le FPN a une résolution limitée.
- Dépendance au RPN : Si le RPN rate un objet (faux négatif), le masque ne peut pas le récupérer.
4 cas d’usage concrets
1. Analyse médicale : segmentation de cellules
Mask R-CNN segmente individuellement les cellules sanguines dans des frottis, permettant un comptage automatisé et une classification de types (globules rouges, blancs, plaquettes). Chaque cellule a son masque précis.
2. Robotique : manipulation d’objets
Un bras robotique utilise Mask R-CNN pour identifier et localiser les objets sur une table. Les masques précis permettent de calculer le centre de masse et la forme exacte de chaque objet pour une préhension optimale.
3. Vidéosurveillance intelligente
Masquer les visages des piétons détectés pour respecter le RGPD tout en conservant les boîtes de détection pour le comptage de foule et l’analyse de mouvement.
4. Agriculture : comptage de fruits
Mask R-CNN détecte et segmente chaque fruit sur un arbre pour estimer le rendement. Les masques précis séparent les fruits qui se chevauchent, améliorant la précision de comptage de 15% par rapport aux méthodes par bounding box.
Conclusion
Mask R-CNN reste l’algorithme de référence pour la segmentation d’instances. Son apport principal — RoI Align — est maintenant utilisé dans de nombreuses architectures de détection ultérieures.
Bien que des architectures plus rapides comme SOLO et YOLO-seg aient émergé, Mask R-CNN conserve son avantage en termes de précision et de maturité d’implémentation, en particulier pour les applications où la précision des masques est critique.
Voir aussi
- Maîtriser la Logique Circulaire en Python : Guide Complet pour Débutants et Experts
- Maîtriser les Arrangements Maximaux en Python : Guide Complet et Astuces pour Développeurs

