【听见课堂 HarmonyOS NEXT 实战系列 34】ImageSource→PixelMap→Core Vision:OCR 资源生命周期全流程

OCR 经常被简化成“传一张图,返回一段字”。在 HarmonyOS 真正运行时,URI 要先创建 ImageSource,再解码成 PixelMap,Core Vision OCR 还要初始化、识别并释放。任何一步抛异常,如果资源释放只写在成功分支,长课堂和连续扫描就可能累积内存与系统能力句柄。

听见课堂把整条资源链路集中在 ScanCaptureService.recognizeText(),并用嵌套 finally 保证释放顺序。本文逐段拆解当前实现,同时指出大图缩放、页面取消和压力测试尚未形成完整证据。

URI 到 ImageSource、PixelMap、Core Vision 与文本结果的生命周期

一、OCR 输入为什么是 URI

系统 CameraPicker 和 PhotoViewPicker 都返回 URI。页面不复制原图到数据库,也不把二进制长期保存在状态中,而是把 URI 传给能力层:

const recognizedText: string = await this.scanService.recognizeText(uri);

URI 是当前处理链路的输入句柄,不是业务记录的永久主键。

二、第一步创建 ImageSource

const imageSource: image.ImageSource = image.createImageSource(uri);

ImageSource 负责解析图片来源和后续解码。创建阶段可能因 URI 不可访问、格式不支持或资源失效而失败,因此调用方必须把它归类为图片读取问题,而不是 OCR 模型空结果。

三、第二步解码 PixelMap

let pixelMap: image.PixelMap | undefined = undefined;
pixelMap = await imageSource.createPixelMap();

Core Vision 接收的是视觉信息中的 PixelMap。原图越大,解码后的像素内存越高;一张压缩 JPEG 只有几 MB,不代表展开后的 PixelMap 也只有几 MB。

四、为什么 PixelMap 初始值是 undefined

如果 createPixelMap() 自身失败,变量仍为 undefined。释放时先判断:

if (pixelMap !== undefined) {
  await pixelMap.release();
}

这避免对不存在的资源调用 release(),也让异常路径保持可读。

五、第三步初始化 Core Vision OCR

let ocrInitialized: boolean = false;
ocrInitialized = await textRecognition.init();
if (!ocrInitialized) {
  throw new Error('Core Vision OCR initialization failed.');
}

初始化返回布尔值,不能默认成功。只有 true 才进入识别,并在释放阶段调用 textRecognition.release()

六、第四步构造 VisionInfo

当前实现直接传入:

{ pixelMap: pixelMap }

官方 Core Vision 文本识别指南同样以 VisionInfo.pixelMap 作为 OCR 输入。页面层不需要知道模型配置,只消费最终文本或错误状态。

七、方向检测为何开启

{ isDirectionDetectionSupported: true }

课堂板书和课件照片可能横拍、竖拍或带轻微旋转。方向检测可帮助识别链路处理非正向文字,但它不是无限纠偏:大角度透视、模糊、反光和遮挡仍需拍摄提示与人工复核。

八、第五步只接收非空文本

const result = await textRecognition.recognizeText(visionInfo, config);
const recognizedText: string = result.value.trim();
if (recognizedText.length === 0) {
  throw new Error('Core Vision OCR returned empty text.');
}
return recognizedText;

trim() 可以过滤只包含空白字符的伪结果。空文本进入失败恢复,不显示“识别成功 0 字”。

成功、解码失败、初始化失败、空结果下的资源释放矩阵

九、最外层 finally 一定执行

recognizeText() 把识别主体放在 try 中,资源释放放在 finally。无论初始化失败、识别异常还是空文本主动抛错,都会进入释放阶段。

这比在每个 catch 里复制三段释放代码更不容易遗漏。

十、为什么释放顺序是 OCR→PixelMap→ImageSource

OCR 可能仍持有 PixelMap 相关能力,PixelMap 又由 ImageSource 解码而来。项目按依赖反向释放:先 textRecognition.release(),再 pixelMap.release(),最后 imageSource.release()

这是一条清晰的所有权栈:后创建、上层依赖的资源先退出。

十一、嵌套 finally 防止释放中断

finally {
  try {
    if (ocrInitialized) await textRecognition.release();
  } finally {
    try {
      if (pixelMap !== undefined) await pixelMap.release();
    } finally {
      await imageSource.release();
    }
  }
}

如果 OCR 的 release() 自己抛异常,外层结构仍会尝试释放 PixelMap 和 ImageSource。单个顺序语句放在同一个 finally 中,第一条异常可能让后两条永远不执行。

十二、当前实现没有显式缩放大图

createPixelMap() 没有传解码尺寸或采样策略。高像素相机图可能带来较大的峰值内存和更长识别时间。

这不是说当前代码一定泄漏,而是缺少大图输入上限和压力数据。后续可在保持文字可读的前提下使用解码选项限制尺寸,并用真实板书图比较识别质量。

十三、并发扫描需要额外门禁

页面通过 isScanProcessing 防止同一页面重复点击,当前每次只处理一张图。若未来支持多页,不能无上限并发创建多个 PixelMap 和 OCR 会话。

更稳妥的方案是有界队列或串行处理,每页独立状态、独立释放,失败不阻塞用户查看已完成页面。

十四、页面退出时还缺少取消协议

当前异步识别完成后会更新页面状态并导航到 P06,但没有看到显式取消令牌。若用户在 OCR 期间离开页面,仍需验证完成回调是否会写回已不可见的页面。

资源最终会由 finally 释放,但“释放完成”与“禁止过期 UI 回写”是两个不同问题。

十五、已有验证和缺口

契约脚本检查了 createImageSourcerecognizeTextrelease();HAP 构建、模拟器安装和本地示例闭环通过。模拟器没有真实图库图片,因此 Core Vision 成功、空结果、异常和资源压力均为 not run

文章只能确认代码路径与释放结构存在,不能声称已经完成大图连续 OCR 稳定性测试。

十六、资源验收清单

用清晰图、空白图、超大图、损坏图、旋转图和重复 50 次扫描分别观察:结果、耗时、峰值内存、错误状态、页面退出、资源释放日志与后续扫描是否仍可用。

同时检查原图 URI 不进数据库、日志不输出敏感路径、失败后仍能人工输入。

总结

OCR 稳定性来自完整生命周期,而不只是 recognizeText() 返回成功。听见课堂的反向释放和嵌套 finally 为异常路径打好了基础;下一步需要补充大图解码策略、页面取消协议和真实设备压力证据。

下一篇继续看最容易被误报的分支:OCR 返回空文本时,为什么必须进入可恢复错误态,而不是填一段预置公式冒充成功。

参考:华为 Core Vision 文本识别指南、项目 ScanCaptureService.recognizeText() 与扫描 OCR 复验报告。

Logo

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

更多推荐