@meiban-ocr/runtime
v0.3.2
Published
Browser-friendly alphanumeric OCR runtime (onnxruntime-web).
Maintainers
Readme
@meiban-ocr/runtime
Browser-friendly OCR for industrial nameplate serials (Ericsson E300MM000032 format).
ONNX CRNN model bundled in-package, no separate model file or CDN required.
- Inference engine:
onnxruntime-web(WebGPU → WASM fallback) - Model: MobileNetV3-Small + Bi-GRU + CTC, ~3 MB FP32 ONNX (bundled inline)
- Detection: built-in sliding-window proposal generator (no OpenCV.js dependency)
- License: Apache-2.0
Install
npm i @meiban-ocr/runtime onnxruntime-web
# or pnpm / yarnonnxruntime-web is a peer-style dependency — you bring your own version.
Usage
import { MeibanOCR } from '@meiban-ocr/runtime';
// init: モデルはバンドル済み、追加ダウンロード不要
const ocr = await MeibanOCR.create({ vendor: 'ericsson' });
// camera frame / Reticle crop / 任意の Canvas 画像を渡せる
const results = await ocr.recognize(canvas);
// → [{ text: 'E300MM000032', confidence: 0.96, bbox: [120, 340, 480, 420] }, ...]
// 該当なし → []
await ocr.dispose();API
type ImageInput = HTMLCanvasElement | OffscreenCanvas | ImageBitmap | ImageData;
interface MeibanOCROptions {
vendor?: 'ericsson'; // 補正パイプライン (default)
executionProviders?: Array<'webgpu' | 'wasm'>; // ORT EP (default: ['webgpu', 'wasm'])
minConfidence?: number; // default 0.5
modelUrl?: string; // バンドル版を上書きしたいとき
modelBytes?: Uint8Array | ArrayBuffer; // ↑ バイト列で渡すとき
detector?: {
maxInputDim?: number; // default 1024
strideX?: number; // default 32
strideY?: number; // default 16
scales?: number[]; // default [1.0]
};
maxBatchSize?: number; // default 64
}
interface OCRResult {
text: string; // 確定値 (regex 通過済み)
confidence: number; // 0-1
bbox: [number, number, number, number]; // 入力画像内座標 [x1,y1,x2,y2]
}
class MeibanOCR {
static create(opts?: MeibanOCROptions): Promise<MeibanOCR>;
recognize(image: ImageInput): Promise<OCRResult[]>;
dispose(): Promise<void>;
}Vendor pattern
Currently supports Ericsson only:
- Strict regex:
/^E[39]\d{2}MM\d{6}$/例:E300MM000032,E300MM999022 - 6-stage correction pipeline (backend
PlateSerialNumber.phpと互換):- Strict full match
- Strict full +
O → 0fallback - Lenient full (Ericsson 対象外、将来用)
- Lenient +
O → 0 - Strict partial +
O → 0 - Strict partial
recognize() の text は 6 段補正後の確定値です。
検出器 (Detector) の差し替え (v0.2.0+)
MeibanOCR.recognize() は内部で 「入力画像 → 候補テキスト行 bbox 列挙 → CRNN 認識 → 補正」
の順に処理する。最初のステップ "候補 bbox 列挙" が detector で、2 つの選択肢がある。
(1) Sliding-window (default、ゼロ依存) ★推奨
const ocr = await MeibanOCR.create(); // 省略 = sliding-window
// or 明示的にチューニング
import { createSlidingWindowDetector } from '@meiban-ocr/runtime';
const ocr = await MeibanOCR.create({
detector: createSlidingWindowDetector({ strideX: 32, strideY: 16 }),
});総当たり方式。Reticle 切り出し済の小入力 (e.g. 600x200) 用途には最適。 外部依存ゼロ・bundle bloat なし。WebGPU でバッチ推論されるので 100-500ms/frame 程度。
Reticle ユースケース (uranus2 等) のチューニング例
const ocr = await MeibanOCR.create({
vendor: 'ericsson',
detector: { strideX: 24, strideY: 12 }, // 細かめ stride で recall 上げる
minConfidence: 0.5,
});
const results = await ocr.recognize(reticleCanvas);(2) 独自検出器 (学習済モデル / Reticle 固定 / OpenCV.js 自作 / etc)
import type { DetectorFn } from '@meiban-ocr/runtime';
const reticleDetector: DetectorFn = (image) => [
// 入力全体を 1 つの候補として扱う (uranus2 Reticle 互換)
[0, 0, image.width, image.height],
];
const ocr = await MeibanOCR.create({ detector: reticleDetector });DetectorFn = (image: ImageData) => BBox[] | Promise<BBox[]> を満たせば何でも OK。
v0.3.0 の変更点 (breaking)
- OpenCV.js helper を削除。
createOpenCvDetector/loadOpenCvの export は廃止。 独自に OpenCV.js を使いたい場合はDetectorFnを自前実装する形に統一。 - フロントエンド軽量化を最優先する方針。デフォルトの sliding-window で多くのユースケース (Reticle 切り出し等) は十分カバーできるため。
- 移行ガイド:
createOpenCvDetector(cv)→DetectorFnで独自実装、もしくは省略して sliding-window を使う。
制限事項
- Single product family: 訓練データは Ericsson 4 製品 (RRU 22F3, RRUS 11 B1, Radio 2218 B42B, Radio 2251 B18 B280) のみ。未学習銘板では精度低下の可能性あり。val_CER 3.85%, val_EM 53.8% (v0 ベンチマーク)。
- Model size: 3 MB FP32 (FP16/INT8 化は次バージョンで検討)。
- 訓練データの非公開: photo / 実シリアル番号は公開リポジトリに含まれません (
samples/,data/,runs/は gitignored)。詳細は SECURITY.md を参照。
Security considerations
OCRResult.text は untrusted な出力として扱うこと
recognize() の返却 text は、ユーザーがカメラ撮影した任意画像から抽出された
文字列です。攻撃者が 意図的な文字列を印字したラベルを撮影させる ことで、
任意文字列を OCRResult.text 経由でアプリに注入する余地があります。
DO:
textContentプロパティ / React の{text}補間 (自動エスケープされる)- SQL は parameterized query
- log では quote / escape
DON'T:
el.innerHTML = result.texteval,Function,setTimeout(string)に渡す- shell command / SQL に直接埋め込む
modelUrl / modelBytes は信頼するソースからのみ
MeibanOCR.create({ modelUrl }) の引数は scheme 検証 (http: / https: / data: /
blob: のみ許可) されますが、host の whitelist は無しです。
利用側は untrusted な値 (URL クエリパラメータ等) を直接渡さないこと:
// ❌ 危険: 攻撃者が ?model= で任意 ONNX を指定可能
const modelUrl = new URLSearchParams(location.search).get('model');
await MeibanOCR.create({ modelUrl });
// ✅ 自分で管理する CDN のみ
await MeibanOCR.create({ modelUrl: 'https://cdn.example.com/meiban/model.onnx' });
// ✅ 整合性検証して bytes で渡す (推奨)
const expectedSha = 'a1b2c3...';
const response = await fetch('https://cdn.example.com/model.onnx');
const buf = await response.arrayBuffer();
const hash = await crypto.subtle.digest('SHA-256', buf);
// hash を expectedSha と照合してから:
await MeibanOCR.create({ modelBytes: buf });ONNX モデル整合性
現状の bundled モデル (@meiban-ocr/runtime に inline されている data URL) は
SRI 不可です。npm パッケージ自体の integrity (shasum/sha512) は npm
レジストリで検証されるため、npm install --ignore-scripts 等の通常導入なら問題なし。
さらに強い検証が必要なら modelBytes 経由で消費側 SHA-256 検証を行ってください。
報告窓口
脆弱性を発見したら public issue ではなく GitHub Private Vulnerability Reporting
で報告してください: https://github.com/kogasura/meiban-ocr/security/advisories/new
詳細は SECURITY.md を参照。
Performance reference (HANDOFF.md 目標値)
1440×1080 nameplate sheet、20 ラベル想定:
| EP | 1 フレーム合計 | 1 ラベルあたり | |---|---|---| | WebGPU | ~120 ms | ~6 ms | | WASM (4 threads) | ~600 ms | ~30 ms |
実測は環境依存。
License
Apache-2.0. Model artifact ships under the same license as part of this package.
