withoutbg/withoutbg-openweights-onnx

Model

withoutBG Open Weights (ONNX)

4

7 commits

6 linked in READMEs

updated Oct 3, 2026

See the code

README

withoutBG Open Weights (ONNX)

Open-source background removal and alpha matting from RGB images, exported as an ONNX bundle for ONNX Runtime. No PyTorch checkpoints are needed at inference time.

A trained router looks at each image and picks the branch suited to it:

  • Fine strands, soft detail, transparency → the withoutBG matting model (Depth Anything V2 small depth + ConvNeXt-fused matting), trained and maintained by withoutBG.
  • Hard opaque objects, flat scenes, vehicles → BiRefNet segmentation.

Only the selected branch runs, and its alpha is upsampled to the image's native resolution. The open-weights release ships no edge refiner; refined edges are part of the Cloud API.

See the results

Example 1 Example 2 Example 3

Open Weights results → · Cloud API results → · Compare →

Model details

FieldValue
Variantoss
Version10.8.0
Pipelinerouted (sidecar schema 3)
FormatONNX (opset 18), 3 graphs
Precisionfp32
Max inference size4096 × 4096 (larger images are downscaled first)
RouterDINOv3 ConvNeXt base @ 448², 6 categories
Matting branchDepthAnythingV2 vits (dav2s) @ 518² + ConvNeXtFusedMattingUNet @ 448²
BiRefNet branchBiRefNet (general) @ 1024²
Size~1.5 GB total

Files

Always distribute all four files together:

FileContentsSize
withoutbg-open-weights-backbone.onnxShared ConvNeXt backbone → router logits + features for matting~350 MB
withoutbg-open-weights.onnxMatting branch: depth + ConvNeXt-fused matting (reuses backbone features)~104 MB
birefnet-general.onnxBiRefNet branch~1.04 GB
withoutbg-open-weights.onnx.jsonSidecar: files, SHA256, input sizes and resize modes, router categories—

Read the sidecar first. It is the authoritative source for every graph's file name, SHA256, input/output names, input size and interpolation, the router categories, and which of them route to BiRefNet (birefnet_categories).

Pipeline

All graph inputs are float32 RGB in [0, 1], NCHW, and a square stretch of the whole working image (no letterbox, no padding).

GraphInputsOutput
backbone (router)rgb_matting [1, 3, 448, 448] (bilinear, no antialias)route_logits [1, 6], f0–f3
matting (coarse)rgb_depth [1, 3, 518, 518] (bicubic, antialias), rgb_matting (same as backbone), f0–f3coarse_alpha [1, 1, 448, 448]
BiRefNet (birefnet)rgb [1, 3, 1024, 1024] (bilinear, no antialias)alpha [1, 1, 1024, 1024]
  1. Convert to RGB. If the image exceeds max_inference_size, downscale it to fit (keeping aspect ratio). This is the working image.
  2. Run the backbone. The category is the argmax of route_logits over categories.
  3. If the category is in birefnet_categories (hard_opaque, flat_scene, vehicle), run BiRefNet. Otherwise run the matting graph with the backbone's f0–f3.
  4. Upsample the branch alpha bilinearly (align_corners=False) to the working size and clamp to [0, 1]. Resize to the original size if step 1 downscaled.

Bilinear resizes match PyTorch F.interpolate(mode="bilinear", align_corners=False) without antialiasing. The bicubic depth input matches PyTorch with antialias=True, which PIL reproduces on float ("F" mode) channels.

Download

hf download withoutbg/withoutbg-openweights-onnx --local-dir withoutbg-open-weights

Or in Python:

from huggingface_hub import snapshot_download

bundle = snapshot_download(
    repo_id="withoutbg/withoutbg-openweights-onnx",
    allow_patterns=["*.onnx", "*.onnx.json"],
)

Usage

The reference host is RoutedPipeline in withoutbg-inference. It verifies every file's SHA256 and runs the pipeline above:

import json
from pathlib import Path
from PIL import Image
from withoutbg_openweights.edge_pipeline import RoutedPipeline

bundle = Path("withoutbg-open-weights")
sidecar = json.loads((bundle / "withoutbg-open-weights.onnx.json").read_text())
pipeline = RoutedPipeline(sidecar, bundle, providers=["CPUExecutionProvider"])

image = Image.open("input.jpg").convert("RGB")
matte, route = pipeline.estimate_alpha_with_route(image)  # route: {"category", "pipeline"}
image.putalpha(matte)
image.save("output.png")

Standalone, with only numpy, pillow and onnxruntime (run from the bundle folder):

import json
from pathlib import Path

import numpy as np
import onnxruntime as ort
from PIL import Image

