withoutbg Python SDK: Local ONNX or Cloud Background Removal
Python SDK for local and cloud background removal (pip install withoutbg)
At a glance
- What is it?
- The withoutbg package gives Python developers one API for two backends: a ~455 MB ONNX model that runs offline, or a paid cloud endpoint with sharper edges on hair and fur. Here is what the code actually does, and where it stops being the right tool.
- Who is it for?
- Adopt withoutbg if you want background removal inside a Python process with no service to run, and you accept either a one-time ~455 MB weight download or per-image API billing.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 58 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 19, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem withoutbg solves, and who it is aimed at
Background removal in Python usually means picking a side. You either run a model on your own machine and accept whatever quality it gives you, or you send the image to a hosted service and accept the latency, the bill and the fact that someone else now has the picture. withoutbg's pitch is that you should not have to choose at the start. The same WithoutBG object, constructed two different ways, gives you either path.
The audience is narrow and identifiable. Scripts, notebooks, backends and batch jobs, according to the README. That means people who already have Python in the loop and want the cutout as a PIL Image they can composite, save or stream onward, not a person clicking a button in a web app. If you need a browser UI or an HTTP endpoint on your own server, the README points at a separate repository, withoutbg/withoutbg-inference, and a Docker image. This package is the in-process path.
The library is published on PyPI as withoutbg, requires Python 3.9 or newer, and declares Development Status 4 - Beta in pyproject.toml. Beta is the honest label here: the API is small, and the project has already renamed things once, which is why docs/MIGRATION.md exists for people coming from WithoutBG.opensource() and ProAPI.
How the two backends differ under one API
WithoutBG.open_weights() and WithoutBG.api() return objects that expose the same methods: remove_background(), remove_background_batch(), and a progress_callback argument on the single-image call. Everything behind that surface is different.
The local path loads an ONNX model through onnxruntime. The dependency list in pyproject.toml is the giveaway: numpy, pillow, onnxruntime, requests, click, tqdm and huggingface-hub. The first local run pulls roughly 455 MB of weights from Hugging Face, and the README states that after that everything stays on the machine. Inference runs on CPU, so no GPU is required. The cloud path instead sends the image over HTTP with requests, authenticated by an API key passed to api_key= or read from WITHOUTBG_API_KEY. Nothing is downloaded; you pay per image and the image leaves your machine.
The quality split is stated plainly in the README's comparison table: local is described as good, cloud as better, especially on hair and fur. That is a real trade-off rather than marketing hedging. Soft alpha edges around fine strands are exactly where a smaller CPU model loses to a server-side one, and the project does not pretend otherwise. It also means the two modes are not interchangeable for a product where cutout quality is visible.
There is a third configuration worth knowing about. Setting WITHOUTBG_MODEL_PATH to a local .onnx file skips the Hugging Face download entirely, which is the escape hatch for air-gapped machines and for CI where a 455 MB fetch per job is unacceptable. The README warns that the sidecar metadata file, withoutbg-open-weights.onnx.json, has to sit next to the ONNX file, so a copy that grabs only the .onnx will not work.
Installing withoutbg and removing a background
The README's install instruction uses uv, Astral's package manager. If you do not have uv, the README links to astral.sh/uv and says to install it once before running the add command.
uv add withoutbgAfter that, three lines are enough to produce a cutout. The model object is created once, the image is passed by path, and the return value is a PIL Image in RGBA mode. Save it as PNG or WebP; the README is explicit that JPEG drops transparency silently, which is the kind of failure that produces a black background in production and no error in the logs.
from withoutbg import WithoutBG
model = WithoutBG.open_weights()
model.remove_background("photo.jpg").save("result.png")The first execution of open_weights() downloads the weights from Hugging Face. Expect that to take a while on a slow connection, and expect it to happen once per environment rather than once per image.
Switching to the cloud is one line. The key can be passed directly or left in the environment as WITHOUTBG_API_KEY, which is the better choice for anything checked into version control.
from withoutbg import WithoutBG
model = WithoutBG.api(api_key="sk_your_key")
result = model.remove_background("input.jpg")
result.save("output.png")For more than a handful of images, the README's batch example is the one to copy. Note the comment in it: the model object must stay alive between calls. Reconstructing WithoutBG.open_weights() inside a loop reloads the weights every iteration.
from withoutbg import WithoutBG
model = WithoutBG.open_weights() # keep this object alive
images = ["photo1.jpg", "photo2.jpg", "photo3.jpg"]
results = model.remove_background_batch(images, output_dir="results/")There is also a command line entry point for shell pipelines. Running withoutbg with no flags uses the local model; --use-api switches to the cloud, and --format jpg --quality 95 fills the transparent area with white instead of preserving alpha.
withoutbg photo.jpg --output result.png
withoutbg portrait.jpg --format jpg --quality 95
withoutbg --helpRunnable versions of all of this live in examples/remove_background_local.py, examples/remove_background_api.py and examples/batch_processing.py.
Where withoutbg breaks down
The most obvious failure mode is the first-run download. The weights come from Hugging Face, and the README's own troubleshooting section treats a failed download as an expected problem, suggesting a connection check or WITHOUTBG_MODEL_PATH pointing at a local copy. In a container built at deploy time this is manageable. In a serverless function with a cold start, a 455 MB fetch is not, and the README offers no caching story beyond the environment variable.
The second is quality control. There is no documented way to tune the matting: no threshold, no edge refinement parameter, no mask return value described in the README. You call remove_background and you get an RGBA image. If a particular subject comes out badly, your options are the cloud API or a different library. For product photography with clean edges that is fine. For a mixed corpus of portraits, it is a coin flip you cannot inspect or adjust.
The third is that the cloud mode is not a fallback in any automatic sense. Nothing in the README describes retrying a local failure against the API, and doing so would mean sending an image to a third party after you had chosen not to. The mode is a decision you make at construction time.
Finally, the licence situation deserves a direct look rather than a shrug. pyproject.toml declares Apache-2.0 and the README badge agrees, but the repository metadata reports NOASSERTION and the tree contains LICENSE-DINOv3, NOTICE and THIRD_PARTY_LICENSES.md. A separate licence file named after a model suggests bundled components under different terms. This is not legal advice, and the only responsible move is to read those three files against your own use case before shipping.
How withoutbg compares to rembg
rembg is the comparison most Python developers will make, and the project itself invites it: rembg appears in the keyword list in pyproject.toml. The two take different approaches to the same job.
rembg is a general-purpose wrapper around a family of segmentation models, selected by name, with its own model download mechanism. Its value is breadth: you pick a model, and different models suit different subjects. withoutbg does not expose model selection at all. There is one open-weights model, downloaded automatically, plus a hosted endpoint. That is a narrower design in both directions: fewer knobs, and fewer ways to be surprised by which model you got.
The second difference is the cloud path. rembg is local-only in its core proposition. withoutbg treats the hosted API as a first-class mode behind the same object, which is convenient if you want to prototype locally and move to better quality later without rewriting the call site, and a liability if you specifically want a dependency that never phones home.
The third is packaging. withoutbg depends on huggingface-hub and onnxruntime and exposes a click-based CLI installed alongside the library. If you are already standardised on uv and want a CLI in the same install, that is a smaller footprint to manage than assembling rembg's model handling yourself. If you want to choose among several model architectures, withoutbg simply does not offer that.
Maintenance, releases and upgrade cost
The last push to the default branch was on 2026-07-23, and the most recent release, v1.1.1, was tagged on 2026-07-19. The repository is not archived. Releases are automated: package.json pulls in semantic-release with the changelog, exec, git and github plugins driven by conventional commits, and .releaserc.json sits at the top level. That means version numbers track commit messages, and CHANGELOG.md is generated rather than hand-written.
Upgrade cost is low if you stay within the documented surface. The public API is small, the return type is a PIL Image, and the library pins only minimum versions of its dependencies (numpy>=1.21.0, pillow>=8.0.0, onnxruntime>=1.12.0, requests>=2.25.0, click>=8.0.0, tqdm>=4.60.0, huggingface-hub>=0.33.5). Loose lower bounds cut both ways: they reduce conflicts in a shared environment, and they mean an onnxruntime major release can change behaviour under you without a version bump in withoutbg.
The breakage that has already happened is naming. WithoutBG.opensource() and ProAPI are gone, replaced by open_weights() and api(), and the README routes anyone affected to docs/MIGRATION.md. Expect that pattern again while the package carries a Beta classifier. Pinning to a specific version and reading CHANGELOG.md before bumping is cheaper here than in a library with a stable API promise.
On licence: Apache-2.0 is declared in pyproject.toml, but the presence of LICENSE-DINOv3 and THIRD_PARTY_LICENSES.md means the distributed artifact may include code under other terms. Redistribution, especially of the weights, is the case to check against those files rather than assume.
Editorial conclusion
Adopt withoutbg if you want background removal inside a Python process with no service to run, and you accept either a one-time ~455 MB weight download or per-image API billing. Do not adopt it if you need fine control over matting quality per image, a guaranteed offline fallback when the Hugging Face download fails, or a licence position you have not checked yourself: the repository declares Apache-2.0 in pyproject.toml but GitHub reports NOASSERTION and the tree carries a separate LICENSE-DINOv3. Before wiring it into anything, run one image through WithoutBG.open_weights(), confirm the RGBA output on a hair-heavy subject, and read LICENSE, NOTICE and THIRD_PARTY_LICENSES.md.
Frequently asked questions
Does withoutbg work offline?
Yes, in local mode. WithoutBG.open_weights() downloads about 455 MB of weights from Hugging Face on the first run, and the README states that after that everything stays on your machine. You can also point WITHOUTBG_MODEL_PATH at a local .onnx file to skip the download, keeping the sidecar withoutbg-open-weights.onnx.json next to it.
Do I need a GPU to run withoutbg locally?
No. The README's comparison table lists GPU required as No for local mode, and inference goes through onnxruntime on CPU. The cloud mode also requires no GPU, since inference happens on the API side.
How do I switch withoutbg from local to cloud mode?
Construct the model differently. WithoutBG.open_weights() runs the local ONNX model, while WithoutBG.api(api_key="sk_your_key") calls the cloud API; the key can also come from the WITHOUTBG_API_KEY environment variable. Both objects expose remove_background(), so the rest of your code stays the same.
Community notes