DEV Community

Nariaki Wada
Nariaki Wada

Posted on

Removing a Portrait Background with BiRefNet ONNX on CPU

Removing a Portrait Background with BiRefNet ONNX on CPU

Hello, everyone.

Background removal is common, but I wanted to see whether a local CPU could preserve thin details such as hair.

Today, I ran an official BiRefNet ONNX model with ONNX Runtime and removed the background from a portrait.

To give the result first, it produced a transparent PNG while preserving the main subject and most of the hair. Inference with a 1024x1024 input took about 4.32 seconds on average on an Apple M1 Max CPU.

What we are verifying

BiRefNet separates a high-resolution image into foreground and background. This is called dichotomous image segmentation (DIS), meaning segmentation into two kinds of regions.

I used the general-purpose Swin-Tiny ONNX model from the official GitHub Release and checked:

  • Whether it runs from Python with only the ONNX Runtime CPU backend
  • Whether it separates a person, white clothing, and thin windblown hair from the background
  • How long preprocessing, inference, post-processing, and PNG saving take
  • How alpha values are distributed in the output

This is the original test image.

Portrait before background removal with BiRefNet

Target lab: kiarina/labs/2026/07/13/birefnet-onnx

Reproducing the environment

You need mise, uv, and an internet connection for the initial model and shared-image downloads.

git clone --depth 1 --filter=blob:none --sparse \
  https://github.com/kiarina/labs.git
cd labs
git sparse-checkout set .gitignore .mise/tasks Makefile mise.toml 2026/07/13/birefnet-onnx
mise -C 2026/07/13/birefnet-onnx run
Enter fullscreen mode Exit fullscreen mode

On the first run, the task downloads the model after verifying its SHA-256 hash. It then creates output_removed_bg.png, with the background represented by the alpha channel.

Model and license

This test uses one model.

Model Role Input and output
BiRefNet-general-bb_swin_v1_tiny-epoch_232.onnx Estimate whether each pixel belongs to the foreground or background 1x3x1024x1024 image → 1x1x1024x1024 logits

Swin-Tiny is the backbone, or the base component that extracts image features. The official model list presents it as a smaller and faster alternative to the larger Swin-Large backbone. The ONNX file used here is 224,005,088 bytes.

As described in the paper, BiRefNet has a localization module that uses the whole image to locate the subject and a reconstruction module that refers to local image details and edges to recover fine structure. Because this test uses the exported ONNX model, these modules are not invoked separately in the code.

The data flow is:

1536x1024 JPEG
  -> Resize to 1024x1024, convert to RGB, and normalize
  -> Estimate foreground logits with BiRefNet ONNX
  -> Convert logits to a 0-1 mask with sigmoid
  -> Resize the mask back to 1536x1024
  -> Add it to the original image as an alpha channel
  -> Transparent PNG
Enter fullscreen mode Exit fullscreen mode

Logits are the model's raw output values. Sigmoid converts them to a range from 0 to 1. The final image treats 0 as transparent and 1 as opaque.

Before redistributing the model or integrating it into a product, check the current license text, attribution requirements, and dependency terms.

Method

The input is one fixed portrait of a person by the sea. It includes white clothing and thin strands of hair extending to the right in the wind.

file: tests/assets/jpg/removebg_1536x1024_141kb.jpg
resolution: 1536x1024
SHA-256: d3d362b876936c57cfaf61eedd0ada05fd4950483ab79502aa5a67ded4a6b910
Enter fullscreen mode Exit fullscreen mode

OpenCV resizes the image to 1024x1024 and normalizes it with the ImageNet mean and standard deviation. After ONNX Runtime inference, the mask is resized to the original resolution and saved as an 8-bit alpha channel.

The timing excludes model download, SHA-256 verification, InferenceSession initialization, and image loading. The inference benchmark runs 10 times after three warm-up runs. Warm-up reduces the effect of setup work that occurs only during the first inference.

Results

This is the generated transparent PNG. Depending on the viewer, transparent areas may appear white or as a checkerboard.

Portrait after background removal with BiRefNet

These results were recorded on the CPU of a MacBook Pro with an Apple M1 Max.

--- One-shot processing time ---
Preprocessing:   23.86 ms
Inference:     4699.83 ms
Postprocessing:  6.80 ms
PNG save:        41.07 ms
Total:         4771.56 ms

--- Inference benchmark (Warmup: 3, Iterations: 10) ---
Average time: 4319.62 ms
Min time:     4206.41 ms
Max time:     4480.35 ms
Std dev:        88.62 ms
Enter fullscreen mode Exit fullscreen mode

I ran the same lab again while writing this article. Average inference time was 4302.47 ms, with a minimum of 4179.94 ms and a maximum of 4477.45 ms. Both runs were close to 4.3 seconds, but these timings are reference values that vary between runs.

The output is a 1536x1024, 8-bit RGBA PNG. The alpha distribution was identical in both runs.

Transparent (alpha=0): 65.92%
Transition (1-254):     9.37%
Opaque (alpha=255):     24.71%
Enter fullscreen mode Exit fullscreen mode

Transition means pixels that are neither fully transparent nor fully opaque. These values help produce smoother hair and clothing boundaries, but their percentage alone does not measure segmentation accuracy.

The verification environment was:

machine: MacBook Pro (Apple M1 Max, arm64)
OS: macOS 26.5.1
Python: 3.12.10
ONNX Runtime: 1.27.0
OpenCV: 5.0.0
NumPy: 2.5.1
provider: CPUExecutionProvider
Enter fullscreen mode Exit fullscreen mode

Interpretation

The detailed numbers are above, but the simpler reading has three main points.

  1. It preserved the person and most of the hair

The model separated the face, white clothing, and most of the hair from the background. It retained many thin strands extending to the right, and partially transparent pixels kept the boundary from ending abruptly.

  1. Some flyaway hair disappeared, and color spill remained

Some especially thin strands above the head and on the right disappeared. A small amount of blue from the original background also remained around the hair and clothing edges. Replacing the background with a different color may require an additional edge-color correction step.

  1. It runs on a CPU, but it is not real-time

Inference took about 4.3 seconds per image. That is usable for processing a small number of local images one by one, but video or large batches would need further tests with a GPU, a smaller model, or quantization. Quantization reduces the numerical precision used for computation to make a model lighter.

This test used only one well-lit outdoor portrait. There was no ground-truth mask, so the quality assessment is visual only. I did not test other subjects, complex backgrounds, low light, low resolution, multiple people, or a quality and speed comparison with the Swin-Large model. The current implementation also stretches the input into a square before inference, so alternative resizing methods remain untested.

Thoughts after verification

I was impressed that a simple ONNX Runtime implementation running only on the CPU could create a transparent image while preserving much of the hair. It seems useful when background removal should stay local instead of sending an image to an external service. This quality should be sufficient for an LLM agent that automatically collects portraits, removes distracting background noise, and passes only the person to the next processing step.

Top comments (0)