HarmonyOS 7/API 26 实战:不给照片打标签,也能用一句话搜出“城市夜景”
测试环境:ALN-AL80 真机,OpenHarmony 7.0.0.32,API 26;测试集为 5 张固定 CC0 照片。
为什么相册需要语义搜索
相册数量少时,文件名、地点和人工标签已经够用。照片增加以后,用户对画面的记忆往往和这些字段对不上:相册记录里写着“城市夜景”,用户输入的却可能是“夜晚亮灯的城市”。
传统关键词检索比较的是文字。字段中没有出现查询词,即使照片内容完全吻合,也不会返回结果。
基线测试使用同一批照片的 fileName、title 和 caption 三个字段,查询“夜晚的城市灯光”时执行直接字符串匹配:
const query = '夜晚的城市灯光';
const matches = fixtures.filter(item =>
[item.fileName, item.title, item.caption]
.some(text => text.includes(query))
);
结果为 0。测试集中的夜景照片标题是“城市夜景”,描述是“河岸灯光与城市建筑形成高对比夜景”,任何字段都不包含完整查询句。要让传统方案命中,只能补标签、拆词或维护“夜晚—夜景—亮灯”等同义词规则。
验证条件固定为同一批图片和同一句查询词。接入 API 26 后,只比较检索方式是否能够根据画面内容返回夜景照片。
API 26 提供了什么
华为官方将这项能力放在 Core Vision Kit → 通过文本搜索图片。textSearchImage 的调用链由六个主要接口组成:init() 初始化服务,insertImage() 和 deleteImage() 维护图片索引,search() 执行文本检索,clearData() 清空索引,release() 释放服务。
Core Vision Kit 官方能力页将该 Kit 定义为机器视觉基础能力;HarmonyOS SDK 文档中心把“通过文本搜索图片”列在 Core Vision Kit 开发指南中;API 变更清单记录了 26.0.0 Beta1 的 Core Vision Kit 新增能力。本机 SDK 中的 @hms.ai.vision.textSearchImage.d.ts 也将上述接口标注为 @since 26.0.0。
接口设计说明了它和传统标签检索的区别:
insertImage()接收图片的沙箱路径和scope,不接收标题、标签或描述;search()接收自然语言和scope,返回匹配图片的imagePath与similarity;- 图片内容的特征提取和图文语义匹配由 Core Vision Kit 完成。
应用因此不必为每张图片生成“城市”“夜景”“灯光”等内容标签,也不必自建图文向量模型和近邻检索服务。应用仍要管理图片文件、访问权限,以及哪些路径进入或离开索引。准确地说,API 26 省掉的是内容标注和检索模型,不是文件生命周期管理。
建立五张图片的语义索引
测试集包含湖边日落、长城、森林小路、海岸线和城市夜景。启动测试页后,应用把五张公开图片复制到沙箱,再把路径逐张交给 insertImage();传入索引的只有图片路径,没有 title 和 caption。
const initialized = await textSearchImage.init();
if (!initialized) {
throw new Error('系统语义搜图服务暂不可用');
}
for (const imagePath of imagePaths) {
const inserted = await textSearchImage.insertImage(
imagePath,
'PublicPhotos'
);
if (!inserted) {
throw new Error(`索引写入失败:${imagePath}`);
}
}

图 1:ALN-AL80 真机完成 5 张测试图片的索引写入。
固定数据集便于复查同一句查询在不同系统版本上的表现,也避免测试过程读取私人相册。
接入后:同一句话搜到夜景照片
搜索阶段不再读取标题和描述,查询词直接传给 search():
const results = await textSearchImage.search(
'夜晚的城市灯光',
'PublicPhotos',
100
);
真机返回一张城市夜景照片。前后两次测试使用同一批图片和同一句“夜晚的城市灯光”,唯一变化是检索方式。
| 检索方式 | 交给检索器的数据 | 返回结果 |
|---|---|---|
| 文件名、标题和描述直接匹配 | 三个文字字段 | 0 张 |
API 26 textSearchImage |
五个图片路径 | 1 张城市夜景 |

图 2:输入图片标题中的“城市夜景”,结果区只显示一张城市夜景照片。
“城市夜景”先确认索引和搜索链路能够返回目标图片;随后换成数据字段中不存在的“夜晚的城市灯光”,验证自然语言与画面内容之间的语义匹配。
“湖边日落”为什么没有搜到
同一轮真机测试还出现了一次漏召回。测试集中有一张湖边日落照片,但“湖边的日落”和“湖边晚霞”都返回 0 张。

