相册照片不用上传:HarmonyOS Core Vision 端侧识别动物与植物

相册照片不用上传:HarmonyOS Core Vision 端侧识别动物与植物

真机环境:ALN-AL80,OpenHarmony 7.0.0.32(Beta2),API 26。测试照片来自系统相册,识别结果由同一台真机上的 Core Vision Kit 返回。

为什么相册应用需要先理解照片

照片进入应用以后,如果仍然只能按时间浏览,数量一多就很难整理。文件名通常只是时间戳,拍摄备注也不一定存在,依赖人工逐张添加"动物""植物""建筑"等标签,实际使用中很难坚持。

双镜记忆相机需要在照片导入后完成一轮本地内容分析:识别照片中的常见目标,生成可用于分类、筛选和封面推荐的基础标签。这个过程不需要把原图上传到服务器,也不需要读取整套系统图库。用户在系统选择器里选中哪张照片,应用就分析哪张。

Core Vision Kit 官方能力介绍把多目标识别列为基础视觉能力之一,可识别图片中的动物、植物、建筑物、文本等常见目标,并返回目标区域。本文用相册中的绿植和鹦鹉照片完成两轮真机测试,重点观察三个结果:业务标签、有效目标数量,以及更换照片后结果是否会随之刷新。

实现链路

完整流程包含四步:

  1. PhotoViewPicker 让用户选择一张照片;
  2. 把选择器返回的 URI 复制到应用沙箱;
  3. 解码为 PixelMap,交给 ObjectDetector
  4. 过滤低置信度结果,把数字类别转换成相册可用的中文标签。

这四步都在真机本地完成。相册选择器只授权本次选中的照片,符合最小访问范围原则。华为的应用隐私保护指南也建议使用 Picker 获取用户明确选择的图片,避免为了单张照片申请整库访问权限。

第一步:只获取用户选中的照片

选择器限制为图片类型和单选:

ts 复制代码
const options = new photoAccessHelper.PhotoSelectOptions();
options.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
options.maxSelectNumber = 1;

const picker = new photoAccessHelper.PhotoViewPicker();
const result = await picker.select(options);
const sourceUri = result.photoUris?.[0];

sourceUri 适合读取本次选择结果,但不应被当成应用长期管理的普通文件路径。双镜记忆相机在用户完成选择后立即把内容复制到应用沙箱,后续的解码、识别和记录保存都使用沙箱文件。

ts 复制代码
const sourceFile = await fs.open(sourceUri, fs.OpenMode.READ_ONLY);
const targetFile = fs.openSync(
  localPath,
  fs.OpenMode.CREATE | fs.OpenMode.WRITE_ONLY | fs.OpenMode.TRUNC
);
await fs.copyFile(sourceFile.fd, targetFile.fd);

这里要保留 try/finally,无论复制成功还是失败,都关闭源文件和目标文件。连续导入多张照片时,如果文件描述符没有及时释放,后面的选择和解码会出现难以复现的失败。

第二步:控制解码尺寸

手机相册原图可能达到数千万像素。目标识别不需要先把完整原图以 RGBA 形式全部展开到内存,因此解码前先读取尺寸,把最长边限制在 1600 像素:

ts 复制代码
const sourceData = readFile(localPath);
const imageSource = image.createImageSource(sourceData);
const sourceInfo = await imageSource.getImageInfo();

const scale = Math.min(
  1,
  1600 / Math.max(sourceInfo.size.width, sourceInfo.size.height)
);

const pixelMap = await imageSource.createPixelMap({
  editable: false,
  desiredSize: {
    width: Math.floor(sourceInfo.size.width * scale),
    height: Math.floor(sourceInfo.size.height * scale)
  },
  desiredPixelFormat: image.PixelMapFormat.RGBA_8888
});

缩放只作用于分析输入,沙箱中的原始照片没有被覆盖。这样既保留后续查看和导出的原图,又避免识别阶段占用过多内存。

第三步:调用 ObjectDetector

ObjectDetector 接收包含 PixelMap 的请求,返回目标数组。每个目标包含类别编号、置信度和区域坐标。

ts 复制代码
const detector = await objectDetection.ObjectDetector.create();
const request: visionBase.Request = {
  inputData: { pixelMap }
};

const response = await detector.process(request);
const objects = response.objects;

分析器、PixelMapImageSource 都属于需要显式释放的资源:

ts 复制代码
try {
  // 创建 PixelMap 并执行 detector.process()
} finally {
  await detector?.destroy();
  await pixelMap?.release();
  await imageSource?.release();
}

页面没有直接展示底层数字类别。识别结果先经过阈值过滤,再转换成相册业务标签:

ts 复制代码
const visibleObjects = response.objects.filter(item =>
  Number.isFinite(item.score) && item.score >= 0.32
);

function getSceneLabel(code: number): string {
  switch (code) {
    case 0: return '风景';
    case 1: return '动物';
    case 2: return '植物';
    case 3: return '建筑';
    case 9: return '猫咪';
    case 10: return '狗狗';
    case 11: return '美食';
    case 12: return '汽车';
    default: return '';
  }
}

