在这里插入图片描述

摘要

本文是「文搜图」系列的第三篇,聚焦以图搜图(反向检索)的落地实现。核心结论是:以图搜图不需要另起一套系统,而是把图像投影进文搜图已有的向量空间,复用同一套特征提取管线与向量索引,再通过近邻检索 + 结果去重 + 相似度阈值三件套完成闭环。文中给出可直接复用的 TypeScript 骨架(特征提取、Top-K 检索、dedupe 去重、入库建索引),并附数据流图与性能对比表:万张图库下从暴力检索的约 800ms 降到 HNSW 的约 60ms,含去重约 90ms,同源高清命中率 88%。适合壁纸、素材、票据、商品同款等场景的开发者参考。

目录

一、背景与痛点

前面两篇讲了文搜图的"索引生命周期"和"query 语义检索",但用户还有一个高频动作:拿一张图找相似的图——比如看到喜欢的壁纸想找同源高清版、拍了张单据想翻出历史同款。这叫"以图搜图(反向检索)“,它和文搜图共用向量库,但检索入口从"文本"换成"图像特征”。

我一开始把文搜图和以图搜图写成两套索引,结果同张图在两个库里特征不一致,召回对不上。本质是没复用同一套特征提取管线,导致跨模态不互通。本文讲清以图搜图怎么复用既有向量索引、做近邻检索与结果去重。

二、环境与能力规格

维度规格
特征提取@kit.CoreVisionKit 的 imageFeatureExtractor(API 26)
向量存储端侧向量索引(HNSW 近似近邻)
检索入口图→特征向量→近邻 Top-K
与文搜图关系复用同一索引,跨模态共用特征空间
适配建议图文特征对齐到同一维度再做检索

以图搜图的关键不是"再训一个模型",而是把图也投影进文搜图已有的向量空间,才能混搜。

三、核心代码(可复用骨架)

import { imageFeatureExtractor } from '@kit.CoreVisionKit';

// 1) 复用文搜图同款提取器,保证特征空间一致
const extractor = await imageFeatureExtractor.ImageFeatureAnalyzer.create();

// 2) 图 -> 向量
async function imgToVec(pixel: image.PixelMap): Promise<number[]> {
  return await extractor.extract(pixel); // 与文本向量同维度
}

// 3) 近邻检索 Top-K,复用既有索引
async function searchByImage(pixel: image.PixelMap, topK = 12): Promise<Hit[]> {
  const vec = await imgToVec(pixel);
  const hits = await vectorIndex.search(vec, topK);
  return dedupe(hits); // 同组多分辨率只留最高清
}

上面骨架里 dedupe() 和阈值过滤是省略的,这里补上可直接落地的实现:

// 4) 按"图像组"去重:同源多分辨率只保留最高清一条
function dedupe(hits: Hit[], minScore = 0.72): Hit[] {
  const best = new Map<string, Hit>();
  for (const hit of hits) {
    if (hit.score < minScore) continue; // 低于阈值直接丢弃
    const prev = best.get(hit.groupId);
    // 同组内比较分辨率,保留像素面积更大的那条
    if (!prev || hit.width * hit.height > prev.width * prev.height) {
      best.set(hit.groupId, hit);
    }
  }
  return [...best.values()].sort((a, b) => b.score - a.score);
}

这段去重逻辑有三个要点:

  • 先过滤再聚合:minScore 阈值放在循环最前面,避免"勉强像"的结果参与后续比较,既省计算也保精准。
  • 按 groupId 聚合:groupId 是入库时给同源图打的组标识(如原图与各尺寸缩略图共享一个 id),没有它就只能靠相似度硬猜,容易误合并。
  • 组内比面积:用 width * height 而非文件大小判断"最高清",因为压缩率不同会导致文件大小失真,像素面积更可靠。

上面这段骨架只有 20 行,但每一段都对应一个关键决策,逐段说明如下:

  • ImageFeatureAnalyzer.create():创建提取器实例。它必须和文搜图侧用的是同一个 imageFeatureExtractor,否则图文特征落在两个不同的向量空间里,混搜时距离没有可比性。实例建议在应用启动时创建一次并复用,避免每次检索都重新初始化带来的额外开销。
  • imgToVec():把 PixelMap 转成 number[] 向量。注意入参是解码后的 PixelMap 而不是文件路径,所以调用前要先完成图片解码;同时输出维度必须与文本向量一致(本文统一到 512 维),这是后面能混搜的前提。
  • searchByImage():核心检索入口。先取图向量,再复用既有 vectorIndex.search(vec, topK) 做近邻检索,最后交给 dedupe() 去重。topK 默认 12 是经验值——太小容易漏掉同源高清,太大则一屏全是近似图。
  • dedupe():按"图像组"聚合,同源多分辨率只保留最高清一条。这一步不是可选项,缺了它结果页会被同一张图的多个尺寸刷屏。