bundle = Path(".")  # folder holding the four files
sidecar = json.loads((bundle / "withoutbg-open-weights.onnx.json").read_text())
providers = ["CPUExecutionProvider"]
sessions = {
    name: ort.InferenceSession(str(bundle / sidecar[name]["file"]), providers=providers)
    for name in ("router", "coarse", "birefnet")
}


def bilinear(x, height, width):
    """(C, H, W) -> (C, height, width); torch bilinear, align_corners=False, no antialias."""
    def axis(src, dst):
        pos = np.maximum((np.arange(dst) + 0.5) * (src / dst) - 0.5, 0.0)
        i0 = pos.astype(np.int64)
        return i0, np.minimum(i0 + 1, src - 1), (pos - i0).astype(np.float32)

    _, h, w = x.shape
    r0, r1, rl = axis(h, height)
    c0, c1, cl = axis(w, width)
    rows = x[:, r0] * (1 - rl)[:, None] + x[:, r1] * rl[:, None]
    return rows[:, :, c0] * (1 - cl) + rows[:, :, c1] * cl


def feed(rgb, spec):
    """Square-stretch native (H, W, 3) float RGB as each graph input's spec says."""
    feeds = {}
    for name, s in spec["inputs"].items():
        if s["interpolation"] == "bicubic":  # antialiased; PIL "F" mode matches torch
            x = np.stack([np.asarray(Image.fromarray(np.ascontiguousarray(rgb[..., c]))
                                     .resize((s["size"],) * 2, Image.Resampling.BICUBIC))
                          for c in range(3)])
        else:
            x = bilinear(np.ascontiguousarray(rgb.transpose(2, 0, 1)), s["size"], s["size"])
        feeds[name] = np.ascontiguousarray(x[None], dtype=np.float32)
    return feeds


image = Image.open("input.jpg").convert("RGB")
w, h = image.size
max_w, max_h = sidecar["max_inference_size"]
if w > max_w:
    w, h = max_w, int(max_w / (image.width / image.height))
if h > max_h:
    w, h = int(max_h * (image.width / image.height)), max_h
work = image.resize((w, h)) if (w, h) != image.size else image
rgb = np.asarray(work, dtype=np.float32) / 255.0

# 1. Route: shared ConvNeXt backbone -> category logits + features.
router = sidecar["router"]
router_feeds = feed(rgb, router)
logits, *feats = sessions["router"].run([router["logits_name"], *router["feature_names"]], router_feeds)
category = router["categories"][int(np.argmax(logits[0]))]

# 2. Run only the selected branch.
if category in router["birefnet_categories"]:
    spec = sidecar["birefnet"]
    alpha = sessions["birefnet"].run([spec["output_name"]], feed(rgb, spec))[0][0]
else:
    spec = sidecar["coarse"]
    feeds = {**router_feeds, **feed(rgb, {"inputs": {"rgb_depth": spec["inputs"]["rgb_depth"]}})}
    feeds.update(zip(spec["feature_names"], feats))
    alpha = sessions["coarse"].run([spec["output_name"]], feeds)[0][0]

# 3. Upsample to the working size, then to the original size.
alpha = np.clip(bilinear(alpha, h, w), 0.0, 1.0)[0]
matte = Image.fromarray(np.clip(alpha * 255.0 + 0.5, 0, 255).astype(np.uint8))
matte = matte.resize(image.size, Image.Resampling.BILINEAR)

out = image.copy()
out.putalpha(matte)
out.save("output.png")
print(category, "->", "birefnet" if category in router["birefnet_categories"] else "matting")

Runtime dependencies

python >=3.10
numpy
pillow
onnxruntime

For Hugging Face downloads, also install huggingface_hub.

More than weights

This repo hosts the ONNX weights. Same open-weights technology powers ready-made surfaces:

SurfaceChoose when
Python packageYou want to embed withoutBG in scripts, notebooks, or backends
Docker / self-hostYou want an HTTP API or browser UI on your own server (CPU or NVIDIA GPU)
Mac appYou want a native desktop cutout tool, with an optional Local API for plugins and scripts
GIMP pluginYou edit in GIMP 3 and want a private, mask-first workflow via Mac Local API or Docker
SpaceYou want to try a browser demo
Cloud APIYou need maximum quality without running inference yourself

License

The withoutBG Open Weights model is distributed under the withoutBG Open Weights license. See LICENSE and withoutbg.com/open-model/license.

Built with DINOv3.

Third-party terms

This bundle includes upstream components under their respective licenses. See THIRD_PARTY_NOTICES.md.

ComponentUsed inLicense
DINOv3Router backbone, mattingDINOv3 License
Depth Anything V2 (small)Matting branch depthApache-2.0
BiRefNet (ZhengPeng7)birefnet-general.onnxMIT
alpha-matting
background-removal
computer-vision
image-segmentation
onnx
onnxruntime
remove-background

