鸿蒙端侧OCR实战:Core Vision Kit离线文字识别

需求与选型

移动端要做文字识别(OCR),第一反应往往是接云端 API。但在实际项目里,端侧方案越来越值得优先考虑,尤其这三个场景:图片涉及证件、票据等敏感信息,不能出设备;弱网或离线环境(巡检、仓储)必须可用;调用量大、按次计费的云 API 成本压不住。

两条路线对比:

维度云端 OCR API端侧(Core Vision Kit)
网络依赖必须无,离线可用
隐私图片上传到服务端数据不出设备
成本按次计费免费
延迟网络往返,几百 ms 起端侧推理,通常百 ms 级
模型能力强,场景丰富印刷体为主,手写/艺术字是边界

鸿蒙侧的端选方案是 Core Vision Kit 的 textRecognition:模型跑在设备上,支持简体中文、繁体中文、英文、日文、韩文的印刷体识别,无需申请任何权限(OCR 本身不涉及敏感权限),Stage 模型下即可直接调用。本文用"选图 → 识别 → 取词"这个小工具,把完整链路走一遍。

调用范式:三步拿到识别结果

import { textRecognition } from '@kit.CoreVisionKit';
import { image } from '@kit.ImageKit';

let visionInfo: textRecognition.VisionInfo = { pixelMap: pixelMap };
let result: textRecognition.TextRecognitionResult =
    await textRecognition.recognizeText(visionInfo);

范式极简:构造 VisionInfo(包一个 PixelMap)→ 调 recognizeText → 解析结果。但有几个约束要先交代清楚,都是实际会踩的点:

  • 输入只认 PixelMap,格式必须 RGBA_8888。相册、相机、资源文件解出的 PixelMap 默认就是这个格式,一般不用手动转;从网络拿 JPEG/PNG 解码后同样成立。
  • 图片建议不超过 4096×4096。超大图先缩放,速度和精度都更稳。
  • 可以传第二个参数 TextRecognitionConfiguration,目前只有一个开关 isDirectionDetectionSupported(默认 true)。如果图片方向已知,关掉它能省一次方向检测,提速明显:
let config: textRecognition.TextRecognitionConfiguration = {
  isDirectionDetectionSupported: false // 方向已知时跳过检测
};
let result = await textRecognition.recognizeText(visionInfo, config);

结果解析:块/行/字三级树 + 坐标

TextRecognitionResult 是一棵从粗到细的三级树:blocks(文本块)→ lines(文本行)→ words(单词)。每层都同时给文本内容和 cornerPoints 边界框坐标(顺时针四点,第一个点是左上角)。

// 只要全文:拼接块的文本即可
const fullText = result.blocks.map(b => b.value).join('\n');

// 要行级/字级信息(取词、框选、高亮)时下钻
for (const block of result.blocks) {
  for (const line of block.lines) {
    console.info(`行文本: ${line.value}`);       // 行内容
    console.info(`行坐标: ${JSON.stringify(line.cornerPoints)}`);
    for (const word of line.words) {
      // word.value / word.cornerPoints:单词级内容和坐标
    }
  }
}

cornerPoints 是整套 API 里最值钱的部分——它意味着不仅能拿到"图里写了什么",还能拿到"写在哪里"。点击取词、划选翻译、区域高亮、票据字段定位,全都建立在坐标之上。

完整实现:选图取词工具

下面是一个可以直接跑的页面:相册选图 → 转 PixelMap → 识别 → 展示全文,错误路径全部兜住。

import { textRecognition } from '@kit.CoreVisionKit';
import { image } from '@kit.ImageKit';
import { fileIo } from '@kit.CoreFileKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct OcrPage {
  @State resultText: string = '';
  @State loading: boolean = false;

  async pickAndRecognize(): Promise<void> {
    const picker = new photoAccessHelper.PhotoViewPicker();
    const res = await picker.select({
      MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE,
      maxSelectNumber: 1
    });
    const uri = res.photoUris[0];
    if (!uri) return;

    this.loading = true;
    try {
      // 1. URI → PixelMap
      const file = await fileIo.open(uri, fileIo.OpenMode.READ_ONLY);
      const source = image.createImageSource(file.fd);
      const pixelMap = await source.createPixelMap();

      // 2. 端侧识别
      const visionInfo: textRecognition.VisionInfo = { pixelMap };
      const result = await textRecognition.recognizeText(visionInfo);

      // 3. 解析结果
      this.resultText = result.blocks.map(b => b.value).join('\n');
      if (!this.resultText) {
        this.resultText = '未识别到文字,请换一张更清晰的图片';
      }

      pixelMap.release();
      source.release();
    } catch (err) {
      const e = err as BusinessError;
      this.resultText = `识别失败(code=${e.code}),请重试`;
    } finally {
      this.loading = false;
    }
  }

  build() {
    Column({ space: 16 }) {
      Button('选择图片并识别')
        .onClick(() => this.pickAndRecognize())

      if (this.loading) {
        LoadingProgress().width(40).height(40)
      } else {
        Scroll() {
          Text(this.resultText)
            .fontSize(16)
            .copyOption(CopyOptions.InApp) // 识别结果可直接长按复制
        }
        .layoutWeight(1)
        .padding(16)
      }
    }
    .width('100%')
    .height('100%')
    .padding(16)
  }
}

两个工程细节:loading 态让识别期间 UI 有明确反馈(端侧推理虽快,大图也要几百 ms);pixelMap 和 ImageSource 用完即 release(),列表页反复识别的场景漏释放会持续涨内存。

踩坑与调优

错误码要逐个兜。recognizeText 的 Promise 里会抛业务错误,实际遇到最多的是这几个:

  • 200 识别超时——大图或后台负载时偶发,提示重试即可;
  • 401 参数校验失败——九成是 PixelMap 无效或已 release;
  • 1001400001 运行失败——可重试一次;
  • 1001400002 服务异常——引导重启应用。

识别效果差的排查顺序。端侧模型对"清晰、正对、背景简单"的印刷体效果最好,效果差时按这个顺序查:

  1. 图片是否过小或过模糊(先看分辨率,低于几十像素高的文本行基本没戏);
  2. 文字是否倾斜严重——方向检测只管 90°/180° 级别的旋转,不管小角度纠偏;
  3. 背景是否杂乱、文字是否被遮挡。

手写体和艺术字是能力边界,需要这类识别时再考虑云端方案,端侧别硬扛。

小结

Core Vision Kit 的 textRecognition 用极低的集成成本换来了免费、离线、隐私安全的端侧 OCR:一次 Promise 调用,拿到带坐标的三级文本树。它适合印刷体识别的主流场景(票据、名片、截图取词、无网录入),调用范式也是 Core Vision Kit 全家族端侧 AI 能力(人脸检测、卡证识别等)的通用模板,学会这一个,其余能力照葫芦画瓢即可。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