06百度OCR手写识别接入-鸿蒙PC端Electron开发

欢迎加入开源鸿蒙 PC社区

https://harmonypc.csdn.net/

效果截图

第6篇:百度OCR手写识别接入

系列教程导航

篇号 标题 状态
01 环境搭建与项目创建
02 数据模型与单词仓库
03 主入口页面与导航结构
04 极速划词页面实现
05 手写画布实现
06 百度OCR手写识别接入 本篇
07 答案比对与反馈UI 下一篇
08 单词切换与底部导航
09 词根分解与水印展示
10 项目总结与优化方向

源码仓库https://gitcode.com/qq_33247427/englishProject

一、为什么选择百度 OCR

1.1 华为端侧 OCR 的问题

HarmonyOS 提供了 @kit.CoreVisionKittextRecognition API,理论上可以在设备端完成文字识别,无需网络。但在实际开发中遇到了两个严重问题:

问题 错误信息 原因
服务未初始化 "The service is abnormal" 需要先调用 init()
识别超时 "Run timed out, please try again later" PixelMap 太大,端侧推理超时

即使加了 init() 和图片缩放,在部分真机上仍然不稳定。

1.2 百度手写 OCR 的优势
对比项 华为 CoreVisionKit 百度手写 OCR
网络依赖 无(端侧) 需要网络
稳定性 部分设备超时 稳定可靠
手写识别质量 一般 专门针对手写优化
首次使用 需下载模型 即用
免费额度 无限 每天 500 次(足够开发测试)
响应速度 不稳定 通常 1-2 秒
1.3 百度 OCR 申请步骤
  1. 注册 百度智能云账号
  2. 进入控制台 → 文字识别 → 创建应用
  3. 勾选「手写文字识别」能力
  4. 获取 API KeySecret Key

二、网络权限配置

百度 OCR 需要网络请求,必须在 module.json5 中声明权限:

json5 复制代码
{
  "module": {
    "requestPermissions": [
      { "name": "ohos.permission.INTERNET" }
    ]
  }
}

HarmonyOS 的 INTERNET 权限属于系统授权权限,声明即可使用,不需要动态申请。

三、BaiduOCRService 完整实现

3.1 文件结构

创建 electron/src/main/ets/services/BaiduOCRService.ets

typescript 复制代码
import { image } from '@kit.ImageKit';
import { util } from '@kit.ArkTS';
import { http } from '@kit.NetworkKit';

const API_KEY = 'your_api_key_here';
const SECRET_KEY = 'your_secret_key_here';
let cachedToken: string = '';
3.2 获取 Access Token

百度 API 使用 OAuth 2.0 认证,需要先用 API Key + Secret Key 换取 Access Token:

typescript 复制代码
async function getAccessToken(): Promise<string> {
  // Token 缓存,避免重复请求
  if (cachedToken) {
    return cachedToken;
  }

  const url = `https://aip.baidubce.com/oauth/2.0/token` +
    `?grant_type=client_credentials` +
    `&client_id=${API_KEY}` +
    `&client_secret=${SECRET_KEY}`;

  const req = http.createHttp();
  try {
    const resp = await req.request(url, {
      method: http.RequestMethod.POST
    });
    const data = JSON.parse(resp.result as string) as Record<string, string>;
    cachedToken = data['access_token'];
    console.log('BaiduOCR token 获取成功');
    return cachedToken;
  } finally {
    req.destroy();  // 必须销毁,否则内存泄漏
  }
}

关键点

  • Token 有效期 30 天,缓存后不需要每次都请求
  • http.createHttp() 创建的实例用完必须 destroy()
  • 生产环境应该把 Key 放在服务端,不要硬编码在客户端
3.3 PixelMap 转 Base64

百度 OCR 接受 Base64 编码的图片:

typescript 复制代码
async function pixelMapToBase64(pixelMap: image.PixelMap): Promise<string> {
  const packer = image.createImagePacker();
  try {
    // 将 PixelMap 编码为 JPEG(比 PNG 小很多)
    const buffer = await packer.packing(pixelMap, {
      format: 'image/jpeg',
      quality: 90  // 90% 质量,平衡大小和清晰度
    });

    // ArrayBuffer → Uint8Array → Base64 字符串
    const helper = new util.Base64Helper();
    const uint8 = new Uint8Array(buffer);
    const b64 = helper.encodeToStringSync(uint8);
    return b64;
  } finally {
    packer.release();  // 释放 packer 资源
  }
}