withoutbg/withoutbg-openweights-onnx

Model

withoutBG Open Weights (ONNX)

4

7 commits

6 linked in READMEs

updated Oct 3, 2026

See the code

README

withoutBG Open Weights (ONNX)

Open-source background removal and alpha matting from RGB images, exported as an ONNX bundle for ONNX Runtime. No PyTorch checkpoints are needed at inference time.

A trained router looks at each image and picks the branch suited to it:

  • Fine strands, soft detail, transparency → the withoutBG matting model (Depth Anything V2 small depth + ConvNeXt-fused matting), trained and maintained by withoutBG.
  • Hard opaque objects, flat scenes, vehicles → BiRefNet segmentation.

Only the selected branch runs, and its alpha is upsampled to the image's native resolution. The open-weights release ships no edge refiner; refined edges are part of the Cloud API.

See the results

Example 1 Example 2 Example 3

Open Weights results → · Cloud API results → · Compare →

Model details

FieldValue
Variantoss
Version10.8.0
Pipelinerouted (sidecar schema 3)
FormatONNX (opset 18), 3 graphs
Precisionfp32
Max inference size4096 × 4096 (larger images are downscaled first)
RouterDINOv3 ConvNeXt base @ 448², 6 categories
Matting branchDepthAnythingV2 vits (dav2s) @ 518² + ConvNeXtFusedMattingUNet @ 448²
BiRefNet branchBiRefNet (general) @ 1024²
Size~1.5 GB total

Files

Always distribute all four files together:

FileContentsSize
withoutbg-open-weights-backbone.onnxShared ConvNeXt backbone → router logits + features for matting~350 MB
withoutbg-open-weights.onnxMatting branch: depth + ConvNeXt-fused matting (reuses backbone features)~104 MB
birefnet-general.onnxBiRefNet branch~1.04 GB
withoutbg-open-weights.onnx.jsonSidecar: files, SHA256, input sizes and resize modes, router categories—

Read the sidecar first. It is the authoritative source for every graph's file name, SHA256, input/output names, input size and interpolation, the router categories, and which of them route to BiRefNet (birefnet_categories).

Pipeline

All graph inputs are float32 RGB in [0, 1], NCHW, and a square stretch of the whole working image (no letterbox, no padding).

GraphInputsOutput
backbone (router)rgb_matting [1, 3, 448, 448] (bilinear, no antialias)route_logits [1, 6], f0–f3
matting (coarse)rgb_depth [1, 3, 518, 518] (bicubic, antialias), rgb_matting (same as backbone), f0–f3coarse_alpha [1, 1, 448, 448]
BiRefNet (birefnet)rgb [1, 3, 1024, 1024] (bilinear, no antialias)alpha [1, 1, 1024, 1024]
  1. Convert to RGB. If the image exceeds max_inference_size, downscale it to fit (keeping aspect ratio). This is the working image.
  2. Run the backbone. The category is the argmax of route_logits over categories.
  3. If the category is in birefnet_categories (hard_opaque, flat_scene, vehicle), run BiRefNet. Otherwise run the matting graph with the backbone's f0–f3.
  4. Upsample the branch alpha bilinearly (align_corners=False) to the working size and clamp to [0, 1]. Resize to the original size if step 1 downscaled.

Bilinear resizes match PyTorch F.interpolate(mode="bilinear", align_corners=False) without antialiasing. The bicubic depth input matches PyTorch with antialias=True, which PIL reproduces on float ("F" mode) channels.

Download

hf download withoutbg/withoutbg-openweights-onnx --local-dir withoutbg-open-weights

Or in Python:

from huggingface_hub import snapshot_download

bundle = snapshot_download(
    repo_id="withoutbg/withoutbg-openweights-onnx",
    allow_patterns=["*.onnx", "*.onnx.json"],
)

Usage

The reference host is RoutedPipeline in withoutbg-inference. It verifies every file's SHA256 and runs the pipeline above:

import json
from pathlib import Path
from PIL import Image
from withoutbg_openweights.edge_pipeline import RoutedPipeline

bundle = Path("withoutbg-open-weights")
sidecar = json.loads((bundle / "withoutbg-open-weights.onnx.json").read_text())
pipeline = RoutedPipeline(sidecar, bundle, providers=["CPUExecutionProvider"])

image = Image.open("input.jpg").convert("RGB")
matte, route = pipeline.estimate_alpha_with_route(image)  # route: {"category", "pipeline"}
image.putalpha(matte)
image.save("output.png")

Standalone, with only numpy, pillow and onnxruntime (run from the bundle folder):

import json
from pathlib import Path

import numpy as np
import onnxruntime as ort
from PIL import Image

