Rust+WASM 실전 프로젝트 시리즈 2: 브라우저에서 실행되는 문자 인식
지난 글에서는 Rust로 작성한 코드를 WebAssembly(WASM)로 컴파일해서 브라우저에서 실행하는 기본적인 구조를 살펴봤습니다. 이번에는 그 구조를 바탕으로, 실제로 동작하는 문자 인식기를 만들어 보겠습니다. 마우스로 직접 글자를 그리고, 브라우저 상에서 실시간으로 그 글자가 무엇인지 인식해내는 기능입니다.
단순한 예제로 보일 수도 있지만, Rust와 WebAssembly를 실제로 접목해보는 좋은 연습이 될 것입니다. 성능, 최적화, 브라우저와의 상호작용 등 여러 요소가 녹아 있는 프로젝트입니다.
목표
- HTML 캔버스 위에 마우스로 글자를 그릴 수 있다.
- JS에서 그림 데이터를 캡처해 Rust(WASM)로 전달한다.
- Rust에서 이미지 데이터를 전처리하고 간단한 머신러닝 모델로 문자 인식을 시도한다.
- 인식된 결과를 브라우저에 표시한다.
준비물
- Rust (nightly 추천)
wasm-pack
- Node.js (로컬 테스트용)
- HTML/JS 기초
- 머신러닝 프레임워크 없이 Rust 로직으로 분류기 구현
1. HTML + JS: 입력 인터페이스 구성
사용자가 문자를 입력할 수 있는 캔버스를 HTML로 구성합니다.
<canvas id="draw-canvas" width="280" height="280" style="border:1px solid black;"></canvas>
<button id="recognize-btn">문자 인식</button>
<p id="result">결과: 없음</p>
<script type="module" src="main.js"></script>
main.js
에서는 마우스 입력을 감지해 캔버스에 그림을 그리고, 버튼 클릭 시 캔버스 데이터를 Rust로 넘기는 구조로 작성합니다. 캔버스 데이터를 28x28 픽셀로 리사이즈해서 전달하면 MNIST 포맷과 유사한 형태가 됩니다.
2. Rust로 이미지 데이터 처리하기
Rust에서는 wasm-bindgen을 사용해 JS와의 인터페이스를 구성합니다.
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn recognize(image_data: &[u8]) -> String {
let processed = preprocess_image(image_data);
let prediction = classify(processed);
prediction
}
preprocess_image
함수는 흑백 이미지로 정규화하거나, 노이즈를 제거하는 역할을 하며, classify
함수는 간단한 KNN 기반 또는 평균 패턴 매칭 등으로 문자를 분류합니다.
3. 간단한 문자 분류기 구현
성능보다는 구조 학습이 목적이므로 간단한 룰 기반 분류기로 충분합니다.
fn classify(pixels: Vec<f32>) -> String {
let reference_digits = load_reference_digits();
let mut best_match = "?";
let mut best_score = f32::MAX;
for (label, ref_pixels) in reference_digits.iter() {
let score = euclidean_distance(&pixels, ref_pixels);
if score < best_score {
best_score = score;
best_match = label;
}
}
best_match.to_string()
}
이 방식은 MNIST처럼 숫자 패턴이 입력됐을 때 비교적 정확한 결과를 냅니다. Rust 기반이므로 성능도 빠르고 브라우저에서 실행하기에도 적합합니다.
4. JS-WASM 연동: 호출 및 결과 표시
wasm-pack build
로 빌드 후 JS에서 WASM 모듈을 로딩하여 사용합니다.
import init, { recognize } from './pkg/char_recognizer.js';
async function main() {
await init();
document.getElementById('recognize-btn').onclick = () => {
const canvas = document.getElementById('draw-canvas');
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, 28, 28).data;
const grayData = convertToGrayscale(imageData);
const result = recognize(grayData);
document.getElementById('result').textContent = `결과: ${result}`;
};
}
main();
convertToGrayscale
는 RGBA → 흑백 변환을 담당하는 JS 함수입니다.
성능과 가능성
이 프로젝트의 진짜 재미는, Rust의 성능을 브라우저에서 직접 경험해볼 수 있다는 점입니다. JS로도 가능하지만 Rust는 메모리 효율과 연산 속도에서 훨씬 유리하므로, 복잡한 알고리즘을 도입하기에도 부담이 적습니다.
이미지 필터링, 시뮬레이션, 실시간 게임 로직 등 다양한 분야로 확장 가능합니다. WebGL이나 WebGPU와 결합하면 고성능 웹 애플리케이션도 꿈은 아닙니다.
마치며
이번 시리즈에서는 Rust와 WASM을 활용해 브라우저에서 실행되는 문자 인식기를 구현했습니다. 성능 민감한 연산을 WASM으로 오프로드하는 구조는 앞으로 점점 중요해질 것입니다.
다음 글에서는 실제 딥러닝 모델을 WASM에 포팅하여 보다 정밀한 인식을 구현해보겠습니다. tract
나 ONNX
포맷 활용을 다룰 예정이니 기대해 주세요.