基于 Embedding 的本地图像搜索

前言

通过上一篇 LLM多模态嵌入 - 图片嵌入 ,我们学习和了解了嵌入技术在图片领域的使用,并通过 openai/clip 的简单示例感受到了当文本嵌入和图片嵌入结合之后的强大之处。可以看到通过嵌入将两个图片的内容是否类似一张猫的图片和一段描述一只可爱的小猫 这类抽象的问题转换为数学问题。实现嵌入之后,比较向量的相似度即可。

既然通过嵌入可以理解图片的内容,我们自然而然会想到用这种技术去进行图片搜索。下面我们尝试借助 clip ,探索图像搜索功能,支持通过文字从批量图片中找到符合描述的图片。

图像搜索实现

有了上一篇 LLM多模态嵌入 - 图片嵌入 的基础,对于图像搜索我们很容易想到如下方案

  1. 对批量的图片进行 embedding ,获得每一张图片的 embedding 结果,将图片的地址(或者任意可以标识图片唯一性)和 embedding 保存下来。
  2. 搜索时将输入内容文本或者图片进行 embedding 后和已经存储的结果进行比较,返回相似度 topN 的图片地址。

下面我们就按照以上思路进行实操,看看实际效果如何。

批量图片的嵌入和存储

这里简单起见,我们将嵌入结果通过 Python 内置的 Pickle 序列化格式进行转换后存储在 .pkl 文件中。

在 Python 中,.pkl 文件(或 .pickle)是 ​​Pickle 序列化格式​​ 的文件扩展名,用于将 Python 对象(如变量、函数、类实例等)​​序列化为二进制数据​​并保存到磁盘,或从磁盘反序列化还原对象。Pickle 是 Python 标准库的一部分(pickle 模块),专为 Python 对象设计。

  • 基于图片目录预先进行嵌入
python 复制代码
def compute_image_embeddings(image_paths):
    embeddings = []
    final_image_paths = []
    for path in tqdm(image_paths, desc="提取图像特征"):
        try:
            img = Image.open(path).convert("RGB")
            inputs = processor(images=img, return_tensors="pt", padding=True, use_fast=True).to(device)
            image_features = model.get_image_features(inputs.pixel_values).squeeze(0)
            embeddings.append(image_features)
            final_image_paths.append(path)
        except UnidentifiedImageError:
            print(path, "unsupported")
            continue

    return torch.stack(embeddings), final_image_paths

需要注意的是,由于在 clip 中默认是按批次处理图片的,因此返回 embedding 结果默认形状是 [1,512],这里为了方便处理,我们通过 squeeze 去掉第一个维度,只保留了特征数据。

  • 保存嵌入结果
python 复制代码
def compute_embeddings():
    paths = get_all_images(IMAGE_DIR)
    features, paths = compute_image_embeddings(paths)
    os.makedirs(os.path.dirname(PKL_CACHE_PATH), exist_ok=True)
    with open(PKL_CACHE_PATH, "wb") as f:
        pickle.dump((features, paths), f)
    return features, paths

这里我们在 pickle 文件中保存的是每张图片的嵌入结果和其路径的字典,这样后续使用的时候我们就可以直接复用这个嵌入后的结果,不用每次都耗费时间去做嵌入,只有图片列表发生变化的时候再去做嵌入。

文本搜索

有了图片的嵌入结果,我们就可以基于文本就行搜索了。

python 复制代码
def search(text, image_features, image_paths, top_k=2):
    text_inputs = processor(text=text, return_tensors="pt", padding=True).to(device)
    text_emb = model.get_text_features(**text_inputs)

    # ✅ 1. image_features 是 [N, 512],text_emb 是 [1, 512]
    print(image_features.shape, text_emb.shape)
    sims = torch.nn.functional.cosine_similarity(image_features, text_emb, dim=1)  # shape: [N]
    
    # ✅ 2. 取 Top-K 索引(确保是 1D)
    top_idx = torch.topk(sims, top_k).indices  # shape: [top_k]

    # ✅ 5. 遍历索引,取图像路径和相似度
    return [(image_paths[i], sims[i].item()) for i in top_idx]

对文本进行嵌入后,计算当前文本的向量表达和所有图片向量的相似度,这里我们取 top 2 的值作为结果。

为了快速验证效果,我们以上一篇示例中用到的图片为例,进行效果验证,总共有 7 张图片

shell 复制代码
features: torch.Size([7, 512])
['imgs\\animal.jpeg', 'imgs\\cat.jpg', 'imgs\\centaur_warrior.jpeg', 'imgs\\child.jpeg', 'imgs\\dog.jpeg', 'imgs\\horse.jpeg', 'imgs\\woman_with_horse.jpeg']
text = :cat
torch.Size([7, 512]) torch.Size([1, 512])
top_idx: torch.Size([2])
cat.jpg: 0.2941
animal.jpeg: 0.2254

可以看到输入的文本 cat 嵌入之后和图片嵌入集合进过相似度计算后返回了值最高的 2 个结果,我们将这两个结果画出来。

可以看到返回结果就是张 7 张图片中唯一一张猫的图片和另外一张和猫长得相似的图片,结果似乎还不错。我们再换一个词 woman 试试

shell 复制代码
text = :woman 
centaur_warrior.jpeg: 0.2489
woman_with_horse.jpeg: 0.2352

这次也是返回来图片集合中唯二包含woman 元素的图片。

小结

从以上结果来看,这个思路是可行的,用 clip 分别嵌入图像和文本数据后,再次计算相似度也能获得较好的结果。但是,有一点我们需要注意的是,单独计算文本和图片的嵌入之后,再次计算二者相似度的时候,相似度结果就没有之前统一计算那么高了。

相关推荐
ASIAZXO1 小时前
机器学习——决策树详解
人工智能·决策树·机器学习
IT古董4 小时前
【第五章:计算机视觉-项目实战之图像分割实战】1.图像分割理论-(2)图像分割衍生:语义分割、实例分割、弱监督语义分割
人工智能·计算机视觉
大明者省6 小时前
《青花》歌曲,使用3D表现出意境
人工智能
一朵小红花HH6 小时前
SimpleBEV:改进的激光雷达-摄像头融合架构用于三维目标检测
论文阅读·人工智能·深度学习·目标检测·机器学习·计算机视觉·3d
Daitu_Adam6 小时前
R语言——ggmap包可视化地图
人工智能·数据分析·r语言·数据可视化
weixin_377634846 小时前
【阿里DeepResearch】写作组件WebWeaver详解
人工智能
AndrewHZ6 小时前
【AI算力系统设计分析】1000PetaOps 算力云计算系统设计方案(大模型训练推理专项版)
人工智能·深度学习·llm·云计算·模型部署·大模型推理·算力平台
AI_gurubar6 小时前
[NeurIPS‘25] AI infra / ML sys 论文(解析)合集
人工智能
胡耀超7 小时前
PaddleLabel百度飞桨Al Studio图像标注平台安装和使用指南(包冲突 using the ‘flask‘ extra、眼底医疗分割数据集演示)
人工智能·百度·开源·paddlepaddle·图像识别·图像标注·paddlelabel
聆思科技AI芯片7 小时前
【AI入门课程】2、AI 的载体 —— 智能硬件
人工智能·单片机·智能硬件