网页端OCR, 加载6mb大小模型, 又快又准, 百度这次真香

40岁了, 还在做开发, 再多卷一会, 哎

我想给内部系统加个 OCR 功能,用户上传截图自动识别文字。然后发现了百度的PaddleOCR

毕竟专用OCR模型, 识别率比大模型优秀

传统做法:后端起个 Python 服务,装 PaddleOCR,部署 Flask。麻烦不说,还得考虑并发和资源占用。

我就想:能不能让浏览器自己干这事儿?

这是我的做demo, 完整代码最后

1. onnxruntime-web 是什么

ONNX Runtime 是微软开源的推理引擎,onnxruntime-web 是它的浏览器版本。

底层用 WebGL 或 WebGPU,你的显卡在浏览器里给 AI 打工了。

接入一行:

如果项目用 npm:

bash 复制代码
npm install onnxruntime-web

用 bun 也一样:

bash 复制代码
bun add onnxruntime-web

安装后创建 src/index.ts 作为入口:

bash 复制代码
import * as ort from 'onnxruntime-web';
ort.env.wasm.wasmPaths = '/ort/';
(window as any).ort = ort;

用构建工具打包到静态目录:

bash 复制代码
# bun
bun build ./src/index.ts --outfile=./static/ort.js --target=browser

# 或用 webpack / vite 都行

HTML 里引用打包产物:

bash 复制代码
<script src="ort.js"></script>

加载模型、准备输入、推理、拿结果,四步完成,没有服务端,全在浏览器。

2. 下载模型

PP-OCRv6 的 ONNX 模型用魔塔 ModelScope 下载,国内速度快:

在魔搭上搜索PP-OCRv6_tiny

然后下载对应的onxx文件

解释下这2个文件干啥的:

PP-OCRv6_tiny_det_onnx:文本检测模型核心功能:在图像中精确定位并标记所有文本区域的位置,通常用边界框(Bounding Boxes) 圈出。

主要特点:该模型属于tiny(微型)系列,专为端侧和IoT场景设计,追求极致轻量和高速度。模型参数量仅43万(0.43M),大小约1.9 MB。

应用场景:擅长处理手写、印刷、旋转、弯曲和艺术字体等多种复杂场景的文字。

PP-OCRv6_tiny_rec_onnx:文本识别模型核心功能:接收检测模型给出的文本区域图像,将其中的视觉信息转化为计算机可以编辑和搜索的电子文本。

主要特点:同样作为tiny系列,它是PP-OCRv6中最轻量的识别模型。模型参数量为110万(1.1M),大小约4.4 MB,并支持49种语言的识别。

应用场景:能高效识别简体中文、繁体中文、英文、日文等,并能处理手写、竖排、拼音、生僻字等复杂文本。

bash 复制代码
static/
  models/
    PP-OCRv6_det_tiny.onnx
    PP-OCRv6_rec_tiny.onnx

3. OCR 流水线

OCR 不是单一模型,是文本检测加文本识别两个模型串联。

我选的是 PP-OCRv6,百度最新的开源 OCR 模型,有 tiny、small、medium 三个尺寸。

bash 复制代码
上传图片
  → 缩放至 960 以内(32 倍数)
  → 检测模型推理,得到概率图
  → 后处理:二值化 → 连通域 → 文本框
  → 对每个文本框裁剪
  → 缩放至 48xW
  → 识别模型推理
  → CTC 解码 → 得到文字

tiny 版本检测模型 1.74 MB,识别模型 4.28 MB,加起来 6 MB 多,浏览器加载很快。

4. 图像预处理

检测模型需要特定尺寸的输入:长边不超过 960,且必须是 32 的倍数。

为什么 32?因为模型内部有步长为 32 的下采样层,输入尺寸不是 32 的倍数会导致输出截断。

预处理三件事:

参数来自官方模型配置,不是随便定的。

5. 文本检测:DBNet 后处理

检测模型输出一张概率图,每个像素表示"这里是文字"的概率。

后处理分三步:

二值化:像素值大于 0.2 标为文字,否则标为背景。

连通域标记:用 BFS 把相邻的白点连成一片。每个连通域就是一个文字区域。

unclip 外扩 :模型输出的边界通常比真实文字略小,按 面积 × 系数 / 周长 的比例向外扩张,系数取 1.4。

过滤掉太小的区域(短边小于 3 像素)和置信度太低的区域。

按 y 坐标排序,保证阅读顺序。

6. 文本识别:CRNN + CTC 解码

每个文本框裁剪出来,缩放到 48 像素高,宽度按比例保持。

识别模型输出 [1, T, C],T 是时间步数,C 是字符类别数(6906)。

用 CTC 贪心解码把概率变成文字:

每个时间步取概率最大的索引,去掉空白符(索引 0),合并连续重复的索引。映射到字符集就是文字。

