[WebTranslator 개발기 #18] 이미지 번역 R&D 3차: PaddleOCR 메모리 폭증과 v1.0 탑재 철회
앞서 17편에서는 웹페이지 내 비정형 이미지(<img>, <canvas>, background-image) 번역을 위해 시도했던 Tesseract.js WebAssembly와 멀티모달 Vision AI(Gemini 2.0 / GPT-4o)의 Bounding Box 좌표 추출 실험 과정을 다루었습니다.
당시 Tesseract.js는 세로쓰기나 게임 폰트 등의 비정형 문자를 검출하지 못했고, 클라우드 Vision AI는 뛰어난 번역 품질에도 불구하고 불규칙한 바운딩 박스 좌표 오차로 인해 캔버스 오버레이 렌더링이 붕괴되는 한계를 드러냈습니다. 이에 더해 매 이미지마다 발생하는 클라우드 API 호출 비용 부담도 상용 배포에 앞서 해결해야 할 숙제였습니다.
이러한 문제를 극복하고자, 지속적인 API 비용이 발생하지 않고 정밀한 텍스트 검출(Detection) 성능이 검증된 오픈소스 경량 OCR 모델인 PaddleOCR(PP-OCRv5 Mobile)을 브라우저 WebAssembly(ONNX Runtime WASM) 환경에 직접 탑재하는 3차 R&D를 진행했습니다.
이번 글에서는 브라우저 탭 내에서 전문 온디바이스 OCR 모델을 구동하는 과정에서 겪은 500MB 이상의 심각한 탭 메모리 점유율 폭증(Memory Spike)과 텍스트 오인식 문제, 그리고 v1.0.0 정식 배포의 안정성을 지키기 위해 실험 코드를 완전히 격리하고 롤백한 엔지니어링 판단 과정을 상세히 기록하고자 합니다.
1. 3차 시도 설계: PaddleOCR(PP-OCRv5)과 ONNX Runtime WASM 파이프라인
PaddleOCR은 바이두(Baidu)에서 개발한 고성능 오픈소스 OCR 엔진으로, 특히 모바일/경량 환경에 최적화된 PP-OCRv5_mobile 모델군은 수십 MB 수준의 컴팩트한 용량 대비 뛰어난 문자 검출 및 인식 성능을 자랑합니다.
구상했던 아키텍처는 다음과 같은 2단계 온디바이스 파이프라인이었습니다:
- 텍스트 검출 (PP-OCRv5_mobile_det): 캔버스에서 캡처한 이미지 픽셀 텐서를 입력받아 글자가 위치한 정확한 다각형 영역(Bounding Polygon)을 검출합니다.
- 크롭 및 인식 (PP-OCRv5_mobile_rec): 검출된 각 텍스트 영역을 직사각형으로 잘라내어 정규화한 뒤, 텍스트 판독 모델에 전달하여 원문 문자열을 추출합니다.
- 기존 번역 엔진 연결: 추출된 텍스트를 기존에 구축된 경량 번역 파이프라인으로 넘겨 번역문을 생성하고 원본 이미지 위에 캔버스로 오버레이합니다.
이를 브라우저 확장 프로그램 내에서 순수 클라이언트 연산으로 처리하기 위해 @paddleocr/paddleocr-js 및 ppu-paddle-ocr 모듈을 도입하고, onnxruntime-web WASM 런타임을 통해 모델 가중치를 로컬 캐시에서 로드하도록 구현했습니다.
/* modules/image_translator/paddle_ocr.js - ONNX WASM 세션 초기화 및 추론 시도 */
import * as ort from "onnxruntime-web";
export class PaddleOCRService {
constructor() {
this.detSession = null;
this.recSession = null;
this.isInitialized = false;
}
async initialize() {
if (this.isInitialized) return;
console.log("[WT PaddleOCR] WASM 런타임 및 모델 가중치 로드 시작...");
const detModelPath = chrome.runtime.getURL("lib/models/PP-OCRv5_mobile_det.onnx");
const recModelPath = chrome.runtime.getURL("lib/models/PP-OCRv5_mobile_rec.onnx");
// ONNX Runtime WASM 세션 생성 (Detection + Recognition 가중치 동시 로드)
this.detSession = await ort.InferenceSession.create(detModelPath, {
executionProviders: ["wasm"]
});
this.recSession = await ort.InferenceSession.create(recModelPath, {
executionProviders: ["wasm"]
});
this.isInitialized = true;
console.log("[WT PaddleOCR] 초기화 완료: PP-OCRv5 mobile 세션 준비됨");
}
async detectAndRecognize(imageElement) {
await this.initialize();
// 1. 캔버스 렌더링 및 Float32Array 텐서 전처리
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 tensor = this.preprocessToTensor(imageData);
// 2. Detection 추론 실행
const detResults = await this.detSession.run({ x: tensor });
const boxes = this.extractBoxes(detResults);
// 3. 각 검출 영역 크롭 및 Recognition 추론
const textResults = [];
for (const box of boxes) {
const croppedTensor = this.cropAndPreprocess(imageData, box);
const recResults = await this.recSession.run({ x: croppedTensor });
const text = this.decodeRecognition(recResults);
if (text) textResults.push({ box, text });
}
return textResults;
}
}
2. 첫 번째 장애: WASM 선형 힙 비반환과 500MB+ 메모리 폭증
모듈 연동 후 실제 웹페이지에서 이미지 번역을 트리거하자마자, 사용자 경험을 심각하게 해치는 치명적인 메모리 및 성능 문제가 발생했습니다.
| 측정 지표 | 기존 텍스트 번역 (v1.0.0 기준) | PaddleOCR WASM 탑재 시 (이미지 번역 시도) |
|---|---|---|
| 초기 로딩 및 초기화 지연 | 0.05초 미만 (즉각 반응) | 8~10초 지연 (WASM 힙 할당 및 역직렬화 대기) |
| 브라우저 탭 RAM 점유율 | 약 40~60MB | 500MB~650MB 이상 급증 (Memory Spike) |
| 탭 UI 반응성 | 부드러운 DOM 조작 유지 | 추론 중 탭 렌더링 일시 프리징 (Main Thread 부하) |
| 메모리 회수 (GC 동작) | 번역 완료 후 즉시 정상 반환 | WASM 힙 메모리 고착 (GC 회수 불가) |
이러한 메모리 폭증의 기술적 원인은 WebAssembly와 onnxruntime-web의 메모리 관리 구조에 있었습니다:
- WASM 선형 메모리(Linear Memory Heap)의 비축소 특성: WebAssembly 가상 머신의 힙 메모리는 런타임 피크 시점에 한 번 확장되면, 작업이 끝나더라도 브라우저 V8 가비지 컬렉터로 메모리를 자발적으로 반환(Shrink)하지 않습니다.
- 대형 가중치 역직렬화 부하: 수십 MB에 달하는 Detection 모델과 Recognition 모델 파일 2종을 브라우저 탭 컨텍스트에서 동시에 메모리에 역직렬화하고 중간 텐서 버퍼를 캐싱하면서 수백 MB의 힙이 순식간에 확장되었습니다.
- 메인 스레드 블로킹: 별도의 생명주기를 분리한 Web Worker 환경이 아닌 탭의 Content Script 컨텍스트에서 모델 연산을 시도했기 때문에, 연산 중 브라우저 스크롤과 클릭 이벤트가 멈추는 현상이 동반되었습니다.
3. 두 번째 장애: Bounding Box 파편화와 배경 노이즈 오인식
메모리 부하를 감수하고라도 번역 품질이 우수했다면 Web Worker 분리 최적화를 모색할 수 있었겠으나, 정작 출력된 OCR 검출 및 인식 결과 역시 실전 배치 기준에 미치지 못했습니다.
paddle_ocr.js:63 [WT PaddleOCR] 캐시 히트: PP-OCRv5_mobile_det paddle_ocr.js:63 [WT PaddleOCR] 캐시 히트: PP-OCRv5_mobile_rec paddle_ocr.js:108 [WT PaddleOCR] 경고: 신뢰도 미달 텍스트 필터링 (결과: 텍스트 없음) paddle_ocr.js:108 [WT PaddleOCR] 텍스트 없음 (에러 다수 발생)
실제 웹 배너 및 스크린샷 이미지 테스트에서 드러난 구체적인 실패 양상은 다음과 같았습니다:
- 박스 세분화 파편화: 일반 문서와 달리 다양한 폰트와 자간이 섞인 웹 이미지에서 Detection 모델이 문장이나 단어 단위가 아닌 글자·획 단위로 바운딩 박스를 과도하게 잘게 쪼개어 화면에 어지러운 사각형만 대량 생성했습니다.
- 배경 하이라이트 및 노이즈의 허위 텍스트 생성: 이미지 배경의 빛 반사, 그라데이션, UI 버튼의 테두리 장식을 글자로 오인식하여 의미를 알 수 없는 임의의 허위 문자열(환각 텍스트)을 생성하는 문제가 발생했습니다.
- 높은 오류율로 인한 최종 필터링 탈락: 오류율 필터를 비활성화하여 텍스트를 강제로 추출했을 때에는 일부 글자를 가져오기도 했으나, 인식된 텍스트의 품질과 정확도가 현저히 떨어졌습니다. 프로덕션 수준의 신뢰도 임계값을 적용하면 대다수 결과가 노이즈로 분류되어 최종적으로
텍스트 없음으로 드롭 처리되었습니다.
4. 엔지니어링 결단: v1.0.0 탑재 철회와 안전한 코드 격리(Rollback)
당시 개발 과정에서 AI 어시스턴트가 "PaddleOCR 연동이 완료되어 정상 작동한다"는 식의 환각 보고서를 작성하려 했으나, 실측 데이터와 콘솔 로그를 검증한 결과 실사용이 불가능한 수준임을 객관적으로 확인했습니다.
이에 따라 2건의 단호한 의사결정과 지시 번복(Instruction Reversal)을 단행했습니다:
- 이미지 번역 R&D 전면 일시 중단: Tesseract의 미검출, Vision AI의 불규칙 좌표 오차, PaddleOCR의 메모리 폭증과 인식 붕괴를 확인한 이상, 불완전한 기술을 억지로 포장하지 않고 개발을 즉시 중단했습니다.
- v1.0.0 탑재 취소 및 실험 코드 격리: 크롬 웹 스토어 v1.0.0 정식 출시의 핵심은 텍스트 번역의 견고함이었습니다. 따라서 작성되었던 이미지 번역 관련 코드를
modules/image_translator/로 완전히 격리하고,manifest.json및 Background 라우팅에서 관련 엔드포인트를 깔끔하게 비활성화했습니다.
이 조치를 통해 기존의 전체 페이지 번역(Alt+A), 단어 사전 팝업, 다중 LLM 엔진(Gemini, OpenAI, Claude, Ollama 등) 통신 파이프라인에 단 1줄의 사이드 이펙트도 남기지 않는 클린한 베이스라인(Clean Baseline)을 성공적으로 복원했습니다.
5. 정리: 온디바이스 OCR의 조건과 이미지 번역 차기 로드맵
3차례에 걸친 이미지 번역 R&D를 통해 얻은 기술적 교훈과 차후 개선 방향은 다음과 같습니다:
| R&D 단계 | 시도 기술 | 핵심 실패 요인 | 향후 해결 방향 |
|---|---|---|---|
| 1차 시도 | Tesseract.js WASM | 비정형 폰트/세로쓰기 검출 불가 | OpenCV 전처리 결합 또는 만화 전용 모델 탐색 |
| 2차 시도 | Multimodal Vision AI | 불규칙 Bounding Box 좌표 오차 | Gemini Visual Spatial Grounding 및 정밀 프롬프트 고도화 |
| 3차 시도 | PaddleOCR WASM | 500MB+ RAM 점유 및 인식 붕괴 | Web Worker 수명 주기 관리, WebGPU 가속, 양자화 모델 적용 |
브라우저 환경에서 온디바이스 딥러닝을 안정적으로 운영하기 위해서는 다음 조건들이 필수적으로 충족되어야 합니다:
- 격리된 Web Worker 수명 주기 제어: 작업 종료 시 Worker 프로세스를 강제 종료(Terminate)하여 WASM 선형 힙 메모리를 브라우저로 100% 반환시키는 구조.
- WebGPU 백엔드 활용: CPU 기반 WASM 연산의 병목을 없애고 GPU 셰이더를 통해 텐서 연산 속도를 가속.
- int8/FP16 가중치 양자화: 모델 크기를 수 MB 단위로 압축하여 초기 로딩 지연과 메모리 사용량을 최소화.
마무리하며
이번 18편에서는 온디바이스 이미지 번역을 목표로 시도했던 PaddleOCR의 탭 메모리 폭증 실패와, v1.0.0 정식 출시의 완성도를 위해 과감히 코드를 롤백하고 격리했던 과정을 기록했습니다.
모든 기술적 시도가 항상 성공으로 끝나지는 않지만, 실패의 원인을 명확한 데이터로 남기고 기존 시스템을 오염시키지 않도록 격리하는 경험이야말로 프로덕션 소프트웨어를 더욱 견고하게 만드는 자산이라고 생각합니다.
혹시 크롬 확장 프로그램이나 웹 브라우저 환경에서 ONNX Runtime WASM 기반 모델을 구동하며 메모리 누착이나 가비지 컬렉션 문제를 해결하셨던 경험이 있다면 댓글로 의견을 남겨주시면 감사하겠습니다. 다음 글에서는 실패한 이미지 번역을 과감히 덜어낸 후 v1.0.0 정식 출시를 앞두고 진행한 실전 검수에서, 한국어 문장 복사 시 마우스 커서를 방해하던 단어 팝업 문제를 해결한 스마트 드래그 필터링 및 CJK 언어 판별 로직 개선 과정을 살펴보겠습니다.