行业动态
dev.to ·
@ai startup
用 ONNX Runtime Web 在浏览器端去除图片背景(无需服务器)
开发者分享在浏览器内运行 IS-Net 分割模型去除图片背景的方案:模型导出为 ONNX 并量化为 int8(fp32 约 178MB,int8 约 46MB),使用 onnxruntime-web 的 WASM 后端配合 canvas 完成推理,图片无需上传服务器。文中还记录了实际遇到的问题,例如多线程需要跨源隔离响应头。作者称在真实照片上自测,但未给出精度或性能基准。
AI推荐理由 作者自述在 AI 助手协助下完成项目并亲自在真实照片上测试,提供了具体技术细节:IS-Net 导出 ONNX 并量化为 int8(fp32 约 178MB、int8 约 46MB),使用 onnxruntime-web 的 WASM 后端,并指出多线程需跨源隔离头等实际限制。属于可复现的开发者实践,对端侧推理和隐私友好应用有参考价值;但为个人项目分享,未提供精度对比或性能基准,效果证据有限。
72
正文
Disclosure: I built this project with a lot of help from an AI assistant (Claude). I tested the result myself on real photos, and the notes below are what I actually ran into.
I wanted a background remover that never uploads the photo. Most online tools send your image to a server. That is a privacy cost, and a hosting cost for whoever runs it. It turns out you can run a segmentation model entirely in the browser with ONNX Runtime Web and WebAssembly.
Here is the approach, and the problems I hit.
The pieces
Model: IS-Net (general use), exported to ONNX and quantised to int8. The fp32 file was about 178 MB; the int8 file is about 46 MB.
Runtime: onnxruntime-web with the WASM backend.
Everything else: plain canvas.
Loading and running the model
import * as ort from 'onnxruntime-web';
ort.env.wasm.wasmPaths = '/js/vendor/ort/';
ort.env.wasm.numThreads = 1; // multi-thread needs cross-origin isolation headers
const session = await ort.InferenceSession.create(modelBytes, {
executionProviders: ['wasm']
});
Enter fullscreen mode
Exit fullscreen mode
numThreads = 1 is a deliberate choice. Multi-threaded WASM needs SharedArrayBuffer, which needs special COOP/COEP headers. On a static host that is more trouble than it is worth for a free tool.
Pre-processing
The model expects a 1024×1024 input. I resize the photo on a canvas, then convert it to a float tensor in NCHW order:
const S = 1024;
const c = document.createElement('canvas'); c.width = c.height = S;
const ctx = c.getContext('2d');
ctx.drawImage(img, 0, 0, S, S);
const { data } = ctx.getImageData(0, 0, S, S);
const input = new Float32Array(3 * S * S);
for (let i = 0; i < S * S; i++) {
input[i] = data[i * 4] / 255;
input[i + S * S] = data[i * 4 + 1] / 255;
input[i + 2 * S * S] = data[i * 4 + 2] / 255;
}
const tensor = new ort.Tensor('float32', input, [1, 3, S, S]);
Enter fullscreen mode
Exit fullscreen mode
Check the model's expected mean and std. This one needed different normalisation than the small U2-Net model I used as a fallback, and wrong values silently give poor masks.
Post-processing is where quality comes from
The raw output is a soft mask. Two things made a big difference:
Normalise the mask to the 0..1 range (min-max), because the output range differs per image.
Refine the edges with a guided filter using the original photo (grey) as the guide. It snaps the mask edge to real edges in the photo, which removes the blurry halo you get from just upscaling the mask.
Then a smoothstep curve on the alpha gives a cleaner outline.
Problems I hit
Hosting a 46 MB file. Cloudflare Pages has a 25 MB per-file limit. I split the model into 8 MB parts, hosted them on a separate Pages project, and concatenate them in the browser. Dedicated caching headers make repeat visits fast.
Stale cache. After updating the JS, users kept running the old file. Adding a ?v=NN query to script tags fixed it.
Hard images. Hair, glass and shiny surfaces still leave rough edges. A small in-browser model will not match big paid cloud tools there. I added Restore and Erase brushes so people can fix details by hand.
Result
The tool is at picfitter.online. It is free, has no signup, and the photo stays on the device. For clear products on plain backgrounds it works well; for hard cases expect to touch up.
If you have tips on faster WASM inference or better refinement, I would like to hear them in the comments.