多模态向量搜索:图片、音频、视频统一检索的工程实现 🖼️
🔥 本文是《向量数据库实战:选型、调优与落地》专栏第 15 篇
⏱️ 阅读时间:约 13 分钟
🎯 开篇:不只是文本
2025 年,AI 应用早已不局限于文本------
- 淘宝"拍照搜同款":拍一张照片,找到相似商品 📸
- 抖音内容推荐:根据视频内容推荐相似视频 🎬
- Spotify 歌曲推荐:根据音频特征推荐相似音乐 🎵
这些背后的核心技术,就是多模态向量搜索!
🧠 多模态 Embedding 模型
核心思想
多模态模型 能把不同类型的媒体(文本、图片、音频)映射到同一个向量空间中。
┌─────────────────────────────────────────────────────────┐
│ 多模态向量空间 │
├─────────────────────────────────────────────────────────┤
│ │
│ 文本: "一只猫在草地上玩耍" │
│ → [0.12, -0.34, 0.56, ...] │
│ ↕ 距离很近! │
│ 图片: 🐱🌿(猫在草地上的照片) │
│ → [0.13, -0.33, 0.55, ...] │
│ ↕ 距离较远 │
│ 图片: 🚗🏙️(城市汽车照片) │
│ → [0.89, 0.12, -0.45, ...] │
│ │
│ 文本和图片在同一个向量空间中! │
│ → 可以用文本搜图片,也可以用图片搜文本 │
│ │
└─────────────────────────────────────────────────────────┘
主流多模态模型
| 模型 | 类型 | 维度 | 支持模态 | 开源 |
|---|---|---|---|---|
| CLIP | 图文 | 512/768 | 文本+图片 | ✅ |
| SigLIP | 图文 | 1024 | 文本+图片 | ✅ |
| ImageBind | 多模态 | 1024 | 文本+图片+音频+视频+深度+热成像 | ✅ |
| Chinese-CLIP | 图文(中文) | 768 | 中文文本+图片 | ✅ |
| Jina CLIP v2 | 图文 | 768 | 文本+图片 | ✅ |
💻 实战:图文混合搜索
Step 1:使用 CLIP 生成多模态向量
python
import torch
from transformers import CLIPModel, CLIPProcessor
# 加载中文 CLIP 模型
model = CLIPModel.from_pretrained("OFA-Sys/chinese-clip-vit-large-patch14")
processor = CLIPProcessor.from_pretrained("OFA-Sys/chinese-clip-vit-large-patch14")
# 文本向量化
def encode_text(text):
inputs = processor(text=[text], return_tensors="pt", padding=True)
with torch.no_grad():
text_features = model.get_text_features(**inputs)
return text_features[0].numpy()
# 图片向量化
def encode_image(image_path):
from PIL import Image
image = Image.open(image_path)
inputs = processor(images=image, return_tensors="pt")
with torch.no_grad():
image_features = model.get_image_features(**inputs)
return image_features[0].numpy()
# 测试
text_vec = encode_text("一只可爱的猫咪")
image_vec = encode_image("cat_photo.jpg")
# 计算相似度
from numpy.linalg import norm
similarity = np.dot(text_vec, image_vec) / (norm(text_vec) * norm(image_vec))
print(f"文本和图片的相似度: {similarity:.4f}")
Step 2:存入向量数据库
python
from pymilvus import Collection, FieldSchema, DataType
# 创建多模态集合
fields = [
FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
FieldSchema(name="content_type", dtype=DataType.VARCHAR, max_length=20), # text/image/video
FieldSchema(name="original_text", dtype=DataType.VARCHAR, max_length=65535),
FieldSchema(name="image_url", dtype=DataType.VARCHAR, max_length=1024),
FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=768), # CLIP 维度
]
schema = CollectionSchema(fields=fields)
collection = Collection("multimodal", schema)
# 插入文本数据
text_embedding = encode_text("向量数据库入门教程")
collection.insert([
["text"], # content_type
["向量数据库入门教程"], # original_text
[""], # image_url
[text_embedding.tolist()]
])
# 插入图片数据
image_embedding = encode_image("tutorial_cover.jpg")
collection.insert([
["image"],
[""],
["tutorial_cover.jpg"],
[image_embedding.tolist()]
])
Step 3:跨模态搜索
python
# 用文本搜索图片!
collection.load()
query_text = "教程封面图片"
query_embedding = encode_text(query_text)
results = collection.search(
data=[query_embedding.tolist()],
anns_field="embedding",
param={"metric_type": "COSINE", "params": {"ef": 128}},
limit=5,
expr='content_type == "image"', # 只搜图片
output_fields=["content_type", "original_text", "image_url"]
)
# 用图片搜索文本!
query_image = encode_image("some_photo.jpg")
results = collection.search(
data=[query_image.tolist()],
anns_field="embedding",
param={"metric_type": "COSINE", "params": {"ef": 128}},
limit=5,
expr='content_type == "text"', # 只搜文本
output_fields=["content_type", "original_text"]
)
📊 多模态搜索应用场景
| 应用场景 | 查询方式 | 检索目标 | 典型案例 |
|---|---|---|---|
| 以图搜图 | 图片 → 图片 | 相似图片 | 淘宝拍照搜同款 |
| 以文搜图 | 文本 → 图片 | 匹配的图片 | 素材网站搜索 |
| 以图搜文 | 图片 → 文本 | 图片描述/相关文档 | 图片内容理解 |
| 跨模态推荐 | 文本 → 图文混合 | 图文内容 | 小红书推荐 |
| 视频检索 | 文本/图片 → 视频帧 | 视频关键帧 | 视频内容搜索 |
🏗️ 视频搜索架构
┌─────────────────────────────────────────────────────────┐
│ 视频向量搜索架构 │
├─────────────────────────────────────────────────────────┤
│ │
│ 输入视频 │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ 视频抽帧 │ 每秒抽 1 帧 / 关键帧检测 │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ 帧向量化 │ CLIP 对每帧生成向量 │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ 视频向量聚合 │ 平均池化 / 最大池化 │
│ │ │ 将 N 帧向量 → 1 个视频向量 │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ 向量数据库 │ 存储视频向量 + 元数据 │
│ │ │ (视频URL、时间戳、标签) │
│ └──────────────┘ │
│ │
│ 查询: "找一段日落的海边视频" │
│ → 文本向量化 → 向量搜索 → 返回匹配视频 │
│ │
└─────────────────────────────────────────────────────────┘
⚠️ 多模态搜索的坑
| 坑 | 说明 | 解决方案 |
|---|---|---|
| 模型选择 | 不同模型的向量空间不兼容 | 同一集合必须用同一模型 |
| 中文支持 | 原版 CLIP 中文效果差 | 用 Chinese-CLIP |
| 图片预处理 | 尺寸、格式不统一 | 统一 resize 到模型要求 |
| 向量维度 | 不同模态维度可能不同 | 确认模型输出维度一致 |
| 存储成本 | 图片/视频占空间 | 只存向量+URL,原始文件存 OSS |
🔑 本篇核心要点回顾
| 要点 | 说明 |
|---|---|
| 多模态模型 | CLIP/Chinese-CLIP 将文本和图片映射到同一空间 |
| 跨模态搜索 | 可以用文本搜图片,也可以用图片搜文本 |
| 视频搜索 | 抽帧 → 向量化 → 聚合 → 存储 |
| 中文场景 | 推荐 Chinese-CLIP 或 Jina CLIP v2 |
| 存储策略 | 向量存数据库,原始文件存 OSS |
📌 下篇预告:《Milvus 分布式部署实战:从单机到集群的完整踩坑记录 🏗️》
💬 有问题欢迎评论区讨论,觉得有用请点赞收藏 👍
作者:高炉炼铁智能化技术研究者,专注钢铁冶金与人工智能 交叉领域。
👍 如果觉得有帮助,请点赞、收藏、转发!
版权归作者所有,未经许可请勿抄袭,套用,商用(或其它具有利益性行为) 。
🔔 关注专栏,不错过后续精彩内容