Complete Guide to DETR: Object Detection with Transformers
DETR (DEtection TRansformer) is a revolutionary approach to object detection developed by Facebook AI Research. DETR replaces complex traditional pipelines with an elegant end-to-end Transformer architecture.
In this tutorial, we'll learn DETR from basic concepts to practical implementation for object detection and segmentation.
Why DETR?
DETR Advantages:
Comparison with Traditional Methods:
| Aspect | Traditional (Faster R-CNN) | DETR |
|--------|---------------------------|------|
| Anchor Boxes | Yes | No |
| NMS Post-processing | Yes | No |
| Hand-crafted Components | Many | Minimal |
| End-to-End Training | No | Yes |
| Global Context | Limited | Full |
DETR Architecture
Main Components:
Input Image → CNN Backbone → Transformer Encoder → Transformer Decoder → FFN → Predictions
↓ ↓ ↓
Features Positional Object Queries
Encoding (Learned)
1. CNN Backbone
# ResNet-50 or ResNet-101 as feature extractor
Input: Image (3, H, W)
Output: Feature map (2048, H/32, W/32)
2. Transformer Encoder
# Processes flattened feature map with self-attention
Adds positional encoding
Output: Encoded features with global context
3. Transformer Decoder
# Uses learned object queries (default: 100)
Cross-attention with encoded features
Self-attention between object queries
Output: 100 embeddings for predictions
4. Prediction Heads
# FFN for class prediction
FFN for bounding box prediction (centerx, centery, width, height)
Special class: "no object" for queries that don't detect objects
Installation
Requirements
# Clone repository
git clone https://github.com/facebookresearch/detr.git
cd detr
Install dependencies
conda create -n detr python=3.8
conda activate detr
PyTorch and Torchvision
conda install pytorch torchvision cudatoolkit=11.3 -c pytorch
Other dependencies
conda install cython scipy
pip install -U 'git+https://github.com/cocodataset/cocoapi.git#subdirectory=PythonAPI'
For panoptic segmentation (optional)
pip install git+https://github.com/cocodataset/panopticapi.git
Verify Installation
import torch
import torchvision
print(f"PyTorch version: {torch.version}")
print(f"Torchvision version: {torchvision.version}")
print(f"CUDA available: {torch.cuda.isavailable()}")
Quick Start: Inference with Pretrained Model
1. Load Model via PyTorch Hub
import torch
from PIL import Image
import requests
import matplotlib.pyplot as plt
Load pretrained DETR model
model = torch.hub.load('facebookresearch/detr:main', 'detrresnet50', pretrained=True)
model.eval()
COCO classes
CLASSES = [
'N/A', 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus',
'train', 'truck', 'boat', 'traffic light', 'fire hydrant', 'N/A',
'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse',
'sheep', 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'N/A', 'backpack',
'umbrella', 'N/A', 'N/A', 'handbag', 'tie', 'suitcase', 'frisbee', 'skis',
'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove',
'skateboard', 'surfboard', 'tennis racket', 'bottle', 'N/A', 'wine glass',
'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich',
'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake',
'chair', 'couch', 'potted plant', 'bed', 'N/A', 'dining table', 'N/A',
'N/A', 'toilet', 'N/A', 'tv', 'laptop', 'mouse', 'remote', 'keyboard',
'cell phone', 'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'N/A',
'book', 'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush'
]
Colors for visualization
COLORS = [[0.000, 0.447, 0.741], [0.850, 0.325, 0.098], [0.929, 0.694, 0.125],
[0.494, 0.184, 0.556], [0.466, 0.674, 0.188], [0.301, 0.745, 0.933]]
2. Preprocessing and Inference
import torchvision.transforms as T
Standard COCO transforms
transform = T.Compose([
T.Resize(800),
T.ToTensor(),
T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
def detect(imagepath, model, transform, threshold=0.7):
"""
Detect objects in an image using DETR.
Args:
imagepath: Path to image or URL
model: DETR model
transform: Image transforms
threshold: Confidence threshold
Returns:
Detected objects with boxes and labels
"""
# Load image
if imagepath.startswith('http'):
im = Image.open(requests.get(imagepath, stream=True).raw)
else:
im = Image.open(imagepath)
# Transform
img = transform(im).unsqueeze(0)
# Inference
with torch.nograd():
outputs = model(img)
# Post-process
probas = outputs['predlogits'].softmax(-1)[0, :, :-1]
keep = probas.max(-1).values > threshold
# Convert boxes from [0, 1] to image coordinates
bboxesscaled = rescalebboxes(outputs['predboxes'][0, keep], im.size)
return probas[keep], bboxesscaled, im
def rescalebboxes(outbbox, size):
"""Convert normalized boxes to image coordinates."""
imgw, imgh = size
b = boxcxcywhtoxyxy(outbbox)
b = b torch.tensor([imgw, imgh, imgw, imgh], dtype=torch.float32)
return b
def boxcxcywhtoxyxy(x):
"""Convert boxes from center format to corner format."""
xc, yc, w, h = x.unbind(-1)
b = [(xc - 0.5 w), (yc - 0.5 h),
(xc + 0.5 w), (yc + 0.5 h)]
return torch.stack(b, dim=-1)
3. Visualize Results
def plotresults(pilimg, prob, boxes):
"""Visualize detection results."""
plt.figure(figsize=(16, 10))
plt.imshow(pilimg)
ax = plt.gca()
colors = COLORS 100
for p, (xmin, ymin, xmax, ymax), c in zip(prob, boxes.tolist(), colors):
ax.addpatch(plt.Rectangle(
(xmin, ymin), xmax - xmin, ymax - ymin,
fill=False, color=c, linewidth=3
))
cl = p.argmax()
text = f'{CLASSES[cl]}: {p[cl]:0.2f}'
ax.text(xmin, ymin, text, fontsize=15,
bbox=dict(facecolor='yellow', alpha=0.5))
plt.axis('off')
plt.tightlayout()
plt.savefig('detectionresult.png', dpi=150, bboxinches='tight')
plt.show()
Run detection
url = 'http://images.cocodataset.org/val2017/000000039769.jpg'
probas, bboxes, image = detect(url, model, transform)
plotresults(image, probas, bboxes)
print(f"Detected {len(probas)} objects")
for p, box in zip(probas, bboxes):
cl = p.argmax()
print(f" {CLASSES[cl]}: {p[cl]:.2f} at {box.tolist()}")
Pretrained Models
Available Models
| Model | Backbone | Box AP | URL |
|-------|----------|--------|-----|
| DETR | ResNet-50 | 42.0 | detrresnet50 |
| DETR-DC5 | ResNet-50 | 43.3 | detrresnet50dc5 |
| DETR | ResNet-101 | 43.5 | detrresnet101 |
| DETR-DC5 | ResNet-101 | 44.9 | detrresnet101dc5 |
Load Different Models
# ResNet-50 backbone
modelr50 = torch.hub.load('facebookresearch/detr:main', 'detrresnet50', pretrained=True)
ResNet-50 with dilated C5 stage
modelr50dc5 = torch.hub.load('facebookresearch/detr:main', 'detrresnet50dc5', pretrained=True)
ResNet-101 backbone
modelr101 = torch.hub.load('facebookresearch/detr:main', 'detrresnet101', pretrained=True)
ResNet-101 with dilated C5 stage
modelr101dc5 = torch.hub.load('facebookresearch/detr:main', 'detrresnet101dc5', pretrained=True)
Load from Checkpoint
# Download checkpoint
import urllib.request
checkpointurl = 'https://dl.fbaipublicfiles.com/detr/detr-r50-e632da11.pth'
urllib.request.urlretrieve(checkpointurl, 'detr-r50.pth')
Load checkpoint
checkpoint = torch.load('detr-r50.pth', maplocation='cpu')
model.loadstatedict(checkpoint['model'])
Training DETR
1. Prepare COCO Dataset
# Download COCO 2017
mkdir -p data/coco
cd data/coco
Download images
wget http://images.cocodataset.org/zips/train2017.zip
wget http://images.cocodataset.org/zips/val2017.zip
Download annotations
wget http://images.cocodataset.org/annotations/annotationstrainval2017.zip
Extract
unzip train2017.zip
unzip val2017.zip
unzip annotationstrainval2017.zip
Folder structure:
data/
└── coco/
├── train2017/
├── val2017/
└── annotations/
├── instancestrain2017.json
└── instancesval2017.json
2. Training Command
# Single GPU training
python main.py --cocopath /path/to/coco
Multi-GPU training (8 GPUs)
python -m torch.distributed.launch \
--nprocpernode=8 \
--useenv main.py \
--cocopath /path/to/coco
Training with custom settings
python main.py \
--cocopath /path/to/coco \
--batchsize 2 \
--epochs 300 \
--lr 1e-4 \
--lrbackbone 1e-5 \
--numqueries 100 \
--outputdir outputs/detrcustom
3. Training Configuration
# Default hyperparameters (from paper)
args = {
# Optimizer
'lr': 1e-4, # Learning rate for transformer
'lrbackbone': 1e-5, # Learning rate for backbone
'weightdecay': 1e-4, # Weight decay
# Architecture
'backbone': 'resnet50', # Backbone CNN
'numqueries': 100, # Number of object queries
'hiddendim': 256, # Transformer hidden dimension
'nheads': 8, # Attention heads
'enclayers': 6, # Encoder layers
'declayers': 6, # Decoder layers
'dimfeedforward': 2048, # FFN dimension
'dropout': 0.1, # Dropout rate
# Training
'epochs': 300, # Total epochs
'batchsize': 2, # Batch size per GPU
'clipmaxnorm': 0.1, # Gradient clipping
# Loss weights
'setcostclass': 1, # Class cost for Hungarian matching
'setcostbbox': 5, # L1 box cost
'setcostgiou': 2, # GIoU cost
'bboxlosscoef': 5, # Box L1 loss coefficient
'gioulosscoef': 2, # GIoU loss coefficient
'eoscoef': 0.1, # "No object" class weight
}
4. Custom Training Script
import torch
from torch.utils.data import DataLoader
from detr.models import buildmodel
from detr.datasets import builddataset
from detr.engine import trainoneepoch, evaluate
def traindetr(args):
# Build model
model, criterion, postprocessors = buildmodel(args)
model.to(args.device)
# Build dataset
datasettrain = builddataset(imageset='train', args=args)
datasetval = builddataset(imageset='val', args=args)
# DataLoaders
samplertrain = torch.utils.data.RandomSampler(datasettrain)
samplerval = torch.utils.data.SequentialSampler(datasetval)
dataloadertrain = DataLoader(
datasettrain,
batchsize=args.batchsize,
sampler=samplertrain,
collatefn=utils.collatefn,
numworkers=args.numworkers
)
dataloaderval = DataLoader(
datasetval,
batchsize=args.batchsize,
sampler=samplerval,
collatefn=utils.collatefn,
numworkers=args.numworkers
)
# Optimizer
paramdicts = [
{"params": [p for n, p in model.namedparameters()
if "backbone" not in n and p.requiresgrad]},
{"params": [p for n, p in model.namedparameters()
if "backbone" in n and p.requiresgrad],
"lr": args.lrbackbone},
]
optimizer = torch.optim.AdamW(paramdicts, lr=args.lr, weightdecay=args.weightdecay)
# LR Scheduler
lrscheduler = torch.optim.lrscheduler.StepLR(optimizer, args.lrdrop)
# Training loop
for epoch in range(args.epochs):
trainstats = trainoneepoch(
model, criterion, dataloadertrain,
optimizer, args.device, epoch, args.clipmaxnorm
)
lrscheduler.step()
# Evaluate
teststats, cocoevaluator = evaluate(
model, criterion, postprocessors,
dataloaderval, args.device
)
# Save checkpoint
if (epoch + 1) % 10 == 0:
torch.save({
'model': model.statedict(),
'optimizer': optimizer.statedict(),
'lrscheduler': lrscheduler.statedict(),
'epoch': epoch,
}, f'checkpoint{epoch:04d}.pth')
Evaluation
1. Evaluate Pretrained Model
python main.py \
--batchsize 2 \
--noauxloss \
--eval \
--resume https://dl.fbaipublicfiles.com/detr/detr-r50-e632da11.pth \
--cocopath /path/to/coco
2. Evaluation Script
from detr.datasets.cocoeval import CocoEvaluator
from detr.datasets.coco import makecocotransforms
def evaluatemodel(model, dataloader, device):
model.eval()
cocoevaluator = CocoEvaluator(dataloader.dataset.coco, ['bbox'])
for samples, targets in dataloader:
samples = samples.to(device)
targets = [{k: v.to(device) for k, v in t.items()} for t in targets]
with torch.nograd():
outputs = model(samples)
# Post-process
origtargetsizes = torch.stack([t["origsize"] for t in targets], dim=0)
results = postprocessors'bbox'
res = {target['imageid'].item(): output
for target, output in zip(targets, results)}
cocoevaluator.update(res)
cocoevaluator.synchronizebetweenprocesses()
cocoevaluator.accumulate()
cocoevaluator.summarize()
return cocoevaluator
Run evaluation
stats = evaluatemodel(model, dataloaderval, device)
print(f"AP: {stats.cocoeval['bbox'].stats[0]:.3f}")
print(f"AP50: {stats.cocoeval['bbox'].stats[1]:.3f}")
print(f"AP75: {stats.cocoeval['bbox'].stats[2]:.3f}")
Fine-tuning on Custom Dataset
1. Prepare Custom Dataset
import json
from PIL import Image
from torch.utils.data import Dataset
class CustomCocoDataset(Dataset):
"""
Custom dataset in COCO format.
"""
def init(self, imgfolder, annfile, transforms=None):
self.imgfolder = imgfolder
self.transforms = transforms
with open(annfile, 'r') as f:
self.coco = json.load(f)
self.ids = [img['id'] for img in self.coco['images']]
# Build image id to annotations mapping
self.imgtoanns = {}
for ann in self.coco['annotations']:
imgid = ann['imageid']
if imgid not in self.imgtoanns:
self.imgtoanns[imgid] = []
self.imgtoanns[imgid].append(ann)
def len(self):
return len(self.ids)
def getitem(self, idx):
imgid = self.ids[idx]
# Get image info
imginfo = next(img for img in self.coco['images'] if img['id'] == imgid)
imgpath = f"{self.imgfolder}/{imginfo['filename']}"
img = Image.open(imgpath).convert('RGB')
# Get annotations
anns = self.imgtoanns.get(imgid, [])
# Prepare target
boxes = []
labels = []
for ann in anns:
x, y, w, h = ann['bbox']
boxes.append([x, y, x + w, y + h])
labels.append(ann['categoryid'])
target = {
'boxes': torch.tensor(boxes, dtype=torch.float32),
'labels': torch.tensor(labels, dtype=torch.int64),
'imageid': torch.tensor([imgid]),
'origsize': torch.tensor([img.height, img.width]),
'size': torch.tensor([img.height, img.width]),
}
if self.transforms:
img, target = self.transforms(img, target)
return img, target
2. COCO Annotation Format
{
"images": [
{"id": 1, "filename": "image1.jpg", "width": 640, "height": 480},
{"id": 2, "filename": "image2.jpg", "width": 800, "height": 600}
],
"annotations": [
{"id": 1, "imageid": 1, "categoryid": 1, "bbox": [100, 100, 200, 150]},
{"id": 2, "imageid": 1, "categoryid": 2, "bbox": [300, 200, 100, 100]},
{"id": 3, "imageid": 2, "categoryid": 1, "bbox": [50, 50, 300, 200]}
],
"categories": [
{"id": 1, "name": "cat"},
{"id": 2, "name": "dog"}
]
}
3. Modify Model for Custom Classes
def buildcustomdetr(numclasses):
"""
Build DETR for custom number of classes.
"""
# Load pretrained model
model = torch.hub.load('facebookresearch/detr:main', 'detr
resnet50', pretrained=True)
# Modify class prediction head
# Original: 91 COCO classes + 1 "no object" = 92
# Custom: numclasses + 1 "no object"
model.classembed = torch.nn.Linear(
model.classembed.infeatures,
numclasses + 1
)
# Reinitialize the new layer
torch.nn.init.normal(model.classembed.weight, std=0.01)
torch.nn.init.constant(model.classembed.bias, 0)
# Set "no object" bias to higher value (makes model predict "no object" more often initially)
model.classembed.bias.data[-1] = -2.0
return model
Example: 5 custom classes
numclasses = 5
model = buildcustomdetr(numclasses)
print(f"Model output classes: {model.classembed.outfeatures}")
4. Fine-tuning Script
def finetunedetr(model, trainloader, valloader, numepochs=50):
"""
Fine-tune DETR on custom dataset.
"""
device = torch.device('cuda' if torch.cuda.isavailable() else 'cpu')
model.to(device)
# Freeze backbone for first few epochs
for param in model.backbone.parameters():
param.requiresgrad = False
# Optimizer
optimizer = torch.optim.AdamW([
{'params': model.transformer.parameters(), 'lr': 1e-4},
{'params': model.classembed.parameters(), 'lr': 1e-3},
{'params': model.bboxembed.parameters(), 'lr': 1e-4},
{'params': model.queryembed.parameters(), 'lr': 1e-4},
], weightdecay=1e-4)
# Training loop
for epoch in range(numepochs):
model.train()
totalloss = 0
for batchidx, (images, targets) in enumerate(trainloader):
images = images.to(device)
targets = [{k: v.to(device) for k, v in t.items()} for t in targets]
outputs = model(images)
lossdict = criterion(outputs, targets)
loss = sum(lossdict.values())
optimizer.zerograd()
loss.backward()
torch.nn.utils.clipgradnorm(model.parameters(), 0.1)
optimizer.step()
totalloss += loss.item()
# Unfreeze backbone after epoch 10
if epoch == 10:
for param in model.backbone.parameters():
param.requiresgrad = True
optimizer.addparamgroup({
'params': model.backbone.parameters(),
'lr': 1e-5
})
print(f"Epoch {epoch+1}/{numepochs}, Loss: {totalloss/len(trainloader):.4f}")
return model
Panoptic Segmentation
1. Load Panoptic Model
# Load DETR with panoptic head
model = torch.hub.load(
'facebookresearch/detr:main',
'detrresnet50panoptic',
pretrained=True,
returnpostprocessor=True,
numclasses=250 # COCO panoptic classes
)
detr, postprocessor = model
detr.eval()
2. Panoptic Inference
def panopticinference(imagepath, model, postprocessor):
"""
Run panoptic segmentation.
"""
im = Image.open(image
path)
# Transform
transform = T.Compose([
T.Resize(800),
T.ToTensor(),
T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
img = transform(im).unsqueeze(0)
with torch.nograd():
outputs = model(img)
# Post-process
result = postprocessor(outputs, torch.tensor([[im.height, im.width]]))[0]
# result contains:
# - 'segmentation': Segmentation mask (H, W) with segment IDs
# - 'segmentsinfo': List of dicts with info for each segment
return result
Example
result = panopticinference('image.jpg', detr, postprocessor)
print(f"Number of segments: {len(result['segmentsinfo'])}")
for seg in result['segmentsinfo']:
print(f" ID: {seg['id']}, Category: {seg['categoryid']}, "
f"Is Thing: {seg['isthing']}")
3. Visualize Panoptic
import numpy as np
from panopticapi.utils import rgb2id
def visualizepanoptic(image, result):
"""
Visualize panoptic segmentation result.
"""
# Get segmentation mask
segmask = result['segmentation'].numpy()
# Create color map
np.random.seed(42)
colors = np.random.randint(0, 255, (256, 3), dtype=np.uint8)
# Apply colors
coloredmask = colors[segmask % 256]
# Blend with original image
imagenp = np.array(image)
blended = (0.5 imagenp + 0.5 coloredmask).astype(np.uint8)
plt.figure(figsize=(16, 8))
plt.subplot(1, 3, 1)
plt.imshow(image)
plt.title('Original')
plt.axis('off')
plt.subplot(1, 3, 2)
plt.imshow(coloredmask)
plt.title('Segmentation')
plt.axis('off')
plt.subplot(1, 3, 3)
plt.imshow(blended)
plt.title('Blended')
plt.axis('off')
plt.tightlayout()
plt.savefig('panopticresult.png')
plt.show()
Best Practices
1. Memory Optimization
# Gradient checkpointing to reduce memory
from torch.utils.checkpoint import checkpoint
Mixed precision training
from torch.cuda.amp import autocast, GradScaler
scaler = GradScaler()
for images, targets in trainloader:
with autocast():
outputs = model(images)
loss = criterion(outputs, targets)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
2. Data Augmentation
import albumentations as A
from albumentations.pytorch import ToTensorV2
traintransform = A.Compose([
A.RandomResizedCrop(800, 800, scale=(0.5, 1.0)),
A.HorizontalFlip(p=0.5),
A.ColorJitter(brightness=0.4, contrast=0.4, saturation=0.4),
A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
ToTensorV2(),
], bboxparams=A.BboxParams(format='coco', labelfields=['labels']))
3. Inference Optimization
# TorchScript for faster inference
scriptedmodel = torch.jit.script(model)
scriptedmodel.save('detrscripted.pt')
ONNX export
torch.onnx.export(
model,
dummyinput,
'detr.onnx',
inputnames=['image'],
outputnames=['predlogits', 'predboxes'],
dynamicaxes={'image': {0: 'batch', 2: 'height', 3: 'width'}}
)
4. Batch Inference
def batchinference(imagepaths, model, transform, batchsize=8):
"""
Efficient batch inference.
"""
allresults = []
for i in range(0, len(imagepaths), batchsize):
batchpaths = imagepaths[i:i+batchsize]
# Load and transform images
images = []
sizes = []
for path in batchpaths:
img = Image.open(path)
sizes.append(img.size)
images.append(transform(img))
# Stack into batch
batch = torch.stack(images)
with torch.nograd():
outputs = model(batch.to(device))
# Process each image in batch
for j, (prob, boxes) in enumerate(zip(
outputs['predlogits'].softmax(-1),
outputs['predboxes']
)):
# Filter by threshold
keep = prob[:, :-1].max(-1).values > 0.7
allresults.append({
'path': batchpaths[j],
'scores': prob[keep],
'boxes': rescalebboxes(boxes[keep], sizes[j])
})
return allresults
Troubleshooting
1. Out of Memory
# Reduce batch size
args.batchsize = 1
Use gradient accumulation
accumulationsteps = 4
for i, (images, targets) in enumerate(trainloader):
loss = criterion(model(images), targets)
loss = loss / accumulationsteps
loss.backward()
if (i + 1) % accumulationsteps == 0:
optimizer.step()
optimizer.zerograd()
2. Training Not Converging
# Check learning rate
LR too high can cause divergence
args.lr = 1e-5 # Start with smaller LR
Make sure data augmentation isn't too aggressive
Make sure normalization is correct
3. Low AP on Custom Dataset
# Make sure annotation format is correct
Check class balance
Consider more epochs
Fine-tune from pretrained model
Conclusion
DETR is an innovative approach to object detection:
Key Takeaways:
When to Use DETR:
Use DETR when:- Need end-to-end object detection
- Dataset has objects with complex relationships
- Want to avoid hand-crafted components
- Need panoptic segmentation
- Research or prototyping
- Real-time inference is required (DETR is slower than YOLO)
- Dataset is very small (DETR needs lots of data)
- Resources are limited (DETR is memory-intensive)
Resources:
- Paper: "End-to-End Object Detection with Transformers"
- GitHub: https://github.com/facebookresearch/detr
- Colab Notebooks available in repository
Happy coding with DETR!