Panduan Lengkap DETR: Object Detection dengan Transformers
DETR (DEtection TRansformer) adalah pendekatan revolusioner untuk object detection yang dikembangkan oleh Facebook AI Research. DETR menggantikan pipeline tradisional yang kompleks dengan arsitektur Transformer yang elegan dan end-to-end.
Dalam tutorial ini, kita akan mempelajari DETR dari konsep dasar hingga implementasi praktis untuk object detection dan segmentation.
Mengapa DETR?
Keunggulan DETR:
Perbandingan dengan Metode Tradisional:
| Aspek | Traditional (Faster R-CNN) | DETR |
|-------|---------------------------|------|
| Anchor Boxes | Ya | Tidak |
| NMS Post-processing | Ya | Tidak |
| Hand-crafted Components | Banyak | Minimal |
| End-to-End Training | Tidak | Ya |
| Global Context | Terbatas | Penuh |
Arsitektur DETR
Komponen Utama:
Input Image → CNN Backbone → Transformer Encoder → Transformer Decoder → FFN → Predictions
↓ ↓ ↓
Features Positional Object Queries
Encoding (Learned)
1. CNN Backbone
# ResNet-50 atau ResNet-101 sebagai feature extractor
Input: Image (3, H, W)
Output: Feature map (2048, H/32, W/32)
2. Transformer Encoder
# Memproses flattened feature map dengan self-attention
Menambahkan positional encoding
Output: Encoded features dengan global context
3. Transformer Decoder
# Menggunakan learned object queries (default: 100)
Cross-attention dengan encoded features
Self-attention antar object queries
Output: 100 embeddings untuk prediksi
4. Prediction Heads
# FFN untuk class prediction
FFN untuk bounding box prediction (centerx, centery, width, height)
Special class: "no object" untuk query yang tidak mendeteksi objek
Instalasi
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 dan Torchvision
conda install pytorch torchvision cudatoolkit=11.3 -c pytorch
Dependencies lainnya
conda install cython scipy
pip install -U 'git+https://github.com/cocodataset/cocoapi.git#subdirectory=PythonAPI'
Untuk panoptic segmentation (optional)
pip install git+https://github.com/cocodataset/panopticapi.git
Verifikasi Instalasi
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 dengan 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 untuk visualisasi
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 dan 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. Visualisasi Hasil
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
Model yang Tersedia
| 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 dari 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. Persiapan Dataset COCO
# 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
Struktur folder:
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 dengan 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 (dari paper)
args = {
# Optimizer
'lr': 1e-4, # Learning rate untuk transformer
'lrbackbone': 1e-5, # Learning rate untuk backbone
'weightdecay': 1e-4, # Weight decay
# Architecture
'backbone': 'resnet50', # Backbone CNN
'numqueries': 100, # Jumlah object queries
'hiddendim': 256, # Hidden dimension transformer
'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 untuk 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 pada Custom Dataset
1. Persiapan Dataset Custom
import json
from PIL import Image
from torch.utils.data import Dataset
class CustomCocoDataset(Dataset):
"""
Custom dataset dalam format COCO.
"""
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. Format Annotation COCO
{
"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 untuk Custom Classes
def buildcustomdetr(numclasses):
"""
Build DETR untuk 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) dengan segment IDs
# - 'segmentsinfo': List of dicts dengan info tiap 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. Visualisasi 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 untuk mengurangi 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 untuk 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 Tidak Converge
# Cek learning rate
LR terlalu tinggi dapat menyebabkan divergence
args.lr = 1e-5 # Mulai dengan LR lebih kecil
Pastikan data augmentation tidak terlalu agresif
Pastikan normalization benar
3. Low AP pada Custom Dataset
# Pastikan format annotation benar
Cek class balance
Pertimbangkan lebih banyak epochs
Fine-tune dari pretrained model
Kesimpulan
DETR adalah pendekatan inovatif untuk object detection:
Key Takeaways:
Kapan Menggunakan DETR:
Use DETR when:- Butuh end-to-end object detection
- Dataset memiliki objek dengan relasi kompleks
- Ingin menghindari hand-crafted components
- Memerlukan panoptic segmentation
- Research atau prototyping
- Real-time inference diperlukan (DETR lebih lambat dari YOLO)
- Dataset sangat kecil (DETR butuh banyak data)
- Resource terbatas (DETR memory-intensive)
Resources:
- Paper: "End-to-End Object Detection with Transformers"
- GitHub: https://github.com/facebookresearch/detr
- Colab Notebooks tersedia di repository
Selamat menggunakan DETR!