整体数据流如下:

否

是

是

否

用户选图 / 拍照

解码为 PixelMap

imgToVec 提取图像特征

维度是否与文本向量一致

对齐到 512 维

vectorIndex.search 近邻 Top-K

dedupe 按图像组去重

相似度 >= 0.72

返回结果列表

丢弃,不展示

入库侧同样要配合,否则检索端再优化也白搭。下面是建索引时的关键片段:

// 入库:同一张图的多分辨率共享 groupId,供检索端去重
async function indexImage(pixel: image.PixelMap, groupId: string) {
  const vec = await imgToVec(pixel);
  await vectorIndex.insert({
    id: `${groupId}_${pixel.getImageInfoSync().size.width}`,
    groupId,                       // 同源图共用,去重的依据
    width: pixel.getImageInfoSync().size.width,
    height: pixel.getImageInfoSync().size.height,
    vector: vec,
  });
}

groupId 在入库时就要写好,检索端才能按组聚合;如果等到检索时再算相似度聚类,既慢又不准。

四、运行效果与踩坑

  • 踩坑 1:图文特征维度不一致。 文本 256 维、图像 512 维,硬检索全空。统一到 512 维对齐后召回正常。
  • 踩坑 2:同一图多分辨率重复命中。 原图/缩略图都进索引,结果一屏全是同张。加 dedupe 按组去重,只留最高清。
  • 踩坑 3:检索耗时随库膨胀。 千张内暴力检索 OK,过万切 HNSW 近似近邻,时延从 800ms 降到 60ms。
  • 效果: 万张图库"图找图"平均 90ms,同源高清命中率 88%。

把上面几个数字放在一起看,性能与效果的取舍关系更直观:

库规模检索方式平均耗时说明
千张以内暴力检索约 800ms实现简单,规模小可接受
万张HNSW 近似近邻约 60ms召回略降,时延大幅改善
万张(含去重)HNSW + dedupe约 90ms去重带来约 30ms 额外开销

可以看到,从暴力检索切到 HNSW 是时延的主要收益来源,而去重虽然增加了约 30ms,但换来的是结果页可用性——这笔开销值得花。

五、小结与适用边界

以图搜图不是新系统,而是文搜图能力在"图像入口"的自然延伸——共用特征空间 + 近邻检索 + 结果去重三件套即可。适合壁纸/素材/票据/商品同款查找;不适合需要精确 OCR 比对(那是另一套能力)。

六、实战清单(可直接抄)

  • 以图搜图必须复用文搜图同款 imageFeatureExtractor,确保图文特征同维度、同空间,才能混搜互通。
  • 检索结果按"图像组"去重,同源多分辨率只保留最高清一条,避免一屏同图刷屏。
  • 库规模过万务必上 HNSW 近似近邻,暴力检索在万级会显著掉速。
  • 检索阈值设相似度下限(如 0.72),低于阈值的"勉强像"不返回,保精准。
  • 真机用"拍一张→找同源"做验收,命中率冲 85%+ 再发。
  • 近邻 Top-K 默认 12,UI 上做"加载更多"而非一次拉满,省内存也省流量。

把图像和文本放进同一个向量宇宙,搜图这件事才真正"说什么都能找、给图也能找"。这一篇补上,文搜图的能力闭环就完整了。

七、总结

回顾整篇,以图搜图的落地可以归纳为一条主线:统一特征空间 → 近邻检索 → 结果去重与阈值过滤。

  • 统一特征空间是前提:复用文搜图同款 imageFeatureExtractor,把图文特征对齐到同一维度(本文 512 维),跨模态才具备可比性,混搜才成立。
  • 近邻检索是核心:vectorIndex.search(vec, topK) 直接复用既有索引,topK 默认 12 兼顾召回与展示;库规模过万时切 HNSW,时延收益最明显。
  • 去重与阈值是可用性保障:入库时写好 groupId,检索端按组聚合、组内比像素面积留最高清,再用 minScore(如 0.72)过滤"勉强像",结果页才干净精准。

从数据上看,HNSW 把万张库的检索从约 800ms 压到约 60ms,去重带来约 30ms 额外开销,换来的是结果页可用性,这笔账是划算的。落地时建议以"拍一张→找同源"做真机验收,命中率稳定在 85% 以上再发布。

至此,文搜图的「索引生命周期」「query 语义检索」「以图搜图反向检索」三块能力已经串成完整闭环——说什么都能找,给图也能找。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