
LiteRT.js로 지워버리겠습니다
요약
LiteRT.js와 U²-Net 모델을 활용하여 브라우저 환경에서 이미지 배경을 제거하는 Web 앱 구현 사례를 소개합니다. 서버 업로드 없이 로컬 브라우저 내에서 WebGPU나 Wasm을 통해 이미지를 처리함으로써 개인정보 보호와 보안성을 강화한 것이 특징입니다.
핵심 포인트
- LiteRT.js를 사용하여 .tflite 모델을 브라우저에서 실행
- WebGPU, Wasm, WebNN 백엔드 지원을 통한 온디바이스 추론
- 이미지를 외부 서버로 전송하지 않아 보안 및 프라이버시 강화
- U²-Net LiteRT 모델을 활용한 정교한 피사체 추출
만든 것
LiteRT.js를 사용하여 사진의 배경을 지우는 작은 Web 앱을 만들었습니다.
제목은 조금 기세가 넘치지만, 하고 있는 일은 착실합니다. 이미지에서 피사체(Subject)를 찾아내어 배경을 투명하게 만들고, 누끼(Cutout) PNG로 만드는 것입니다. 스마트폰 사진 앱에 있는 지우개 기능 중, 피사체만 남기는 기능에 가깝습니다.
Google이 출시한 LiteRT.js와 Hugging Face의 U²-Net LiteRT 모델(.tflite)을 사용하고 있습니다.
글로 설명하기 전에, 먼저 작동하는 모습을 보는 것이 빠를 것 같습니다.

