【听见课堂 HarmonyOS NEXT 实战系列 34】ImageSource→PixelMap→Core Vision:OCR 资源生命周期全流程
【听见课堂 HarmonyOS NEXT 实战系列 34】ImageSource→PixelMap→Core Vision:OCR 资源生命周期全流程
OCR 经常被简化成“传一张图,返回一段字”。在 HarmonyOS 真正运行时,URI 要先创建 ImageSource,再解码成 PixelMap,Core Vision OCR 还要初始化、识别并释放。任何一步抛异常,如果资源释放只写在成功分支,长课堂和连续扫描就可能累积内存与系统能力句柄。
听见课堂把整条资源链路集中在 ScanCaptureService.recognizeText(),并用嵌套 finally 保证释放顺序。本文逐段拆解当前实现,同时指出大图缩放、页面取消和压力测试尚未形成完整证据。

一、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 回写”是两个不同问题。
十五、已有验证和缺口
契约脚本检查了 createImageSource、recognizeText 和 release();HAP 构建、模拟器安装和本地示例闭环通过。模拟器没有真实图库图片,因此 Core Vision 成功、空结果、异常和资源压力均为 not run。
文章只能确认代码路径与释放结构存在,不能声称已经完成大图连续 OCR 稳定性测试。
十六、资源验收清单
用清晰图、空白图、超大图、损坏图、旋转图和重复 50 次扫描分别观察:结果、耗时、峰值内存、错误状态、页面退出、资源释放日志与后续扫描是否仍可用。
同时检查原图 URI 不进数据库、日志不输出敏感路径、失败后仍能人工输入。
总结
OCR 稳定性来自完整生命周期,而不只是 recognizeText() 返回成功。听见课堂的反向释放和嵌套 finally 为异常路径打好了基础;下一步需要补充大图解码策略、页面取消协议和真实设备压力证据。
下一篇继续看最容易被误报的分支:OCR 返回空文本时,为什么必须进入可恢复错误态,而不是填一段预置公式冒充成功。
参考:华为 Core Vision 文本识别指南、项目 ScanCaptureService.recognizeText() 与扫描 OCR 复验报告。
更多推荐



所有评论(0)