为什么用 JPEG 而不是 PNG?

  • 手写内容是黑白线条,JPEG 90% 质量足够清晰
  • JPEG 文件通常比 PNG 小 3-5 倍
  • 上传更快,百度 API 有请求体大小限制(4MB)
3.4 英文手写识别
typescript 复制代码
export async function baiduOCRRecognize(pixelMap: image.PixelMap): Promise<string> {
  try {
    const base64 = await pixelMapToBase64(pixelMap);
    const token = await getAccessToken();

    // 百度手写文字识别接口
    const url = `https://aip.baidubce.com/rest/2.0/ocr/v1/handwriting?access_token=${token}`;
    const body = `image=${encodeURIComponent(base64)}`;

    const req = http.createHttp();
    try {
      const resp = await req.request(url, {
        method: http.RequestMethod.POST,
        header: { 'Content-Type': 'application/x-www-form-urlencoded' },
        extraData: body
      });

      const result = JSON.parse(resp.result as string) as Record<string, Object>;
      const wordsResult = result['words_result'] as Array<Record<string, string>>;

      if (wordsResult && wordsResult.length > 0) {
        // 清洗结果:只保留英文字母和空格
        const tokens = wordsResult
          .map((w: Record<string, string>) =>
            w['words'].replace(/[^a-zA-Z\s]/g, '').trim().toLowerCase())
          .filter((s: string) => s.length > 0);

        if (tokens.length === 0) return '';

        // 去重(水印和手写可能被重复识别)
        const seen = new Set<string>();
        const unique: string[] = [];
        for (const t of tokens) {
          if (!seen.has(t)) {
            seen.add(t);
            unique.push(t);
          }
        }
        return unique.join(' ').trim();
      }
      return '';
    } finally {
      req.destroy();
    }
  } catch (e) {
    console.error('BaiduOCR 识别失败:', JSON.stringify(e));
    return '';
  }
}
3.5 中文手写识别
typescript 复制代码
export async function baiduOCRRecognizeChinese(pixelMap: image.PixelMap): Promise<string> {
  try {
    const base64 = await pixelMapToBase64(pixelMap);
    const token = await getAccessToken();
    const url = `https://aip.baidubce.com/rest/2.0/ocr/v1/handwriting?access_token=${token}`;
    const body = `image=${encodeURIComponent(base64)}`;

    const req = http.createHttp();
    try {
      const resp = await req.request(url, {
        method: http.RequestMethod.POST,
        header: { 'Content-Type': 'application/x-www-form-urlencoded' },
        extraData: body
      });

      const result = JSON.parse(resp.result as string) as Record<string, Object>;
      const wordsResult = result['words_result'] as Array<Record<string, string>>;

      if (wordsResult && wordsResult.length > 0) {
        // 只保留中文字符
        return wordsResult
          .map((w: Record<string, string>) =>
            w['words'].replace(/[^\u4e00-\u9fa5]/g, '').trim())
          .filter((s: string) => s.length > 0)
          .join('');
      }
      return '';
    } finally {
      req.destroy();
    }
  } catch (e) {
    console.error('BaiduOCR 中文识别失败:', JSON.stringify(e));
    return '';
  }
}

四、在页面中调用

4.1 导入服务
typescript 复制代码
import { componentSnapshot } from '@kit.ArkUI';
import { image } from '@kit.ImageKit';
import { baiduOCRRecognize, baiduOCRRecognizeChinese } from '../services/BaiduOCRService';
4.2 doRecognize 方法
typescript 复制代码
async doRecognize() {
  if (this.isRecognizing || this.currentWord === null) {
    return;
  }
  this.isRecognizing = true;
  this.feedbackText = '识别中...';
  this.feedbackColor = '#6B7280';

  try {
    // 1. 截取画布组件为 PixelMap
    const pixelMap: image.PixelMap = await componentSnapshot.get('speedDictCanvas');

    // 2. 调用百度 OCR
    const text = await baiduOCRRecognize(pixelMap);

    // 3. 比对答案
    this.recognizedText = text;
    this.checkAnswer(text);
  } catch (e) {
    const err = e as Record<string, string>;
    this.feedbackText = '识别失败:' + (err['message'] ?? '');
    this.feedbackColor = '#B5533C';
  }

  this.isRecognizing = false;
}
4.3 Loading 状态

