D-FINE-seg - detection, instance and semantic segmentation in one model
Overview
D-FINE-seg is a framework for real-time object detection, instance segmentation, and semantic segmentation - one codebase, one config flag (task: detect | segment | sem_seg), five model sizes (N โ X).
- End-to-end workflow: dataset prep โ training (DDP, EMA, AMP, mosaic) โ export (ONNX, TensorRT, OpenVINO, CoreML, LiteRT) โ benchmarked multi-backend inference
- Accuracy: on Cityscapes, beats YOLO26 and RF-DETR on detection & instance-seg F1 and leads mIoU on semantic segmentation, at real-time latency with 2-3x fewer params; also higher F1 than YOLO26 on TACO and VisDrone (TensorRT FP16, end-to-end protocol)
- Paper: D-FINE-seg: Object Detection and Instance Segmentation Framework with Multi-Backend Deployment
- Not a fork: the detection core follows the D-FINE paper; segmentation heads, training, export and inference are implemented from scratch.
One Frame, Three Tasks, One Config Flag
Instance segmentation head (task: segment)
Lightweight mask head on top of D-FINE's HybridEncoder PAN outputs: stride 8/16/32 features fused to 1/4 resolution, then a dot-product between per-query mask embeddings (3-layer MLP) and the shared mask features yields per-instance masks.
Semantic segmentation head (task: sem_seg)
Reuses the pretrained instance-seg mask fuser on full-frame features, followed by a small conv neck and 1ร1 classifier: no queries, no NMS.
Mask-aware training
- Box-cropped BCE + Dice mask losses (instance seg) and CE + multi-class soft Dice with
ignore_index(semantic seg), mask supervision inside contrastive denoising, and Dice + sigmoid-focal mask costs in the Hungarian matcher - all train-time only, zero inference cost - COCO-pretrained weights for detection and instance segmentation, auto-downloaded on first use
- Fine-tuning starts from a trained mask decoder, not from scratch
Multi-channel inputs
Train on RGB + thermal / depth / NIR stacks (4-channel .npy), not just RGB.
Modern training stack
Muon optimizer, DDP, EMA, mosaic + affine augs, OneCycle, early stopping, WandB.
Beyond the model
ByteTrack tracking, SAM3 auto-labeling, Gradio demo, INT8 quantization (OpenVINO / CoreML / LiteRT).
Setup
git clone https://github.com/ArgoHA/D-FINE-seg.git
cd D-FINE-seg
uv sync
This creates a .venv/ with all dependencies pinned by uv.lock. Activate it with source .venv/bin/activate, or run anything via uv run ... (the Makefile already does this). Pretrained weights are auto-downloaded from Hugging Face into pretrained/ on first use, so no manual setup is needed. To download manually instead, grab dfine_<size>_<dataset>.pt (size โ {n, s, m, l, x}, dataset โ {coco, obj2coco}) and place it in pretrained/. Segmentation weights are also available in the Hugging Face model card.
Dataset Formats
Two annotation formats are supported: YOLO (default) and COCO JSON. Semantic segmentation uses PNG masks instead (see below).
data/dataset/
โโโ images/ # all images: .jpg, .png, etc. (.npy for multi-channel - see below)
โโโ labels/ # all labels: one .txt per image (same filename stem)
Detection labels: class_id xc yc w h (normalized)
Segmentation labels: class_id x1 y1 x2 y2 ... xN yN (normalized polygon coordinates)
Input types & channel order: 3-channel .jpg/.png (BGR, read via cv2.imread), 3-channel .npy (RGB, read via np.load), or 4-channel .npy (RGB+extras, e.g. RGB+thermal).
Semantic segmentation data layout
data/dataset/
โโโ images/ # same as YOLO layout
โโโ labels/ # one single-channel uint8 .png per image (same stem), pixel value = class id
Every pixel gets a class from label_to_name (background included). Pixels with value train.sem_seg.ignore_index (default 255) are excluded from loss and metrics and during inference 255 is the "background" or "ignored" class. make split works unchanged; keep_ratio: True is supported (letterbox pad is filled with ignore_index, so pad pixels don't supervise); coco_dataset: True is not supported for this task.
Multi-channel input setup
Set train.in_channels: N (default 3) to train on stacks beyond plain RGB. Supported range is N=3 (RGB) or N=4 (RGB + one extra modality, e.g. thermal, depth, NIR). Higher channel counts are not supported - cv2 / Albumentations ops cap at 4.
Layout is the same; drop the stacks as .npy files (uint8 HWC arrays):
data/dataset/
โโโ images/ # one .npy per sample, shape (H, W, N), dtype uint8
โโโ labels/ # YOLO .txt (same as 3-channel case)
Loader rules (see src/dl/dataset.py): np.load is byte-faithful - channels come back exactly as you saved them.
- A file whose channel count doesn't match
train.in_channelsis skipped with aaloguru.warningline (path + reason). Mosaic re-samples another index automatically. - Pretrained 3-channel backbone weights are reused: the stem conv is inflated to N input channels by tiling/averaging the RGB filters (
inflate_stem_weightinsrc/d_fine/utils.py), so fine-tuning from COCO still works. - Stem freeze (
freeze_atinsrc/d_fine/configs.py) is auto-bypassed whenin_channels > 3so the inflated extra-channel weights can train; the size-configuredfreeze_atstill applies for plain 3-channel RGB. - Channel-order convention: write the RGB triplet in the first three planes (channels 0..2) so they line up with the pretrained RGB stem; extra modalities go in channels 3..N-1. Example for RGB + thermal: stack as
[R, G, B, T].
Example: src/etl/m3fd_to_yolo.py converts the M3FD RGB+thermal detection benchmark (PASCAL VOC XML + paired Vis/Ir PNGs) into this exact layout.
COCO JSON format
Place standard COCO JSON annotation files alongside your images folder. Splits are detected automatically by filename:
data/dataset/
โโโ images/ # all images
โโโ train.json # COCO-format annotations for train split
โโโ val.json # COCO-format annotations for val split
โโโ test.json # (optional) COCO-format annotations for test split
Enable COCO mode by setting coco_dataset: True in your config (see below). No CSV split generation step is needed - the splits are read directly from the JSON files.
Training
Configuration
Edit config.yaml - key settings:
task: detect # detect | segment | sem_seg
exp_name: my_exp # experiment name (used in output paths)
model_name: s # n / s / m / l / x
train:
root: /path/to/project
data_path: /path/to/dataset # folder with images/ and labels/ (YOLO) or *.json files (COCO)
coco_dataset: False # set True to use COCO JSON annotations
label_to_name:
0: class_a
1: class_b
epochs: 75
batch_size: 8
img_size: [640, 640] # (h, w)
Commands
make split # create train/val CSV splits (test split if configured)
make train # train the model
make export # export to ONNX, TensorRT, OpenVINO, CoreML, LiteRT
make bench # benchmark all exported models on the val set
make infer # run on test folder, save visualizations + YOLO txt predictions
make check_errors # compare predictions against GT, save only mismatches (FP/FN)
make test_batching # find optimal batch size for your GPU
make ov_int8 # INT8 accuracy-aware quantization for OpenVINO (can take hours)
Notes:
- YOLO format:
make trainrequirestrain.csvandval.csvintrain.data_path(generated bymake split). - COCO format: set
coco_dataset: True-train.jsonandval.jsonare loaded directly;make splitis not needed. make inferruns Torch inference ontrain.path_to_test_dataand writes totrain.infer_path.- Or run in sequence:
make# train โ export โ bench (does not run split) - Or run overwriting configs from CLI:
uv run python -m src.dl.train exp_name=my_exp - Enable DDP (multi-GPU) by setting
train.ddp.enabled: Trueandtrain.ddp.n_gpus: Nin config. Then just runmake train- it auto-launches withtorchrun.
Training features
| Feature | Description |
|---|---|
| Muon optimizer | Optional Newton-Schulz optimizer for encoder/decoder attention+MLP matrices |
| DDP | Multi-GPU distributed training with SyncBatchNorm |
| AMP | Automatic mixed precision (~40% less VRAM, ~15% faster) |
| EMA | Exponential moving average of weights |
| Gradient accumulation | Effective batch size = batch_size ร b_accum_steps |
| Gradient clipping | Configurable max norm |
| Mosaic augmentation | 4-image mosaic with affine transforms (recommended for detection) |
| Albumentations | Rotation, flip, blur, noise, gamma, grayscale, coarse dropout, multiscale |
| OneCycleLR scheduler | Separate learning rates for backbone and head |
| Early stopping | Configurable patience |
| WandB integration | Automatic experiment tracking |
| Optimal threshold search | Auto-finds best confidence threshold after training |
| Background warm-up | Ignore background-only images for N initial epochs |
| Autoresearch harness | Tooling to run agent in autoresearch format, leaves under experiments/ |
Export
| Format | Half Precision | Notes |
|---|---|---|
| ONNX | - | With optional fused postprocessor |
| TensorRT | FP16 | Must be exported on the target GPU. Static input shape only |
| OpenVINO | FP16, INT8 | Single export for FP32 or FP16 (pick during inference) and separate INT8 quantization script |
| CoreML | FP16, INT8 | Cross-platform export, inference on macOS / iOS. FP32 and INT8 exported by default |
| LiteRT | INT8 | On-device TFLite (mobile / edge). FP32 and INT8 exported by default |
Tip: FP16 is the best latency/accuracy trade-off for GPU (TensorRT) and CPU (OpenVINO). For Apple Silicon (CoreML), FP32 is faster.
After export, a parity self-check (export.parity, on by default) runs each backend on a shared input and writes one cosine per backend - over the sorted top-K detection scores vs torch - to parity.csv next to the weights. For task: sem_seg every backend gets the same fused-argmax graph: a single int32 sem_seg output [B, H, W] (label map at input resolution, no detection postprocessor), and parity compares per-pixel argmax agreement instead of score cosine.
Inference Backends
Six inference backends in src/infer/:
| Backend | Format | Devices |
|---|---|---|
| Torch | .pt |
CUDA, MPS, CPU |
| TensorRT | .engine |
CUDA |
| OpenVINO | .xml |
CPU, iGPU |
| ONNX Runtime | .onnx |
CUDA, CPU |
| CoreML | .mlpackage |
macOS (GPU), iOS |
| LiteRT | .tflite |
CPU, mobile / edge (Android) |
Output contract: detection / instance segmentation wrappers return labels, boxes, scores (+ masks [N, H, W] for segment); sem_seg wrappers return a single sem_seg [H, W] label map at original image resolution. For sem_seg, make infer writes palette overlays + GT-style grayscale PNG label maps (crops and tracking are box-based and skipped).
Also provided:
- ByteTrack - simple implementation of object tracker
- SAM3 - text-promptable zero-shot segmentation for auto-labeling
A simplified ByteTrack (Zhang et al., ECCV 2022) is included for persistent object tracking across video frames - uses constant-velocity motion prediction with EMA-smoothed velocity instead of a Kalman filter, blends IoU with centroid distance in the match cost, and does per-class matching by default.
uv run python -m demo.demo
A web UI for uploading images and running inference interactively.
Benchmarks
Evaluation metrics
Detection / Instance segmentation:
- GT objects and predictions are matched one-to-one: a prediction is a TP if IoU > 0.5 (box for
detect, mask forsegment) and the class matches; only the highest-IoU prediction per GT counts, extra overlapping ones are FPs; a class mismatch is one FP + one FN. - F1 / Precision / Recall - computed from those TP/FP/FN counts at
train.conf_thresh. - IoU (penalized) - mean IoU over all outcomes: TPs contribute their IoU, FPs and FNs contribute 0 (= sum of TP IoUs / (TPs + FPs + FNs)).
- mAP_50 / mAP_50_95 - COCO-style average precision (mask versions for
segment).
Semantic segmentation:
- All metrics come from one pixel confusion matrix accumulated over the whole eval set at original image resolution (
ignore_indexpixels excluded). A pixel of class i predicted as j counts as an FN for i and an FP for j - each confused pixel penalizes both classes. - mIoU (decision metric) - macro-averaged: per-class pixel IoU = TP / (TP + FP + FN), averaged over classes present in GT, so every class has equal weight regardless of pixel count.
- pixel_acc - micro: fraction of all valid pixels classified correctly, so it is dominated by large classes.
Cityscapes - Detection (500 val images, original 2048ร1024, TensorRT 10.13 FP16, batch 1, RTX 5070 Ti)
Every framework runs its own shipped inference code, scored by one validator against the same GT. Confidence thresholds were calculated for each framework separately to maximize F1. Two latency columns - e2e (end-to-end, including each framework's CPU preprocessing) and engine (pure TensorRT execute) - because they can disagree. Full protocol and every known asymmetry: cityscapes-benchmark.
| model | params (M) | input | conf | F1 | precision | recall | IoU | e2e ms | engine ms |
|---|---|---|---|---|---|---|---|---|---|
| D-FINE-seg S | 10.29 | 640ร640 | 0.5 | 0.703 | 0.817 | 0.617 | 0.446 | 2.0 | 1.38 |
| YOLO26-M | 21.79 | 640ร640 | 0.25 | 0.691 | 0.792 | 0.613 | 0.432 | 3.03 | 1.59 |
| RF-DETR-medium | 33.39 | 576ร576 | 0.35 | 0.673 | 0.769 | 0.599 | 0.409 | 10.2 | 1.45 |
Cityscapes - Instance Segmentation
| model | params (M) | input | conf | F1 | precision | recall | IoU | e2e ms | engine ms |
|---|---|---|---|---|---|---|---|---|---|
| D-FINE-seg S | 11.87 | 640ร640 | 0.5 | 0.661 | 0.749 | 0.591 | 0.376 | 4.1 | 1.91 |
| YOLO26-M | 26.98 | 640ร640 | 0.25 | 0.599 | 0.688 | 0.53 | 0.312 | 5.24 | 2.08 |
| RF-DETR-seg-medium | 35.4 | 432ร432 | 0.35 | 0.62 | 0.789 | 0.51 | 0.346 | 16.33 | 1.8 |
Cityscapes - Semantic Segmentation
RF-DETR has no semantic segmentation task, so this one is D-FINE-seg vs YOLO26.
| model | params (M) | input | mIoU | pixel acc | e2e ms | engine ms |
|---|---|---|---|---|---|---|
| D-FINE-seg S | 8.02 | 640ร640 | 0.728 | 0.95 | 1.79 | 1.5 |
| D-FINE-seg M | 16 | 640ร640 | 0.753 | 0.954 | 2.24 | 2.06 |
| YOLO26-L | 17.87 | 640ร640 | 0.739 | 0.949 | 3.56 | 1.63 |
| YOLO26-M | 14.32 | 640ร640 | 0.733 | 0.947 | 3.08 | 1.16 |
VisDrone - Object Detection
VisDrone dataset - a large-scale drone-captured benchmark with 10 categories across... [text truncated].
Comments
No comments yet. Start the discussion.