Removing a background from a photo used to require hours in Photoshop. Today, an AI model can do it in under a second. Whether you need a transparent PNG for a product listing, a cutout for a presentation, or batch-processing thousands of product photos, there are good programmatic options in every major language.
How background removal works
There are three fundamentally different approaches:
| Approach | How it works | Best for |
|---|---|---|
| AI segmentation | Neural network predicts a per-pixel mask of the foreground subject | People, products, animals — complex edges |
| Chroma keying | Replace a known colour (e.g. green screen) | Studio photos with solid-colour backdrops |
| Threshold masking | Remove pixels within a colour range or below a luminance threshold | Simple images with high contrast backgrounds |
AI segmentation is the default choice in 2024 because it handles hair, fur, and semi-transparent objects that rule-based approaches fail on.
Output format: PNG with alpha
Background removal produces a transparent image. Always save the result as PNG (or WebP with transparency). JPEG does not support transparency — saving a transparent image as JPEG fills the background with black.
Input: product.jpg → opaque JPEG, white background
Output: product.png → transparent PNG, alpha channel carries the mask
JavaScript — browser Canvas API (simple colour removal)
For solid-colour backgrounds in the browser, Canvas gives you direct pixel access:
function removeBackground(imageElement, tolerance = 30) {
const canvas = document.createElement('canvas');
canvas.width = imageElement.naturalWidth;
canvas.height = imageElement.naturalHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(imageElement, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
// Sample the background colour from the top-left corner
const [bgR, bgG, bgB] = [data[0], data[1], data[2]];
for (let i = 0; i < data.length; i += 4) {
const dr = Math.abs(data[i] - bgR);
const dg = Math.abs(data[i + 1] - bgG);
const db = Math.abs(data[i + 2] - bgB);
if (dr + dg + db < tolerance * 3) {
data[i + 3] = 0; // set alpha to transparent
}
}
ctx.putImageData(imageData, 0, 0);
return canvas.toDataURL('image/png');
}
// Usage
const img = document.getElementById('myImage');
const resultDataUrl = removeBackground(img, 40);
Limitation: works well for flat, single-colour backgrounds. Fails on gradients, shadows, and complex edges. Use an AI approach for those.
JavaScript (Node.js) — Remove.bg API
The Remove.bg API returns a transparent PNG in one request:
import fs from 'node:fs';
import FormData from 'form-data';
import fetch from 'node-fetch';
async function removeBg(inputPath, outputPath) {
const form = new FormData();
form.append('image_file', fs.createReadStream(inputPath));
form.append('size', 'auto');
const response = await fetch('https://api.remove.bg/v1.0/removebg', {
method: 'POST',
headers: { 'X-Api-Key': process.env.REMOVEBG_API_KEY, ...form.getHeaders() },
body: form,
});
if (!response.ok) {
throw new Error(`Remove.bg error: ${response.status} ${await response.text()}`);
}
const buffer = await response.buffer();
fs.writeFileSync(outputPath, buffer);
console.log(`Saved transparent PNG: ${outputPath}`);
}
await removeBg('input.jpg', 'output.png');
Cost: Remove.bg offers 50 free API calls/month; paid plans start at ~$9 for 200 images.
Python — rembg (local AI, no API key)
rembg runs the U2Net or BiRefNet model locally — no network call, no API cost:
pip install rembg[cpu] Pillow
from rembg import remove
from PIL import Image
import io
def remove_background(input_path: str, output_path: str) -> None:
with open(input_path, 'rb') as f:
input_bytes = f.read()
output_bytes = remove(input_bytes)
# Save via Pillow to ensure correct PNG output
img = Image.open(io.BytesIO(output_bytes)).convert('RGBA')
img.save(output_path, 'PNG')
print(f'Saved: {output_path} ({img.size[0]}×{img.size[1]})')
remove_background('product.jpg', 'product_nobg.png')
Batch processing a folder:
from pathlib import Path
from rembg import remove, new_session
# Reuse model session across images (faster)
session = new_session('u2net')
for jpg in Path('input/').glob('*.jpg'):
output = Path('output') / jpg.with_suffix('.png').name
output.parent.mkdir(exist_ok=True)
with open(jpg, 'rb') as f:
data = remove(f.read(), session=session)
output.write_bytes(data)
print(f'✓ {jpg.name}')
The first run downloads the model (~170 MB for U2Net). Subsequent runs are instant.
Go — using an HTTP API or subprocess
Go doesn't have a mature native background-removal library. The practical approach is to call the Remove.bg API or shell out to rembg:
package main
import (
"bytes"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
"path/filepath"
)
func removeBgAPI(inputPath, outputPath, apiKey string) error {
file, err := os.Open(inputPath)
if err != nil {
return err
}
defer file.Close()
var body bytes.Buffer
writer := multipart.NewWriter(&body)
part, _ := writer.CreateFormFile("image_file", filepath.Base(inputPath))
io.Copy(part, file)
writer.WriteField("size", "auto")
writer.Close()
req, _ := http.NewRequest("POST", "https://api.remove.bg/v1.0/removebg", &body)
req.Header.Set("X-Api-Key", apiKey)
req.Header.Set("Content-Type", writer.FormDataContentType())
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
b, _ := io.ReadAll(resp.Body)
return fmt.Errorf("remove.bg: %s — %s", resp.Status, b)
}
out, _ := os.Create(outputPath)
defer out.Close()
_, err = io.Copy(out, resp.Body)
return err
}
func main() {
apiKey := os.Getenv("REMOVEBG_API_KEY")
if err := removeBgAPI("input.jpg", "output.png", apiKey); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Println("Done: output.png")
}
For offline processing, call rembg as a subprocess:
import "os/exec"
func removeBgLocal(inputPath, outputPath string) error {
cmd := exec.Command("rembg", "i", inputPath, outputPath)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
PHP — GD chroma-key removal
For controlled studio images with a known background colour, PHP GD is sufficient without external dependencies:
<?php
function removeBackground(
string $inputPath,
string $outputPath,
int $tolerance = 40
): void {
$src = imagecreatefromstring(file_get_contents($inputPath));
if (!$src) {
throw new RuntimeException("Cannot read: $inputPath");
}
$w = imagesx($src);
$h = imagesy($src);
// Create destination image with alpha support
$dst = imagecreatetruecolor($w, $h);
imagesavealpha($dst, true);
$transparent = imagecolorallocatealpha($dst, 0, 0, 0, 127);
imagefill($dst, 0, 0, $transparent);
// Sample background colour from top-left pixel
$bgColour = imagecolorat($src, 0, 0);
$bgR = ($bgColour >> 16) & 0xFF;
$bgG = ($bgColour >> 8) & 0xFF;
$bgB = $bgColour & 0xFF;
for ($y = 0; $y < $h; $y++) {
for ($x = 0; $x < $w; $x++) {
$colour = imagecolorat($src, $x, $y);
$r = ($colour >> 16) & 0xFF;
$g = ($colour >> 8) & 0xFF;
$b = $colour & 0xFF;
$distance = abs($r - $bgR) + abs($g - $bgG) + abs($b - $bgB);
if ($distance < $tolerance * 3) {
// Background pixel — make transparent
imagesetpixel($dst, $x, $y, $transparent);
} else {
// Foreground pixel — copy as-is
$newColour = imagecolorallocatealpha($dst, $r, $g, $b, 0);
imagesetpixel($dst, $x, $y, $newColour);
}
}
}
imagepng($dst, $outputPath);
imagedestroy($src);
imagedestroy($dst);
}
removeBackground('input.jpg', 'output.png', tolerance: 35);
For AI-powered removal in PHP, use the Remove.bg API via cURL:
<?php
function removeBgAPI(string $inputPath, string $outputPath, string $apiKey): void {
$ch = curl_init('https://api.remove.bg/v1.0/removebg');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ["X-Api-Key: $apiKey"],
CURLOPT_POSTFIELDS => [
'image_file' => new CURLFile($inputPath),
'size' => 'auto',
],
]);
$result = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
throw new RuntimeException("Remove.bg API error $httpCode: $result");
}
file_put_contents($outputPath, $result);
}
Quick reference
| Method | Language | AI quality | Offline | Cost |
|---|---|---|---|---|
| Canvas colour replace | JS (browser) | Low | Yes | Free |
| Remove.bg API | JS / Go / PHP | High | No | 50 free/mo |
| rembg (U2Net) | Python | High | Yes | Free |
| rembg (BiRefNet) | Python | Very high | Yes | Free |
| GD chroma key | PHP | Low | Yes | Free |
ImageMagick -fuzz |
CLI | Medium | Yes | Free |
Common mistakes
Saving as JPEG after removal — JPEG has no alpha channel; the transparent pixels become black. Always output PNG or WebP.
Sampling background from the wrong pixel — Top-left is a common default but fails when the subject is flush against the edge. Let users click to pick the background colour, or use AI.
Using colour tolerance that's too tight — Similar-coloured areas of the subject (white shirt on white background) will be removed. Increase tolerance only until the backdrop is gone.
Not adding a margin / padding before removal — Cropping the image tight to the subject before background removal sometimes clips edge pixels. Leave a few pixels of background around the subject.
Forgetting to handle the alpha composite when compositing later — If you place a transparent PNG onto a canvas, ensure you draw the background first, then the foreground layer; otherwise the composite produces a black outline.
Ignoring EXIF orientation — A JPEG rotated 90° via EXIF will process upside-down. Call
exif_transpose()(PHP) /ImageOps.exif_transpose()(Python) /.rotate()(Sharp) before the background removal step.
Frequently asked questions
Can I remove a complex background (trees, buildings) without green screen? Yes — AI segmentation models like U2Net and BiRefNet handle arbitrary backgrounds. Chroma key and colour-tolerance methods only work on uniform solid-colour backgrounds.
How accurate is rembg on hair and fur? U2Net is reasonably good. BiRefNet (the newer model, also available in rembg) handles fine details like hair significantly better at the cost of more RAM and a slightly slower run time.
What resolution limit does the free Remove.bg tier have? Free API calls return images at 0.25 megapixels (approximately 500×500 px). Paid calls return the full original resolution.
Can I use rembg on a server without a GPU?
Yes. The rembg[cpu] install uses ONNX Runtime on CPU. Expect roughly 2–8 seconds per image on a modern CPU core, versus ~0.2 seconds on a GPU.
Is the result always a PNG? The transparent result must be a format that supports alpha — PNG or WebP. If your pipeline needs JPEG, composite the cutout onto a white (or brand-colour) background before encoding.
My output has a faint grey fringe around the subject — why?
This is "matting artefacts" from alpha compositing over a white background during training. Apply alpha matting or use the --alpha-matting flag in rembg to refine the edge.