更新记录

1.0.1(2026-07-10)

-优化鸿蒙异步执行不阻塞UI,优化性能

1.0.0(2026-07-10)


平台兼容性

uni-app(4.18)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
× × 5.0 4.18
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
- - - - - - - - - - - -

uni-app x(4.18)

Chrome Safari Android iOS 鸿蒙 微信小程序
- - - - - -

其他

多语言 暗黑模式 宽屏模式
×

ly028-OCR 离线文字识别插件

扫码下载体验
📱 下载体验 APK

基于 PaddleOCR(PP-OCR) 的离线文字识别 UTS 原生插件,Android / iOS / HarmonyOS Next 三端完美支持,导出函数完全一致,一套代码三端运行。全离线运行,无需网络连接,数据不出设备。

特性

  • 🔒 全离线 — 本地推理,无需联网,图片数据不出设备
  • 📱 三端完美一致 — Android / iOS / HarmonyOS Next 导出 API 完全相同,调用端无平台差异
  • 🔤 通用文字识别 — 支持中英文混合识别,自动方向检测(0°/180°)
  • 🆔 身份证识别 — 正面(姓名/性别/民族/出生/住址/身份证号)+ 背面(签发机关/有效期限),自动正反面判定,五策略回退解析
  • 🚗 车牌识别 — 支持多车牌同时检测,涵盖蓝牌/新能源/黄牌/白牌/黑牌/港澳/教练/警用/军牌/使馆/领事,IoU 去重,O/0 I/1 混淆修复
  • 📄 驾驶证识别 — 准驾车型/姓名/性别/国籍/住址/证号/档案编号/有效期等 11 个字段结构化提取
  • 🧩 模型热切换 — 支持多模型注册、切换、动态下载,无需重新打包
  • 🧪 图片质量预检 — 亮度/模糊/文字边缘/方向检测,识别前即可给出建议
  • 异步非阻塞 — 所有耗时操作在后台线程执行,绝不阻塞 UI
  • 🌐 多语言 — 内置国际化支持

平台兼容

平台 最低版本 架构
Android 7.0 (API 21) arm64-v8a, armeabi-v7a
iOS 12.0 arm64
HarmonyOS Next API 9+ arm64-v8a

集成与调试

HBuilderX 自定义基座运行

  1. 插件市场购买插件后,导入到项目
  2. 自定义调试基座:菜单栏 → 运行 → 运行到手机或模拟器 → 制作自定义调试基座
  3. 制作完成后,选择自定义基座运行到真机即可调试所有功能

首次使用请确保已关联 uni-app 开发者证书,自定义基座有效期为 7 天,过期后需重新制作。

离线打包

插件为原生代码插件,离线打包需自行集成原生依赖。

模型资源准备

方式一:内置到应用(推荐)

将 OCR 模型文件放置到 static/models/ 目录下(可自定义目录名),模型包目录结构如下:

static/models/
└── tiny/                         # 模型名称目录(可自定义,如 small、std 等)
    ├── det/
    │   └── *.onnx                 # 文本检测模型(必选,任意 .onnx 文件名均可)
    ├── rec/
    │   └── *.onnx                 # 文字识别模型(必选,任意 .onnx 文件名均可)
    ├── cls/
    │   └── *.onnx                 # 方向分类模型(可选,建议包含,任意 .onnx 文件名均可)
    └── *keys*.txt / *dict*        # 字典文件(必选,引擎自动识别含 keys 的 .txt 或含 dict 的文件)

初始化时插件自动定位模型路径,无需手动配置。如需自定义目录名,在 initEngineAsyncoptions 中传入 modelAssetDir: "your-dir-name" 即可。

方式二:应用启动后动态下载

插件内置模型下载功能,支持从远程 URL 下载 ZIP 包并自动解压注册(参见"模型下载"章节)。

方式三:手动放置到设备存储

通过 getModelStoragePathsAsync 获取设备上的推荐模型存放路径,将模型包手动复制到该目录后调用 scanModelDirectoryAsync / autoDiscoverModelsAsync 扫描注册。

快速开始

import * as OCR from '@/uni_modules/ly028-OCR'

