Hacker News

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_channels is skipped with a aloguru.warning line (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_weight in src/d_fine/utils.py), so fine-tuning from COCO still works.
  • Stem freeze (freeze_at in src/d_fine/configs.py) is auto-bypassed when in_channels > 3 so the inflated extra-channel weights can train; the size-configured freeze_at still 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 train requires train.csv and val.csv in train.data_path (generated by make split).
  • COCO format: set coco_dataset: True - train.json and val.json are loaded directly; make split is not needed.
  • make infer runs Torch inference on train.path_to_test_data and writes to train.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: True and train.ddp.n_gpus: N in config. Then just run make train - it auto-launches with torchrun.

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 for segment) 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_index pixels 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.