왼쪽이 원본 이미지, 가운데가 누끼 결과, 오른쪽이 마스크(Mask)입니다. 체크무늬 패턴 위에 피사체만 남아 있습니다.
이것의 장점은 무엇인가
배경을 지우는 것만 목적이라면, 이미 편리한 Web 서비스들이 있습니다. 사진을 선택하고, 잠시 기다린 뒤, 누끼 PNG를 다운로드합니다. 그것만으로도 충분히 편리합니다.
하지만 프로필 사진이나 업무에서 다루는 이미지가 되면, "이 사진 한 장을 누끼 따기 위해 외부 서비스에 업로드해야 하나?"라고 순간적으로 망설여집니다. 이미 공개된 상품 사진이라면 상관없을지 몰라도, 얼굴 사진이나 사내 자료의 스크린샷이라면 이야기가 달라집니다.
그렇다면, 모델 쪽을 브라우저(Browser)로 가져와서 이미지는 로컬에 남겨둔 채 처리할 수는 없을까? 이번에 시도하고 싶었던 것이 바로 그것입니다.
LiteRT.js를 사용하면 .tflite 모델을 Wasm이나 WebGPU로 브라우저에서 실행할 수 있습니다. 서버 측에 추론(Inference) API나 GPU를 준비할 필요도 없습니다. 처음에 모델과 Wasm을 가져오지만, 선택한 이미지 자체는 추론을 위해 외부로 보내지 않습니다.
편리함의 핵심은 화려한 마법 같은 느낌보다는 "그 이미지, 업로드하지 않아도 괜찮습니다"라는 점에 있었습니다.
왜 LiteRT.js인가
LiteRT는 이전에 TensorFlow Lite라고 불렸던 온디바이스(On-device)용 런타임(Runtime)입니다. LiteRT.js는 그 JavaScript 바인딩(Binding)으로, .tflite를 브라우저에서 구동할 수 있습니다.
백엔드(Backend)는 크게 다음 세 가지입니다.
wasm… CPU (XNNPACK)webgpu… GPU (이번의 주전장)webnn… NPU용 (실험적)
이번에는 LiteRT community가 공개하고 있는 U²-Net의 .tflite를 사용했습니다. 입력 크기나 정규화(Normalization) 방법, 출력 마스크의 형태까지 모델 카드(Model Card)에 적혀 있어서 브라우저 측의 처리를 구성하기 쉬웠습니다.
실행해 보기
git clone https://github.com/masanori0209/litert-cutout.git
cd litert-cutout
bash scripts/setup.sh
...
setup.sh에서는 의존성(Dependency) 설치, LiteRT.js의 Wasm 동기화, U²-Net 모델 취득을 수행합니다. 모델은 약 88 MB 정도 됩니다. 이 부분은 가볍다고 말하기 어렵습니다.
브라우저에서 http://localhost:5173을 열고, "샘플" → "지우기"를 통해 전반적인 과정을 확인할 수 있습니다. 이미지를 드래그 앤 드롭(Drag & Drop)해도 작동합니다.
백엔드는 auto, webgpu, wasm 중에서 선택할 수 있습니다. auto에서는 WebGPU를 사용할 수 있으면 WebGPU를, 사용할 수 없으면 Wasm을 선택합니다.
지우는 것이 아니라, 남길 곳을 결정하는 것
처음에는 "배경을 지우는 처리"라고 생각했지만, 내용을 살펴보니 조금 반대였습니다.
U²-Net이 내놓는 것은 완성된 누끼 이미지가 아닙니다. 이미지의 어느 부분을 남기고 싶은지를 나타내는 흑백 마스크(Mask)입니다.
- 흰색에 가까운 곳은 피사체로서 남김
- 검은색에 가까운 곳은 배경으로서 투명하게 만듦
- 회색인 곳은 약간 투명하게 처리
화면 오른쪽에 표시하고 있는 흑백 이미지가 바로 그 마스크입니다. 이것을 원본 이미지의 알파(Alpha, 투명도)로 사용하면 배경이 투명해진 PNG를 만들 수 있습니다.
즉, 지우개로 배경을 문지르는 것이 아니라, 남길 부분의 본(Pattern)을 만들고 있는 것입니다.
LiteRT.js로 추론하기
모델을 구동하는 부분은 생각보다 짧게 작성할 수 있었습니다.
import { loadLiteRt, loadAndCompile, Tensor } from '@litertjs/core'
await loadLiteRt('/wasm/')
const model = await loadAndCompile('/models/u2net_fp16.tflite', {
...
이 부분에서는 약어가 한꺼번에 등장하므로, 미리 대응 표를 정리하겠습니다.
| 용어 | 이 기사에서의 의미 |
|---|---|
| Tensor | 수치를 다차원으로 나열한 컨테이너. 이번에는 이미지와 마스크를 모델에 전달하기 위해 사용 |
| ... |
[1, 3, 320, 320]
을 NCHW에 대입하면, "이미지 1장, RGB의 3채널, 높이 320, 너비 320"입니다. 브라우저의 Canvas에서 가져오는 화소는 보통 1픽셀마다 RGB가 나열되는 형태이지만, U²-Net은 색상별로 면을 나눈 NCHW를 요구합니다. 따라서 순서를 바꾸어 Float32Array에 채워 넣고 있습니다.
정규화(Normalization)에서는 RGB 값을 모델 카드에 적힌 mean(평균)과 std(표준편차)에 맞춥니다. 이미지를 보기 좋게 가공하기 위해서가 아니라, 모델이 학습할 때 사용한 입력 조건에 맞추기 위한 처리입니다.
출력값인 [1, 1, 320, 320]은 "이미지 1장, 마스크 1장, 높이 320, 너비 320"입니다. 이 마스크가 saliency mask(돌출 마스크)입니다. 여기서 말하는 saliency는 "이미지 속에서 주인공 같은 장소" 정도의 의미이며, 사람이나 상품의 종류를 이해하여 이름을 붙이는 것은 아닙니다.
마지막으로, 이 320×320 마스크를 원본 이미지 크기로 확대하여 각 픽셀의 알파(Alpha) 값에 넣습니다.
말로만 하면 어려워 보일 수 있지만, 역할을 나누면 "이미지를 모델용으로 정돈하기", "흑백 마스크 만들기", "투명도로 입히기"의 3단계입니다.
동작시켜보고 느낀 점
추론 시간만으로는 첫 체감을 알 수 없다
가지고 있는 Mac(Chromium, WebGPU 지원)에서 640×640 샘플 이미지로 다음 수치가 나왔습니다.
| 항목 | 값 |
|---|---|
| accelerator | webgpu |
| ... |
model.run만 보면 2.2 ms였지만, "지우기"를 누른 후 결과가 나오기까지는 약 1.7초가 걸렸습니다. 첫 실행 시에는 모델 로딩과 컴파일(Compile)이 지배적입니다.
숫자만 보면 2.2 ms가 눈에 띄지만, 처음 접하는 사람이 느끼는 것은 1.7초 쪽입니다. 이번에는 UI에 inference(추론)와 total(전체)을 모두 표시하도록 했습니다.
이는 개인 환경에서의 실행 예시이며, 부하 테스트가 아닙니다. 단말기, 브라우저, GPU, 캐시 상태에 따라 달라집니다.
88 MB를 배포하는 무게는 남는다
이미지를 서버에 올리지 않아도 된다고 해서 아무런 통신이 없는 것은 아닙니다. 처음에 약 88 MB의 모델을 사용자 측에 전달해야 합니다.
일회성 사내 도구라면 허용될 수 있겠지만, 누구나 접속하는 공개 페이지에서 무조건 로드하기에는 무겁습니다. 실제로 도입한다면 처리를 시작하기 전에 다운로드량을 보여주거나, 캐시를 사용하거나, 더 작은 모델을 검토하는 등의 설계가 필요합니다.
텐서(Tensor)는 직접 정리해야 한다
LiteRT.js는 수동 메모리 관리(Manual Memory Management) 방식입니다. 입력, GPU로 복사한 텐서, 추론 결과, CPU로 되돌린 출력을 사용이 끝난 시점에서 delete()하고 있습니다.
특히 처리가 반복되는 화면에서는 미루지 않는 것이 좋아 보입니다. 배경만 지우고 GPU 메모리는 계속 남아있게 된다면 곤란하기 때문입니다.
깔끔하게 지워질지는 별개의 문제
이번 샘플 이미지는 검증용으로 생성한 머그컵 이미지입니다. 배경과 피사체의 색이 구분되어 있고 윤곽도 뚜렷합니다. 누끼(배경 제거) 작업에 상당히 유리한 이미지입니다.
실제 프로필 사진에서는 머리카락, 반투명한 옷, 배경과 비슷한 색의 부분이 나타납니다. 상품 사진에서도 유리나 가는 케이블은 어려울 것 같습니다. U²-Net이 마스크를 반환하고 브라우저에서 투명 PNG로 만들 수 있는 단계까지는 확인했지만, 상용 배경 제거 서비스와 비교될 만한 이미지 세트로 테스트하지는 않았습니다.
따라서 현시점에서는 "브라우저 내부에서만 배경 제거의 일련의 과정을 구동할 수 있다"는 단계까지입니다. "어떤 사진이든 깔끔하게 지울 수 있다"고는 말하지 않겠습니다.
다음 단계로 살펴본다면, 깔끔하게 따지는 사진을 늘리기보다 머리카락, 유리, 배경과 비슷한 색의 옷 등을 나열해보고 싶습니다. 실패하는 경계가 어디인지 알아야 이 메커니즘을 어디에 사용할 수 있을지 판단하기 쉬워지기 때문입니다.
요약
LiteRT.js와 U²-Net을 사용하여 이미지의 전처리, WebGPU를 통한 추론 (Inference), 마스크 (Mask) 합성, 그리고 투명 PNG 저장까지의 전 과정을 브라우저 내에서 실행했습니다.
시작하기 전에는 "브라우저에서 모델을 구동하면 배경을 더 빠르게 지울 수 있다"는 점에만 주목했습니다. 하지만 실제로 구현해 보니, 추론 시간 (Inference time)보다 초기의 88 MB를 어떻게 보여줄 것인지, 어떤 사진이라면 마스크를 신뢰할 수 있는지, 그리고 텐서 (Tensor)를 어떻게 정리할 것인지가 더 신경 쓰였습니다.
그럼에도 불구하고, 얼굴 사진이나 개인 소장 자료를 외부로 전송하지 않고 그 자리에서 바로 잘라낼 수 있다는 점은 매우 만족스럽습니다.
LiteRT.js로 다 지워버리겠습니다. 우선은 깔끔하게 지워지지 않는 사진들까지 포함해서 시도해 보려고 합니다.
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기