HarmonyOS 7.0 文搜图实战①:从“一句话找照片”开始
想找一张照片时,我们往往记得画面,却记不住它的拍摄日期和文件名。例如,“雨天窗边的一杯咖啡”,或者“海边看日落的人”。如果能直接输入这段描述,再由应用找出相关图片,找照片的过程或许会更简单。
于是,我准备围绕 HarmonyOS 7.0 的相关能力,做一个“一句话找照片”的 Demo,并用五篇文章记录从环境准备、功能接入到体验优化的过程。
这里先解决三个问题:这项能力替我们完成什么工作,应用还需要承担什么责任,以及第一个可以检查的开发成果应该是什么。本文代码用于说明接口和搭建基础页面,尚未作为完整工程通过真机验证;搜索效果与性能会留到接入后记录。

图1:先把想要的体验画清楚。目标界面不代表已经取得这样的检索结果。
从一个小问题开始
“做一个智能相册”很容易成为没有边界的目标。相册可以包含备份、分类、编辑、分享和人物管理,每一项都足以展开成独立项目。
这里先问一个小得多的问题:给定一组图片,输入“海边的落日”,应用能否返回内容相关的图片?
“给定一组图片”是一个有用的限制。它固定了搜索范围,使不同查询和不同实现之间的比较有意义。否则,今天新增了几十张照片,明天又修改了查询方式,我们很难判断结果变化究竟来自哪里。
第一版准备三十张左右的图片即可,覆盖风景、食物和动物等几类场景。数量不是关键,关键是知道图片里有什么,并且愿意逐项检查返回结果。
还应放入一些容易混淆的图片。例如,查询“海边的落日”时,图库里同时有海边白天、山间落日和海边落日。这样才能观察检索是否理解了描述中的组合关系。全部图片都明显不同,一次成功搜索能够提供的信息其实很少。
文搜图是基于对画面的描述
文件名搜索依赖名称,标签搜索依赖预先填写的分类,OCR 搜索依赖图片中可识别的文字。文搜图关注文本描述与图片内容之间的语义关联。
假设一张照片名为 IMG\_0012.jpg,画面里没有文字,也没有附加“咖啡”标签。用户仍然希望通过“窗边的咖啡”找到它。这就是本次 Demo 要探索的情形。
| 搜索方式 | 用户可能输入什么 | 应用主要依据什么匹配 |
|---|---|---|
| 文件名搜索 | 输入“旅行”,寻找名称包含“旅行”的文件 | 文件名称 |
| 标签搜索 | 输入“宠物”,寻找带有“宠物”标签的图片 | 已有标签 |
| OCR 搜索 | 输入“会议时间”,寻找包含这些文字的截图 | 图片内的文字 |
| 文搜图 | 输入“草地上奔跑的小狗”,寻找相关画面 | 文本与画面内容的语义关联 |
这些方式各有用途。一张收据上的订单号适合文字检索,一段对旅行画面的回忆则更适合语义检索。明确这一点,有助于选择测试材料,也能避免把不同能力的效果混在一起评价。

图2:搜索方式的区别,首先体现在它依赖什么信息。
对于具体物体、场景描述和比较模糊的表达,检索效果会有什么差异?这些问题将通过同一组测试图片逐步验证。
平台能力与应用责任
HarmonyOS 的 Core Vision Kit(基础视觉服务)提供了 textSearchImage 模块。官方将其描述为基于文本语意的图片检索能力,适用于图片检索、相册管理、内容推荐等场景。[1]
模块的导入方式很直接:
import { textSearchImage } from '@kit.CoreVisionKit';
更值得注意的是接口接受的数据。应用先提供图片的沙箱路径,通过 insertImage 将图片特征插入数据库,再通过 search 查询已经插入的图片集合。[2]
这决定了应用的工作顺序。安装了应用,并不意味着系统相册中的照片自动成为可检索数据。我们需要先准备应用能够使用的图片文件,再把这些图片纳入检索范围。
平台已经提供了图片特征入库和检索接口。第一版可以直接使用这组能力,把注意力放在文件准备、操作状态和结果展示上。过早引入自己的向量数据库,会增加一个目前尚无明确需求的维护对象。
把双方责任列出来,比画一张复杂的架构图更有帮助:
| 部分 | 本次承担的责任 |
|---|---|
| 应用代码 | 准备图片文件,组织图库,收集查询,展示结果与错误 |
| textSearchImage | 初始化服务,插入图片特征,执行检索,管理相关记录 |
| 实际验证 | 检查设备可用性,观察检索效果、耗时和异常行为 |

