🖼️

background-removal-js — 브라우저에서 AI로 배경을 제거하는 원리

IS-Net 모델 + ONNX Runtime Web + WebGPU, 서버 없이 클라이언트에서 세그멘테이션

import { removeBackground } from '@imgly/background-removal';
const blob = await removeBackground(imageUrl);

이 한 줄 뒤에서 일어나는 일이 꽤 많다.

사용 모델: IS-Net

IS-Net(Intermediate Supervision Network)은 U-Net 계열의 salient object detection 모델이다. DIS(Dichotomous Image Segmentation) 논문에서 발표됐다. SAM이나 MODNet이 아니다.

인코더-디코더 구조에서 중간 레이어마다 supervision을 걸어서 정밀한 바이너리 마스크를 생성한다. 사람, 동물, 물건 — "눈에 띄는 객체(salient object)"를 배경에서 분리하는 게 목적.

3가지 모델 변형이 있다:

  • large (isnet) — FP32 풀 정밀도, ~176MB

  • medium (isnet_fp16, 기본값) — FP16 반정밀도, ~88MB

  • small (isnet_quint8) — INT8 양자화, ~44MB

기본값이 medium인 이유: 88MB도 크지만, 176MB는 브라우저에서 로드하기에 너무 크다. 양자화 모델은 경계가 약간 거칠어질 수 있다.

전처리 → 추론 → 후처리 파이프라인

1단계: 입력 변환

입력(URL, Blob, ArrayBuffer, ImageData)을 RGBA Uint8Array 텐서 [H, W, 4]로 변환한다. 브라우저에서는 createImageBitmap() → Canvas getImageData() 경로.

2단계: 리사이즈 + 정규화

원본을 1024×1024로 bilinear interpolation 리사이즈한다. 비율 무시 — 모델 입력 크기가 고정이라 어쩔 수 없다. 그리고 HWC → BCHW 변환하면서 정규화:

float32[i] = (uint8[i] - 128) / 256

RGB 3채널만 사용, 알파는 버린다. 최종 입력 shape: [1, 3, 1024, 1024]

3단계: ONNX 추론

ONNX Runtime Web으로 모델을 실행한다. 입력 'input', 출력 'output'. 출력은 [1024, 1024, 1] float32 — 각 픽셀의 "전경일 확률"이 0.0~1.0으로.

4단계: 마스크 적용

float32 값에 255를 곱해서 uint8로 변환한 뒤, 원본 해상도로 다시 리사이즈. 원본 이미지의 알파 채널(4번째)에 이 마스크를 직접 대입한다:

outImageTensor.data[4 * i + 3] = alphamask.data[i];

배경 픽셀은 알파가 0(투명), 전경은 255(불투명). 별도의 alpha matting 후처리는 없다 — IS-Net 자체가 충분히 정밀한 마스크를 출력하기 때문.

ONNX Runtime Web — 브라우저에서 AI 추론

두 가지 백엔드:

  • WASM (기본) — WebAssembly SIMD + 멀티스레드. navigator.hardwareConcurrency만큼 스레드 활성화. SharedArrayBuffer가 필요하므로 Cross-Origin-Isolated 헤더 필수.

  • WebGPU (device: 'gpu') — navigator.gpu.requestAdapter()로 탐지. GPU가 있으면 자동 사용. 없으면 WASM 폴백.

ONNX Runtime 자체도 CDN에서 WASM 바이너리를 동적 로드한다. 모델 파일도 CDN에서 chunk 단위로 병렬 다운로드.

모델은 어디서 오는가

@imgly/background-removal-data 패키지에 ONNX 모델과 WASM 바이너리가 포함되어 있다. 기본적으로 imgly CDN(staticimgly.com)에서 로드하지만, publicPath 옵션으로 자체 호스팅도 가능.

대용량 모델(최대 176MB)을 여러 chunk로 분할 저장하고 Promise.all로 병렬 다운로드한다. 진행률 콜백도 chunk 단위로 보고.

브라우저 vs Node.js

같은 API지만 내부 구현이 다르다:

  • 브라우저: onnxruntime-web (WASM/WebGPU) + Canvas 기반 이미지 처리

  • Node.js: onnxruntime-node (네이티브 C++) + sharp 라이브러리 — WASM 오버헤드 없이 더 빠르다

IS-Net 파이프라인

입력 URL/Blob/ArrayBuffer → createImageBitmap() → Canvas getImageData() → RGBA [H, W, 4]
전처리 bilinear resize → 1024×1024 → HWC→BCHW → (pixel - 128) / 256 → [1, 3, 1024, 1024] float32
추론 ONNX Runtime Web (WASM or WebGPU) → IS-Net → [1024, 1024, 1] float32 알파마스크
후처리 ×255 → uint8 → 원본 해상도 resize → data[4*i+3] = mask[i] (알파 채널 직접 대입)

모델 크기 비교

모델 크기 정밀도 비고
isnet (large) ~176MB FP32 최고 품질, 브라우저 로드 부담
isnet_fp16 (medium) ~88MB FP16 기본값 — 품질/크기 균형
isnet_quint8 (small) ~44MB INT8 가장 작지만 경계가 거칠 수 있음

ONNX Runtime Web 백엔드

백엔드 설정 특징
WASM (기본) device: 'cpu' WebAssembly SIMD + 멀티스레드. SharedArrayBuffer 필요 (COOP/COEP 헤더)
WebGPU device: 'gpu' GPU 가속. Chrome/Edge만. 미지원 시 WASM 자동 폴백

브라우저 vs Node.js

영역 브라우저 Node.js
ONNX Runtime onnxruntime-web (WASM/WebGPU) onnxruntime-node (네이티브 C++)
이미지 처리 createImageBitmap + Canvas sharp 라이브러리
모델 로드 CDN (HTTPS) chunk 병렬 다운로드 로컬 file:// (node_modules)

핵심 포인트

1

입력 이미지를 RGBA Uint8Array [H, W, 4] 텐서로 변환

2

1024×1024로 bilinear 리사이즈 → HWC→BCHW 변환 + 정규화 (mean=128, std=256)

3

IS-Net 모델이 [1024, 1024, 1] float32 알파마스크 출력 (전경=1.0, 배경=0.0)

4

마스크를 원본 해상도로 리사이즈 → 원본 이미지의 알파 채널에 직접 대입

5

ONNX Runtime: WASM(기본) 또는 WebGPU 백엔드 — 모델은 CDN에서 chunk 병렬 다운로드

사용 사례

프로필 사진 편집 — 서버 없이 브라우저에서 즉시 배경 제거 e-커머스 상품 이미지 — 배경을 흰색으로 자동 교체 화상회의 가상 배경 — 실시간은 아니지만 정적 이미지 마스킹