图 3:数据集中存在湖边日落图,两种查询表达均返回 0 张,结果区没有混入占位图片。
公开接口只返回命中项,没有提供未命中的内部原因。画面主体、构图和系统模型都可能影响相关性,调用方无法从返回值中确定具体原因。空状态文案因此使用“没有找到相关照片,换一种描述试试”,不把本次检索结果解释为相册中不存在该照片。
另一个负例“白色北极熊驾驶红色跑车”同样返回 0 张:

图 4:测试集中没有对应画面,页面返回 0 张,结果区保持为空。
五张图片适合验证接口链路,无法用于评价模型召回率。阈值和查询策略需要在人物、食物、城市、自然、夜景和文字等更大数据集上测试。
命中结果怎样回到相册详情
search() 返回 ImageObject 数组,其中包含图片路径和相似度。双镜记忆相机根据 imagePath 查找当前相册记录,过滤已经删除或改为私密的照片,再按 similarity 从高到低展示。
一条双拍记录可能包含前后两张图片。如果两个路径同时命中,页面只保留相似度较高的一项,避免同一组照片重复出现。
for (const result of results) {
const record = findPublicRecordByPath(records, result.imagePath);
if (!record) {
continue;
}
const index = matches.findIndex(item => item.recordId === record.id);
if (index < 0) {
matches.push({
recordId: record.id,
imagePath: result.imagePath,
similarity: result.similarity
});
} else if (result.similarity > matches[index].similarity) {
matches[index] = {
recordId: record.id,
imagePath: result.imagePath,
similarity: result.similarity
};
}
}
matches.sort((left, right) => right.similarity - left.similarity);

图 5:点击搜索结果卡片后进入照片详情。该页面验证 imagePath 与相册记录的跳转关系,不作为搜索命中的证据。
Core Vision Kit 决定哪些图片与查询相关;相册记录决定这张图片当前是否允许展示。两部分通过返回的 imagePath 接在一起。
新增和删除照片时怎样更新索引
应用不需要管理图片的内容标签,但需要让系统索引与当前相册保持一致。双镜记忆相机在 Preferences 中保存上次成功写入的路径集合,每次同步只处理差异:
const desiredPaths = collectPublicImagePaths(records);
const indexedPaths = await loadIndexedPaths(context);
for (const path of indexedPaths) {
if (!desiredPaths.includes(path)) {
await textSearchImage.deleteImage(path, SEARCH_SCOPE);
}
}
for (const path of desiredPaths) {
if (!indexedPaths.includes(path)) {
await textSearchImage.insertImage(path, SEARCH_SCOPE);
}
}
await saveIndexedPaths(context, desiredPaths);
新增照片调用 insertImage(),删除照片或公开状态变化调用 deleteImage()。本地路径集合只能在系统接口成功后更新,否则一次写入异常会让后续同步误判。
SDK 错误码 1013100003 表示能力已经更新。收到该错误后调用 clearData(),再以当前公开照片路径重建索引。scope 用于区分检索集合,示例使用含义明确的 PublicPhotos;照片是否公开仍以相册记录为准。
模拟器和真机的结果不同
同一个 HAP 安装到 API 26 三折叠模拟器后,Core Vision 返回 1013100002 Service abnormal;相同代码在 ALN-AL80 真机上完成索引,并成功返回夜景照片。
模拟器可检查输入框、加载状态、空状态和折叠布局。Core Vision 语义检索的能力结论来自真机结果。
最终效果:一句话返回唯一夜景照片

图 6:输入“夜晚的城市灯光”,页面同屏显示查询词、“找到 1 组相关照片”和唯一的城市夜景结果卡。
图 6 构成搜索成功的完整证据:查询词没有出现在测试图片的文件名、标题和描述中,结果页仍只返回城市夜景;搜索结果卡与图 5 的照片详情承担不同职责,前者证明语义命中,后者验证点击后的业务跳转。
API 26 让相册获得了一条新的找图路径:照片入库时提交图片路径,Core Vision Kit 建立内容语义索引;搜索时提交自然语言,系统返回相关图片。应用不需要为每张照片补齐“城市、夜景、灯光”等内容标签,也不需要部署自己的图文检索模型。
这项能力适合作为相册浏览之外的第二入口。用户记得画面却记不清时间和地点时,可以直接输入一句描述;imagePath 返回后仍要经过当前相册记录和可见性检查,再进入照片详情。
漏召回决定了搜索页还要保留两个出口:换一种说法重新搜索,或者返回时间、地点和相册分类继续查找。空状态写“没有找到相关照片”即可,不能把一次检索结果解释为相册中不存在这张照片。
接入真实相册时,核心工作集中在三处:新增和删除照片时同步索引;索引能力更新后使用当前路径重建;展示前重新检查照片权限。完成这三步,语义搜索才能从一段 API 示例变成可以长期使用的相册功能。
更多推荐


所有评论(0)