// 1. 初始化 OCR 引擎
OCR.initEngineAsync({
  modelAssetDir: 'models',     // static/models/ 目录(可自定义)
  threadNum: 4,                // 推理线程数
}, (res) => {
  const result = JSON.parse(res)
  if (result.code === 0) {
    console.log('OCR 引擎初始化成功')
  } else {
    console.error('初始化失败:', result.msg)
  }
})

// 2. 通用文字识别(从文件路径)
OCR.runOcrFileAsync('/path/to/image.jpg', (res) => {
  const result = JSON.parse(res)
  if (result.code === 0) {
    const data = result.data
    console.log('识别结果:', data.text)
    console.log('文本框数:', data.boxes.length)
    console.log('耗时:', data.elapsedMs, 'ms')
  }
})

// 3. 释放引擎
OCR.releaseEngineAsync((res) => {
  console.log('引擎已释放')
})

全量 API 参考

响应信封格式

所有 API 通过 callback 返回 JSON 字符串,前端 JSON.parse() 后检查 code

// 成功
{"code": 0, "data": {...}}

// 失败
{"code": 1, "subCode": 400, "msg": "错误描述"}

状态码说明:

subCode 说明
400 请求参数错误
404 资源不存在
422 引擎未初始化 / 解码失败
500 内部错误
501 功能未实现
503 服务不可用(引擎忙)

🔧 引擎生命周期

API 说明
initEngineAsync(options, callback) 初始化 OCR 引擎
releaseEngineAsync(callback) 释放引擎资源
releaseOCRAsync(callback) releaseEngineAsync 的别名(统一 API)

initEngineAsync — OcrOptions 参数

type OcrOptions = {
  modelDir?: string              // 模型目录绝对路径(不常用,一般用 modelAssetDir)
  modelAssetDir?: string | null  // static/ 下的模型目录名,默认 "models"
  threadNum?: number             // 推理线程数,默认 4
  language?: string              // 语言

  // — 检测调优参数 —
  boxThresh?: number            // det_db_box_thresh (0.0-1.0, 默认 0.3, 越低框越多)
  detThresh?: number            // det_db_thresh (0.0-1.0, 默认 0.3, 二值化阈值)
  unclipRatio?: number          // det_db_unclip_ratio (默认 2.0, 框扩展系数)
  scoreMode?: string            // det_db_score_mode: "slow"(精确) | "fast"(快速)
  maxCandidates?: number        // 最大输出框数 (默认 1000)
  enableCls?: boolean           // 是否启用方向分类 (默认 true)
  enableRec?: boolean           // 是否启用文字识别 (默认 true)

  // — 图像预处理调优(漏检修复) —
  minContourArea?: number       // 输出图最小轮廓面积 (默认 2)
  minBoxSize?: number           // 原图最小框宽高 (默认 3)
  limitSideLen?: number         // 检测缩放最大边长 (默认 1280)
  claheClipLimit?: number       // CLAHE 对比度限制 (默认 2.5)
  claheTileSize?: number        // CLAHE 分块大小 (默认 8)
  enhanceBrightnessThr?: number // CLAHE 亮度阈值 (默认 95)
  enableEnhance?: boolean       // 图像增强总开关 (默认 true)
}

示例: 调高检测灵敏度(适合小文字、密集文字)

OCR.initEngineAsync({
  boxThresh: 0.2,          // 降低检测阈值 → 更多候选框
  detThresh: 0.2,
  unclipRatio: 2.5,        // 更大扩展系数
  limitSideLen: 960,       // 缩小检测图像 → 小文字更好检
  enableEnhance: true,
}, callback)

🔤 通用文字识别

API 说明
runOcrFileAsync(filePath, callback) 从文件路径识别
runDetectOnlyFileAsync(filePath, callback) 仅文本检测(不识别文字,文件版)
generalOCRAsync(imagePath, options?, callback?) 通用 OCR(统一 API 别名)
precheckFileAsync(filePath, callback) 图片质量预检

runOcrFileAsync — 文件路径识别

适用于从相册选择或已有图片文件:

OCR.runOcrFileAsync('/path/to/image.jpg', (res) => {
  const result = JSON.parse(res)
  if (result.code === 0) {
    const data = result.data
    // data.text        — 全文拼接文本
    // data.boxes       — 文本框数组
    // data.elapsedMs   — 耗时(毫秒)
    // data.imageWidth  — 图片宽度(前端坐标映射用)
    // data.imageHeight — 图片高度
  }
})

