🖼️

background-removal-js — ブラウザで背景除去が動く本当の仕組み

IS-Net ONNXモデル + WASM推論 + Canvas API、サーバーなしでクライアント完結するパイプライン

画像を入れると背景が消えたPNGが出る。サーバーに送らない。ブラウザ内で全て処理される。ONNX RuntimeのWASMバックエンドでディープラーニングモデルを実行する構造。

コードを開くとパイプラインが驚くほど単純だ。

5段階パイプライン

第1段階 — 画像デコードcodecs.ts

入力がURLでもBlobでもArrayBufferでも全てNdArray<Uint8Array>(RGBA、[H, W, 4])に変換。ブラウザではcreateImageBitmap → Canvas → getImageData経路。Node.jsではsharp使用。

第2段階 — 前処理inference.ts + utils.ts

核心は2つ:リサイズと正規化。

画像を1024x1024にリサイズ。アスペクト比を維持せず強制的に正方形にする。bilinear interpolation使用。次にHWC uint8 RGBAをBCHW float32 RGBに変換。正規化は(pixel - 128) / 256 — 値範囲が約[-0.5, 0.5]になる。

この変換がtensorHWCtoBCHW関数で起こる:float32Data[j] = (imageBufferData[i] - 128) / 256 — R、G、Bチャネルをstrideを掛けてplanar配置。

第3段階 — モデル推論onnx.ts

ONNX RuntimeでIS-Net(Dichotomous Image Segmentation)モデルを実行。入力テンソル[1, 3, 1024, 1024]、出力[1024, 1024, 1](前景確率0.0〜1.0)。

実行プロバイダは2つ:wasm(デフォルト)とwebgpu(config.deviceがgpuの場合)。ort.env.wasm.numThreads = navigator.hardwareConcurrencyでマルチスレッドWASMを有効化。

モデルは3サイズ:isnet(FP32)、isnet_fp16(FP16、デフォルト)、isnet_quint8(INT8量子化)。CDNからチャンク単位でダウンロード。

第4段階 — 後処理inference.ts

float32マスクをuint8に変換(×255)し、元の解像度に再リサイズ。元画像のアルファチャネルにマスクを上書き:

outImageTensor.data[4 * i + 3] = alphamask.data[i] — この1行が背景除去の核心。マスク値0(背景)は透明に、255(前景)は維持。

第5段階 — エンコードcodecs.ts

RGBAテンソルをCanvasにputImageDataで描画、convertToBlobでPNG/JPEG/WebP Blobを返却。

正直に言うと

パイプラインが単純なのはいいが、代価がある。

1024x1024強制リサイズが致命的。元が4000x3000でも200x200でも全て1024x1024に押しつぶす。アスペクト比無視。縦長の人物写真は横に引き伸ばされ、横長の風景は縦に圧縮される。モデル入力前に歪みが発生するのでマスク品質が落ちざるを得ない。

trimapもalpha mattingもない。他の背景除去システム(MODNet、RVM等)はtrimap生成→alpha mattingで髪の毛のような半透明境界を処理する。このライブラリはIS-Net一発に依存。結果品質がモデル性能に100%依存。髪の毛境界がぼやけるのは構造的限界。

セッションメモ化がlodash.memoize。設定JSONをキーにメモ化、同じ設定なら数百MBモデルを再ロードしない。合理的。だがメモリ解放タイミングがない。

AGPL-3.0ライセンス。商用利用にはソース公開義務。MIT/Apacheではない。

69個のオープンissue、多くが未回答。メンテナ2名、最終コミット2025年7月。8ヶ月間mainに何も入っていない。

それでも、ブラウザでONNX Runtime WASMによるディープラーニング推論を実行するパターン自体に価値がある。Web Workerでこのライブラリを実行すればUIをブロックせずに背景除去が可能 — web-workers-internalsの記事で扱ったまさにそのパターンだ。

リポジトリ現況(2026-03基準)

Star 6,973、Fork 453。JS背景除去ライブラリで最も有名。メンテナ2名。main最終コミット2025-07-18。オープンissue 69個。AGPL-3.0。TypeScript 59%。npm最新版1.5.7(2024-11-27)。

パイプラインコード探索

各カードをクリックすると実際のソースコードが展開されます

Stage 1画像デコードutils.ts ↗
async function imageSourceToImageData(image, config) { if (typeof image === "string") image = new URL(image); if (image instanceof URL) image = await (await fetch(image)).blob(); if (image instanceof Blob) image = await imageDecode(image); return image; }
Stage 2前処理 — 1024x1024 + BCHW正規化inference.ts ↗
const resolution = 1024; let resized = tensorResizeBilinear(img, resolution, resolution, false); const input = tensorHWCtoBCHW(resized); // (pixel - 128) / 256 → 範囲 [-0.5, 0.5]
Stage 3ONNX推論 — IS-Net(WASM/WebGPU)onnx.ts ↗
const providers = [useWebGPU ? "webgpu" : "wasm"]; const session = await ort.InferenceSession.create(model, { executionProviders: providers, graphOptimizationLevel: "all" });
Stage 4アルファチャネル — 核心の1行api/v1.ts ↗
for (let i = 0; i < stride; i++) { outImageTensor.data[4 * i + 3] = alphamask.data[i]; // 0 → 透明(背景) 255 → 不透明(前景) }
Stage 5エンコード — Canvas → Blobcodecs.ts ↗
canvas.getContext("2d").putImageData(imageData, 0, 0); return canvas.convertToBlob({ quality, type: format });

IS-Netモデル3種比較

設定値精度サイズ速度
largeFP32~176MB遅い
medium(デフォルト)FP16~88MB普通
smallINT8~44MB速い

キーポイント

1

画像入力(URL/Blob/Buffer)→ createImageBitmap + CanvasでRGBA NdArrayに変換

2

1024x1024リサイズ(比率無視)+ HWC→BCHW変換 + (pixel-128)/256正規化

3

ONNX Runtime(WASM/WebGPU)でIS-Net推論 → [1024,1024,1]前景確率マスク

4

マスクをuint8変換 → 元解像度リサイズ → アルファチャネルに上書き

5

Canvas putImageData → convertToBlobでPNG/JPEG/WebP出力

ユースケース

Web Worker内で実行しUIブロッキングなしでクライアントサイド背景除去 プロフィール写真アップロード時にサーバー送信前にブラウザで背景除去(プライバシー保証) Node.jsサーバーでsharp + onnxruntime-nodeでバッチ画像処理