阈值不是越低越好。低阈值会让弱结果大量进入相册标签,后续搜索和分类反而变得不可靠。双镜记忆相机当前使用 0.32 作为通用目标下限,猫狗等容易影响分类的标签还会设置更高门槛。

第一轮真机测试:绿植照片

第一张照片包含成片的绿色叶片和粉色花朵。系统选择器完成授权后,照片被复制到应用沙箱,ObjectDetector 返回一个有效目标,类别转换后显示"植物"。

图 1:原图、识别状态和"植物"标签在同一张真机截图中;本轮返回目标 1、人脸 0。

页面中的"封面评分 34%"不是 Core Vision 的原始字段,而是双镜记忆相机根据目标置信度、标签数量、人脸构图等信息计算的业务评分。模型结果和应用计算结果分开标注,避免把产品策略误写成系统 API 返回值。

第二轮真机测试:鹦鹉照片

不退出实验页,重新打开系统选择器并改选鹦鹉照片。第二轮分析完成后,上一轮的"植物"被替换为"动物",有效目标仍为 1,人脸数量为 0。

图 2:更换为鹦鹉照片后,真机返回"动物";页面同时显示本轮分析时间和目标数量。

这次结果没有读取文件名,也没有根据页面文案预置标签。两张照片经过同一个 pick → copy → decode → process 流程,输入内容变化后,模型结果从"植物"切换为"动物"。

识别结果怎样进入相册业务

目标识别完成后,双镜记忆相机把分析结果和照片记录一起保存:

ts 复制代码
interface GallerySceneAnalysis {
  labels: Array<string>;
  labelCodes: Array<number>;
  primaryLabel: string;
  objects: Array<GallerySceneObject>;
  faceCount: number;
  analyzedAt: number;
  provider: 'core-vision';
}

这份结构可以直接支持三个功能:

  • 相册筛选:点击"动物"或"植物"查看对应照片;
  • 搜索召回:把本地目标标签作为结构化检索条件;
  • 封面选择:结合目标置信度、人脸构图和清晰度挑选更合适的封面。

导入任务采用串行分析队列。用户一次选择多张照片时,先完成文件复制和记录创建,再逐张执行端侧识别,避免同时解码多张大图造成内存峰值。识别失败不会阻止照片入库,记录会保留"待分析"状态,下一次启动后可以继续补齐。

实践中容易踩的三个坑

1. 把 Picker URI 当作长期文件路径

选择器 URI 用于访问用户本次授权的内容。需要持久处理时,应复制到应用沙箱,再保存沙箱路径;不要对 URI 做字符串裁剪后交给普通文件接口。

2. 原图尺寸直接解码

高像素照片完整解码会显著增加内存占用。先读取尺寸、按最长边计算缩放比例,再创建分析用 PixelMap,原图文件仍可完整保留。

3. 忽略空结果和低置信度结果

process() 成功只表示分析流程完成,不表示每张照片都一定有可用目标。页面应同时处理"有标签""无有效目标"和"分析失败"三种状态,业务层还要使用统一阈值过滤弱结果。

最终效果

本次 API 26 真机实践完成了从系统相册授权到端侧目标识别的完整闭环:绿植照片返回"植物",鹦鹉照片返回"动物",两轮结果都包含一个有效目标,并随照片更换实时刷新。原图没有上传,应用也没有申请读取整个系统图库。

对于照片管理类应用,这条链路的价值不止是展示一个 AI 标签。它把"用户选择照片、应用获得稳定文件、端侧理解内容、结果进入分类与搜索"连接成了可落地的数据入口,后续的相册筛选、封面推荐和本地检索都可以复用同一份分析结果。

相关推荐
OH_TPC2 小时前
HarmonyOS APP开发---"刷刷"短视频App,需要用到这个库
harmonyos
超爱西西鸭3 小时前
思维导图编辑体验:触摸交互与节点操作(ArkTS)
学习·华为·harmonyos
特立独行的猫A3 小时前
cc-switch 鸿蒙 PC移植:使用Tauri官方 feat/open-harmony 分支最新版本移植实录
harmonyos
世人万千丶4 小时前
HarmonyOS 股票 K 线图完整源码与金融图表模板
学习·华为·金融·harmonyos·鸿蒙
大雷神4 小时前
HarmonyOS 真机实战:一张普通照片怎样自动抠出人物并生成旅行海报
harmonyos
yuhulkjv3354 小时前
告别“复制-粘贴-崩格式”:ChatGPT鸿蒙版导出word格式的底层技术重构
ai·chatgpt·word·harmonyos·ai导出鸭
YM52e5 小时前
ArkTS 2048 手感调优:棋盘渲染与滑动交互
华为·harmonyos
晴天166 小时前
HarmonyOS 和 React 对比-Day22
前端·华为·harmonyos
lilian2338 小时前
Harmony os 技术实战|拼豆制图20:用 finally 关闭 ImageSource、PixelMap 与文件句柄
华为·harmonyos