runDetectOnlyFileAsync — 仅文本检测

只检测文本区域位置,不执行文字识别(速度更快),适合需要自定义识别的场景:

OCR.runDetectOnlyFileAsync('/path/to/image.jpg', (res) => {
  const result = JSON.parse(res)
  if (result.code === 0) {
    // result.data.boxes — 只含位置信息的检测框
  }
})

precheckFileAsync — 图片质量预检

在 OCR 识别前进行质量评估,避免无效识别:

OCR.precheckFileAsync('/path/to/image.jpg', (res) => {
  const result = JSON.parse(res)
  if (result.code === 0) {
    const q = result.data
    // q.brightness:    number    — 亮度均值 (0-255)
    // q.blurScore:     number    — Laplacian 方差(>200 清晰)
    // q.textEdgeRatio: number    — Canny 边缘占比(>0.05 有文字)
    // q.isHorizontal:  boolean   — 是否横拍
    // q.advice:        string    — 建议文案(空=质量合格)
    console.log('质量建议:', q.advice)
  }
})

generalOCRAsync — 统一 API 别名

runOcrFileAsync 的别名,更简洁的调用签名:

OCR.generalOCRAsync('/path/to/image.jpg', (res) => {
  const result = JSON.parse(res)
  console.log('识别结果:', result.data?.text)
})

🆔 身份证识别

API 说明
idCardOCRAsync(imagePath, callback) 身份证识别(文件路径版)

身份证识别结果已自动完成结构化解析,返回的 fields 对象包含所有字段,side 为自动判定的正反面。

身份证识别结果结构:

{
  "code": 0,
  "data": {
    "fields": {
      "name": "刘永",
      "gender": "男",
      "ethnicity": "汉",
      "birthDate": "1990年01月01日",
      "address": "湖南省长沙市岳麓区...",
      "idNumber": "43010419900101001X",
      "issuedBy": "长沙市公安局",
      "validDate": "2017.08.01 至 2037.08.01"
    },
    "side": "front",
    "sideConfidence": 0.95,
    "confidence": 1.0,
    "elapsedMs": 0
  }
}

字段说明:

字段 说明
name 姓名
gender 性别(男/女)
ethnicity 民族
birthDate 出生日期(YYYY年MM月DD日 标准化格式)
address 住址(跨框自动合并)
idNumber 身份证号(18 位,含校验位自动验证)
issuedBy 签发机关(背面提取)
validDate 有效期限(背面提取)

自动正反面判定:

解析器通过关键词加权评分自动判定是正面还是背面:

权重关键词
正面 姓名(5)、公民身份号码(5)、性别(4)、民族(4)、出生(3)、住址(3)
背面 签发(5)、有效(5)、机关(3)、期限(3)、长期(2)

五策略回退解析:

