toolgarden.xyz
EN
浏览器工具开发OCRONNX Runtime WebWeb Worker计算机视觉

怎么用 ONNX Runtime Web 实现浏览器本地 OCR

用 Web Worker、ONNX WASM、文本检测、方向分类、文字识别、语言偏置解码与阅读顺序合并实现完整浏览器 OCR。

ToolGarden 推荐的工具优先在浏览器本地运行,文件和文本不必上传到服务器,适合更注重安全隐私的日常处理。

发布于 2026年7月22日约 14 分钟阅读作者 ToolGarden

OCR 并不是调用一次神经网络。真正可用的浏览器流水线要先找到文字区域、纠正倒置裁剪、识别不同宽度的文本行、解码字符概率、恢复阅读顺序,并让这些重计算远离 UI 主线程。

这套实现把检测、方向分类和识别三个 ONNX session 放进 module Web Worker。图片通过可转移字节进入 worker,OffscreenCanvas 完成预处理,模型 session 只加载一次,进度与结果则通过 request ID 对应。

把 OCR 拆成三个模型阶段

检测模型输出文本像素概率图。后处理对概率图做阈值化和膨胀,寻找连通区域,过滤弱区域,扩展边界框,合并同一行碎片,并按阅读顺序排序。

每个检测裁剪先经过 0/180 度分类,再送入识别模型。识别结果按 CTC 方式去掉 blank 和连续重复类。最终合并会保留换行,并根据英文或 CJK 文本决定是否插入空格。

阶段输入输出
检测缩放后的整图张量排序后的文本框
方向分类固定尺寸文本裁剪0° 或 180°
识别高度 48px 的变宽裁剪文字与置信度
合并识别块与坐标框可读多行文本

把图片字节转移到独立 Worker

页面复用一个 worker,并为每个请求分配唯一 ID。把 ArrayBuffer 放进 transfer list,会转移所有权而不是克隆一份大图片。进度和结果按 ID 过滤,避免无关响应错误地结束另一个 Promise。

每次请求注册临时 message 与 error listener,结束后移除,并设置超时。worker 崩溃时要 terminate 并清空缓存实例,下一次不能继续复用损坏 worker。

const worker = new Worker(
  new URL('../workers/ocr-accurate.worker.ts', import.meta.url),
  { type: 'module' },
);
const data = await file.arrayBuffer();

worker.postMessage({
  id: requestId,
  type: 'recognize',
  file: { data, type: file.type, name: file.name, size: file.size },
  language,
}, [data]);

只加载一次 ONNX Session

ONNX Runtime Web 使用同源 WASM 路径、顺序执行、图优化和 WASM provider。三个模型与字符字典并行加载,再缓存共享 Promise。第一张图片承担模型成本,后续请求复用 session。

单线程配置不需要跨源隔离,内存行为也更可预测,代价是峰值吞吐较低。对一次处理一张图片的工具来说,这通常是合理取舍。

const [det, cls, rec, dictionary] = await Promise.all([
  ort.InferenceSession.create('/models/ocr/det-mobile.onnx', options),
  ort.InferenceSession.create('/models/ocr/cls.onnx', options),
  ort.InferenceSession.create('/models/ocr/rec-unified-mobile.onnx', options),
  fetch('/models/ocr/rec-unified-dict.txt').then(response => response.text()),
]);

const boxes = await detectTextBoxes(det, sourceImage);
for (const box of boxes) {
  const crop = cropCanvas(sourceImage, box);
  const oriented = await classifyTextOrientation(cls, crop);
  blocks.push(await recognizeTextCrop(rec, oriented.canvas, dictionary, language));
}
return mergeRecognizedBlocks(blocks, language);

每个模型都要匹配自己的预处理

createImageBitmap 解码 Blob,OffscreenCanvas 让像素操作留在 worker。分配更多画布前先检查像素上限。检测阶段缩放最长边并把尺寸对齐到 32;分类阶段拉伸到固定输入;识别阶段保留比例并在右侧补白。

检测使用 ImageNet normalization,分类和识别使用 Paddle 风格 normalization。百分位对比度拉伸能改善浅色文字,但只应在真实对比范围足够时启用。

  • 绘制进 OffscreenCanvas 后立即关闭 ImageBitmap。
  • 生成 CHW Float32 张量并明确拆分 RGB 通道。
  • 识别高度固定,文本行最大宽度受限。
  • 补白区域填充白色,避免透明像素变成黑色。

后处理和模型推理同样重要

原始概率图并不是文本行列表。阈值、膨胀、连通区域、得分过滤、边界扩展、行碎片合并与阅读排序,决定识别器看到的是完整文本还是破碎片段。

识别输出也要正确解码:忽略 blank、折叠连续相同类别、累计置信度,并在候选分数接近时优先选择目标语言字符。这样既能减少跨文字体系噪声,又不会硬性屏蔽标点与拉丁字符。

诚实展示进度与能力边界

模型、准备、检测、分类、识别和合并应成为独立进度阶段。逐框处理时还要显示已处理数量与总数,因为文本区域很多的页面,大部分时间可能花在检测之后。

轴对齐文本框和 0/180 分类无法彻底解决透视、曲线文字、竖排、手写或 90 度旋转。要求文档级准确率时,需要几何矫正、更完整的方向处理或专业服务。

总结

浏览器 OCR 的质量来自模型推理与传统后处理共同设计。把工作移入 Worker,缓存三个 session,分别匹配预处理,转移大缓冲,重建文本框与阅读顺序,并清楚说明剩余限制,才能形成完整工具。

常见问题

Q.为什么 OCR 需要三个模型?

检测负责找到文字位置,分类负责纠正倒置裁剪,识别才把每个裁剪变成字符。直接对整图做一次识别会浪费分辨率并丢失布局结构。

Q.为什么要把 OCR 放进 Web Worker?

图片预处理、连通区域分析和 ONNX 推理都很消耗 CPU。Worker 能保持主线程响应,并允许使用 OffscreenCanvas 远离 React 渲染完成像素处理。

Q.每次识别会把 ONNX 模型和图片一起上传吗?

不会。模型是下载到浏览器并缓存的静态应用资源;选中的图片字节只从页面转移到同一页面的 worker,不会提交到远程 OCR 接口。

Q.语言偏置为什么能改善识别?

多语言模型可能给外形接近的不同文字体系字符相似分数。软偏置可以在分数接近时优先选择目标文字,同时继续允许数字、拉丁字符和标点。