查看: 364|回复: 3

HarmonyOS端侧OCR工程化:解码容错、像素扁平化与重试策略实践

[复制链接]
发表于 昨天 10:00 | 显示全部楼层 |阅读模式
在HarmonyOS的文档扫描增强管线中,将增强后的图片送入系统OCR并输出文本,并非简单的API调用。系统OCR对输入格式要求苛刻,且存在冷启动延迟等问题。本文分享一套封装方案,总代码不足300行,但每个函数都来自实际踩坑。
  1. 用户选图
  2. decodeImageForOcr() — 解码+降分辨率+HDR容错
  3. cropPixelMapToMaxAspectRatioIfNeeded() — 超长图居中裁剪
  4. pixelMapToFlatRgba8() — 像素格式扁平化
  5. recognizeTextFromPixelMapWithRetry() — 带重试的OCR调用
  6. sanitizeRecognizedText() — 控制字符清洗
  7. 返回纯文本
复制代码

一、解码容错:三级回退链
OCR推荐RGBA_8888格式,动态范围SDR或AUTO。但实际图片可能是HEIF、HDR、非标准JPEG等。使用三级回退:先尝试AUTO动态范围,失败后尝试SDR,最后不限格式。注意每次try失败后必须重新createImageSource,因为HarmonyOS的ImageSource在解码失败后状态不确定,复用会抛异常。
  1. async function decodeImageForOcr(filePath: string): Promise<image.PixelMap> {
  2.     const src = image.createImageSource(filePath);
  3.     const sizeOpt = { width: 1600, height: 1600 };
  4.     // 第一优先:RGBA+AUTO
  5.     try { return await src.createPixelMap({ desiredSize: sizeOpt, editable: true, desiredPixelFormat: image.PixelMapFormat.RGBA_8888, desiredDynamicRange: image.DecodingDynamicRange.AUTO }); } catch (_e) {}
  6.     // 第二优先:RGBA+SDR
  7.     try { src = image.createImageSource(filePath); return await src.createPixelMap({ desiredSize: sizeOpt, editable: true, desiredPixelFormat: image.PixelMapFormat.RGBA_8888, desiredDynamicRange: image.DecodingDynamicRange.SDR }); } catch (_e2) {}
  8.     // 第三优先:不限格式
  9.     return await src.createPixelMap({ desiredSize: sizeOpt, editable: true });
  10. }
复制代码
AUTO优先是因为部分HDR图片只有用AUTO才能正确解码;SDR优先于不限格式,因为OCR期望RGBA_8888,不限格式可能返回BGRA或YUV导致异常。

二、超长图裁剪
系统建议OCR输入宽高比不超过2:1。超长截图如果不裁剪,OCR可能只识别前半段。采用居中裁剪,丢弃边角内容。
  1. async function cropPixelMapToMaxAspectRatioIfNeeded(pm: PixelMap): Promise<PixelMap> {
  2.     const info = await pm.getImageInfo();
  3.     const w = info.size.width, h = info.size.height;
  4.     const ratio = w > h ? w / h : h / w;
  5.     if (ratio <= 2 + 1e-6) return pm;
  6.     let region: image.Region;
  7.     if (w / h > 2) {
  8.         const nw = Math.floor(h * 2);
  9.         const nx = Math.floor((w - nw) / 2);
  10.         region = { x: nx, y: 0, size: { width: nw, height: h } };
  11.     } else {
  12.         const nh = Math.floor(w * 2);
  13.         const ny = Math.floor((h - nh) / 2);
  14.         region = { x: 0, y: ny, size: { width: w, height: nh } };
  15.     }
  16.     await pm.crop(region);
  17.     return pm;
  18. }
复制代码
裁剪失败不中断,catch后用原图。

三、像素扁平化:为什么需要重建PixelMap
HarmonyOS的PixelMap内部可能使用多种格式(RGBA、BGRA、YUV等)。即使解码指定RGBA_8888,某些设备底层可能偷偷用BGRA。OCR引擎直接读像素数据会出错。通过readPixelsToBuffer读出原始字节,再用RGBA_8888格式重新创建PixelMap,强制统一格式。
  1. async function pixelMapToFlatRgba8(pm: PixelMap): Promise<PixelMap> {
  2.     const info = await pm.getImageInfo();
  3.     const w = info.size.width, h = info.size.height;
  4.     const buf = new ArrayBuffer(pm.getPixelBytesNumber());
  5.     await pm.readPixelsToBuffer(buf);
  6.     const init = { editable: true, pixelFormat: image.PixelMapFormat.RGBA_8888, size: { width: w, height: h } };
  7.     return await image.createPixelMap(buf, init);
  8. }