图3:图片进入检索范围,是一个需要显式执行的步骤。
运行环境
这一步很容易被跳过:创建工程,粘贴示例,然后开始处理编译错误。然而,编译通过只说明代码被工具链接受,不能证明设备具备对应服务。
官方文档要求使用 Stage 模型,支持 Phone、Tablet、PC/2in1,并明确说明 Core Vision Kit 暂不支持模拟器。该 Kit 的地区范围为中国境内,不含港澳台。[3]
因此,开始接入前就应准备符合能力要求的真机。DevEco Studio、SDK 和设备系统需要彼此匹配,具体版本应记录为自己实际使用的值。本文不提供一组未经验证的环境数字。
建议在工程 README 中保留以下记录,后续遇到问题时,它比“我这里可以运行”更有用。
DevEco Studio:填写实际版本
HarmonyOS SDK:填写实际版本
设备型号:填写实际型号
设备系统:填写完整系统版本
工程模型:Stage
能力初始化:待真机验证
图片本身也有约束。文搜图要求宽、高均大于 100px 且小于 10000px,并建议高度小于宽度的十倍。第一版选择尺寸适中、主体清晰的图片,可以减少图像质量和检索逻辑同时变化带来的干扰。

图4:环境记录应来自实际开发设备,不能用目标配置替代。
测试图库
测试图片应有使用权限,并避免包含私人聊天记录、证件或其他个人信息。对于这一阶段,自己拍摄的风景、食物和动物照片已经足够。
给图片一个稳定的标识,并单独记录我们对画面的人工描述。人工描述只是评测笔记,不作为文搜图接口的输入。这一区分很重要,否则容易在不知不觉中把测试变成基于人工标签的搜索。
| 图片标识 | 人工记录的内容 | 观察目的 |
|---|---|---|
| sea01 | 白天的海边 | 检查是否只匹配“海边” |
| sea02 | 海边落日 | 目标场景 |
| hill01 | 山间落日 | 检查是否只匹配“落日” |
| coffee01 | 窗边的咖啡 | 物体与位置组合 |
| dog01 | 草地上的小狗 | 动物与环境组合 |
第一组查询也固定下来:“海边的落日”“窗边的一杯咖啡”“草地上的小狗”。之后增加图库或调整界面时,可以回到这组查询,检查原有行为是否发生变化。
本篇允许先用资源图片验证页面布局。接入检索时,需要将测试图片准备为真实存在的沙箱文件;资源引用和沙箱路径是不同的输入,不能直接互换。第二篇将补上这段文件准备流程。