识别过程需要 1-2 秒,用 @State isRecognizing 控制按钮状态:

typescript 复制代码
Button() {
  Row({ space: 4 }) {
    if (this.isRecognizing) {
      LoadingProgress()
        .width(14).height(14).color('#FFFFFF')
    }
    Text(this.isRecognizing ? '识别中' : '识别')
      .fontSize(13).fontColor('#FFFFFF')
  }
}
.enabled(!this.isRecognizing)  // 识别中禁用按钮

五、API 响应格式

百度手写 OCR 返回的 JSON 格式:

json 复制代码
{
  "log_id": 1234567890,
  "words_result_num": 2,
  "words_result": [
    { "words": "apple" },
    { "words": "Apple" }
  ]
}
  • words_result:识别到的文字块数组
  • 每个块的 words 字段是识别出的文字
  • 手写体可能被分成多个块(多行书写)
  • 水印文字也可能被识别到(需要去重)

六、结果清洗策略

6.1 为什么需要清洗

百度 OCR 会识别画布上所有可见文字,包括:

  • 用户手写的内容(我们需要的)
  • 水印文字(需要过滤或去重)
  • 误识别的噪点
6.2 清洗流程
复制代码
原始结果 → 去除非字母字符 → 转小写 → 去空格 → 去重 → 拼接

示例:

复制代码
输入: ["Apple", "apple", "app le"]
处理: ["apple", "apple", "app le"] → 去非字母 → ["apple", "apple", "apple"]
去重: ["apple"]
输出: "apple"

七、错误处理

7.1 常见错误
错误码 含义 处理方式
110 Access Token 无效 清除缓存重新获取
216201 图片为空 提示用户先书写
17 每日调用量超限 提示明天再试
网络错误 无网络 提示检查网络
7.2 容错代码
typescript 复制代码
try {
  const text = await baiduOCRRecognize(pixelMap);
  // ...
} catch (e) {
  const err = e as Record<string, string>;
  this.feedbackText = '识别失败:' + (err['message'] ?? '网络异常');
  this.feedbackColor = '#B5533C';
}

八、本篇小结

通过本篇教程,我们完成了:

  • 理解了选择百度 OCR 的原因(端侧 OCR 不稳定)
  • 完成了百度 OCR 服务申请和权限配置
  • 实现了 BaiduOCRService(Token 缓存 + PixelMap 转 Base64 + API 调用)
  • 掌握了 componentSnapshot 截图 + OCR 的完整链路
  • 实现了识别结果清洗和去重
  • 处理了 Loading 状态和错误情况

下一篇预告

第 7 篇:答案比对与反馈 UI --- 我们将实现识别结果与正确答案的比对逻辑,以及画布上的大字体反馈浮层。

相关推荐
万物智能信息科技1 小时前
PWM散热风扇设置—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】
人工智能·华为·开源·harmonyos·鸿蒙
楚识科技1 小时前
云端 API 与私有化 OCR 的架构取舍:数据边界、并发模型与成本测算
ocr
AI人工智能+3 小时前
基于深度学习的车辆合格证识别技术,通过图像预处理、文字检测、文字识别到结构化提取、后处理校验,实现车辆合格证信息的结构化提取
深度学习·自然语言处理·ocr·车辆合格证识别
aqi004 小时前
一文读懂 HarmonyOS 7.0 带来的十大API重要升级
android·华为·harmonyos·鸿蒙·harmony
贾伟康4 小时前
【HarmonyOS 7新能力|037】数字盾工程封装:把接入逻辑放进可维护的分层结构
安全·harmonyos·arkts·软件架构·数字签名
ai安歌5 小时前
不调用外部 typst 命令:Draftmark 用 FRB 内嵌排版引擎的实现
harmonyos
威哥爱编程5 小时前
HarmonyOS 首开提速实战:首屏白屏的三种归因与对症方案
harmonyos·arkts
威哥爱编程5 小时前
HarmonyOS 折叠屏与鸿蒙电脑适配:布局一崩,八成是断点没定义
华为·harmonyos·arkts