想找一张照片时,我们往往记得画面,却记不住它的拍摄日期和文件名。例如,“雨天窗边的一杯咖啡”,或者“海边看日落的人”。如果能直接输入这段描述,再由应用找出相关图片,找照片的过程或许会更简单。

于是,我准备围绕 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]

遇到这种不一致,先选择符合参数表的输入更稳妥。因此本系列初版使用中文查询和字母数字作用域。是否存在其他允许的输入形式,应以进一步核实和真机验证为依据。

类似地,当前尚未测量首次初始化耗时、重复插入行为以及不同查询的效果,就不应在文章里填入一个看起来合理的数字。把这些问题列出来,可以让下一次实验更有方向。

到这里,我们已经界定了应用的目标、平台与应用的分工,以及基础页面的检查标准。接下来要连接其中最关键的两段:把图片准备成可用的沙箱文件,再让一次中文查询真正返回图片。

下一篇将从“海边的落日”开始,接入图片特征入库与检索,并把初始化失败、入库失败和没有匹配结果分别呈现在界面上。只有区分这些情况,我们才有条件讨论搜索效果。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