Object Detection vs Segmentation: Confronto e Use Cases
Quando si affronta un problema di computer vision, la scelta del task e dell'architettura giusta e fondamentale. Object Detection, Segmentazione Semantica, Segmentazione di Istanza e Segmentazione Panoptica non sono alternative intercambiabili: ognuna risponde a domande diverse, ha requisiti computazionali diversi e si presta a use cases specifici. Scegliere l'approccio sbagliato significa sprecare risorse o, peggio, non risolvere il problema del cliente.
In questo articolo faremo un confronto rigoroso tra i principali task di computer vision visiva, con implementazioni pratiche in PyTorch e linee guida concrete per scegliere l'approccio giusto nel tuo progetto.
Cosa Imparerai
- Le differenze fondamentali tra detection, segmentazione semantica, di istanza e panoptica
- Quando usare quale approccio: decision tree pratico
- Architetture principali per ogni task e loro tradeoff
- Implementazione completa di un pipeline multi-task in PyTorch
- Metriche di valutazione per ogni task (mAP, mIoU, PQ)
- Benchmark di velocità e accuratezza su hardware reale
- Case studies: veicoli autonomi, sorveglianza medica, retail analytics
1. I Task Principali di Computer Vision
Prima di confrontare gli approcci, definiamo precisamente ogni task con esempi visivi:
Immagine input: una strada con 3 persone e 2 auto
┌─────────────────────────────────────────────────────────────────┐
│ IMAGE CLASSIFICATION: "strada con veicoli e persone" │
│ Output: 1 label per tutta l'immagine │
│ Non dice WHERE ne QUANTI oggetti ci sono │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ OBJECT DETECTION: 5 bounding boxes │
│ [persona(0.95) x1,y1,x2,y2] │
│ [persona(0.88) x1,y1,x2,y2] │
│ [persona(0.91) x1,y1,x2,y2] │
│ [auto(0.97) x1,y1,x2,y2] │
│ [auto(0.94) x1,y1,x2,y2] │
│ Sa WHERE e QUANTI, ma non la forma precisa │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ SEGMENTAZIONE SEMANTICA: ogni pixel ha una classe │
│ pixel(100,200)="persona", pixel(300,400)="auto" │
│ Sa la FORMA precisa, ma non distingue le istanze │
│ Tutte le "persone" = stessa categoria, non identità separate │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ SEGMENTAZIONE DI ISTANZA: maschera per ogni oggetto │
│ persona_1 = {pixel: (100,200),(101,200),...} │
│ persona_2 = {pixel: (250,180),(251,180),...} │
│ Sa la FORMA e distingue le ISTANZE separate │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ SEGMENTAZIONE PANOPTICA: unione di semantica + istanza │
│ "cose" (countable): istanza per persona e auto │
│ "stuff" (uncountable): semantica per strada, cielo, edifici │
│ Sa TUTTO: forma, classe, istanza, sfondo │
└─────────────────────────────────────────────────────────────────┘
1.1 Confronto Tecnico Dettagliato
Confronto tra Task di Computer Vision
| Task | Output | Complessità | Velocita | Memoria GPU | Metrica |
|---|---|---|---|---|---|
| Classification | Label + prob | Bassa | Molto alta | Bassa | Top-1/5 Acc |
| Object Detection | BBox + label | Media | Alta | Media | mAP@0.5 |
| Seg. Semantica | Mappa pixel-label | Media-Alta | Media | Alta | mIoU |
| Seg. Istanza | BBox + maschera | Alta | Bassa-Media | Alta | mAP@mask |
| Seg. Panoptica | Tutto | Molto alta | Bassa | Molto alta | PQ |
2. Object Detection: Architetture e Implementazione
2.1 Single-Stage vs Two-Stage
I detector di oggetti si dividono in due grandi categorie architetturali:
Single-Stage vs Two-Stage Detectors
| Caratteristica | Single-Stage (YOLO, SSD, RetinaNet) | Two-Stage (Faster R-CNN, Mask R-CNN) |
|---|---|---|
| Pipeline | Un'unica rete, predizione diretta | RPN propone regioni, poi classificazione |
| Velocita | Alta (30-150+ FPS) | Bassa (5-15 FPS) |
| Accuratezza | Leggermente inferiore su oggetti piccoli | Migliore accuratezza, specialmente oggetti piccoli |
| Uso tipico | Real-time, edge, video | Analisi offline, massima precisione |
| Esempi moderni | YOLO26, RT-DETR, DINO-DETR | Faster R-CNN, Cascade R-CNN, DETR |
import torch
import torchvision
from torchvision.models.detection import fasterrcnn_resnet50_fpn
from torchvision.models.detection import FasterRCNN_ResNet50_FPN_Weights
from torchvision.models.detection.faster_rcnn import FastRCNNPredictor
def create_faster_rcnn(num_classes: int) -> torch.nn.Module:
"""
Faster R-CNN con backbone ResNet-50 + FPN pre-addestrato.
Two-stage: RPN (Region Proposal Network) + classificatore.
"""
# Carica con pesi COCO pre-addestrati
model = fasterrcnn_resnet50_fpn(
weights=FasterRCNN_ResNet50_FPN_Weights.DEFAULT
)
# Sostituisce il classificatore per il numero di classi custom
# +1 perchè la classe 0 e riservata al "background"
in_features = model.roi_heads.box_predictor.cls_score.in_features
model.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes + 1)
return model
def train_detection_model(model, data_loader, num_epochs: int = 10, lr: float = 0.005):
"""
Training loop per Faster R-CNN.
Il modello calcola automaticamente le loss interne (classification + bbox regression + RPN).
"""
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.to(device)
model.train()
optimizer = torch.optim.SGD(
model.parameters(),
lr=lr,
momentum=0.9,
weight_decay=0.0005
)
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)
for epoch in range(num_epochs):
total_loss = 0.0
for images, targets in data_loader:
images = [img.to(device) for img in images]
targets = [{k: v.to(device) for k, v in t.items()} for t in targets]
# Faster R-CNN restituisce un dizionario di loss in training mode
loss_dict = model(images, targets)
losses = sum(loss for loss in loss_dict.values())
optimizer.zero_grad()
losses.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()
total_loss += losses.item()
scheduler.step()
avg_loss = total_loss / len(data_loader)
print(f"Epoch {epoch+1}/{num_epochs} | Loss: {avg_loss:.4f}")
def inference_faster_rcnn(model, image_tensor: torch.Tensor,
score_threshold: float = 0.5) -> list[dict]:
"""Inference con Faster R-CNN - restituisce predizioni filtrate."""
device = next(model.parameters()).device
model.eval()
with torch.no_grad():
predictions = model([image_tensor.to(device)])
results = []
pred = predictions[0]
for i, score in enumerate(pred['scores']):
if score >= score_threshold:
results.append({
'bbox': pred['boxes'][i].tolist(),
'score': float(score),
'label': int(pred['labels'][i])
})
return results
3. Segmentazione Semantica
La segmentazione semantica assegna una label di classe a ogni singolo pixel dell'immagine. Non distingue le istanze: tutte le "persone" appartengono alla stessa classe. E ideale per analisi della scena completa (guida autonoma, analisi medica, telerilevamento).
3.1 DeepLabv3: Atrous Convolutions
DeepLabv3 (Chen et al., 2017) usa le atrous convolutions (o dilated convolutions): convoluzioni con "buchi" che aumentano il campo ricettivo senza aumentare i parametri, fondamentali per catturare contesto multi-scala senza ridurre la risoluzione.
import torch
import torch.nn as nn
import torchvision.models.segmentation as seg_models
from torchvision.models.segmentation import DeepLabV3_ResNet50_Weights
def create_deeplabv3(num_classes: int) -> nn.Module:
"""
DeepLabv3 con backbone ResNet-50 pre-addestrato su COCO.
Usa Atrous Spatial Pyramid Pooling (ASPP) per multi-scale context.
"""
model = seg_models.deeplabv3_resnet50(
weights=DeepLabV3_ResNet50_Weights.DEFAULT
)
# Sostituisce il classificatore finale per il numero di classi custom
model.classifier[-1] = nn.Conv2d(
in_channels=256,
out_channels=num_classes,
kernel_size=1
)
# Anche l'auxiliary classifier (per training stability)
model.aux_classifier[-1] = nn.Conv2d(
in_channels=256,
out_channels=num_classes,
kernel_size=1
)
return model
def train_semantic_segmentation(model, data_loader, num_epochs: int = 20):
"""
Training loop per segmentazione semantica.
Loss: CrossEntropyLoss (ignora label -1 per pixel non annotati)
"""
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.to(device)
criterion = nn.CrossEntropyLoss(ignore_index=255) # 255 = unlabeled pixel
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4, weight_decay=1e-4)
scheduler = torch.optim.lr_scheduler.PolynomialLR(optimizer, total_iters=num_epochs)
for epoch in range(num_epochs):
model.train()
total_loss = 0.0
for images, masks in data_loader:
images = images.to(device)
masks = masks.long().to(device) # [B, H, W] con valori 0..num_classes-1
# DeepLabv3 restituisce dict con 'out' e 'aux'
outputs = model(images)
main_loss = criterion(outputs['out'], masks)
aux_loss = criterion(outputs['aux'], masks) * 0.4 # peso ridotto
loss = main_loss + aux_loss
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
scheduler.step()
avg_loss = total_loss / len(data_loader)
miou = compute_miou(model, data_loader, device)
print(f"Epoch {epoch+1}/{num_epochs} | Loss: {avg_loss:.4f} | mIoU: {miou:.3f}")
def compute_miou(model, data_loader, device, num_classes: int = 21) -> float:
"""Calcola Mean IoU (metrica standard per segmentazione semantica)."""
model.eval()
intersection = torch.zeros(num_classes, device=device)
union = torch.zeros(num_classes, device=device)
with torch.no_grad():
for images, masks in data_loader:
images = images.to(device)
masks = masks.long().to(device)
preds = model(images)['out'].argmax(dim=1) # [B, H, W]
for cls in range(num_classes):
pred_cls = preds == cls
true_cls = masks == cls
intersection[cls] += (pred_cls & true_cls).sum()
union[cls] += (pred_cls | true_cls).sum()
iou = intersection / (union + 1e-10)
return float(iou[union > 0].mean())
4. Segmentazione di Istanza con Mask R-CNN
La segmentazione di istanza combina object detection (bounding box + classe) con segmentazione a livello di pixel per ogni singola istanza. Ogni oggetto ha la propria maschera binaria indipendente. Mask R-CNN (He et al., 2017) estende Faster R-CNN aggiungendo un terzo "head" parallelo per la predizione delle maschere.
import torch
import torchvision
from torchvision.models.detection import maskrcnn_resnet50_fpn
from torchvision.models.detection import MaskRCNN_ResNet50_FPN_Weights
from torchvision.models.detection.mask_rcnn import MaskRCNNPredictor
from torchvision.models.detection.faster_rcnn import FastRCNNPredictor
def create_mask_rcnn(num_classes: int) -> torch.nn.Module:
"""
Mask R-CNN: Faster R-CNN + Mask Head.
Output per ogni istanza: bbox + classe + maschera binaria 28x28.
"""
model = maskrcnn_resnet50_fpn(
weights=MaskRCNN_ResNet50_FPN_Weights.DEFAULT
)
# Sostituisce box predictor
in_features_box = model.roi_heads.box_predictor.cls_score.in_features
model.roi_heads.box_predictor = FastRCNNPredictor(
in_features_box, num_classes + 1
)
# Sostituisce mask predictor
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 + 1
)
return model
def prepare_instance_target(boxes: list, labels: list, masks: list) -> dict:
"""
Prepara il target nel formato richiesto da Mask R-CNN.
masks: lista di array booleani [H, W] per ogni istanza.
"""
return {
'boxes': torch.tensor(boxes, dtype=torch.float32),
'labels': torch.tensor(labels, dtype=torch.int64),
'masks': torch.tensor(masks, dtype=torch.uint8) # [N, H, W]
}
def visualize_instance_predictions(image, predictions, score_threshold: float = 0.5):
"""
Visualizza bounding boxes e maschere di istanza su un'immagine.
"""
import numpy as np
import cv2
img = np.array(image)
colors = [(np.random.randint(100, 255), np.random.randint(100, 255),
np.random.randint(100, 255)) for _ in range(100)]
pred = predictions[0]
valid_idx = pred['scores'] >= score_threshold
for i, (box, mask, score, label) in enumerate(zip(
pred['boxes'][valid_idx],
pred['masks'][valid_idx],
pred['scores'][valid_idx],
pred['labels'][valid_idx]
)):
color = colors[i % len(colors)]
# Disegna bounding box
x1, y1, x2, y2 = [int(c) for c in box]
cv2.rectangle(img, (x1, y1), (x2, y2), color, 2)
# Applica maschera semitrasparente
mask_binary = (mask[0].numpy() > 0.5).astype(np.uint8)
overlay = img.copy()
overlay[mask_binary == 1] = color
img = cv2.addWeighted(img, 0.6, overlay, 0.4, 0)
# Label con confidence
text = f"class {int(label)}: {float(score):.2f}"
cv2.putText(img, text, (x1, y1-5),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
return img
5. Decision Tree: Quale Task Scegliere?
Problema: "Cosa voglio sapere dell'immagine?"
|
├─ Solo "che oggetti ci sono"?
│ └── IMAGE CLASSIFICATION
│ Architetture: ResNet, EfficientNet, ViT
│ Esempi: quality gate industriale, filtro contenuti
│
├─ "Dove sono gli oggetti + quanti sono"?
│ └── OBJECT DETECTION
│ │
│ ├─ Serve velocità real-time (>30 FPS)?
│ │ └── Single-Stage: YOLO26, RT-DETR
│ │
│ └─ Serve massima accuratezza (oggetti piccoli)?
│ └── Two-Stage: Faster R-CNN, DETR
│
├─ "Che classe e ogni pixel" (no distinzione istanze)?
│ └── SEGMENTAZIONE SEMANTICA
│ Architetture: DeepLabv3, FCN, SegFormer
│ Esempi: analisi stradale, medica, telerilevamento
│
├─ "Separare ogni oggetto + sua forma esatta"?
│ └── SEGMENTAZIONE DI ISTANZA
│ Architetture: Mask R-CNN, SOLOv2, YOLACT
│ Esempi: conteggio oggetti, robotica, biologia
│
└─ "Tutto: oggetti separati + sfondo classificato"?
└── SEGMENTAZIONE PANOPTICA
Architetture: Panoptic FPN, Mask2Former
Esempi: guida autonoma completa, scene understanding
Use Cases per Ogni Task
| Settore | Detection | Seg. Semantica | Seg. Istanza | Panoptica |
|---|---|---|---|---|
| Automotive | Pedestrian/vehicle detection | Segmenta strada/corsia | Separa ogni pedone | Scena completa autonoma |
| Medico | Localizza lesioni in CT | Segmenta organi | Separa ogni tumore | Analisi anatomica completa |
| Retail | Conta prodotti scaffale | Mappa planogramma | Identifica ogni prodotto | Analisi scaffale completa |
| Industriale | Rileva difetti (bounding box) | Classifica zona difettosa | Segmenta ogni difetto | Ispezione completa pezzo |
| Agricoltura | Conta frutti su albero | Segmenta vegetazione | Separa ogni frutto | Mappa campo completa |
6. Pipeline Multi-Task: Detection + Segmentation
In molte applicazioni reali conviene combinare più task in una singola architettura per efficienza computazionale. Un esempio pratico: nel retail analytics vogliamo sia localizzare i prodotti (detection) che segmentare la zona occupata su scaffale (segmentazione semantica).
import torch
import torch.nn as nn
import torchvision.models as models
class MultiTaskDetectionSegmentation(nn.Module):
"""
Architettura multi-task che condivide un backbone ResNet-50 + FPN
tra due head: detection e segmentazione semantica.
"""
def __init__(self, num_det_classes: int, num_seg_classes: int):
super().__init__()
# Backbone condiviso: ResNet-50 con FPN
backbone = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V2)
# Estrae feature a più scale
self.layer1 = nn.Sequential(backbone.conv1, backbone.bn1,
backbone.relu, backbone.maxpool,
backbone.layer1) # 1/4 risoluzione
self.layer2 = backbone.layer2 # 1/8
self.layer3 = backbone.layer3 # 1/16
self.layer4 = backbone.layer4 # 1/32
# FPN (Feature Pyramid Network) per multi-scale features
self.fpn = nn.ModuleDict({
'p5': nn.Conv2d(2048, 256, 1),
'p4': nn.Conv2d(1024, 256, 1),
'p3': nn.Conv2d(512, 256, 1),
'p2': nn.Conv2d(256, 256, 1),
})
# Detection head (semplificato)
self.det_head = nn.Sequential(
nn.Conv2d(256, 256, 3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(256, num_det_classes * (4 + 1), 1)
# 4 bbox coords + 1 objectness per ogni classe
)
# Segmentation head (decoder con upsampling)
self.seg_head = nn.Sequential(
nn.Conv2d(256, 256, 3, padding=1),
nn.ReLU(inplace=True),
nn.ConvTranspose2d(256, 128, 4, stride=2, padding=1), # 2x upsample
nn.ReLU(inplace=True),
nn.ConvTranspose2d(128, 64, 4, stride=2, padding=1), # 4x upsample
nn.ReLU(inplace=True),
nn.Conv2d(64, num_seg_classes, 1)
)
def forward(self, x: torch.Tensor) -> dict:
# Backbone
c2 = self.layer1(x) # 1/4
c3 = self.layer2(c2) # 1/8
c4 = self.layer3(c3) # 1/16
c5 = self.layer4(c4) # 1/32
# FPN top-down pathway
p5 = self.fpn['p5'](c5)
p4 = self.fpn['p4'](c4) + nn.functional.interpolate(p5, scale_factor=2)
p3 = self.fpn['p3'](c3) + nn.functional.interpolate(p4, scale_factor=2)
p2 = self.fpn['p2'](c2) + nn.functional.interpolate(p3, scale_factor=2)
# Task-specific heads
det_output = self.det_head(p3) # detection sul livello P3
seg_output = self.seg_head(p2) # segmentation su P2 (più alta risoluzione)
# Upsample seg output a dimensione input
seg_output = nn.functional.interpolate(
seg_output, size=x.shape[-2:], mode='bilinear', align_corners=False
)
return {'detection': det_output, 'segmentation': seg_output}
def compute_multitask_loss(outputs: dict, det_targets, seg_targets) -> torch.Tensor:
"""
Loss combinata multi-task con pesi bilanciati.
Loss totale = w_det * L_det + w_seg * L_seg
"""
det_criterion = nn.BCEWithLogitsLoss()
seg_criterion = nn.CrossEntropyLoss(ignore_index=255)
det_loss = det_criterion(outputs['detection'], det_targets)
seg_loss = seg_criterion(outputs['segmentation'], seg_targets)
# Pesi relativi (da tuning sperimentale)
total_loss = 1.0 * det_loss + 0.5 * seg_loss
return total_loss, {'det': det_loss.item(), 'seg': seg_loss.item()}
7. Best Practices e Confronto Performance
Benchmark Performance su Dataset COCO (2025)
| Modello | Task | mAP/mIoU | FPS (V100) | Params |
|---|---|---|---|---|
| YOLO26m | Detection | 57.2 mAP | 100+ | 25M |
| Faster R-CNN R50 | Detection | 40.2 mAP | 18 | 41M |
| DeepLabv3 R50 | Seg. Semantica | 74.3 mIoU | 45 | 39M |
| SegFormer-B5 | Seg. Semantica | 83.1 mIoU | 15 | 85M |
| Mask R-CNN R50 | Seg. Istanza | 36.1 mAP | 14 | 44M |
| Mask2Former R50 | Panoptica | 51.9 PQ | 8 | 44M |
Errori di Design Comuni
- Usare segmentazione quando basta detection: Se devi solo contare o localizzare oggetti, usa detection. La segmentazione e molto più costosa da annotare e addestrare.
- Ignorare i requisiti real-time: Mask R-CNN a 14 FPS non e accettabile per un sistema di sorveglianza live. Scegli l'architettura in base ai requisiti di latenza.
- Dataset non bilanciato per segmentazione: Se una classe occupa il 95% dei pixel (es. sfondo), il modello la imparera trivialmente. Usa weighted loss o class sampling.
- Confondere mIoU e mAP: Sono metriche diverse. mIoU misura la precisione pixel per pixel (segmentazione), mAP misura la qualità dei bounding box (detection).
- Multi-task senza bilanciamento: In architetture multi-task, le loss di task diversi possono avere scale molto diverse. Usa gradient normalization o uncertainty weighting.
Conclusioni
Abbiamo esplorato l'intero spettro dei task di computer vision, dalle loro differenze fondamentali alle implementazioni pratiche:
- Classification, Detection, Segmentazione Semantica, di Istanza e Panoptica hanno output, costi e use cases diversi
- YOLO26 e il re del real-time detection; Faster R-CNN eccelle nell'accuratezza offline
- DeepLabv3 e ottimo per segmentazione semantica; Mask R-CNN aggiunge la distinzione di istanza
- Le architetture multi-task permettono di combinare più task con un backbone condiviso
- Il decision tree presentato guida la scelta dell'approccio giusto per ogni problema
Navigazione Serie
- Precedente: YOLO e Object Detection: Dalla Teoria alla Pratica
- Successivo: Segmentazione: U-Net, Mask R-CNN e SAM