身份证解析器采用五策略逐级回退,保证在 OCR 检测不完美时仍能尽可能提取到正确字段:

  1. 策略 1:单框内正则提取(如 "姓名刘永""刘永"
  2. 策略 2:空间邻近搜索 — 按框坐标找最近的右侧/下方非标签框(含置信度加权评分)
  3. 策略 3:顺序下一框提取(如 ["姓名", "刘永"]
  4. 策略 4:跨行搜索
  5. 策略 5:全文兜底扫描 + 孤立值推断(民族关键字、地址关键字等)

额外特性:

  • 地址跨框合并:遇到标签后合并后续非标签框直到遇到新字段,支持换行续行
  • 身份证号多框拼接:检测一行中多个纯数字框自动拼接
  • 日期格式标准化:各种格式 → YYYY年MM月DD日
  • OCR 混淆修正:O/0I/1 等常见误识别修复
  • 校验位自动验证 + 性别从身份证号推断 + 出生日期交叉校验

🚗 车牌识别

API 说明
plateOCRAsync(imagePath, callback) 车牌识别(文件路径版)

车牌识别结果已自动完成结构化解析,返回结果中包含车牌号、颜色、类型及置信度。

快速示例:

OCR.plateOCRAsync('/path/to/car.jpg', (res) => {
  const result = JSON.parse(res)
  if (result.code === 0) {
    const data = result.data
    console.log('车牌号:', data.plateNumber)
    console.log('颜色:', data.plateColor)
    console.log('类型:', data.plateType)
    console.log('置信度:', data.confidence)
  }
})

单条结果结构:

{
  "code": 0,
  "data": {
    "plateNumber": "京A12345",
    "plateColor": "蓝牌",
    "plateType": "蓝牌",
    "confidence": 0.98,
    "elapsedMs": 0
  }
}

多条结果结构(多车牌场景):

{
  "code": 0,
  "data": {
    "plates": [
      {
        "plateNumber": "京A12345",
        "plateColor": "蓝牌",
        "plateType": "蓝牌",
        "confidence": 0.98,
        "sourceConfidence": 0.98,
        "box": [100, 200, 300, 400],
        "rawText": "京A12345"
      },
      {
        "plateNumber": "沪B67890",
        "plateColor": "绿牌",
        "plateType": "新能源",
        "confidence": 0.92,
        "sourceConfidence": 0.92,
        "box": [400, 500, 600, 700],
        "rawText": "沪B67890"
      }
    ],
    "count": 2,
    "elapsedMs": 0
  }
}

车牌类型自动判定:

类型 判定规则 颜色
新能源 8 位,第 2 位为 D/F 绿牌
蓝牌 7 位,第 2 位为字母 蓝牌
黄牌 7 位 黄牌
港澳 末位为"港"/"澳" 黑牌
使馆 以"使"开头 黑牌
教练 末位为"学" 黄牌
警用 末位为"警" 白牌
武警 以 WJ 开头 白牌
军牌 特定字母开头 白牌

OCR 混淆自动修复:

车牌解析器内置常用 OCR 混淆映射:

字符位置 混淆映射
第 2 位(省份字母) 0→O, 1→I, 2→Z
后续位(数字字母) O→0, I/ l→1, Z→2, S→5, B→8, G→6

📄 驾驶证识别

驾驶证识别结果已自动完成结构化解析,返回 11 个字段。解析算法与身份证共享相同的五策略回退架构,对 OCR 检测不完美场景有良好的容错性。

结构化字段:

字段 说明
licenseType 准驾车型(如 C1、A2)
name 姓名
gender 性别
nationality 国籍
birthDate 出生日期
address 住址(跨框合并)
licenseNumber 驾驶证号(18 位)
archiveNumber 档案编号(12 位数字)
issueDate 初次领证日期
validFrom 有效起始日期
validTo 有效截止日期(支持"长期")

📦 模型管理

ly028-OCR 支持多模型注册、切换和动态下载,可在不更新应用的情况下切换不同精度的 OCR 模型。

模型扫描与发现

API 说明
scanModelDirectoryAsync(path, callback) 扫描目录发现所有模型包
autoDiscoverModelsAsync(path, callback) 扫描并自动注册所有发现的模型
registerModelAsync(configJson, callback) 手动注册自定义模型
switchModelAsync(modelId, callback) 切换到指定模型(需重新 initEngine)
removeModelAsync(modelId, callback) 注销模型(default 模型不可移除)
getModelListAsync(callback) 获取所有已注册模型列表
getModelStatusAsync(modelId, callback) 获取单个模型状态
getCurrentModelAsync(callback) 获取当前模型 ID
getModelStoragePathsAsync(callback) 获取模型存储路径

模型发现与使用完整示例

// 1. 获取设备推荐存储路径
OCR.getModelStoragePathsAsync((res) => {
  const result = JSON.parse(res)
  console.log('模型存放目录:', result.data.modelStorageDir)
  console.log('下载目录:', result.data.downloadDir)
})

// 2. 扫描并自动注册
OCR.autoDiscoverModelsAsync('/sdcard/OCRModels/', (res) => {
  const result = JSON.parse(res)
  console.log('已注册模型数:', result.data.registered)
})

// 3. 查看已注册模型
OCR.getModelListAsync((res) => {
  const result = JSON.parse(res)
  // result.code === 0 ? result.data 为已注册模型数组
})

// 4. 切换到特定模型(然后重新 initEngine)
OCR.switchModelAsync('my-custom-model', (res) => {
  if (JSON.parse(res).code === 0) {
    OCR.initEngineAsync({ ... }, callback)
  }
})

模型注册配置(registerModelAsync 的 configJson)

{
  "modelId": "my-model",
  "modelDir": "/path/to/model/dir",
  "name": "高精度模型",
  "version": "1.0",
  "detModel": "det/inference.onnx",
  "recModel": "rec/inference.onnx",
  "clsModel": "cls/inference.onnx",
  "recVocab": "rec/vocab.txt",
  "boxThresh": 0.3,
  "detThresh": 0.3,
  "unclipRatio": 2.0,
  "threadNum": 4,
  "enableCls": true,
  "enableRec": true
}

⬇️ 模型下载

API 说明
downloadModelZipAsync(url, modelName, callback) 下载模型 ZIP 包并自动解压注册
onDownloadProgress(callback) 注册下载进度监听
offDownloadProgress(callback) 移除下载进度监听

示例

// 注册进度监听
OCR.onDownloadProgress((json) => {
  const progress = JSON.parse(json)
  // progress.name    — 模型名称
  // progress.pct     — 0-100
  // progress.status  — "downloading" | "unzipping" | "done" | "error"
  // progress.msg     — 状态描述
  console.log(`下载进度 ${progress.name}: ${progress.pct}%`)
})

// 开始下载
OCR.downloadModelZipAsync(
  'https://example.com/models/small-model.zip',
  'small',
  (res) => {
    const result = JSON.parse(res)
    if (result.code === 0) {
      console.log('模型下载并注册成功:', result.data.modelId)
    }
  }
)

// 不再需要时移除监听
OCR.offDownloadProgress(myHandler)

进度事件格式:

{
  "name": "small",
  "pct": 67,
  "status": "downloading",
  "downloaded": 8388608,
  "total": 12582912,
  "msg": "正在下载..."
}

完整返回值格式

通用 OCR 结果(OcrResult)

{
  "code": 0,
  "data": {
    "text": "识别出的全文",
    "boxes": [
      {
        "points": [{"x": 10, "y": 20}, {"x": 100, "y": 20}, {"x": 100, "y": 50}, {"x": 10, "y": 50}],
        "text": "单行文字",
        "confidence": 0.98,
        "textConfidence": 0.95
      }
    ],
    "elapsedMs": 123,
    "imageWidth": 1920,
    "imageHeight": 1080
  }
}

处理复杂框坐标(前端):

// OCR 引擎返回两种兼容的框坐标格式方便前端处理:
// 1. OcrTextBox: points = [{x,y}, {x,y}, {x,y}, {x,y}](四角坐标)
// 2. OcrBox:     box    = [xmin, ymin, xmax, ymax](轴对齐框)
//              points = [x0,y0, x1,y1, x2,y2, x3,y3](旋转框 8 个值)

// 简单场景(无需旋转校正)直接用 box 坐标:
// const rect = { left: box[0], top: box[1], width: box[2]-box[0], height: box[3]-box[1] }

检测框类型定义

// OcrTextBox(通用 OCR 结果中的框格式)
type OcrTextBox = {
  points: OcrPoint[]     // [{x,y}, {x,y}, {x,y}, {x,y}]
  text: string
  confidence: number
  textConfidence: number
}

// OcrBox(统一 API 的扁平格式,用于 JSON 序列化/反序列化)
type OcrBox = {
  text: string
  box: number[]         // [xmin, ymin, xmax, ymax]
  points?: number[]     // [x0,y0,x1,y1,x2,y2,x3,y3](旋转框 8 个值)
  confidence: number
  textConfidence?: number
  angle?: number        // 0 或 180(方向分类结果)
  fieldType?: string    // 字段类型(idcard 模式时标记)
}

图片质量预检结果

{
  "code": 0,
  "data": {
    "brightness": 180,
    "blurScore": 350,
    "textEdgeRatio": 0.15,
    "isHorizontal": true,
    "advice": ""
  }
}

预检建议文案对照:

advice 说明
"" (空字符串) 质量合格,可直接进行 OCR
"图片过暗,建议调整亮度" brightness < 60
"图片过亮,建议减少曝光" brightness > 200
"图片模糊,建议保持稳定拍摄" blurScore < 200
"图片中未检测到文字,请确认内容" textEdgeRatio < 0.05
"请横屏拍摄" 非横拍(文字识别推荐横拍)

类型定义速查

所有类型均在 interface.utsexport type,在 TypeScript 中可直接引用:

import type {
  OcrResult, OcrTextBox, OcrBox, OcrPoint, OcrRect,
  IdCardFields, IdCardResult, IdCardOptions,
  PlateResult, PlateInfo, MultiPlateResult, PlateOptions,
  PrecheckQuality,
  ModelConfig, DiscoveredModel, ModelStoragePaths,
  DownloadableModel, DownloadProgress,
  OcrOptions, OcrCallback, ResponseEnvelope
} from '@/uni_modules/ly028-OCR'

使用模式参考

模式一:从相册选图识别

uni.chooseImage({
  count: 1,
  sizeType: ['compressed'],
  success: (res) => {
    const filePath = res.tempFilePaths[0]
    OCR.precheckFileAsync(filePath, (precheck) => {
      const pq = JSON.parse(precheck)
      if (pq.data?.advice) {
        console.warn('质量警告:', pq.data.advice)
      }
      OCR.generalOCRAsync(filePath, (ocrRes) => {
        const result = JSON.parse(ocrRes)
        if (result.code === 0) {
          this.ocrText = result.data.text
        }
      })
    })
  }
})

模式二:仅检测文本位置(不识别文字)

适用于需要自定义识别的场景(例如对检测到的文字区域做额外处理):

OCR.runDetectOnlyFileAsync('/path/to/image.jpg', (res) => {
  const result = JSON.parse(res)
  if (result.code === 0) {
    const boxes = result.data.boxes  // 只含位置信息
    for (const box of boxes) {
      console.log('文字区域坐标:', box.points)
    }
  }
})

注意事项

  1. 引擎生命周期:引擎只能初始化一次,releaseEngineAsync 后可再次初始化
  2. Model 文件:模型需放在 static/models/<模型名称>/ 目录下(如 static/models/tiny/),模型目录内需包含检测、识别模型及字典文件,以及可选的方向分类模型
  3. 必选模型文件清单:
文件 用途 说明
det/*.onnx 文本检测模型 ✅ 必选,C++ 引擎自动识别子目录下任意 .onnx 文件
rec/*.onnx 文字识别模型 ✅ 必选,同上
*keys*.txt*dict* 字典文件 ✅ 必选,引擎自动识别含 keys.txt 文件或含 dict 的文件
cls/*.onnx 方向分类模型 ❌ 可选,工程推荐包含
  1. 异步回调:所有 API 均为异步+回调模式,在后台线程执行,不阻塞 UI
  2. 并发限制:内部有互斥锁,同一时间只允许一次识别操作
  3. 图片尺寸:建议图片最长边不超过 1280px(可通过 limitSideLen 调整),超大图片自动缩放
  4. 结果解析:身份证和车牌的结构化解析由插件内置算法自动完成,OCR 引擎本身只做纯文字识别,返回结果中已包含结构化字段
  5. 图像增强:默认启用 CLAHE 图像增强(enableEnhance: true),对低对比度、背光场景有显著改善,如不需要可关闭以提升速度
  6. 模型热切换switchModelAsync 切换后必须重新 initEngineAsync 使配置生效
  7. 三端一致:Android / iOS / HarmonyOS Next 的导出函数名和参数签名完全一致,调用代码无需平台判断

隐私声明

  • 本插件不采集任何用户数据
  • 所有文字识别在设备端离线完成
  • 无需网络权限,不上传任何图像数据

体验

扫码下载体验
📱 下载体验 APK

许可

  • 插件市场购买后获得使用授权
  • 禁止反编译、二次分发

隐私、权限声明

1. 本插件需要申请的系统权限列表:

Android: CAMERA, READ_EXTERNAL_STORAGE(读取相册图片);iOS: 相机/相册(由前端 uni.chooseImage 触发);鸿蒙: 相册读取

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据。所有OCR识别在设备端离线完成,无需网络权限,不上传任何图像数据。

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率: