OCR文字识别封面

做笔记应用的时候,经常有用户提需求:能不能拍一张票据或者截图,直接把上面的文字识别出来,不用手动敲?这个需求听起来简单,真做起来才发现,鸿蒙的 OCR 能力虽然开箱即用,但要做到识别准、不卡顿、异常兜底到位,还是有不少细节要抠的。

这篇是端侧 AI 视觉系列的第一篇,就从最基础的文字识别开始讲。我们做的是一个笔记类应用,需要从用户拍的图片里提取文字,自动填充到笔记里。从最开始调 API 的 Demo 能跑,到最后上线前调优,中间踩的坑都记下来了。

一、真实开发中遇到的问题

最开始用官方 OCR API 写了个 Demo,传一张清晰的图片进去,返回识别结果,看起来挺简单的。但真放到项目里用,问题就来了:

第一,识别准确率不够稳定。拍一张发票,有的角度识别出来全是乱码;光线暗一点,数字就认错。Demo 里用的是标准测试图,实际用户拍的图五花八门。

第二,识别速度慢。一张高分辨率的手机截图,识别要 2-3 秒,用户以为应用卡了。

第三,结果解析麻烦。API 返回的是按行、按区域的结构化数据,不是直接给你一段文字。怎么把这些区域数据拼成用户能读的文本,还要保持换行和段落,得自己处理。

第四,识别失败怎么处理。图片模糊、文字太小、没有文字,这些情况 API 返回什么?UI 上怎么给用户反馈?

二、这个能力怎么接入

HarmonyOS 7 的 OCR 能力在 TextRecognition Kit 里,端侧运行,不需要联网。整个接入流程分三步:

1. 初始化识别器:调用 TextRecognitionKit 创建识别器实例,指定识别语言(中文/英文/混合)。

2. 准备图片数据:把用户选的图片转成 PixelMap,预处理一下(降噪、二值化可选)。

3. 执行识别并解析结果:调用异步识别接口,拿到结果后解析成结构化文本。

这里要注意:识别器实例不要每次都创建,创建一次复用就行。识别是异步的,要在子线程做,不要阻塞 UI。

OCR识别流程图

三、关键代码怎么写

下面是我们实际封装的 OCR 工具类,文件位置在 entry/src/main/ets/utils/OcrUtil.ets

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

export class OcrUtil {
  private static recognizer: textRecognition.TextRecognition | null = null;

  // 初始化识别器(中文+英文混合)
  static async initRecognizer(): Promise<void> {
    if (!this.recognizer) {
      this.recognizer = await textRecognition.TextRecognition.createInstance(
        textRecognition.RecognitionMode.FREE_FORM,
        'zh-CN'
      );
    }
  }

  // 从图片中提取文字
  static async extractTextFromImage(
    pixelMap: image.PixelMap
  ): Promise<OcrResult> {
    if (!this.recognizer) {
      await this.initRecognizer();
    }

    try {
      // 执行识别
      const result = await this.recognizer!.recognize(pixelMap);
      
      // 解析识别结果
      return this.parseResult(result);
    } catch (e) {
      console.error('OCR failed: ' + e.message);
      return {
        success: false,
        text: '',
        errorMsg: '文字识别失败,请换一张清晰的图片试试'
      };
    }
  }

  // 解析识别结果
  private static parseResult(
    result: textRecognition.RecognitionResult
  ): OcrResult {
    if (!result.blocks || result.blocks.length === 0) {
      return {
        success: true,
        text: '',
        errorMsg: ''
      };
    }

    // 按 block 拼接文本,保持段落结构
    let fullText = '';
    for (const block of result.blocks) {
      for (const line of block.lines) {
        fullText += line.text + '\n';
      }
      fullText += '\n'; // 段落间空一行
    }

    return {
      success: true,
      text: fullText.trim(),
      errorMsg: ''
    };
  }
}

// 识别结果类型
interface OcrResult {
  success: boolean;
  text: string;
  errorMsg: string;
}

这段代码的核心:recognize 方法是异步的,返回的 RecognitionResult 里有 blocks → lines → words 三层结构。我们按行拼接,段落间加换行,还原原始排版。

调用侧在页面里怎么用?文件位置 pages/OcrPage.ets

@Entry
@Component
struct OcrPage {
  @State extractedText: string = '';
  @State isRecognizing: boolean = false;
  @State errorMsg: string = '';

  // 选择图片并识别
  async onImageSelected(pixelMap: image.PixelMap) {
    this.isRecognizing = true;
    this.errorMsg = '';
    this.extractedText = '';

    const result = await OcrUtil.extractTextFromImage(pixelMap);
    
    this.isRecognizing = false;
    
    if (result.success) {
      this.extractedText = result.text;
      if (!result.text) {
        this.errorMsg = '这张图片里没有识别到文字';
      }
    } else {
      this.errorMsg = result.errorMsg;
    }
  }

  build() {
    Column({ space: 16 }) {
      // 图片预览
      Image(this.imagePath)
        .width('100%')
        .height(200)
      
      // 识别结果
      if (this.isRecognizing) {
        LoadingProgress().width(40).height(40)
        Text('正在识别文字...')
      } else if (this.errorMsg) {
        Text(this.errorMsg).fontColor(Color.Red)
      } else {
        Text(this.extractedText)
          .fontSize(14)
          .width('100%')
      }
    }
    .padding(16)
  }
}

OCR识别效果对比

四、运行过程中怎么处理异常

OCR 的异常场景比想象中多,我们处理了这几种:

1. 图片里没有文字:blocks 为空数组。这时候不算错误,UI 上提示"未识别到文字"就行。

2. 图片模糊/光线暗:识别出来全是乱码或者空。这种情况 API 一般不会报错,结果质量差。我们在 UI 上给个"识别效果不好?"的提示,让用户重新拍。

3. 图片太大:4000×3000 的原图识别很慢。我们在识别前先把图片缩放到合适尺寸(最长边不超过 2000px),识别速度提升了一倍,准确率几乎没影响。

4. 识别器初始化失败:极少数情况下设备不支持 OCR,或者模型加载失败。要 catch 住初始化异常,降级提示用户"当前设备不支持文字识别"。

5. 内存问题:连续识别多张图片,PixelMap 没有及时释放,内存会涨。识别完记得调用 pixelMap.release() 释放资源。

五、实际开发中容易忽略的问题

1. 识别语言要选对:zh-CN 是中英混合,如果只选英文,中文识别率会暴跌。根据用户场景选对语言模型。

2. 结果排版不要简单拼接:直接把所有文字连起来是不行的。要按 blocks 和 lines 的结构来拼,保持原有的段落和换行,用户体验才好。

3. 票据识别有专门模式:发票、营业执照这类有固定版式的图片,用专门的版式识别模式比自由模式准。不过我们暂时用自由模式够了。

4. 识别是端侧的:HarmonyOS 7 的 OCR 完全在本地跑,不上传图片。这个对用户隐私很重要,写在隐私说明里。

5. 预热识别器:第一次创建识别器要加载模型,有几百毫秒延迟。可以在应用启动的时候后台预热,用户点识别的时候就快了。

文字识别是视觉能力的基础。后面几篇会在这个基础上,做图片超分、文搜图、人脸检测这些更复杂的能力。OCR 这个点看起来小,但真要做到用户觉得"好用",细节还是很多的。

Logo

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

更多推荐