举个例子:输出 [15, 15, 15, 0, 0, 23, 23, 0, 5] → 合并去重 → [15, 23, 5] → 字符集映射 → "你好"。

7. 踩坑:识别出乱码

一开始跑测试,结果是这样:

bash 复制代码
#1 沼桷轻哔茸藩舅爪锵喇
#2 ,辜唤
#3 沼桷轻哔暖敬茸藩舅爪锵喇梓备字蹿心航瞠郊

全乱码。

问题在字符集。ONNX 模型输出 6906 维,但我之前下载的字典只有 6622 字符。

argmax 算出来的索引如果大于 6622,就映射不到字符,全变成

解决方案:直接从 ONNX 模型元数据里提取字符集。

PP-OCRv6 的 ONNX 模型在内部嵌入了字符列表。我写了个 Python 脚本解析 protobuf 元数据,用 varint 解码读取字段长度,提取出 6904 个字符。

加上索引 0(blank)和最后一个(space),正好对应模型的 6906 维输出。

保存为 JSON 文件,前端加载时拼接成字符数组。

8. 踩坑:softmax 算出 NaN

加上置信度计算后,又出新问题:

bash 复制代码
#1  conf NaN%  undefined字

原因是模型输出含 NaN 值,Math.exp(NaN) 在整行 softmax 里传播扩散。

加了防护:argmax 时跳过 !isFinite(v) 的值;softmax 时跳过 diff < -50!isFinite(diff) 的值。整行都不可用时跳过该时间步。置信度用 Math.max(0.001, Math.min(p, 0.999)) 兜底。

9. 踩坑:softmax 阈值过滤掉所有结果

加上置信度过滤后,结果又全没了:

bash 复制代码
⚠ 未检测到文本

原因是 softmax 在 6906 维时,单字符概率很小,最高也就 0.05 左右。我设的 0.3 阈值把全部结果都过滤了。

改成 0 不过滤,等看到实际分布再调。

10. 构建完整应用

把检测和识别串起来,再加个 UI 就是完整应用。

页面布局:左侧拖拽上传区加 Canvas 画布,右侧显示模型状态、进度条、识别结果。

识别结果按行显示,每行带序号、置信度百分比、识别文字。置信度高的绿色,中等的黄色,低的不显示。

画布上用彩色矩形框标注每个文本框的位置,上方显示文字和置信度。

11. 踩坑:small 模型架构完全不同

我想换更大的 small 模型试试。结果发现:

| 对比项 | tiny | small | | :-- | :-- | :-- | | Det 输出 | 1,3,960,960 DBNet 三通道 | 1,1,640,640 单通道二值图 | | Rec 词表 | 6906(6904 字符) | 18710(18708 字符) | | 检测后处理 | DBNet 阈值 + unclip | 轮廓检测 | | 输入尺寸 | 1,3,960,960 | 1,3,640,640 |

small 模型换了检测头架构,不再是 DBNet,是输出二值图后用轮廓法找文字区域。前端后处理代码需要重写。

tiny 已经够用,small 改天再研究。

11. 完整项目结构

bash 复制代码
demo/2/
├── package.json
├── server.ts              # Bun 静态服务器
└── static/
    ├── index.html         # OCR 应用
    ├── ppocr_keys_v6_tiny.json  # 6904 字符集
    ├── ppocr_keys_v6_tiny.txt   # 字符集(文本格式)
    └── models/
        ├── PP-OCRv6_det_tiny.onnx   # 检测模型 1.74MB
        └── PP-OCRv6_rec_tiny.onnx   # 识别模型 4.28MB

启动:

bash 复制代码
cd demo/2
bun install
bun run dev

打开 http://localhost:3001 就能用。

13. 完整代码

公众号: 半刻纬度, 发送关键词"ocr"即可得到完整代码下载链接

相关推荐
程序员-Benothing1 小时前
Java集合:List实现类深度对比
java·开发语言·后端·面试·list
Web极客码1 小时前
如何在WordPress中添加动态内容
服务器·前端·wordpress
云浪1 小时前
给 AI Agent 加上语音交互:ASR + 流式 TTS 实战
前端·人工智能·后端
卷无止境1 小时前
当代码要上线前,谁在替你把关?——Python安全审查的方法论与实践
后端·python
晓得迷路了2 小时前
栗子前端技术周刊第 140 期 - pnpm、Node 新 API 文档网站、Oxlint...
前端·javascript·node.js
卷无止境2 小时前
Python Dataclasses:让类定义回归简洁的艺术
后端·python
weixin_3823952311 小时前
为小工厂量身打造:本地部署的物料管理系统带缺料计算
前端·制造
__zRainy__11 小时前
解决pnpm v10+不自动构建
前端·pnpm·工程化