bundle = Path(".")  # folder holding the four files
sidecar = json.loads((bundle / "withoutbg-open-weights.onnx.json").read_text())
providers = ["CPUExecutionProvider"]
sessions = {
    name: ort.InferenceSession(str(bundle / sidecar[name]["file"]), providers=providers)
    for name in ("router", "coarse", "birefnet")
}


def bilinear(x, height, width):
    """(C, H, W) -> (C, height, width); torch bilinear, align_corners=False, no antialias."""
    def axis(src, dst):
        pos = np.maximum((np.arange(dst) + 0.5) * (src / dst) - 0.5, 0.0)
        i0 = pos.astype(np.int64)
        return i0, np.minimum(i0 + 1, src - 1), (pos - i0).astype(np.float32)

    _, h, w = x.shape
    r0, r1, rl = axis(h, height)
    c0, c1, cl = axis(w, width)
    rows = x[:, r0] * (1 - rl)[:, None] + x[:, r1] * rl[:, None]
    return rows[:, :, c0] * (1 - cl) + rows[:, :, c1] * cl


def feed(rgb, spec):
    """Square-stretch native (H, W, 3) float RGB as each graph input's spec says."""
    feeds = {}
    for name, s in spec["inputs"].items():
        if s["interpolation"] == "bicubic":  # antialiased; PIL "F" mode matches torch
            x = np.stack([np.asarray(Image.fromarray(np.ascontiguousarray(rgb[..., c]))
                                     .resize((s["size"],) * 2, Image.Resampling.BICUBIC))
                          for c in range(3)])
        else:
            x = bilinear(np.ascontiguousarray(rgb.transpose(2, 0, 1)), s["size"], s["size"])
        feeds[name] = np.ascontiguousarray(x[None], dtype=np.float32)
    return feeds


image = Image.open("input.jpg").convert("RGB")
w, h = image.size
max_w, max_h = sidecar["max_inference_size"]
if w > max_w:
    w, h = max_w, int(max_w / (image.width / image.height))
if h > max_h:
    w, h = int(max_h * (image.width / image.height)), max_h
work = image.resize((w, h)) if (w, h) != image.size else image
rgb = np.asarray(work, dtype=np.float32) / 255.0

# 1. Route: shared ConvNeXt backbone -> category logits + features.
router = sidecar["router"]
router_feeds = feed(rgb, router)
logits, *feats = sessions["router"].run([router["logits_name"], *router["feature_names"]], router_feeds)
category = router["categories"][int(np.argmax(logits[0]))]

# 2. Run only the selected branch.
if category in router["birefnet_categories"]:
    spec = sidecar["birefnet"]
    alpha = sessions["birefnet"].run([spec["output_name"]], feed(rgb, spec))[0][0]
else:
    spec = sidecar["coarse"]
    feeds = {**router_feeds, **feed(rgb, {"inputs": {"rgb_depth": spec["inputs"]["rgb_depth"]}})}
    feeds.update(zip(spec["feature_names"], feats))
    alpha = sessions["coarse"].run([spec["output_name"]], feeds)[0][0]

# 3. Upsample to the working size, then to the original size.
alpha = np.clip(bilinear(alpha, h, w), 0.0, 1.0)[0]
matte = Image.fromarray(np.clip(alpha * 255.0 + 0.5, 0, 255).astype(np.uint8))
matte = matte.resize(image.size, Image.Resampling.BILINEAR)

out = image.copy()
out.putalpha(matte)
out.save("output.png")
print(category, "->", "birefnet" if category in router["birefnet_categories"] else "matting")

Runtime dependencies

python >=3.10
numpy
pillow
onnxruntime

For Hugging Face downloads, also install huggingface_hub.

More than weights

This repo hosts the ONNX weights. Same open-weights technology powers ready-made surfaces:

SurfaceChoose when
Python packageYou want to embed withoutBG in scripts, notebooks, or backends
Docker / self-hostYou want an HTTP API or browser UI on your own server (CPU or NVIDIA GPU)
Mac appYou want a native desktop cutout tool, with an optional Local API for plugins and scripts
GIMP pluginYou edit in GIMP 3 and want a private, mask-first workflow via Mac Local API or Docker
SpaceYou want to try a browser demo
Cloud APIYou need maximum quality without running inference yourself

License

The withoutBG Open Weights model is distributed under the withoutBG Open Weights license. See LICENSE and withoutbg.com/open-model/license.

Built with DINOv3.

Third-party terms

This bundle includes upstream components under their respective licenses. See THIRD_PARTY_NOTICES.md.

ComponentUsed inLicense
DINOv3Router backbone, mattingDINOv3 License
Depth Anything V2 (small)Matting branch depthApache-2.0
BiRefNet (ZhengPeng7)birefnet-general.onnxMIT
alpha-matting
background-removal
computer-vision
image-segmentation
onnx
onnxruntime
remove-background