HarmonyOS 相机 + 文字识别实现拍照识字 29 从 Buffer 创建图像源
29 从 Buffer 创建图像源
引言
传统图片处理往往先写文件再读文件,多一次磁盘 IO 就多一份延迟与风险。在 HarmonyOS 上,image.createImageSource(buffer) 允许我们直接从内存字节创建图像源,实现"拍照 → 取 JPEG 字节 → 解码 → 识别"的无文件 IO 链路。本工程正是这条链路的完整范例:相机回调拿到 ArrayBuffer,不经落盘直接解码成 PixelMap 交给 OCR(源码参考:entry/src/main/ets/common/utils/Camera.ets)。本文讲清 byteBuffer 从哪来、如何走通"Buffer → ImageSource → PixelMap"的解码链路,以及 Buffer 解码的数据完整性与格式注意事项。

正文知识点
1. byteBuffer 的来源:相机 photoAvailable 回调
工程中,拍照结果通过 PhotoOutput 的 photoAvailable 事件异步投递:
// 源码参考:entry/src/main/ets/common/utils/Camera.ets
this.photoOutput.on('photoAvailable', (errCode: BusinessError, photo: camera.Photo): void => {
let imageObj = photo.main;
imageObj.getComponent(image.ComponentType.JPEG, async (errCode: BusinessError, component: image.Component) => {
if (errCode || component === undefined) {
return;
}
let buffer: ArrayBuffer;
buffer = component.byteBuffer; // ← JPEG 原始字节,就是我们解码的输入
this.result = await this.recognizeImage(buffer);
})
})
photo.main:Photo 的主图像对象;getComponent(ComponentType.JPEG):异步取回 JPEG 组件;component.byteBuffer:JPEG 编码的原始字节(ArrayBuffer)。
2. 无文件 IO 的解码链路
拿到 buffer 后,工程 recognizeImage 中仅两行即完成解码(源码参考:entry/src/main/ets/common/utils/Camera.ets):
let imageResource = image.createImageSource(buffer);
let pixelMapInstance = await imageResource.createPixelMap();
链路可以画成:
camera.Photo → getComponent(JPEG) → component.byteBuffer (ArrayBuffer)
→ image.createImageSource(buffer) // 同步,创建解码源,不落盘
→ imageResource.createPixelMap() // 异步,解码出像素图
→ textRecognition.recognizeText({ pixelMap }) // 消费像素
全程无文件 IO、无路径处理、无沙箱权限问题,天然适合"即拍即识别"这类对延迟敏感的场景。这也是 createImageSource 三种入参(path / fd / buffer)中 buffer 形式的最大价值:数据还在内存,解码立刻开始。
3. Buffer 解码注意事项
- 数据完整性:传入的 ArrayBuffer 必须是完整的单帧图像编码数据(JPEG 文件头 SOI 到文件尾 EOI)。截断、拼接、多帧混合都会导致解码失败或花屏。相机
getComponent(JPEG)返回的字节通常直接可用,但如果做了自定义处理(如拼帧、加包头),要确保还原成标准格式再解码。 - 数据生命周期:
createImageSource(buffer)内部会引用该 buffer 做解析,若后续业务要修改/复用同一 ArrayBuffer(如连续两帧),建议先new Uint8Array(original)拷贝一份再传入,避免解码源与写入方互相干扰。 - 格式识别:
createImageSource会解析头部字节自动识别 JPEG/PNG/WEBP 等格式,无需显式指定;传入非图像数据时抛异常(如62980096参数错误 / 解码失败类错误码),必须 try-catch。 - 容量提示:工程 CommonConstants 中
ARRAYBUFFER_SIZE = 4069、IMAGE_RECEIVER_WIDTH = 640、IMAGE_RECEIVER_HEIGHT = 480、IMAGE_RECEIVER_CAPACITY = 8是一组"接收器规格"参考值:若通过image.ImageReceiver接收相机输出,缓冲区容量(如 8 帧)不足会导致丢帧,尺寸偏小则图像过糊,可据此估算 buffer 峰值内存(640×480×4 ≈ 1.2 MB/帧)。 - 释放:识别完成后
pixelMapInstance.release()、imageResource.release()(工程已有),buffer 本身由 GC 回收,无需手动 release。
代码示例
一个"从 Buffer 解码并识别 + 异常兜底"的完整示例(可直接替换工程 recognizeImage 的解码段):
// 基于 entry/src/main/ets/common/utils/Camera.ets 的完整示例
import { image } from '@kit.ImageKit';
import { textRecognition } from '@kit.CoreVisionKit';
import { BusinessError } from '@kit.BasicServicesKit';
async function recognizeFromJpegBuffer(buffer: ArrayBuffer): Promise<string> {
// 1. 安全拷贝,避免外部复用 buffer 影响解码
const safeBuffer: ArrayBuffer = buffer.slice(0);
// 2. 从内存创建图像源(无文件 IO)
const imageResource: image.ImageSource = image.createImageSource(safeBuffer);
let pixelMap: image.PixelMap | undefined = undefined;
try {
// 3. 异步解码出像素图
pixelMap = await imageResource.createPixelMap();
const info: image.ImageInfo = pixelMap.getImageInfoSync();
console.info(`decode from buffer, size=${info.size.width}x${info.size.height}`);
// 4. 交给 OCR
const visionInfo: textRecognition.VisionInfo = { pixelMap: pixelMap };
const config: textRecognition.TextRecognitionConfiguration = {
isDirectionDetectionSupported: true
};
const result = await textRecognition.recognizeText(visionInfo, config);
return result.value;
} catch (error) {
const err = error as BusinessError;
console.error(`decode/recognize failed. code=${err.code}, message=${err.message}`);
return '识别失败';
} finally {
// 5. 无论成败都释放原生资源
pixelMap?.release();
imageResource.release();
}
}
运行效果与注意事项
- 正确运行时,log 会打印
decode from buffer, size=…x…,随后 OCR 返回识别文本,整个过程不产生任何临时图片文件,卸载应用也无需清理残留图片。 - 若相机输出分辨率很大(如 4000×3000),
createPixelMap默认按原尺寸解码,内存峰值约 4000×3000×4 ≈ 45 MB;内存紧张时可传DecodingOptions.desiredSize降采样,或用ImageReceiver直接限定输出 640×480(对应工程IMAGE_RECEIVER_WIDTH/HEIGHT)。 - 连续拍照场景下,
photoAvailable回调并发到来,注意不要在上一帧识别完成前复用/覆盖 buffer;必要时加队列串行处理。 - 遇到解码报错先自查三件事:buffer 是否为空或截断、是否为合法图像格式、是否在
release()之后仍在使用。这三条覆盖了绝大多数 Buffer 解码失败。
总结
"从 Buffer 创建图像源"是 ImageKit 面向内存数据的高效解码入口:相机回调的 component.byteBuffer 提供 JPEG 字节,image.createImageSource(buffer) + createPixelMap() 在内存中完成解码,OCR 直接消费像素图。掌握数据完整性、生命周期与降采样三项要点,即可把这条无文件 IO 链路跑稳、跑省,这正是"拍照识别文字"工程性能的关键所在。
更多推荐



所有评论(0)