欢迎加入开源鸿蒙 PC社区
https://harmonypc.csdn.net/
效果截图
第6篇:百度OCR手写识别接入
系列教程导航
| 篇号 | 标题 | 状态 |
|---|---|---|
| 01 | 环境搭建与项目创建 | ✅ |
| 02 | 数据模型与单词仓库 | ✅ |
| 03 | 主入口页面与导航结构 | ✅ |
| 04 | 极速划词页面实现 | ✅ |
| 05 | 手写画布实现 | ✅ |
| 06 | 百度OCR手写识别接入 | 本篇 |
| 07 | 答案比对与反馈UI | 下一篇 |
| 08 | 单词切换与底部导航 | |
| 09 | 词根分解与水印展示 | |
| 10 | 项目总结与优化方向 |
源码仓库:https://atomgit.com/qq_33247427/englishProject
一、为什么选择百度 OCR
1.1 华为端侧 OCR 的问题
HarmonyOS 提供了@kit.CoreVisionKit的textRecognitionAPI,理论上可以在设备端完成文字识别,无需网络。但在实际开发中遇到了两个严重问题:
| 问题 | 错误信息 | 原因 |
|---|---|---|
| 服务未初始化 | “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';constAPI_KEY='your_api_key_here';constSECRET_KEY='your_secret_key_here';letcachedToken:string='';3.2 获取 Access Token
百度 API 使用 OAuth 2.0 认证,需要先用 API Key + Secret Key 换取 Access Token:
asyncfunctiongetAccessToken():Promise<string>{// Token 缓存,避免重复请求if(cachedToken){returncachedToken;}consturl=`https://aip.baidubce.com/oauth/2.0/token`+`?grant_type=client_credentials`+`&client_id=${API_KEY}`+`&client_secret=${SECRET_KEY}`;constreq=http.createHttp();try{constresp=awaitreq.request(url,{method:http.RequestMethod.POST});constdata=JSON.parse(resp.resultasstring)asRecord<string,string>;cachedToken=data['access_token'];console.log('BaiduOCR token 获取成功');returncachedToken;}finally{req.destroy();// 必须销毁,否则内存泄漏}}关键点:
- Token 有效期 30 天,缓存后不需要每次都请求
http.createHttp()创建的实例用完必须destroy()- 生产环境应该把 Key 放在服务端,不要硬编码在客户端
3.3 PixelMap 转 Base64
百度 OCR 接受 Base64 编码的图片:
asyncfunctionpixelMapToBase64(pixelMap:image.PixelMap):Promise<string>{constpacker=image.createImagePacker();try{// 将 PixelMap 编码为 JPEG(比 PNG 小很多)constbuffer=awaitpacker.packing(pixelMap,{format:'image/jpeg',quality:90// 90% 质量,平衡大小和清晰度});// ArrayBuffer → Uint8Array → Base64 字符串consthelper=newutil.Base64Helper();constuint8=newUint8Array(buffer);constb64=helper.encodeToStringSync(uint8);returnb64;}finally{packer.release();// 释放 packer 资源}}为什么用 JPEG 而不是 PNG?
- 手写内容是黑白线条,JPEG 90% 质量足够清晰
- JPEG 文件通常比 PNG 小 3-5 倍
- 上传更快,百度 API 有请求体大小限制(4MB)
3.4 英文手写识别
exportasyncfunctionbaiduOCRRecognize(pixelMap:image.PixelMap):Promise<string>{try{constbase64=awaitpixelMapToBase64(pixelMap);consttoken=awaitgetAccessToken();// 百度手写文字识别接口consturl=`https://aip.baidubce.com/rest/2.0/ocr/v1/handwriting?access_token=${token}`;constbody=`image=${encodeURIComponent(base64)}`;constreq=http.createHttp();try{constresp=awaitreq.request(url,{method:http.RequestMethod.POST,header:{'Content-Type':'application/x-www-form-urlencoded'},extraData:body});constresult=JSON.parse(resp.resultasstring)asRecord<string,Object>;constwordsResult=result['words_result']asArray<Record<string,string>>;if(wordsResult&&wordsResult.length>0){// 清洗结果:只保留英文字母和空格consttokens=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'';// 去重(水印和手写可能被重复识别)constseen=newSet<string>();constunique:string[]=[];for(consttoftokens){if(!seen.has(t)){seen.add(t);unique.push(t);}}returnunique.join(' ').trim();}return'';}finally{req.destroy();}}catch(e){console.error('BaiduOCR 识别失败:',JSON.stringify(e));return'';}}3.5 中文手写识别
exportasyncfunctionbaiduOCRRecognizeChinese(pixelMap:image.PixelMap):Promise<string>{try{constbase64=awaitpixelMapToBase64(pixelMap);consttoken=awaitgetAccessToken();consturl=`https://aip.baidubce.com/rest/2.0/ocr/v1/handwriting?access_token=${token}`;constbody=`image=${encodeURIComponent(base64)}`;constreq=http.createHttp();try{constresp=awaitreq.request(url,{method:http.RequestMethod.POST,header:{'Content-Type':'application/x-www-form-urlencoded'},extraData:body});constresult=JSON.parse(resp.resultasstring)asRecord<string,Object>;constwordsResult=result['words_result']asArray<Record<string,string>>;if(wordsResult&&wordsResult.length>0){// 只保留中文字符returnwordsResult.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 方法
asyncdoRecognize(){if(this.isRecognizing||this.currentWord===null){return;}this.isRecognizing=true;this.feedbackText='识别中…';this.feedbackColor='#6B7280';try{// 1. 截取画布组件为 PixelMapconstpixelMap:image.PixelMap=awaitcomponentSnapshot.get('speedDictCanvas');// 2. 调用百度 OCRconsttext=awaitbaiduOCRRecognize(pixelMap);// 3. 比对答案this.recognizedText=text;this.checkAnswer(text);}catch(e){consterr=easRecord<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{consttext=awaitbaiduOCRRecognize(pixelMap);// ...}catch(e){consterr=easRecord<string,string>;this.feedbackText='识别失败:'+(err['message']??'网络异常');this.feedbackColor='#B5533C';}八、本篇小结
通过本篇教程,我们完成了:
- 理解了选择百度 OCR 的原因(端侧 OCR 不稳定)
- 完成了百度 OCR 服务申请和权限配置
- 实现了 BaiduOCRService(Token 缓存 + PixelMap 转 Base64 + API 调用)
- 掌握了 componentSnapshot 截图 + OCR 的完整链路
- 实现了识别结果清洗和去重
- 处理了 Loading 状态和错误情况
下一篇预告
第 7 篇:答案比对与反馈 UI— 我们将实现识别结果与正确答案的比对逻辑,以及画布上的大字体反馈浮层。