06百度OCR手写识别接入-鸿蒙PC端Electron开发
本文介绍了在开源鸿蒙项目中接入百度OCR手写识别服务的实现过程。针对华为端侧OCR存在的稳定性问题,作者选择百度OCR作为替代方案,详细阐述了其优势(稳定识别、专门优化手写、免费额度等)。文章包含完整的实现代码,包括获取Access Token、图片格式转换(PixelMap转Base64)以及英文手写识别接口调用等关键步骤,并提供了网络权限配置、性能优化建议等实用技巧。该方案解决了手写单词识别问
欢迎加入开源鸿蒙 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.CoreVisionKit 的 textRecognition 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 申请步骤
- 注册 百度智能云账号
- 进入控制台 → 文字识别 → 创建应用
- 勾选「手写文字识别」能力
- 获取 API Key 和 Secret Key
二、网络权限配置
百度 OCR 需要网络请求,必须在 module.json5 中声明权限:
{
"module": {
"requestPermissions": [
{ "name": "ohos.permission.INTERNET" }
]
}
}
HarmonyOS 的 INTERNET 权限属于系统授权权限,声明即可使用,不需要动态申请。
三、BaiduOCRService 完整实现
3.1 文件结构
创建 electron/src/main/ets/services/BaiduOCRService.ets:
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:
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 编码的图片:
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 英文手写识别
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 中文手写识别
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 导入服务
import { componentSnapshot } from '@kit.ArkUI';
import { image } from '@kit.ImageKit';
import { baiduOCRRecognize, baiduOCRRecognizeChinese } from '../services/BaiduOCRService';
4.2 doRecognize 方法
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 控制按钮状态:
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 格式:
{
"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 容错代码
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 — 我们将实现识别结果与正确答案的比对逻辑,以及画布上的大字体反馈浮层。
更多推荐



所有评论(0)