图5:保留相近但不同的场景,才能提出有意义的检索问题。
第一个页面
在 DevEco Studio 中创建采用 Stage 模型的 ArkTS 空工程,保留默认入口结构。为页面准备三张资源图片,分别命名为 sample\_sea、sample\_coffee 和 sample\_dog,放入 entry/src/main/resources/base/media。
然后在 Index.ets 中搭建基础页面。下面代码展示输入区域和测试图片,没有接入检索,所以搜索按钮明确保持禁用。
@Entry
@Component
struct Index {
@State query: string = '';
private samples: Resource\[] = \[
$r('app.media.sample\_sea'),
$r('app.media.sample\_coffee'),
$r('app.media.sample\_dog')
];
build() {
Column({ space: 16 }) {
Text('一句找图')
.fontSize(26)
.fontWeight(FontWeight.Bold)
TextInput({
text: this.query,
placeholder: '试试:海边的落日'
})
.maxLength(100)
.onChange((value: string) => {
this.query = value;
})
Button('搜索')
.enabled(false)
Text('当前展示测试图库,检索能力尚未接入')
.fontSize(14)
.fontColor('#666666')
Grid() {
ForEach(this.samples, (item: Resource) => {
GridItem() {
Image(item)
.width('100%')
.height(120)
.objectFit(ImageFit.Cover)
.borderRadius(8)
}
})
}
.columnsTemplate('1fr 1fr')
.columnsGap(12)
.rowsGap(12)
.layoutWeight(1)
}
.padding(20)
.width('100%')
.height('100%')
}
}
这段代码的检查标准很简单:页面可以打开,输入内容能够保留,三张资源图片正常显示,按钮不会把静态图库伪装成搜索结果。资源名称需要与工程中的实际文件一致。
随着接口接入,页面会出现初始化、图片准备、可搜索和搜索中等状态。我们稍后再根据真实的异步操作补充这些状态。现在先让界面准确表达已经完成的工作。

图6:此处应放基础页面的真实截图,不能用目标设计图代替运行记录。
接入之前检查
下面的函数说明检索链路的主要顺序。它是独立演示流程:调用者必须传入一个已经存在、可被接口访问的沙箱图片路径,并保证它不与其他文搜图操作重叠。
import { textSearchImage } from '@kit.CoreVisionKit';
async function inspectSearch(imagePath: string): Promise<void> {
const initialized = await textSearchImage.init();
if (!initialized) {
throw new Error('文搜图服务初始化失败');
}
try {
const inserted = await textSearchImage.insertImage(
imagePath, 'demo01'
);
if (!inserted) {
throw new Error('图片特征入库失败');
}
const results = await textSearchImage.search(
'海边的落日', 'demo01', 5
);
for (const result of results) {
console.info(`相似度:${result.similarity}`);
}
} finally {
await textSearchImage.release();
}
}
这里故意保留几个显式步骤。初始化和插入接口返回 boolean,因此没有抛出异常也未必成功。检查返回值,才能避免把前一步的失败误判为“搜索不准确”。正式页面还需要捕获错误并展示状态,以及处理释放失败等生命周期问题。
scope 在接口中表示图片作用域。本例使用 demo01,插入与查询保持一致。topKey 设置为 5,表示返回数量的上限;满足条件的结果可能少于五张。
返回对象包含图片路径、作用域和相似度。相似度范围为 [-1, 1],数值越大表示越相似。它不等于准确率,也不是用户会接受某张图片的概率。把 0.8 展示为“80% 准确”,会给读者一个接口并未作出的承诺。
还要区分资源释放和数据清理。release() 释放分析器服务,deleteImage() 删除图片记录,clearData() 清除数据库中的所有数据。页面退出不应顺手清空检索数据,删除检索记录也不能被描述成删除原图文件。
官方说明同一用户不支持并发调用同一特性,因此后续批量入库先采用顺序执行。对于三十张测试图片,这能让失败位置更清楚,也符合能力约束。
问题记录
阅读 API 时还有一个细节:参数表说明查询不支持纯字母,示例却使用了 landscape;作用域参数表说明支持字母或数字,示例又使用了带下划线的名称。[2]
遇到这种不一致,先选择符合参数表的输入更稳妥。因此本系列初版使用中文查询和字母数字作用域。是否存在其他允许的输入形式,应以进一步核实和真机验证为依据。
类似地,当前尚未测量首次初始化耗时、重复插入行为以及不同查询的效果,就不应在文章里填入一个看起来合理的数字。把这些问题列出来,可以让下一次实验更有方向。
到这里,我们已经界定了应用的目标、平台与应用的分工,以及基础页面的检查标准。接下来要连接其中最关键的两段:把图片准备成可用的沙箱文件,再让一次中文查询真正返回图片。
下一篇将从“海边的落日”开始,接入图片特征入库与检索,并把初始化失败、入库失败和没有匹配结果分别呈现在界面上。只有区分这些情况,我们才有条件讨论搜索效果。
更多推荐


所有评论(0)