复制代码
“读出再重建”消除了手动检测BGRA的复杂逻辑。

四、重试策略:处理OCR冷启动
系统OCR服务(CoreVisionKit)首次初始化需要500ms~2s,可能超时。重试2次,每次间隔400ms。同时预初始化:进入OCR页面时调用init()。
  1. async function recognizeTextFromPixelMapWithRetry(pm: PixelMap): Promise<string> {
  2.     const MAX_RETRIES = 2, RETRY_DELAY = 400;
  3.     let lastErr = new Error('OCR failed');
  4.     for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
  5.         try {
  6.             if (attempt > 0) await sleepMs(RETRY_DELAY);
  7.             return await recognizeTextFromPixelMap(pm);
  8.         } catch (e) { lastErr = e instanceof Error ? e : new Error('OCR failed'); }
  9.     }
  10.     throw lastErr;
  11. }
复制代码
预初始化把冷启动延迟提前到页面进入时,用户感知延迟从1~2s降至0ms。

五、调用链与回退
完整流程中,扁平化路径失败后自动回退到非扁平化路径。因为某些设备上readPixelsToBuffer可能抛异常(如内存不足),此时用原始PixelMap调OCR反而成功。

六、OCR结果解析与清洗
OCR返回的TextRecognitionResult结构可能版本不同。采用三级回退:value → textWords → blocks→lines。textWords字段在TypeScript类型定义中不存在,但运行时可能存在,使用Record<string, object>动态访问绕过类型检查。
文本清洗时保留换行、回车、制表符,丢弃其他控制字符。逐charCode比较比正则表达式快3~5倍。

七、错误码映射与踩坑记录
错误码如1001400001直接展示用户看不懂,映射为中文提示并截断到96字符。
常见坑:
1. OCR返回空字符串而非报错——需判断空结果并提示“未检测到文字”。
2. HDR图片像素值可能超255——SDR优先于AUTO强制转8-bit。
3. 大图片内存峰值——扁平化后立即release原始PixelMap。
4. 并发OCR调用导致死锁——用Promise链串行化。

八、性能与总结
冷启动延迟主要由预初始化消除;热启动250~730ms。优势是零包体、系统级维护、原生中文支持;代价是不能自定义模型、离线训练。
核心设计思想:用确定性工程手段应对不确定系统行为——格式不确定就强制重建,服务不确定就重试+预初始化,SDK不确定就动态类型兜底。
回复

使用道具 举报

发表于 昨天 10:10 | 显示全部楼层

Re: HarmonyOS端侧OCR工程化:解码容错、像素扁平化与重试策略实践

感谢楼主分享的实战经验!三级回退链处理解码容错、居中裁剪超长图、像素格式强制统一这几招特别实用,尤其是“读出再重建”绕开了底层格式不一致的坑,思路很清晰。想请教一下,重试策略里预初始化建议在进入OCR页面时调用,具体是放在页面生命周期的哪个阶段(比如onPageShow还是onInit?),有没有遇到预初始化过早导致资源被释放的问题?再次感谢!
回复 支持 反对

使用道具 举报

发表于 昨天 10:10 | 显示全部楼层

Re: HarmonyOS端侧OCR工程化:解码容错、像素扁平化与重试策略实践

这个封装方案非常扎实,三级回退和重建PixelMap这两个点尤其实用——HDR图片的兼容性和底层格式不确定性确实是坑,能提前想到并做兼容真的很厉害。超长图居中裁剪的思路也很合理,既避免OCR只识别前半段,又保留了核心区域。感谢分享这些踩坑经验,代码很清晰,直接拿来参考了。
回复 支持 反对

使用道具 举报

发表于 昨天 10:10 | 显示全部楼层

Re: HarmonyOS端侧OCR工程化:解码容错、像素扁平化与重试策略实践

感谢分享,这些工程细节太实用了!三级回退链和readPixelsToBuffer强制重建PixelMap的思路,确实能避免很多隐式格式导致的坑。超长图居中裁剪也符合实际场景——用户经常拍文档或截图,边缘信息少,裁剪后识别效果更可靠。另外想问下,重试间隔400ms是实测最优值吗?有没有考虑过OCR冷启动失败后的指数退避策略?
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-7-21 07:17 , Processed in 0.028725 second(s), 17 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部